Zaktualizuj wiersz, jeśli istnieje, wstaw logikę za pomocą Entity Framework

179

Czy ktoś ma sugestie dotyczące najbardziej efektywnego sposobu implementacji logiki „zaktualizuj wiersz, jeśli istnieje”, wstaw „logikę” za pomocą Entity Framework?

Jonathan Wood
źródło
2
Jest to coś, co należy zrobić na poziomie silnika bazy danych, w procedurze przechowywanej. W przeciwnym razie będziesz musiał zawrzeć wykrycie / aktualizację / wstawić do transakcji.
Stephen Chung,
1
@Stephen: Tak właśnie skończyłem. Dzięki.
Jonathan Wood,
Jonathan, twoje pytanie jest dla mnie bardzo przydatne. Dlaczego przeszedłeś na procedurę przechowywaną?
anar khalilov
2
@Anar: To było po prostu łatwiejsze i oczekuję dużo bardziej wydajnej pracy.
Jonathan Wood
Czy musisz napisać procedurę składowaną dla każdej tabeli?
tofutim

Odpowiedzi:

174

Jeśli pracujesz z dołączonym obiektem (obiekt załadowany z tej samej instancji kontekstu), możesz po prostu użyć:

if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
    context.MyEntities.AddObject(myEntity);
}

// Attached object tracks modifications automatically

context.SaveChanges();

Jeśli możesz skorzystać z wiedzy na temat klucza obiektu, możesz użyć czegoś takiego:

if (myEntity.Id != 0)
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

Jeśli nie możesz zdecydować o istnieniu obiektu na podstawie jego identyfikatora, musisz sprawdzić zapytanie:

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();
Ladislav Mrnka
źródło
Dzięki. Wygląda na to, czego potrzebuję. Czy mogę zadać ci jedno pytanie, które mnie niepokoi od dłuższego czasu? Zazwyczaj umieszczam mój kontekst w krótkim usingbloku. Czy można pozostawić kontekst na chwilę w pamięci? Na przykład za życia formularza Windows? Zwykle próbuję wyczyścić obiekty bazy danych, aby zapewnić minimalne obciążenie bazy danych. Czy nie ma problemu czekającego na zniszczenie mojego kontekstu EF?
Jonathan Wood,
Sprawdź to: stackoverflow.com/questions/3653009/… kontekst obiektu powinien być możliwie najkrótszy, ale w przypadku winforms lub wpf może to oznaczać, że kontekst żyje tak długo, jak prezenter. Połączone pytanie zawiera link do artykułu msdn na temat używania sesji nhibernate w winforms. To samo podejście można zastosować w kontekście.
Ladislav Mrnka
1
Ale co, jeśli muszę to zrobić z listą obiektów ... w mojej bazie danych znajduje się lista wierszy o tym samym identyfikatorze i chcę je zastąpić, jeśli istnieją, lub wstawić, jeśli nie ... jak to zrobić? dzięki!
Phoenix_uy 14.10.11
1
Ta odpowiedź WYGLĄDA niesamowicie, ale napotykam na ten problem podczas aktualizacji: Obiekt o tym samym kluczu już istnieje w ObjectStateManager. ObjectStateManager nie może śledzić wielu obiektów za pomocą tego samego klucza.
John Zumbrum
1
Wygląda na to, że miałem trochę problemu z pobraniem istniejącego obiektu, aby pobrać jego klucz przed aktualizacją; odłączenie tego obiektu wyszukiwania najpierw pomogło go naprawić.
John Zumbrum
33

Począwszy od Entity Framework 4.3, AddOrUpdatew przestrzeni nazw istnieje metoda System.Data.Entity.Migrations:

public static void AddOrUpdate<TEntity>(
    this IDbSet<TEntity> set,
    params TEntity[] entities
)
where TEntity : class

który przez doktora :

Dodaje lub aktualizuje jednostki według klucza, gdy wywoływane jest SaveChanges. Odpowiednik operacji „upsert” z terminologii bazy danych. Ta metoda może być przydatna podczas inicjowania danych za pomocą migracji.


Aby odpowiedzieć na komentarz @ Smashing1978 , wkleję odpowiednie części z linku dostarczonego przez @Colin

Zadaniem AddOrUpdate jest dopilnowanie, aby nie tworzyć duplikatów podczas inicjowania danych podczas programowania.

Najpierw wykona zapytanie w bazie danych, szukając rekordu, w którym cokolwiek podałeś jako klucz (pierwszy parametr) odpowiada wartości (lub wartościom) mapowanej kolumny podanej w AddOrUpdate. Jest to więc trochę luźno pasujące do dopasowania, ale idealnie nadaje się do wysiewu danych projektowych.

Co ważniejsze, jeśli zostanie znalezione dopasowanie, aktualizacja zaktualizuje wszystkie i wyzeruje wszystkie, których nie było w Twojej AddOrUpdate.

