Как обновить запись в ADO.NET EF 4.1?
Я пытаюсь выполнить что-то действительно простое, и я не могу найти, как это сделать, используя Entity Framework 4.1.
Мне нужен метод контроллера, который принимает объект, а затем выполняет UPSERT (либо вставку, либо обновление в зависимости от того, существует ли запись в базе данных).
Я использую естественный ключ, поэтому мне не нужно смотреть на мой POCO и сказать, что он новый или нет.
Вот как я это делаю, и мне кажется неправильным:
[HttpPost]
public JsonResult SaveMyEntity(MyEntity entity)
{
MyContainer db = new MyContainer(); // DbContext
if (ModelState.IsValid)
{
var existing =
db.MyEntitys.Find(entity.MyKey);
if (existing == null)
{
db.MyEntitys.Add(entity);
}
else
{
existing.A = entity.A;
existing.B = entity.B;
db.Entry(existing).State = EntityState.Modified;
}
db.SaveChanges();
return Json(new { Result = "Success" });
}
}
В идеале все это было бы примерно так:
db.MyEntities.AddOrModify(entity);
Ответы
Ответ 1
К сожалению, нет способа сделать это без запроса базы данных или использования хранимой процедуры. Минималистичный код должен быть:
public void AddOrModify<T>(T entity, string key) where T : class, IEntity // Implements MyKey
{
using (var context = new MyContainer())
{
if (context.Set<T>().Any(e => e.MyKey == key))
{
context.Entry(entity).State = EntityState.Modified;
}
else
{
context.Entry(entity).State = EntityState.Added;
}
context.SaveChanges();
}
}
Ответ 2
В большинстве случаев вам не нужно явно устанавливать EntityState.Modified
, если вы не отключили отслеживание изменений.
Мы решили проверить значение идентификатора объекта:
if (entity.Id == default(int)) {
// transient entity so insert
} else {
// update
}
Ответ 3
Чтобы выполнить операцию UPSERT, вам может потребоваться создать SP, который выполняет MERGE.
http://www.databasejournal.com/features/mssql/article.php/3739131/UPSERT-Functionality-in-SQL-Server-2008.htm
Каким бы способом вы ни выбрали операцию, она должна быть атомарной или у вас будет состояние гонки.
Вашему SP, вероятно, понадобится HOLDLOCK, чтобы аннулировать это...
http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
Ответ 4
на самом деле существует способ информировать контекст db, что объект, который вы пытаетесь вставить, изменяется, а теперь новый
_context.MyEntity.Attach(entity);
_context.MyEntity(entity).State = System.Data.EntityState.Modified;
Ответ 5
Я знаю, что этот вопрос старый и имеет принятый ответ, но я думаю, что есть лучшее решение: для него не требуется реализовать дополнительный интерфейс или тип ключа.
public static class DbSetExtensions
{
public static EntityEntry<TEnt> AddIfNotExists<TEnt, TKey>(this DbSet<TEnt> dbSet, TEnt entity, Func<TEnt, TKey> predicate) where TEnt : class
{
var exists = dbSet.Any(c => predicate(entity).Equals(predicate(c)));
return exists
? null
: dbSet.Add(entity);
}
public static void AddRangeIfNotExists<TEnt, TKey>(this DbSet<TEnt> dbSet, IEnumerable<TEnt> entities, Func<TEnt, TKey> predicate) where TEnt : class
{
var entitiesExist = from ent in dbSet
where entities.Any(add => predicate(ent).Equals(predicate(add)))
select ent;
dbSet.AddRange(entities.Except(entitiesExist));
}
}
Поэтому его можно использовать следующим образом:
using (var context = new MyDbContext())
{
var user1 = new User { Name = "Peter", Age = 32 };
context.Users.AddIfNotExists(user1, u => u.Name);
var user2 = new User { Name = "Joe", Age = 25 };
context.Users.AddIfNotExists(user2, u => u.Age);
// Adds user1 if there is no user with name "Peter"
// Adds user2 if there is no user with age 25
context.SaveChanges();
}