【问题标题】:Using .Find() & .Include() on the same query在同一个查询上使用 .Find() 和 .Include()
【发布时间】:2013-07-10 16:43:26
【问题描述】:

我有以下从带有存储库的脚手架模板自动生成的方法:-

public Group Find(int id)
{
    return context.Groups.Find(id);
}

但是由于 Groups 对象有两个我需要的导航属性,所以我想包含 .Include,所以我将 .find 替换为 .where :-

public Group Find(int id)
{
    return context.Groups.Where(c=>c.GroupID==id)
                         .Include(a => a.UserGroups)
                         .Include(a2 => a2.SecurityRoles)
                         .SingleOrDefault();
}

但我的问题是如何将.Include.find() 一起应用,而不是使用.Where()

【问题讨论】:

  • 您可以通过将过滤器传递给单个调用并删除 where 子句来进一步简化,例如 .SingleOrDefault(c=>c.GroupID==id)
  • 那么 .Where 和 .Find 有什么区别??
  • 看看where和find的区别,msdn.microsoft.com/en-us/data/jj573936.aspx,基本上find只能按键搜索,如果你之前查询过实体,会有细微的优化。这是优化实际上是你不能使用包含的根本原因
  • 您可以在 EF core 5.0 return context.Groups.Include(a => a.UserGroups).Include(a2 => a2.SecurityRoles).FirstOrDefault(c => c.GroupID == id); 中执行此操作

标签: entity-framework


【解决方案1】:

我只是在想 find 实际上做了什么。 @lazyberezovsky 是对的 include 和 find 不能相互结合使用。我认为这是经过深思熟虑的,原因如下:

DbSet 上的 Find 方法使用主键值来尝试查找 由上下文跟踪的实体。如果实体不在 上下文然后查询将被发送到数据库以查找实体 那里。如果在上下文中找不到实体,则返回 Null 或 在数据库中。

查找与使用查询在两个重要方面不同:

  • 只有在上下文中找不到具有给定键的实体时,才会对数据库进行往返。
  • Find 将返回处于已添加状态的实体。也就是说,Find 将返回已添加到上下文中但具有 尚未保存到数据库中。

(来自http://msdn.microsoft.com/en-us/data/jj573936.aspx

因为 find 是一种优化的方法,它可以避免需要访问服务器。如果您已经跟踪了实体,那就太好了,因为 EF 可以更快地返回它。

但是,如果它不仅仅是我们所追求的实体(例如,我们想要包含一些额外的数据),则无法知道该数据是否已经从服务器加载。虽然 EF 可能会结合连接进行这种优化,但由于它对数据库状态做出假设,因此很容易出错。

我认为 include 和 find 不能一起使用是一个非常慎重的决定,以确保数据完整性和不必要的复杂性。它更清洁,更简单 当您想要进行连接时,总是去数据库执行连接。

【讨论】:

  • 但是找到一个处于已添加状态但尚未添加到数据库的对象的想法很可怕,并且可能导致结果不一致,您怎么看?
  • @johnG 是的,我同意这有点奇怪,我不是 100% 了解这方面的规则,但这可能仅适用于具有手动定义键的实体。如果它与具有自动生成的密钥的实体一起使用,那么当您实际将它们添加到数据库时,他们肯定不会真正获得您认为的密钥。无论哪种情况,我个人的偏好一直是使用 .SingleOrDefault 或 .FirstOrDefault 在数据库中查找实体
【解决方案2】:

你不能。 Find 方法在 DbSet<T> 类型上定义,它返回实体。你不能在实体上调用Include,所以唯一可能的选择是调用Find之后Include。您需要DbSet<T> 类型,但Include("UserGroups") 将返回DbQuery<T>Include(g => g.UserGroups) 也将返回DbQuery<T>

public static IQueryable<T> Include<T>(this IQueryable<T> source, string path) 
    where T: class
{
    RuntimeFailureMethods.Requires(source != null, null, "source != null");
    DbQuery<T> query = source as DbQuery<T>;
    if (query != null)    
        return query.Include(path); // your case
    // ...
}

DbQuery&lt;T&gt; 不是DbSet&lt;T&gt; 的子代,因此方法Find 不可用。还要记住,Find 首先在本地对象中查找实体。如果它们尚未加载,它将如何包含一些引用的实体?

【讨论】:

  • 在那张纸条上,我不是 find 的忠实粉丝,.FirstOrDefault() 或 .SingleOrDefault() 实现了类似的目的并适用于任何 IEnumerable
  • @LukeMcGregor 我相信 First() 和类似的东西总会进入数据库。
  • 如果您调用 Include,它无论如何都会访问数据库以获取辅助数据,如果可能的话,最好在一个查询中完成所有操作。除非它是一个优化不佳或需要花费大量时间或服务器资源的大型表,否则最好只使用 FirstOrDefault。我最喜欢 Find 的一点是,我的存储库不需要知道关键字段的名称,尽管...
  • Find 在您处于设计时不知道要查找的类型且无法使用FirstOrDefault() 或任何类似方法的通用场景中非常方便。
【解决方案3】:

您可以尝试这样做:

public static class DbContextExtention
{
    public static TEntity FirstOfDefaultIdEquals<TEntity, TKey>(
        this IQueryable<TEntity> source, TKey otherKeyValue)
        where TEntity : class
    {
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var property = Expression.Property(parameter, "ID");
        var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
        var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
        return source.FirstOrDefault(lambda);
    }
    public static TEntity FirstOfDefaultIdEquals<TEntity>(
        this ObservableCollection<TEntity> source, TEntity enity)
        where TEntity : class
    {
        var value = (int)enity.GetType().GetProperty("ID").GetValue(enity, null);
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var property = Expression.Property(parameter, "ID");
        var equal = Expression.Equal(property, Expression.Constant(value));
        var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
        var queryableList = new List<TEntity>(source).AsQueryable();
        return queryableList.FirstOrDefault(lambda);
    }
}

GetById:

public virtual TEntity GetByIdInclude(TId id, params Expression<Func<TEntity, object>>[] includes)
    {
        var entry = Include(includes).FirstOfDefaultIdEquals(id);
        return entry;
    }

方法包括 EntityFramework Core (look here(EF6 and EF Core)):

protected IQueryable<TEntity> Include(params Expression<Func<TEntity, object>>[] includes)
    {
        IIncludableQueryable<TEntity, object> query = null;

        if (includes.Length > 0)
        {
            query = DbSet.Include(includes[0]);
        }
        for (int queryIndex = 1; queryIndex < includes.Length; ++queryIndex)
        {
            query = query.Include(includes[queryIndex]);
        }

        return query == null ? DbSet : (IQueryable<TEntity>)query;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    • 2012-03-09
    • 1970-01-01
    相关资源
    最近更新 更多