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

我正在做 mvcmusicstore 实践教程,在为专辑管理器创建脚手架时我注意到了一些东西(添加删除编辑)。

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

顺便说一句,我要让这家店更普通:

相册 = 物品

流派 = 类别

艺术家 = 品牌

下面是如何检索索引(由 MVC 生成) :

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

以下是检索删除项的方式:

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

第一个方法返回所有项目,并在项目模型中填充类别和品牌模型。第二个,没有填充类别和品牌。

我如何写第二个函数来查找和填充里面的内容(最好是在一行中) ... ... 理论上——比如:

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

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

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

对我不起作用,但是我通过这样做解决了它。

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

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

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

另一种方法是结合使用 LoadFind来显式加载相关实体..。

一个 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将立即返回,而不会向存储发出请求。

没有真正简单的方法来过滤搜索结果。但是我已经想出了一个近似的方法来复制这个功能,但是请注意我的解决方案中的一些事情。

此解决方案允许您在不知道. net-core 中的主键的情况下进行通用过滤

  1. 查找是完全不同的,因为如果实体在查询数据库之前出现在跟踪中,它就会获取该实体。

  2. 此外,它还可以通过对象进行筛选,这样用户就不必知道主键。

  3. 此解决方案适用于 EntityFrameworkCore。

  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);

必须将 IQueryable 强制转换为 DbSet

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

return dbSet.Find(id);

在这种情况下,必须使用 DbSet<T>.Local

不能将 DbSet<T>.Find(object[] params)组合起来执行所需操作,因为如果实体当前没有被上下文附加和跟踪,它将查询数据库。

DbSet<T>.SingleOrDefault<T>DbSet<T>.FirstOrDefault<T>和相关方法的实现也将在调用后立即查询数据库。

假设类型 MyEntity具有返回 int的属性 Id,那么您可以创建如下方法,或者对其进行调整以满足您的特定需求。

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或实体框架核心一起工作。