C# Entity-Framework:如何在模型对象上组合 .Find 和 .Include?
Posted
技术标签:
【中文标题】C# Entity-Framework:如何在模型对象上组合 .Find 和 .Include?【英文标题】:C# Entity-Framework: How can I combine a .Find and .Include on a Model Object? 【发布时间】:2011-11-13 00:03:09 【问题描述】:我正在做 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 中进行一般性操作,请参阅我的回答 【参考方案1】:您可以先使用Include()
,然后从结果查询中检索单个对象:
Item item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.FirstOrDefault(x => x.ItemId == id);
【讨论】:
我真的推荐使用后者(SingleOrDefault),ToList 会先检索 所有 个条目,然后选择一个 如果我们有一个复合主键并且正在使用相关的查找重载,这就会崩溃。 这可行,但使用“Find”和“SingleOrDefault”是有区别的。 “Find”方法从本地跟踪存储中返回对象(如果存在),避免往返数据库,使用“SingleOrDefault”将强制查询数据库。 @Iravanchi 是正确的。这可能对用户有用,但据我所知,该操作及其副作用并不等同于 Find。 实际上并没有回答 ops 问题,因为它没有使用 .Find【参考方案2】:您必须将 IQueryable 强制转换为 DbSet
var dbSet = (DbSet<Item>) db.Set<Item>().Include("");
return dbSet.Find(id);
【讨论】:
dbSet 中没有 .Find 或 .FindAsync。这是 EF Core 吗? ef 核心上也有 ef 6 我满怀希望,然后“InvalidCastException”【参考方案3】:没有真正简单的方法来通过查找进行过滤。但我想出了一种接近的方法来复制该功能,但请注意我的解决方案的一些事项。
此解决方案允许您在不知道 .net-core 中的主键的情况下进行一般过滤
Find 完全不同,因为它在查询数据库之前获取实体(如果它存在于跟踪中)。
此外,它可以按对象过滤,因此用户不必知道主键。
此解决方案适用于 EntityFramework Core。
这需要访问上下文这里有一些扩展方法可以帮助你按主键进行过滤
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);
【讨论】:
您的解决方案认为主键名为 Id。 Expression.Constant(new id = id[i] ), "id")。您创建一个具有名为 id 的属性的匿名对象。这被翻译为 Id = ... 。我实际上测试它不仅阅读它。 @vaggelanos 不,那是表达式中的临时变量。 @vaggelanos 这也适用于复合键。我的项目中有这个工作。 看它返回Expression<Func<T, object[]>>
【参考方案4】:
丹尼斯的回答是使用Include
和SingleOrDefault
。后者往返于数据库。
另一种方法是使用Find
,结合Load
,显式加载相关实体...
下面an MSDN example:
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
会立即返回而不向存储区发出请求,如果该实体已被上下文加载。
【讨论】:
此方法使用Find
,因此如果实体存在,实体本身就不会往返于数据库。但是,对于Load
ing 的每个关系,您都会有一个往返,而 SingleOrDefault
与 Include
的组合会一次性加载所有内容。
当我比较 SQL 分析器中的 2 时,Find/Load 更适合我的情况(我有 1:1 的关系)。 @Iravanchi:你的意思是说如果我有 1:m 关系,它会调用 m 次商店吗?...因为没有多大意义。
不是 1:m 关系,而是多重关系。每次调用Load
函数时,都应在调用返回时填充关系。因此,如果您多次调用Load
为多个关系,每次都会有一个往返。即使对于单个关系,如果Find
方法在内存中找不到实体,它也会进行两次往返:一次用于Find
,第二次用于Load
。但据我所知,Include
.SingleOrDefault
方法一次性获取实体和关系(但我不确定)
如果能够以某种方式遵循 Include 设计,而不是必须以不同的方式处理集合和引用,那就太好了。这使得创建一个 GetById() 外观变得更加困难,它只接受一个可选的 Expression对我没用。但我通过这样做解决了它。
var item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.Where(x => x.ItemId == id)
.First();
不知道这是否是一个好的解决方案。但是丹尼斯给我的另一个在.SingleOrDefault(x => x.ItemId = id);
中给了我一个布尔错误@
【讨论】:
Dennis 的解决方案也必须有效。也许您在SingleOrDefault(x => x.ItemId = id)
中出现此错误只是因为错误的单=
而不是双==
?
是的,看起来你使用了 = 而不是 ==。语法错误;)
我都试过了 == 和 = 仍然给我一个错误 .SingleOrDefault(x => x.ItemId = id); =/ 必须是我的代码中的其他错误。但我做的方式不好?也许我不明白你的意思丹尼斯在他的代码中也有一个单一的 =。以上是关于C# Entity-Framework:如何在模型对象上组合 .Find 和 .Include?的主要内容,如果未能解决你的问题,请参考以下文章