c#实体框架:如何在模型对象上组合.find和.include
本文关键字:对象 组合 find include 模型 框架 实体 | 更新日期: 2023-09-27 18:08:03
我正在做mvcmusicstore练习教程。在为专辑管理器创建脚手架(添加删除编辑)时,我注意到一些事情。
我想写优雅的代码,所以我正在寻找一个干净的方式来写这个。
仅供参考,我正在使商店更通用:
Albums = Items
genre = Categories
Artist = Brand
这是如何检索索引(由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);
您可以先使用Include()
,然后从结果查询中检索单个对象:
Item item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.FirstOrDefault(x => x.ItemId == id);
Dennis的答案是使用Include
和SingleOrDefault
。后者往返于数据库。
另一种选择是将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
将立即返回,而无需向store发出请求。
没有真正简单的方法来过滤查找。但我已经想出了一个接近的方法来复制功能,但请注意我的解决方案的一些事情。
此解决方案允许您在不知道.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);
不适合我。但是我是这样解决的。
var item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.Where(x => x.ItemId == id)
.First();
不知道这是不是一个好的解决方案。但是Dennis给出的另一个在.SingleOrDefault(x => x.ItemId = id);
在这种情况下,您必须使用DbSet<T>.Local
。
你不能组合DbSet<T>.Find(object[] params)
做你想做的事情,因为如果实体当前没有被上下文附加和跟踪,它将查询数据库。
DbSet<T>.SingleOrDefault<T>
、DbSet<T>.FirstOrDefault<T>
和相关方法的实现也会在调用时立即查询数据库。
假设您有类型MyEntity
,属性Id
返回int
,您可以创建如下方法,或调整它以满足您的特定需求。
public MyEntity FindLocalOrRemote(int id)
{
MyEntity entity =
context.MyEntities
.Local
.SingleOrDefault(p => p.Id == id)
??
context.MyEntities
.Include(p => p.PackItems)
.SingleOrDefault(p => p.PackId == id);
return entity;
}
这种方法的一个缺点,很可能是没有内置方法的原因,可能是由于围绕键值设计API的挑战,或者因为使用DbSet<T>.Local
不能保证附加和跟踪的实体具有从数据库填充的相关导航属性。
这个问题真的很老了,但是没有一个人给出简单或正确的答案。
这将适用于实体框架6或实体框架核心
必须将IQueryable类型转换为DbSet
var dbSet = (DbSet<Item>) db.Set<Item>().Include("");
return dbSet.Find(id);