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