2011-06-28 11 views
13

Próbuję osiągnąć coś naprawdę prostego i nie mogę znaleźć sposobu, aby to zrobić za pomocą Entity Framework 4.1.Jak uruchomić rekord w ADO.NET EF 4.1?

Chcę metodę kontrolera, który akceptuje obiekt, a następnie wykonuje UPSERT (wstawiania lub aktualizacji w zależności od tego, czy rekord istnieje w bazie danych).

Używam klucza naturalnego, więc nie ma możliwości, aby spojrzeć na moje POCO i stwierdzić, czy jest nowy, czy nie.

To jak ja to robię i wydaje się źle do mnie:

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

Idealnie, cała sprawa będzie tylko coś takiego:

db.MyEntities.AddOrModify(entity); 
+0

Co się dzieje, kiedy nie tylko 'Save'? –

Odpowiedz

20

Niestety nie ma sposobu, aby to zrobić bez odpytywania bazy danych lub za pomocą procedury przechowywanej. Kod minimalistyczna powinno być:

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

To działa. Sql emitowany przez framework nie jest idealny, ale przynajmniej oszczędza mi kłopotów z ręcznym ustawianiem pól, gdy wymagana jest aktualizacja. Dzięki. –

+2

Polecam test warunków skrajnych dla warunków wyścigu (patrz sugestie SP poniżej). –

+0

Możesz wykryć kolizję po obu stronach drutu, o ile zapewnisz atomowość z transakcją. Jest to szczególnie ważne w przypadku pracy w wielu magazynach danych. Rozwiązanie po stronie serwera powinno być zawsze wydajniejsze, ale niewymagane. Ta odpowiedź jest prawdopodobnie jednym z najczystszych przykładów przeprowadzania upsert poprzez interfejs API DbContext. –

2

W większości przypadków można zrobić nie trzeba jawnie ustawiać EntityState.Modified, chyba że wyłączyłeś śledzenie zmian.

Rozwiązanie wzięliśmy było sprawdzenie wartości identyfikatora jednostki:

if (entity.Id == default(int)) { 
    // transient entity so insert 
} else { 
    // update 
} 
+0

To rozwiązanie nie będzie działać, ponieważ nie używam klucza tożsamości. Używam naturalnego klucza kompozytowego, więc nie ma możliwości, aby spojrzeć na moje POCO i stwierdzić, czy jest nowy, czy nie. –

+0

Następnie naciśnięcie na bazę danych do sprawdzenia wydaje się być jedynym możliwym rozwiązaniem. Byłoby lepiej mieć jasno zdefiniowane akcje dla wstawiania/aktualizacji i naprawdę powinieneś używać viewmodels, ale doceniam to prawdopodobnie jest prosty przykład. –

+0

Możesz rozważyć buforowanie zestawu znaków (używających wątków) i aktualizowanie go po każdej operacji wstawiania/usuwania (chyba że możesz zmienić klucz, ale moim zdaniem jest to bardzo złe), aby przyspieszyć proces, ale jeśli nie jesteś używanie klucza wygenerowanego automatycznie (które są drogą do wyjścia, dlaczego ich nie używasz, nie musisz upuszczać klawisza, możesz nawet indeksować go za pomocą unikalnego ograniczenia), to jedyny sposób, jaki widzę (obsługa duplikatu wyjątku klucza to nie tak naprawdę: D) – luckyluke

2

rzeczywiście istnieje sposób, aby kontekście Informa db, że podmiot, który próbujesz wstawić się zmieniło i teraz nowy

_context.MyEntity.Attach(entity); 
_context.MyEntity(entity).State = System.Data.EntityState.Modified; 
2

Aby wykonać operację upsert Ty może rozważyć utworzenie SP, który wykonuje MERGE.

http://www.databasejournal.com/features/mssql/article.php/3739131/UPSERT-Functionality-in-SQL-Server-2008.htm

Cokolwiek sposób wybrać operację musi być atomową lub będziesz mieć warunek wyścigu. Twój SP będzie prawdopodobnie potrzebować HOLDLOCK aby unieważnić ten ...

http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

+0

Co stanie się, gdy zrobisz tylko "Zapisz"? –

0

Wiem, że to pytanie jest stary i ma zaakceptowane odpowiedź, ale myślę, że jest lepsze rozwiązanie: to nie wymaga dodatkowy interfejs do implementacji lub zdefiniowany typ klucza.

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

Więc później może on być stosowany tak:

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

Nie sądzę, że próbowałeś tego, prawda? –

+0

@GertArnold tak naprawdę nie szukałem "UPSERT" lub "INSERT lub nic nie robić, jeśli podmiot już istnieje", ale wszystkie znalezione rozwiązania wymagają implementacji interfejsów lub użycia wyspecjalizowanej metody dla każdego typu jednostki itp. Więc stworzyłem ta generyczna metoda, której używam w mojej aplikacji i tak, działa. – Salaros

+0

To działa? Kod nawet się nie kompiluje! A jeśli tak, otrzymasz "Typ węzła wyrażeń LINQ" Wywołanie "nie jest obsługiwany w LINQ do jednostek." –

Powiązane problemy