C#实体框架:如何在模型对象上组合.Find和.Include?


145

我正在做mvcmusicstore练习教程。在为相册管理器创建支架(添加删除编辑)时,我注意到了一些东西。

我想优雅地编写代码,因此我正在寻找一种简洁的编写方式。

仅供参考,我正在使商店更通用:

专辑=项目

流派=类别

艺术家=品牌

这是检索索引的方式(由MVC生成):

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

这是检索要删除的项目的方式:

Item item = db.Items.Find(id);

第一个带回所有项目,并在项目模型中填充类别和品牌模型。第二个,不填充类别和品牌。

从理论上讲,我该如何编写第二个代码来进行查找并填充内部内容(最好在1行中)...

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

如果有人需要在.net-core中进行此操作,请参阅我的回答
johnny

Answers:


162

您需要先使用Include(),然后从结果查询中检索单个对象:

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);

24
我真的建议使用后者(SingleOrDefault),ToList将首先检索所有条目,然后选择一个
Sander Rijken 2011年

5
如果我们有一个复合主键并且正在使用相关的find重载,则此操作会失败。
jhappoldt 2011年

78
这可以工作,但是使用“查找”和“ SingleOrDefault”之间是有区别的。“查找”方法从本地跟踪存储中返回对象(如果存在),从而避免了往返数据库的麻烦,在此情况下,使用“ SingleOrDefault”将强制查询到数据库。
Iravanchi 2012年

3
@Iravanchi是正确的。据我所知,这可能对用户有用,但是该操作及其副作用并不等同于Find。
mwilson

3
实际上没有回答ops问题,因为它没有使用.Find
Paul Swetz,

73

丹尼斯的答案是使用IncludeSingleOrDefault。后者往返于数据库。

一种替代方法是Find结合使用和Load,以显式加载相关实体...

MSDN示例下面:

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

当然Find,如果上下文已经加载了该实体,则立即返回而不向存储请求。


30
此方法使用这种方法Find,如果存在实体,则实体本身就不会往返于数据库。但是,对于要建立的每种关系,您都会有一个往返行程Load,而与的SingleOrDefault组合可以Include一次性加载所有内容。
Iravanchi 2014年

当我在SQL事件探查器中比较2时,对于我的情况,“查找/装入”更好(我有1:1关系)。@Iravanchi:你的意思是说,如果我有1:m的恋爱关系,那它会打电话给商店的m倍?……因为没有太大意义。
学习者2014年

3
不是1:m关系,而是多个关系。每次调用该Load函数时,调用返回时都应填充该关系。因此,如果您Load多次呼叫多个关系,那么每次都会有一次往返。即使对于单个关系,如果该Find方法未在内存中找到该实体,它也会进行两次往返:一次为Find,第二次为Load。但是IncludeSingleOrDefault据我所知,这种方法可以一次获取实体和关系(但我不确定)
Iravanchi 2014年

1
如果能够以某种方式遵循Include设计,而不必以不同的方式对待集合和引用,那将是很好的。这使得创建一个仅获取Expression <Func <T,object >>的可选集合的GetById()门面变得更加困难(例如_repo.GetById(id,x => x.MyCollection))
Derek Greer

4
请注意提及您的文章的参考:msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein

1

您必须将IQueryable强制转换为DbSet

var dbSet = (DbSet<Item>) db.Set<Item>().Include("");

return dbSet.Find(id);


dbSet中没有.Find或.FindAsync。这是EF核心吗?
蒂埃里

EF核心上也有EF 6
拉斐尔·R·索萨

我满怀希望,然后是“ InvalidCastException”
ZX9

0

没有为我工作。但是我这样做是解决了这个问题。

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

不知道那是一个好的解决方案。但是丹尼斯给的另一个人给了我一个错误 .SingleOrDefault(x => x.ItemId = id);


4
丹尼斯的解决方案也必须有效。也许SingleOrDefault(x => x.ItemId = id)仅因为错误的单=而不是双的错误而导致此错误==
Slauma 2011年

6
是的,看起来您使用过=不==。语法错误;)
拉尔夫N

我尝试了它们==和=仍然给我一个错误.SingleOrDefault(x => x.ItemId = id); = /必须是我的代码中有其他错误的地方。但是我的做法是不好的方法?也许我不明白您的意思,丹尼斯在他的代码中也有一个singel =。
约翰

0

没有真正简单的方法来查找结果。但是我想出了一种复制功能的接近方法,但是请注意我的解决方案的一些注意事项。

此解决方案使您可以在不知道.net-core中主键的情况下进行常规筛选

  1. Find从根本上有所不同,因为它在查询数据库之前在跟踪中存在时会获取实体。

  2. 此外,它可以按对象进行筛选,因此用户不必知道主键。

  3. 此解决方案适用于EntityFramework Core。

  4. 这需要访问上下文

以下是一些扩展方法,可以帮助您按主键进行过滤,因此

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

一旦有了这些扩展方法,就可以像这样进行过滤:

query.FilterByPrimaryKey(this._context, id);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.