Entity Framework Lazy Loading with generic repository

▼魔方 西西 提交于 2019-12-23 03:31:46

问题


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?


回答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);
}


来源:https://stackoverflow.com/questions/15660619/entity-framework-lazy-loading-with-generic-repository

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!