Perbarui Baris jika Ada Lain Sisipkan Logika dengan Kerangka Entitas


179

Adakah yang punya saran tentang cara paling efisien untuk menerapkan logika "perbarui baris jika ada orang lain yang menyisipkan" menggunakan Entity Framework?


2
Ini adalah sesuatu yang harus dilakukan di tingkat mesin basis data, dalam prosedur tersimpan. Jika tidak, Anda harus membungkus deteksi / perbarui / masukkan dalam transaksi.
Stephen Chung

1
@Stephen: Ini, pada kenyataannya, adalah apa yang akhirnya saya lakukan. Terima kasih.
Jonathan Wood

Jonathan, pertanyaanmu sangat berguna bagiku. Mengapa Anda beralih ke prosedur tersimpan?
anar khalilov

2
@Anar: Itu lebih mudah dan saya berharap jauh lebih efisien.
Jonathan Wood

Apakah Anda harus menulis prosedur tersimpan untuk setiap tabel?
tofutim

Jawaban:


174

Jika Anda bekerja dengan objek terlampir (objek dimuat dari instance konteks yang sama) Anda cukup menggunakan:

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

// Attached object tracks modifications automatically

context.SaveChanges();

Jika Anda dapat menggunakan pengetahuan apa pun tentang kunci objek, Anda dapat menggunakan sesuatu seperti ini:

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

context.SaveChanges();

Jika Anda tidak dapat memutuskan keberadaan objek dengan ID Anda, Anda harus memeriksa permintaan pencarian:

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

Terima kasih. Sepertinya yang saya butuhkan. Bisakah saya mengajukan satu pertanyaan yang mengganggu saya untuk sementara waktu? Biasanya, saya menempatkan konteks saya di usingblok pendek . Apakah saya tetap bisa meninggalkan konteks dalam memori untuk sementara waktu? Misalnya, selama masa pakai formulir Windows? Saya biasanya mencoba dan membersihkan objek database untuk memastikan beban minimum pada database. Apakah tidak ada masalah menunggu untuk menghancurkan konteks EF saya?
Jonathan Wood

Periksa ini: stackoverflow.com/questions/3653009/... konteks objek harus hidup sesingkat mungkin tetapi dalam kasus winforms atau wpf ini dapat berarti bahwa konteks itu hidup selama presenter. Pertanyaan tertaut berisi tautan ke artikel msdn tentang penggunaan sesi nhibernate di winforms. Pendekatan yang sama dapat digunakan untuk konteks.
Ladislav Mrnka

1
Tetapi bagaimana jika saya perlu melakukan ini dengan daftar objek ... dalam database saya ada daftar baris dengan id yang sama dan saya ingin mengganti jika ada atau memasukkan jika mereka tidak .. bagaimana saya melakukannya? Terima kasih!
Phoenix_uy

1
Jawaban ini TERLIHAT luar biasa, tapi saya mengalami masalah ini pada pembaruan: Objek dengan kunci yang sama sudah ada di ObjectStateManager. ObjectStateManager tidak dapat melacak beberapa objek dengan kunci yang sama.
John Zumbrum

1
Sepertinya saya hanya mengalami sedikit masalah dengan mengambil objek yang sudah ada untuk mengambil kuncinya sebelum melakukan pembaruan; melepaskan objek pencarian pertama membantu memperbaikinya.
John Zumbrum

33

Pada Entity Framework 4.3, ada AddOrUpdatemetode di namespace System.Data.Entity.Migrations:

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

yang oleh dokter :

Menambahkan atau memperbarui entitas dengan kunci saat SaveChanges dipanggil. Setara dengan operasi "berani" dari terminologi basis data. Metode ini dapat berguna saat menabur data menggunakan Migrasi.


Untuk menjawab komentar oleh @ Smashing1978 , saya akan menempel bagian yang relevan dari tautan yang disediakan oleh @Colin

Tugas AddOrUpdate adalah memastikan bahwa Anda tidak membuat duplikat saat Anda menyemai data selama pengembangan.

Pertama, itu akan menjalankan kueri dalam database Anda mencari catatan di mana apa pun yang Anda berikan sebagai kunci (parameter pertama) cocok dengan nilai kolom yang dipetakan (atau nilai-nilai) yang disediakan di AddOrUpdate. Jadi ini agak longgar untuk cocok tetapi sangat baik untuk penyemaian data waktu desain.

Lebih penting lagi, jika kecocokan ditemukan maka pembaruan akan memperbarui semua dan membatalkan semua yang tidak ada dalam AddOrUpdate Anda.

