Оновіть рядок, якщо вона існує інша вставка логіки з Entity Framework


179

Хтось має пропозиції щодо найефективнішого способу реалізації логіки "оновлення рядка, якщо вона ще є", використовуючи Entity Framework?


2
Це те, що слід робити на рівні двигуна бази даних, у збереженій процедурі. В іншому випадку вам доведеться завернути виявлення / оновлення / вставлення в транзакції.
Стівен Чунг

1
@Stephen: Насправді це я і зробив. Дякую.
Джонатан Вуд

Джонатане, ваше питання мені дуже корисне. Чому ви перейшли на збережену процедуру?
анар халілов

2
@Anar: Це було просто простіше, і я очікую набагато ефективнішого.
Джонатан Вуд

Чи потрібно писати збережену процедуру для кожної таблиці?
tofutim

Відповіді:


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();

Якщо ви не можете вирішити існування об'єкта за його Id, ви повинні виконати запит пошуку:

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. Еквівалентно операції "вдосконалення" з термінології бази даних. Цей метод може бути корисним при висіванні даних за допомогою міграції.


Щоб відповісти на коментар @ 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щоб визначити, чи це нова організація чи ні, перевірте відповідь Ладіслава Мрнка .


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 Звичайно, це може бути поле класу, або метод може бути статичним і розширенням, але це основи.


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);
      }
  }
[....]

AddOrUpdate також існує як метод розширення в System.Data.Entity.Migrations, тож якби я був ти, я б уникну повторного використання того ж імені методу для власного методу.
AFract

2

На мою думку, варто сказати, що з нещодавно випущеним EntityGraphOperations for Entity Framework Code Спочатку ви можете вберегти себе від написання деяких повторюваних кодів для визначення станів усіх сутностей у графі. Я автор цього продукту. І я опублікував це в github , коді-проект ( включає поетапну демонстрацію і зразок проекту готовий до завантаження) та 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();
       });

Також, як відомо, унікальні ключові властивості можуть грати роль під час визначення стану сутності телефону. Для таких спеціальних цілей у нас є 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);
    }
}

-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
Будь ласка, скористайтеся редагуванням, а не публікуйте іншу відповідь
Suraj Rao
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.