How to simplify access to has-many relationships with entity infrastructure?

Here is what I want to do:

var user = db.User.First(conditions); user.Book.First(); 

This is how I should do it.

 var user = db.User.Include("Book").First(conditionsForUser); user.Book.First(); 

The reason I want to simplify this is because I don’t want to indicate what is included every time I want to access the relationship. Seems very bulky.

For example: I would just like to do the following, given that I previously got the user:

 user.Book.First() user.Blog.First() user.SomeOtherHasManyRelationship.Where(conditions) 

Here is what I still have:

  public object RelationshipFor(string relationship) { using (var db = User.DbContext()) { var relationshipType = TypeRepresentedBy(relationship); // unused for now, not sure if I need the type of the relationship var myTable = ((ICollection)db.Send(RelationshipName)); // RelationshipName is "User" in this instance. var meWithRelationship = myTable.Where(i => i.Send(IdColumn) == Id).Include(relationship); // currently, myTable doesn't know about 'Where' for some reason. return meWithRelationship.Send(relationship); } } 

And then, as it will be used, there will be the following:

 user.RelationshipFor("Book") // returns a list of books 

I have other logic in my code that abstracts away what user.Book.First() would allow me to do. Hopefully I can get permission to open source because of this, as I am modeling a lot of api after crud in ActiveRecord style.

Please note that I am using a set of extensions that I have done to help manage dynamics less painful: https://github.com/NullVoxPopuli/csharp-extensions

UPDATE 1:

  public object RelationshipFor(string relationship) { using (var db = User.DbContext()) { var myTable = (DbSet<DatabaseModels.User>)db.Send(RelationshipName); var myInclude = myTable.Include(i => i.Send(relationship)); var meWithRelationship = myInclude.First(i => (long)i.Send(IdColumn) == Id); return meWithRelationship.Send(relationship); } } 

At the moment, I hard-coded the cast of the user, trying to just get something to work. My mistake:

 Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'. 
+4
source share
1 answer

This is not a trivial problem, and there is no “one size fits all” approach. What you actually represent is lazy loading, which was not included in EF7 for many reasons .

I don’t know what the code you are showing should look like, but one option would be to introduce a repository template in which you specify “objects to include” at the collection level:

 public class UserRepository { private readonly IQueryable<User> _dataSet; public UserRepository(IQueryable<User> userDataSet) { _dataSet = userDataSet; } public IQueryable<User> Include() { return _dataSet.Include(u => u.Book) .Include(u => u.Blog); } } 

And you can move a lot of logic into a common base class, leaving you with only the Include() method. For example, you can work with strings while you show (or enumerations or ...) to select only related objects, which include:

 public class GenericRepository { // ... public IQueryable<User> Include(string includeGroup = null) { return IncludeGroup(includeGroup); } protected virtual IncludeGroup(string includeGroup) { return _dataSet; } } 

And then in the UserRepository :

 protected override IQueryable<User> IncludeGroup(string includeGroup) { switch (includeGroup.ToUpperInvariant()) { case "BOOK": return _dataSet.Include(u => u.Book) .Include(u => u.Book.Author); case "BLOG": return _dataSet.Include(u => u.Blog); default: return base.Include(includeGroup); } } 

And then use it like this:

 var userRepo = new UserRepository(db.User); var userWithBooks = userRepo.Include("Book"); var firstUser = userWithBooks.FirstOrDefault(u => u.Name == "Foo"); var firstUserFirstBook = firstUser.Book.FirstOrDefault(); 

One option is to always include all the navigation properties (recursively), but that would be a terrible approach in terms of query efficiency, since each query will be one massive join with all related tables, regardless of whether it is necessary or not.

+1
source

All Articles