Czy ktoś ma sugestie dotyczące najbardziej efektywnego sposobu implementacji logiki „zaktualizuj wiersz, jeśli istnieje”, wstaw „logikę” za pomocą Entity Framework?
Czy ktoś ma sugestie dotyczące najbardziej efektywnego sposobu implementacji logiki „zaktualizuj wiersz, jeśli istnieje”, wstaw „logikę” za pomocą Entity Framework?
Odpowiedzi:
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();
using
bloku. 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?
Począwszy od Entity Framework 4.3, AddOrUpdate
w 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 AddOrUpdate
w produkcji od ponad 6 miesięcy i tak dalej zdecydowanie nie ma problemów.
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 = 0
czy jest to nowy podmiot, czy nie, sprawdź odpowiedź Ladislava Mrnka .
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.
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);
}
}
[....]
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 Added
lub Modified
. I ręcznie wybierzesz, które elementy należy usunąć, jeśli już nie istnieją.
Przykład:
Powiedzmy, że mam Person
przedmiot. Person
moż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.
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();
}
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();
}
}
Alternatywa dla odpowiedzi @LadislavMrnka. Ma to miejsce w przypadku Entity Framework 6.2.0.
Jeśli masz konkretny DbSet
element, 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 DbSet
klucza 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);
}
}
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();
}