Выбор нескольких столбцов для обновления в Linq

#c# #sql #entity-framework #linq #.net-core

#c# #sql #сущность-фреймворк #linq #.net-ядро

Вопрос:

У меня есть таблица продуктов, которая содержит тысячи продуктов. Я хочу обновить только два столбца (price, isAvailable) этой таблицы. Итак, есть ли способ выбрать только эти два столбца из этой таблицы?

Это код, который я использую. Но я не хочу выделять все столбцы.

      var dbModels = await DbContext.Products
                    .Where(x => x.SellerId == sellerId)
                    .ToListAsync(); 
 

Я уже пробовал это делать

        var db = await DbContext.ProductSkuDetail
                .Where(x => x.SellerId == sellerId)
                .Select(y => new
                {
                    Price = y.Price,
                    IsAvailable = y.IsAvailable
                }).ToListAsync();
 

Но это доступно только для чтения. Я хочу обновить эти столбцы.

Комментарии:

1. С помощью y => new { вы создаете анонимный класс (оболочку). Что, если вы просто удалите Select и обновите только нужные вам столбцы.

2. Спасибо Джеруну. Если я удалю Select, то все столбцы будут выбраны правильно?

3. Вы спрашиваете, как запросить объект и вернуть только 2 столбца? Или у вас уже есть весь объект, и вы просто хотите обновить 2 столбца?

4. «Я хочу обновить эти столбцы». из вашего кода неясно, откуда должны поступать новые значения.

5. Если вам нужно обновить только несколько столбцов, лучше использовать FromSqlRaw или даже Dapper и выполнить UPDATE напрямую. LINQ не изменяет данные. ORM, EF, выполняет. ORM работает с объектами , а не с отдельными свойствами. Когда вы загружаете объект, EF отслеживает, какие свойства изменяются, и генерирует соответствующие инструкции UPDATE.

Ответ №1:

Вы должны включить первичный ключ в анонимный тип:

 var models = await context.Products
    .Where(p => p.SellerId == sellerId)
    .Select(p => new
    {
        Id = p.Id, // primary key
        Price = p.Price,
        IsAvailable = p.IsAvailable
    })
    .ToListAsync();
 

Затем, когда вам нужно сохранить данные обратно в базу данных, вам нужно создать сущности с тем же первичным ключом и присоединить их к контексту.

 foreach (var x in models)
{
    var product = new Product
    {
        Id = x.Id,
        Price = newPrice, // get new price somehow
        IsAvailable = false // get new availability somehow
    };
    context.Attach(product);

    var entry = context.Entry(product);
    entry.Property("Price").IsModified = true;
    entry.Property("IsAvailable").IsModified = true;
}

await context.SaveChangesAsync();
 

Дополнительная информация о Прикреплении

Комментарии:

1. Это приводит к созданию наполовину созданного недопустимого объекта. Чтобы это сработало, все остальные свойства должны быть обнуляемыми и не иметь проверки

2. Attach оставляет объект в Unchanged состоянии. Это не должно приводить к появлению каких-либо обновлений. Я что-то упускаю из виду?

3. Я считаю, что единственная строка, которая «может» отсутствовать, — это an entry.State = EntityState.Modified; . В противном случае сам код выглядит прямо из docs.microsoft.com сама по себе. Также отсутствует строка для отключения проверок проверки, поскольку остальные свойства, имеющие значение null / defaults, могут вызывать ошибки проверки.

Ответ №2:

Да, есть способ точно указать, какие столбцы вы хотите.

Нет, вы не можете использовать этот метод для обновления данных.

При извлечении данных с помощью entity Framework DbSet<...> существует два метода: извлекать полную строку таблицы или извлекать только определенные свойства строки.

Первый метод используется, если вы выполняете запрос без использования Select . Если вы сделаете это, данные будут скопированы в DbContext.ChangeTracker.

Другие методы, такие как DbSet.Find и IQueryble.Включение также скопирует выбранные данные в ChangeTracker.

Если вы используете Select для указания данных, которые вы хотите извлечь, то извлеченные данные не будут скопированы в ChangeTracker.

При вызове DbContext.SaveChanges ChangeTracker используется для определения того, какие элементы изменены или удалены и, следовательно, нуждаются в обновлении.

Средство отслеживания изменений сохраняет исходные извлеченные данные и их копию. Вы получаете ссылку на копию в результате ваших изменений. Поэтому всякий раз, когда вы изменяете значения свойств вашей ссылки на копию, они изменяются в копии, которая находится в ChangeTracker.

При вызове SaveChanges копия сравнивается с оригиналом в ChangeTracker, чтобы определить, какие свойства были изменены.

Для повышения эффективности, если вы не планируете обновлять извлеченные данные, разумно убедиться, что извлеченные данные отсутствуют в ChangeTracker.

При использовании entity Framework для извлечения данных всегда используйте Select и извлекайте только те свойства, которые вы действительно планируете использовать. Запрашивайте только без выбора, если вы планируете изменить выбранные данные.

Изменить = обновить свойства или удалить всю строку. Также: используйте Find и Include только в том случае, если вы планируете обновить извлеченные данные.

Вы хотите обновить выбранную строку

Следовательно, вам нужно получить полную строку: не используйте Select, извлеките полную строку.

Если вы хотите получить элемент по первичному ключу, рассмотрите возможность использования DbSet.Find . Это имеет небольшую оптимизацию, которая заключается в том, что если она уже есть в ChangeTracker, то данные не будут извлечены снова.

Подумайте о том, чтобы написать SQL для этого

Обычно вам не нужно регулярно обновлять тысячи элементов. Однако, если вам приходится делать это часто, подумайте об обновлении с использованием sql:

 using (var dbContext = new MyDbContext(...))
{
    const string sqlText = @"Update products
        SET Price = @Price, IsAvailable = @IsAvailable....
        Where SellerId = @SellerId;";
    var parameters = new object[]
    {
        new SqlParameter("@SellerId", sellerId),
        new SqlParameter("@Price", newPrice),
        new SqlParameter("@IsAvailable", newAvailability),
    };
    dbContext.DataBase.ExecuteSqlCommand(sqlText, parameters);
}
 

(Вам нужно будет проверить правильность команды SQL в моем примере. Поскольку я использую entity framework, мой SQL немного устарел.)

Кстати: хотя этот метод очень эффективен, вы потеряете преимущества entity framework: отделение фактической базы данных от структуры таблицы: имена ваших таблиц и столбцов просачиваются до этого утверждения.

Мой совет состоял бы только в том, чтобы использовать прямой SQL для повышения эффективности: если вам приходится обновляться довольно часто. Ваш DbContext скрывает внутреннюю структуру вашей базы данных, поэтому сделайте этот метод частью вашего DbContext

 public void UpdatePrice(int sellerId, bool IsAvailable, decimal newPrice)
{
    const string sqlText = ...
    var params = ...
    this.Database.ExecuteSqlCommand(sqlText, params);
}
 

Увы, вам придется вызывать это один раз за обновление, нет команды SQL, которая обновит тысячи товаров с разными ценами в одной SqlCommand
}

Ответ №3:

Вы можете использовать команду ExecuteSqlCommandAsync для записи запроса, который будет выполнен на вашем SQL Server.

 await dbContext
.Database
.ExecuteSqlCommandAsync("UPDATE Products SET Price = {0}, IsAvailable = {1} WHERE SelleriId = {2}", new object[] { priceValue, isAvailableValue, sellerId });
 

Ответ №4:

EF плохо подходит для пакетных операций.

Он работает с объектами, а не с таблицами, записями или полями. Если вы хотите обновить или удалить объект (ы), вам нужно сначала прочитать его (их). Это позволяет EF change tracker отслеживать изменения значений полей или состояния всего объекта и генерировать соответствующий SQL.

Для пакетных операций рассмотрите возможность использования необработанных SQL-запросов, легковесных библиотек, таких как Dapper, или сторонних пакетов, таких как Entity Framework Plus.