Yang mengatakan, saya memiliki situasi di mana saya menarik data dari layanan eksternal dan memasukkan atau memperbarui nilai yang ada dengan kunci primer (dan data lokal saya untuk konsumen hanya baca) - telah menggunakan AddOrUpdatedalam produksi selama lebih dari 6 bulan sekarang dan seterusnya Sejauh ini tidak ada masalah.


7
Namespace System.Data.Entity.Migrations berisi kelas yang terkait dengan migrasi berbasis kode dan konfigurasinya. Apakah ada alasan mengapa kita tidak boleh menggunakan ini di repositori kami untuk entitas non-migrasi AddOrUpdates?
Matt Lengenfelder

10
Berhati-hatilah dengan metode AddOrUpdate
Colin

1
Artikel ini menjelaskan mengapa AddOrUpdate tidak boleh digunakan michaelgmccarthy.com/2016/08/24/...
Nolmë Informatique

11

Keajaiban terjadi ketika menelepon SaveChanges()dan tergantung pada arus EntityState. Jika entitas memiliki EntityState.Added, maka akan ditambahkan ke database, jika memiliki EntityState.Modified, maka akan diperbarui dalam database. Jadi, Anda dapat menerapkan InsertOrUpdate()metode sebagai berikut:

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

        context.SaveChanges(); 
    } 
}

Lebih lanjut tentang EntityState

Jika Anda tidak dapat memeriksa Id = 0untuk menentukan apakah itu entitas baru atau tidak, periksa jawaban Ladislav Mrnka .


8

Jika Anda tahu bahwa Anda menggunakan konteks yang sama dan tidak melepaskan entitas apa pun, Anda dapat membuat versi generik seperti ini:

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 tentu saja bisa menjadi bidang kelas, atau metode ini dapat dibuat statis dan ekstensi, tetapi ini adalah dasar-dasarnya.


4

Jawaban Ladislav sudah dekat tetapi saya harus membuat beberapa modifikasi agar ini dapat bekerja di EF6 (database-first). Saya memperluas konteks data saya dengan metode AddOrUpdate saya dan sejauh ini tampaknya berfungsi baik dengan objek terpisah:

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 juga ada sebagai metode ekstensi di System.Data.Entity.Migrations, jadi jika saya jadi Anda saya akan menghindari untuk menggunakan kembali nama metode yang sama untuk metode Anda sendiri.
AFract

2

Menurut pendapat saya, perlu untuk mengatakan bahwa dengan EntityGraphOperations yang baru dirilis untuk Kode Kerangka Kerja Entitas Pertama, Anda dapat menyelamatkan diri dari menulis beberapa kode berulang untuk menentukan status semua entitas dalam grafik. Saya adalah penulis produk ini. Dan saya telah menerbitkannya di github , proyek kode ( termasuk demonstrasi langkah demi langkah dan proyek sampel siap untuk diunduh) dan nuget .

Ini akan secara otomatis mengatur status entitas ke Addedatau Modified. Dan Anda akan secara manual memilih entitas mana yang harus dihapus jika tidak ada lagi.

Contoh:

Katakanlah saya sudah mendapatkan Personobjek. Persondapat memiliki banyak telepon, Dokumen dan dapat memiliki pasangan.

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

Saya ingin menentukan status semua entitas yang termasuk dalam grafik.

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

Seperti yang Anda ketahui, properti kunci unik dapat berperan saat menentukan status entitas Telepon. Untuk tujuan khusus seperti itu, kita memiliki ExtendedEntityTypeConfiguration<>kelas, yang merupakan warisan dari EntityTypeConfiguration<>. Jika kita ingin menggunakan konfigurasi khusus seperti itu maka kita harus mewarisi dari kelas pemetaan kita ExtendedEntityTypeConfiguration<>, bukan EntityTypeConfiguration<>. Sebagai contoh:

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

Itu saja.


2

Sisipkan yang lain perbarui keduanya

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

Saya menggunakan pendekatan ini tetapi dan memeriksa (setelah yang pertama atau default) jika (permintaan == null)
Patrick

2

Periksa baris yang ada dengan Apa saja.

    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

Alternatif untuk jawaban @LadislavMrnka. Ini jika untuk Entity Framework 6.2.0.

Jika Anda memiliki DbSetitem spesifik dan yang perlu diperbarui atau dibuat:

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

Namun ini juga dapat digunakan untuk generik DbSetdengan kunci primer tunggal atau kunci primer komposit.

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

Dikoreksi

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
Silakan gunakan edit alih-alih memposting jawaban lain
Suraj Rao
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.