【问题标题】:Entity Framework Multiple Include at runtime实体框架在运行时多重包含
【发布时间】:2018-09-03 18:17:26
【问题描述】:

我有一个服务,它传入参数来确定我想要包含多少导航属性。基于布尔参数,它连接一个实体列表以包括每个所需的外国实体。

在运行时,我希望不包含导航实体或包含许多实体。 我不能做的是带有.Include().Include 的菊花链,因为我不知道要根据传入的 args 包括哪些和多少。

我想实现这一点,但我似乎无法传入逗号分隔的实体列表。有什么想法吗?

var res = db.Entity.Include(entityListCommaSeparated).Where(_=>_.ID == ID).FirstOrDefault();

【问题讨论】:

  • 正如 Steve Py 下面提到的,我建议使用 lambda 表达式而不是魔术字符串来包含。如果你不得不重构,你会得到编译时错误,而不是更模糊的运行时数据库错误(或者更糟糕的是,没有错误并且没有返回包含)。
  • 我无法在这种情况下使用 lambda 表达式,因为我需要在运行时根据布尔包含参数更改包含。即 15 个布尔值,默认值为 false,其中可能包括相应导航属性的任意组合。使用 2^15 (32768) 个单独的 lambda 表达式来支持所有可能的包含组合。即 GetPolicy(int id, bool includeEntityA = false, bool includeEntityB = false, ....

标签: entity-framework


【解决方案1】:

这看起来像一个存储库模式,如果您想尝试从调用代码中“隐藏”EF / DbContext,通常会变得混乱。

您可以考虑几个选项:

  1. 降低复杂度漏洞:在适用的存储库方法中使用params Expression<Func<TEntity, object>>[] includes,然后准备好在您想要返回多个实体时也传递 OrderBy 表达式以及分页值。
  2. 通过简单的镜像:采用 IQueryable 作为返回类型,让消费者根据需要处理 Includes、OrderBy's、Counts/Any/Skip/Take/First/ToList 和 .Select()

选项 1:

public Order GetById(int id, params Expression<Func<Order, object>>[] includes)
{
     var query = db.Orders.Where(x => x.ID == id);
     // This part can be moved into an extension method or a base repository method.
     if(includes.Any)  
        includes.Aggregate(query, (current, include) => 
        {
            current.Include(include);
        }
     // Don't use .FirstOrDefault() If you intend for 1 record to be returned, use .Single(). If it really is optional to find, .SingleOrDefault()
     return query.Single();
}
//ToDo
public IEnumerable<Order> GetOrders(/* criteria?, includes?, order by?, (ascending/descending) pagination? */)
{ }
// or
public IEnumerable<Order> GetOrdersByCustomer(/* includes?, order by?, (ascending/descending) pagination? */)
{ }
// plus..
public IEnumerable<Order> GetOrdersByDate(/* includes?, order by?, (ascending/descending) pagination? */)
{ }
public bool CustomerHasOrders(int customerId)
{ }
public bool OrderExists(int id)
{ }
public int OrdersOnDate(DateTime date)
{ }
// etc. etc. etc.

请记住,这不处理自定义 order by 子句,返回实体列表的方法也需要这样做。您的存储库还需要公开.Any() (DoesExist) 的方法,因为每个人喜欢 在每次返回时检查#null。 :) 还有.Count()

选项 2:

public IQueryable<Order> GetById(int id)
{
    return db.Orders.Where(x => x.ID == id);
}
public IQueryable<Order> GetOrders()
{
    return db.Orders.AsQueryable();
}

调用者可以在调用.Single() 之前了解Linq 和.Include() 他们想要什么,或者执行.Any().. 他们可能不需要整个实体图,因此他们可以在没有@ 的情况下从实体和相关实体.Select() 987654331@ 编写和执行更有效的查询以填充 ViewModel / DTO。 GetById 可能会在许多地方使用,因此我们可以减少重复并在存储库中支持它。我们不需要所有的过滤场景等,调用者可以调用 GetOrders,然后在他们认为合适的时候进行过滤。

如果存储库只返回 DBSet,为什么还要麻烦它?

  1. 集中低级数据过滤。例如,如果您使用软删除 (IsActive) 或正在运行多租户或显式授权。这些通用规则可以集中在存储库级别,而不必在任何涉及 DbSet 的地方记住。
  2. 测试更简单。虽然您可以模拟 DbContext,或将其指向内存数据库,但模拟返回 IQueryable 的存储库更简单。 (只需填写 List&lt;TEntity&gt; 并返回 .AsQueryable()
  3. 存储库处理创建和删除。创建以充当工厂,以确保为可行的实体建立所有必需的数据和关系。删除以处理数据库在幕后处理之外的软删除场景、级联/审计等。

【讨论】:

  • 我已经尝试了以下与您的答案类似的方法,但我遗漏了一些东西。 // 这个硬编码行有效 policy = db.Policys.Include("EntityA").Include("EntityB").Where(_ => .ID == ID).FirstOrDefault(); // 此代码不起作用 IQueryable policies = db.Policys.Where( => _.ID == ID); foreach (string entity in include.Split(',') { policies.Include(entity); } policy = policies.FirstOrDefault(); 以上适用于顶级对象父对象,但不适用于外部实体。它们返回 null .
  • foreach (string entity in include.Split(',') { policies = policies.Include(entity); } 您错过了返回策略的分配。
猜你喜欢
  • 1970-01-01
  • 2011-06-22
  • 1970-01-01
  • 2020-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
相关资源
最近更新 更多