To powiedziawszy, mam sytuację, w której pobieram dane z usługi zewnętrznej i wstawiam lub aktualizuję istniejące wartości za pomocą klucza podstawowego (a moje lokalne dane dla konsumentów są tylko do odczytu) - używam AddOrUpdatew produkcji od ponad 6 miesięcy i tak dalej zdecydowanie nie ma problemów.

Erki M.
źródło
7
Przestrzeń nazw System.Data.Entity.Migrations zawiera klasy związane z migracjami opartymi na kodzie i ich konfiguracjami. Czy jest jakiś powód, dla którego nie powinniśmy tego używać w naszych repozytoriach dla jednostki niemigrującej AddOrUpdates?
Matt Lengenfelder
10
Zachowaj ostrożność stosując metodę AddOrUpdate: thedatafarm.com/data-access/…
Colin
1
W tym artykule opisano, dlaczego nie należy używać
AddOrUpdate
11

Magia dzieje się podczas dzwonienia SaveChanges()i zależy od prądu EntityState. Jeśli jednostka ma EntityState.Added, zostanie dodana do bazy danych, jeśli tak EntityState.Modified, zostanie zaktualizowana w bazie danych. Możesz więc zaimplementować InsertOrUpdate()metodę w następujący sposób:

public void InsertOrUpdate(Blog blog) 
{ 
    using (var context = new BloggingContext()) 
    { 
        context.Entry(blog).State = blog.BlogId == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 

        context.SaveChanges(); 
    } 
}

Więcej informacji o EntityState

Jeśli nie możesz sprawdzić, Id = 0czy jest to nowy podmiot, czy nie, sprawdź odpowiedź Ladislava Mrnka .

Ułożone
źródło
8

Jeśli wiesz, że używasz tego samego kontekstu i nie odłączasz żadnych bytów, możesz utworzyć ogólną wersję, taką jak ta:

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 może oczywiście być polem klasy lub metoda może być statyczna i być rozszerzeniem, ale to jest podstawa.

Ciscoheat
źródło
4

Odpowiedź Ladislava była bliska, ale musiałem wprowadzić kilka modyfikacji, aby działało to w EF6 (najpierw baza danych). Rozszerzyłem kontekst danych za pomocą metody AddOrUpdate i do tej pory wydaje się, że działa ona dobrze z odłączonymi obiektami:

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
źródło
AddOrUpdate istnieje również jako metoda rozszerzenia w System.Data.Entity.Migrations, więc gdybym był tobą, unikałbym ponownego użycia tej samej nazwy metody dla własnej metody.
AFract
2

Moim zdaniem warto powiedzieć, że dzięki nowo wydanemu kodowi EntityGraphOperations dla Entity Framework Code Najpierw możesz zaoszczędzić sobie przed pisaniem powtarzalnych kodów do definiowania stanów wszystkich encji na wykresie. Jestem autorem tego produktu. I opublikowałem go w githubie , projekcie kodu ( zawiera demonstrację krok po kroku i przykładowy projekt jest gotowy do pobrania) i nuget .

Będzie on automatycznie ustawiony stan podmiotów do Addedlub Modified. I ręcznie wybierzesz, które elementy należy usunąć, jeśli już nie istnieją.

Przykład:

Powiedzmy, że mam Personprzedmiot. Personmoże mieć wiele telefonów, dokument i może mieć małżonka.

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; }
}

Chcę określić stan wszystkich jednostek, które są uwzględnione na wykresie.

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

Jak wiadomo, unikalne właściwości klucza mogą odgrywać rolę podczas definiowania stanu bytu telefonu. Do takich specjalnych celów mamy ExtendedEntityTypeConfiguration<>klasę, która dziedziczy EntityTypeConfiguration<>. Jeśli chcemy użyć takich specjalnych konfiguracji, musimy odziedziczyć nasze klasy mapowania ExtendedEntityTypeConfiguration<>zamiast EntityTypeConfiguration<>. Na przykład:

public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
    {
        public PhoneMap()
        {
             // Primary Key
             this.HasKey(m => m.Id);
              
             // Unique keys
             this.HasUniqueKey(m => new { m.Prefix, m.Digits });
        }
    }

To wszystko.

Farhad Jabiyev
źródło
2

Wstaw inną aktualizację obu

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();
}
Sharad Chougale
źródło
Zastosowałem to podejście, ale sprawdziłem (po pierwszym lub domyślnym), czy (zapytanie == null)
Patrick
2

Sprawdź istniejący wiersz za pomocą 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();

        }

    }
Ali Osman Yavuz
źródło
1

Alternatywa dla odpowiedzi @LadislavMrnka. Ma to miejsce w przypadku Entity Framework 6.2.0.

Jeśli masz konkretny DbSetelement, który należy zaktualizować lub utworzyć:

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

Można to jednak również wykorzystać w przypadku DbSetklucza ogólnego z pojedynczym kluczem podstawowym lub złożonym kluczem podstawowym.

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
źródło
-1

Poprawione

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();
        }
Vadim Rychkow
źródło
2
Skorzystaj z edit zamiast pisać kolejną odpowiedź
Suraj Rao,