С# 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

  1. Поиск принципиально отличается, поскольку он получает объект, если он присутствует в отслеживании, перед запросом базы данных.

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

Ответ 5

Вы должны привести IQueryable к DbSet

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

return dbSet.Find(id);