首页 > 解决方案 > 实体框架在运行时多重包含

问题描述

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

在运行时,我想不包含导航实体或包含许多实体。我不能做的是菊花链,.Include().Include因为我不知道基于传入的 args 包含哪些以及包含多少。

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

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

标签: entity-framework

解决方案


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

您可以考虑几个选项:

  1. 降低复杂度:params Expression<Func<TEntity, object>>[] includes在适用的存储库方法中使用 a ,然后准备在要返回多个实体时传递 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();
}

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

如果存储库只返回 DBSet,为什么还要打扰它呢?

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

推荐阅读