如何简化与实体框架的有多种关系的访问?

时间:2016-01-14 13:11:06

标签: c# entity-framework entity-framework-core

这是我想要做的:

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

目前我必须这样做。

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

我想简化这个的原因是因为我不想在每次想要访问关系时指定包含的内容。看起来非常麻烦。

例如:我希望能够执行以下操作,因为我之前已经检索过用户:

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

这是我到目前为止所做的:

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

然后将如何使用以下内容:

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

我的代码中还有一些其他逻辑,它进一步抽象出来,这将允许我做user.Book.First()。 希望我能获得许多开源的许可,因为我在ActiveRecord风格的crud之后建模了很多api。

请注意,我使用了我制作的一组扩展来帮助处理动态性问题而不那么痛苦:https://github.com/NullVoxPopuli/csharp-extensions

更新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);
        }
    }

目前,我已经对用户的演员进行了硬编码,试图让某些东西正常运转。 我现在的错误是:

Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.

1 个答案:

答案 0 :(得分:1)

这不是一个微不足道的问题,并且没有&#34;一个尺寸适合所有&#34;做法。你真正想要的是lazy loading, which was not included in EF7 for many reasons

我不知道您展示的代码应该做什么,但一种选择是引入一个存储库模式,您可以在其中指定要包含的&#34;实体。在收集层面:

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

您可以将许多逻辑移动到通用基类,只留下Include()方法。例如,您可以在显示(或枚举或...)时使用字符串,以仅选择要包含的相关实体:

public class GenericRepository
{
    // ...

    public IQueryable<User> Include(string includeGroup = null)
    {
        return IncludeGroup(includeGroup);
    }

    protected virtual IncludeGroup(string includeGroup)
    {
        return _dataSet;
    }
}

然后在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);
    }
}

然后像这样使用它:

var userRepo = new UserRepository(db.User);

var userWithBooks = userRepo.Include("Book");

var firstUser = userWithBooks.FirstOrDefault(u => u.Name == "Foo");

var firstUserFirstBook = firstUser.Book.FirstOrDefault();

一种替代方案是始终包含所有导航属性(递归),但就查询效率而言,这将是一种可怕的方法,因为每个查询将是对所有相关表的一个大规模连接,无论是否必要。