У кого-нибудь есть предложения по наиболее эффективному способу реализации логики «обновить строку, если она существует, иначе вставка» с использованием Entity Framework?
У кого-нибудь есть предложения по наиболее эффективному способу реализации логики «обновить строку, если она существует, иначе вставка» с использованием Entity Framework?
Ответы:
Если вы работаете с прикрепленным объектом (объект загружен из того же экземпляра контекста), вы можете просто использовать:
if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
context.MyEntities.AddObject(myEntity);
}
// Attached object tracks modifications automatically
context.SaveChanges();
Если вы можете использовать какие-либо знания о ключе объекта, вы можете использовать что-то вроде этого:
if (myEntity.Id != 0)
{
context.MyEntities.Attach(myEntity);
context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
context.MyEntities.AddObject(myEntity);
}
context.SaveChanges();
Если вы не можете определить наличие объекта по его идентификатору, вы должны выполнить поисковый запрос:
var id = myEntity.Id;
if (context.MyEntities.Any(e => e.Id == id))
{
context.MyEntities.Attach(myEntity);
context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
context.MyEntities.AddObject(myEntity);
}
context.SaveChanges();
using
блок. Можно ли оставить контекст в памяти на некоторое время? Например, при жизни формы Windows? Я обычно пытаюсь очистить объекты базы данных, чтобы обеспечить минимальную нагрузку на базу данных. Нет ли проблем в ожидании уничтожения моего контекста EF?
Начиная с Entity Framework 4.3, AddOrUpdate
в пространстве имен есть метод System.Data.Entity.Migrations
:
public static void AddOrUpdate<TEntity>(
this IDbSet<TEntity> set,
params TEntity[] entities
)
where TEntity : class
который по документу :
Добавляет или обновляет объекты по ключу при вызове SaveChanges. Эквивалент операции "upsert" из терминологии базы данных. Этот метод может быть полезен при заполнении данных с использованием миграций.
Ответить на комментарий @ Smashing1978 , я вставлю соответствующие части по ссылке, предоставленной @Colin
Задача AddOrUpdate состоит в том, чтобы не создавать дубликаты при заполнении данных во время разработки.
Сначала он выполнит запрос в вашей базе данных в поисках записи, в которой все, что вы указали в качестве ключа (первый параметр), соответствует значению сопоставленного столбца (или значениям), указанным в AddOrUpdate. Так что это немного слабовато для подбора, но отлично подходит для посева данных времени проектирования.
Что еще более важно, если совпадение найдено, обновление обновит все и удалит все, чего не было в вашем AddOrUpdate.
Тем не менее, у меня есть ситуация, когда я извлекаю данные из внешнего сервиса и вставляю или обновляю существующие значения по первичному ключу (а мои локальные данные для потребителей доступны только для чтения) - использую AddOrUpdate
в производстве уже более 6 месяцев и так Куда нет проблем.
Магия случается при звонке SaveChanges()
и зависит от тока EntityState
. Если у объекта есть EntityState.Added
, он будет добавлен в базу данных, если у него есть EntityState.Modified
, он будет обновлен в базе данных. Таким образом, вы можете реализовать InsertOrUpdate()
метод следующим образом:
public void InsertOrUpdate(Blog blog)
{
using (var context = new BloggingContext())
{
context.Entry(blog).State = blog.BlogId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
Если вы не можете проверить, Id = 0
является ли это новый объект или нет, проверьте ответ Ладислава Мрнки .
Если вы знаете, что используете тот же контекст и не отсоединяете никаких сущностей, вы можете сделать общую версию, например, такую:
public void InsertOrUpdate<T>(T entity, DbContext db) where T : class
{
if (db.Entry(entity).State == EntityState.Detached)
db.Set<T>().Add(entity);
// If an immediate save is needed, can be slow though
// if iterating through many entities:
db.SaveChanges();
}
db
Конечно, это может быть поле класса, или метод может быть сделан статическим и расширением, но это основы.
Ответ Ладислава был близок, но мне пришлось внести несколько изменений, чтобы заставить его работать в EF6 (сначала база данных). Я расширил свой контекст данных с помощью моего метода AddOrUpdate, и до сих пор он, кажется, хорошо работает с отсоединенными объектами:
using System.Data.Entity;
[....]
public partial class MyDBEntities {
public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) {
if (ID != 0) {
set.Attach(obj);
ctx.Entry(obj).State = EntityState.Modified;
}
else {
set.Add(obj);
}
}
[....]
По моему мнению, стоит сказать, что с недавно выпущенными EntityGraphOperations для Entity Framework Code First вы можете уберечь себя от написания некоторых повторяющихся кодов для определения состояний всех сущностей в графе. Я автор этого продукта. И я опубликовал его в github , code-project ( включает пошаговую демонстрацию и пример проекта готов к загрузке) и nuget .
Он автоматически установит состояние объектов на Added
или Modified
. И вы вручную выберете, какие объекты должны быть удалены, если они больше не существуют.
Пример:
Допустим, у меня есть Person
объект. Person
может иметь много телефонов, документ и может иметь супруга.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public int Age { get; set; }
public int DocumentId {get; set;}
public virtual ICollection<Phone> Phones { get; set; }
public virtual Document Document { get; set; }
public virtual PersonSpouse PersonSpouse { get; set; }
}
Я хочу определить состояние всех объектов, которые включены в график.
context.InsertOrUpdateGraph(person)
.After(entity =>
{
// Delete missing phones.
entity.HasCollection(p => p.Phones)
.DeleteMissingEntities();
// Delete if spouse is not exist anymore.
entity.HasNavigationalProperty(m => m.PersonSpouse)
.DeleteIfNull();
});
Также, как вы знаете, уникальные свойства ключа могут играть роль при определении состояния объекта Phone. Для таких специальных целей у нас есть ExtendedEntityTypeConfiguration<>
класс, который наследуется от EntityTypeConfiguration<>
. Если мы хотим использовать такие специальные конфигурации, мы должны наследовать наши классы отображения от ExtendedEntityTypeConfiguration<>
, а не EntityTypeConfiguration<>
. Например:
public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
{
public PhoneMap()
{
// Primary Key
this.HasKey(m => m.Id);
…
// Unique keys
this.HasUniqueKey(m => new { m.Prefix, m.Digits });
}
}
Вот и все.
Вставьте еще, обновите оба
public void InsertUpdateData()
{
//Here TestEntities is the class which is given from "Save entity connection setting in web.config"
TestEntities context = new TestEntities();
var query = from data in context.Employee
orderby data.name
select data;
foreach (Employee details in query)
{
if (details.id == 1)
{
//Assign the new values to name whose id is 1
details.name = "Sanjay";
details. Surname="Desai";
details.address=" Desiwadi";
}
else if(query==null)
{
details.name="Sharad";
details.surname=" Chougale ";
details.address=" Gargoti";
}
}
//Save the changes back to database.
context.SaveChanges();
}
Проверьте существующую строку с Any.
public static void insertOrUpdateCustomer(Customer customer)
{
using (var db = getDb())
{
db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
}
}
Альтернатива для @LadislavMrnka ответа. Это если для Entity Framework 6.2.0.
Если у вас есть определенный DbSet
элемент, который необходимо обновить или создать:
var name = getNameFromService();
var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
_dbContext.Names.Add(name);
}
else
{
_dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();
Однако это также может быть использовано для универсального DbSet
с одним первичным ключом или составным первичным ключом.
var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");
public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
foreach (var value in values)
{
try
{
var keyList = new List<object>();
//Get key values from T entity based on keyValues property
foreach (var keyValue in keyValues)
{
var propertyInfo = value.GetType().GetProperty(keyValue);
var propertyValue = propertyInfo.GetValue(value);
keyList.Add(propertyValue);
}
GenericAddOrUpdateDbSet(keyList, value);
//Only use this when debugging to catch save exceptions
//_dbContext.SaveChanges();
}
catch
{
throw;
}
}
_dbContext.SaveChanges();
}
public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
//Get a DbSet of T type
var someDbSet = Set(typeof(T));
//Check if any value exists with the key values
var current = someDbSet.Find(keyList.ToArray());
if (current == null)
{
someDbSet.Add(value);
}
else
{
Entry(current).CurrentValues.SetValues(value);
}
}
Исправлено
public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity)
where T : class
where T2 : class
{
foreach(var e in updateEntity)
{
context.Set<T2>().InsertOrUpdate(e);
}
}
public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity)
where T : class
where T2 : class
{
if (context.Entry(updateEntity).State == EntityState.Detached)
{
if (context.Set<T2>().Any(t => t == updateEntity))
{
context.Set<T2>().Update(updateEntity);
}
else
{
context.Set<T2>().Add(updateEntity);
}
}
context.SaveChanges();
}