1

My current project uses a generic repository interface, thus:

public interface IDataSource : IDisposable
{
    void Add<T>(T newItem) where T : EntityBase;

    T Get<T>(Guid id) where T : EntityBase;
    T Get<T>(Expression<Func<T, bool>> predicate) where T : EntityBase;
    IQueryable<T> GetAll<T>(Expression<Func<T, bool>> predicate = null) where T : EntityBase;
    int Count<T>(Expression<Func<T, bool>> predicate = null) where T : EntityBase;
    bool Any<T>(Expression<Func<T, bool>> predicate = null) where T : EntityBase;

    void Update<T>(T updated) where T : EntityBase;

    void Delete<T>(Guid id) where T : EntityBase;
    void Delete<T>(T item) where T : EntityBase;

    void Commit();
}

As an example, the Get method looks like this:

public T Get<T>(Expression<Func<T, bool>> predicate) where T : EntityBase
{
    return db.Set<T>().Single(predicate);
}

where db is an instance my data context, which extends Entity Framework's DbContext. The whole thing implements IDisposable so that I can use it in a scope block for unit-of-work pattern, waiting to the end before committing changes, or disposing the entire thing if something goes wrong before that.

This interface is used by a logic layer to handle more complex queries, to keep business logic entirely separated from data access. So, a query to that layer might go like this:

public List<Product> ItemsBoughtByCustomer(Guid customerID)
{
    using (var db = DataAccess.GetContext())
    {
        List<Purchase> purchaseHistory = db.GetAll<Purchase>(p => p.CustomerID == customerID);
        List<int> IDs = purchaseHistory.Select(p => p.ProductID);
        return db.GetAll<Product>(p => IDs.Contains(p.ID));
    }
}

(Yes, I realise that can be condensed; it is in the app, but for an example, this is clearer.)

My problem is that sometimes I return a set of objects, and then later I might want to get to some of the things it references. For example, when I get a Product to display, the Display might want to do this:

@foreach (Comment comment in Product.Comments)
{
    <div class="comment">
        <span>@Html.UserDisplay(comment.Author)</span>
        <span>@comment.Body</span>
    </div>
}

(ignore the quality of the HTML; again, it's a quick example)

The problem is that this throws errors when Entity Framework's lazy loading leaves these properties null when returning entities from my queries. Now, I'm aware of the Include() method, but if my repository is generic then it's difficult to apply those. I could turn it off entirely, but then EF will start retrieving enormous linked collections of things when I don't need them - the structure of my model and the links that things have out to the audit logs mean a lot of links for EF to follow.

Is there a way that I can lazy-load in a slightly smarter manner? Is there a method like .Single() and .Where() that I can call on the DbSet that will bring child objects as well, so that I can specifically ask for child objects to be included for a certain query?

anaximander
  • 7,083
  • 3
  • 44
  • 62
  • do you try to instanciate your IRepository at controller level, as a parameter of the controller constructor. Then give the the IRepository as a parameter to the business method called in the controller action. As the controller ends after the view render, lazy loading will be available in the view. BTW: it is not a good practice, you should use a model class with the view, but sometimes it helps. – tschmit007 Mar 27 '13 at 14:45
  • My repository is injected into the controller by Ninject when the controller is created. Also, I do create separate object to pass to the view in many cases, but in some places the view model ends up needing all the same properties as the database object, so there seems little point having a separate class as they'd be pretty much identical. – anaximander Mar 27 '13 at 15:09

1 Answers1

1

add an optional parameter for the include path then invoke Include(str) on the DbSet. Example with your Get method:

public T Get<T>(Expression<Func<T, bool>> predicate, string includePath = null) where T : EntityBase
{
    var query = db.Set<T>();

    if( !string.IsNullorWhitespace( includePath ) )
    {
        query = query.Include( includePath );
    }

    return query.Single(predicate);
}
Moho
  • 15,457
  • 1
  • 30
  • 31
  • So, to include the `Company` who sells a `Product`, which in the model is `Product.Vendor`, the syntax would be `Get(p => p.SomeProp == thisValue, "Vendor");`? I try to avoid magic strings; could I pass in an `Expression` instead? [This article](http://msdn.microsoft.com/en-us/data/jj574232.aspx) seems to show `.Include()` taking expressions... – anaximander Mar 27 '13 at 15:21
  • Not if you're using DbContext/DbSet. Here's the MSDN page (no overloads): http://msdn.microsoft.com/en-us/library/gg696785(v=vs.103).aspx – Moho Mar 27 '13 at 15:33
  • and i agree with you; I would prefer and selector expression as well – Moho Mar 27 '13 at 15:34
  • and yes, to eager load the Vendor property of the Product entity, you would pass in "Vendor". This solution allows for only one eager loaded property, but you could pass in an IEnumerable and then iterate through with multiple .Include(...) calls if you need such functionality – Moho Mar 27 '13 at 15:36