Обновите строку, если она существует, вставьте логику с помощью Entity Framework

179

У кого-нибудь есть предложения по наиболее эффективному способу реализации логики «обновить строку, если она существует, иначе вставка» с использованием Entity Framework?

Джонатан Вуд
источник
2
Это то, что должно быть сделано на уровне ядра базы данных, в хранимой процедуре. В противном случае вам придется обернуть обнаружение / обновление / вставку в транзакции.
Стивен Чунг
1
@Stephen: Это то, что я в итоге делал. Спасибо.
Джонатан Вуд
Джонатан, твой вопрос очень полезен для меня. Почему вы переключились на хранимую процедуру?
Анар Халилов
2
@ Anar: это было просто проще, и я ожидаю гораздо более эффективной.
Джонатан Вуд
Вы должны написать хранимую процедуру для каждой таблицы?
тофутим

Ответы:

174

Если вы работаете с прикрепленным объектом (объект загружен из того же экземпляра контекста), вы можете просто использовать:

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?
Джонатан Вуд
Проверьте это: stackoverflow.com/questions/3653009/… контекст объекта должен жить как можно короче, но в случае winforms или wpf это может означать, что контекст живет столько же времени, сколько и ведущий. Связанный вопрос содержит ссылку на статью MSDN об использовании сеанса nhibernate в winforms. Тот же подход может быть использован для контекста.
Ладислав Мрнка
1
Но что, если мне нужно сделать это со списком объектов ... в моей базе данных есть список строк с одинаковым идентификатором, и я хочу заменить, если они существуют, или вставить, если их нет ... как мне это сделать? Спасибо!
Phoenix_uy
1
Этот ответ выглядит потрясающе, но я сталкиваюсь с этой проблемой при обновлении: объект с таким же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом.
Джон Зумбрум
1
Похоже, у меня была небольшая проблема с извлечением существующего объекта, чтобы получить его ключ перед выполнением обновления; отсоединение этого объекта поиска сначала помогло исправить это.
Джон Зумбрум
33

Начиная с 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 месяцев и так Куда нет проблем.

Эрки М.
источник
7
Пространство имен System.Data.Entity.Migrations содержит классы, связанные с миграциями на основе кода и их конфигурациями. Есть ли какая-то причина, по которой мы не должны использовать это в наших репозиториях для не перемещаемых объектов AddOrUpdates?
Мэтт Ленгенфельдер,
10
Будьте осторожны с методом AddOrUpdate: thedatafarm.com/data-access/…
Колин
1
В этой статье описывается, почему AddOrUpdate не следует использовать michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique
11

Магия случается при звонке 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(); 
    } 
}

Подробнее о EntityState

Если вы не можете проверить, Id = 0является ли это новый объект или нет, проверьте ответ Ладислава Мрнки .

Stacked
источник
8

Если вы знаете, что используете тот же контекст и не отсоединяете никаких сущностей, вы можете сделать общую версию, например, такую:

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 Конечно, это может быть поле класса, или метод может быть сделан статическим и расширением, но это основы.

ciscoheat
источник
4

Ответ Ладислава был близок, но мне пришлось внести несколько изменений, чтобы заставить его работать в 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);
      }
  }
[....]
cdonner
источник
AddOrUpdate также существует как метод расширения в System.Data.Entity.Migrations, поэтому на вашем месте я бы избегал повторного использования того же имени метода для вашего собственного метода.
ПОЛУЧИТЬ
2

По моему мнению, стоит сказать, что с недавно выпущенными 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 });
        }
    }

Вот и все.

Фархад Джабиев
источник
2

Вставьте еще, обновите оба

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();
}
Шарад Чугале
источник
Я использовал этот подход, но и проверил (после первого или по умолчанию) if (query == null)
Patrick
2

Проверьте существующую строку с 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();

        }

    }
Али Осман Явуз
источник
1

Альтернатива для @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);
    }
}
Ogglas
источник
-1

Исправлено

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();
        }
Вадим Рычков
источник
2
Пожалуйста, используйте редактировать вместо публикации другого ответа
Сурадж Рао