С# Entity-Framework: как я могу объединить .Find и .Include в объекте модели?
Я занимаюсь учебным курсом mvcmusicstore. Я заметил что-то при создании эшафота для менеджера альбомов (добавьте удаление).
Я хочу написать код элегантно, поэтому я ищу чистый способ написать это.
FYI я делаю магазин более общим:
Альбомы = Элементы
Жанры = Категории
Исполнитель = Бренд
Вот как извлекается индекс (сгенерированный 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);
Ответы
Ответ 1
Сначала вам нужно использовать Include()
, а затем извлечь один объект из результирующего запроса:
Item item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.SingleOrDefault(x => x.ItemId == id);
Ответ 2
Ответ Денниса использует 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
немедленно возвращается без запроса в хранилище, если этот объект уже загружен контекстом.
Ответ 3
Не работает для меня. Но я решил это, сделав вот так.
var item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.Where(x => x.ItemId == id)
.First();
Не знаю, хорошо ли это решение. Но другой, которого дал Деннис, дал мне ошибку bool в .SingleOrDefault(x => x.ItemId = id);
Ответ 4
Там нет действительно простой способ отфильтровать с находкой. Но я придумал близкий способ повторить функциональность, но, пожалуйста, обратите внимание на несколько вещей для моего решения.
Это решение позволяет вам выполнять общую фильтрацию, не зная первичного ключа в .net-core
-
Поиск принципиально отличается, поскольку он получает объект, если он присутствует в отслеживании, перед запросом базы данных.
-
Кроме того, он может фильтровать по объекту, поэтому пользователю не нужно знать первичный ключ.
-
Это решение для 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);
Ответ 5
Вы должны привести IQueryable к DbSet
var dbSet = (DbSet<Item>) db.Set<Item>().Include("");
return dbSet.Find(id);