อัปเดตแถวหากมีลอจิกแทรกที่มี Entity Framework อยู่


179

ใครบ้างมีคำแนะนำเกี่ยวกับวิธีที่มีประสิทธิภาพที่สุดในการใช้ตรรกะ "แถวปรับปรุงถ้ามันมีอยู่แทรก" ตรรกะโดยใช้ Entity Framework?


2
นี่คือสิ่งที่ควรทำในระดับเอ็นจิ้นฐานข้อมูลในขั้นตอนการจัดเก็บ มิฉะนั้นคุณจะต้องปิดตัวตรวจจับ / อัปเดต / แทรกในธุรกรรม
Stephen Chung

1
@ สตีเฟ่น: อันที่จริงแล้วนี่คือสิ่งที่ฉันทำลงไป ขอบคุณ
Jonathan Wood

โจนาธานคำถามของคุณมีประโยชน์มากสำหรับฉัน ทำไมคุณถึงเปลี่ยนไปใช้กระบวนงานที่เก็บไว้?
anar khalilov

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

หากคุณไม่สามารถตัดสินใจว่าจะมีอยู่จริงของวัตถุด้วยรหัสหรือไม่คุณต้องออกจากการค้นหา:

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 วิธีการเดียวกันสามารถใช้กับบริบท
Ladislav Mrnka

1
แต่ถ้าฉันต้องทำสิ่งนี้กับรายการของวัตถุ ... ในฐานข้อมูลของฉันมีรายการของแถวที่มีรหัสเดียวกันและฉันต้องการที่จะแทนที่ถ้าพวกเขามีอยู่หรือแทรกถ้าพวกเขาไม่ .. ฉันจะทำอย่างไร? ขอบคุณ!
Phoenix_uy

1
คำตอบนี้ดูยอดเยี่ยม แต่ฉันพบปัญหานี้ในการอัปเดต: มีวัตถุที่มีคีย์เดียวกันอยู่แล้วใน ObjectStateManager ObjectStateManager ไม่สามารถติดตามวัตถุหลายชิ้นด้วยคีย์เดียวกัน
John Zumbrum

1
ดูเหมือนว่าฉันเพิ่งมีปัญหาเล็กน้อยกับการดึงวัตถุที่มีอยู่เพื่อดึงกุญแจก่อนที่จะทำการอัพเดท การแยกวัตถุการค้นหานั้นช่วยแก้ไขก่อน
John Zumbrum

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 เทียบเท่ากับการดำเนินการ "เพิ่ม" จากคำศัพท์ฐานข้อมูล วิธีนี้มีประโยชน์เมื่อทำการ seed ข้อมูลโดยใช้ Migrations


เพื่อตอบความคิดเห็นโดย @ Smashing1978ฉันจะวางส่วนที่เกี่ยวข้องจากลิงก์ที่ @Colin จัดหาให้

งานของ AddOrUpdate คือเพื่อให้แน่ใจว่าคุณจะไม่สร้างข้อมูลซ้ำเมื่อคุณเก็บข้อมูลระหว่างการพัฒนา

ก่อนอื่นมันจะดำเนินการสืบค้นในฐานข้อมูลของคุณเพื่อค้นหาระเบียนที่สิ่งที่คุณให้ไว้เป็นคีย์ (พารามิเตอร์แรก) ตรงกับค่าคอลัมน์ที่แมป (หรือค่า) ที่ให้ไว้ใน AddOrUpdate ดังนั้นนี่คือห่านตัวเล็ก ๆ ที่หลวมสำหรับการจับคู่ แต่ก็ดีอย่างสมบูรณ์แบบสำหรับการสร้างข้อมูลเวลาการออกแบบ

ที่สำคัญกว่านั้นหากพบการแข่งขันการอัปเดตจะอัปเดตทั้งหมดและลบล้างสิ่งที่ไม่ได้อยู่ใน AddOrUpdate ของคุณ

ที่กล่าวว่าฉันมีสถานการณ์ที่ฉันดึงข้อมูลจากบริการภายนอกและแทรกหรือปรับปรุงค่าที่มีอยู่โดยคีย์หลัก (และข้อมูลท้องถิ่นของฉันสำหรับผู้บริโภคเป็นแบบอ่านอย่างเดียว) - ใช้AddOrUpdateในการผลิตมานานกว่า 6 เดือนแล้ว ไม่มีปัญหา


7
กระบวนการ System.Data.Entity.Migrations เนมสเปซประกอบด้วยคลาสที่เกี่ยวข้องกับการโยกย้ายตามรหัสและการกำหนดค่าของพวกเขา มีเหตุผลใดบ้างที่เราไม่ควรใช้สิ่งนี้ในที่เก็บข้อมูลของเราสำหรับ AddOrUpates เอนทิตีที่ไม่ใช่การย้ายข้อมูล
Matt Lengenfelder

10
ใช้ความระมัดระวังด้วยวิธีAddOrUpdate
Colin

1
บทความนี้อธิบายว่าทำไม AddOrUpdate ไม่ควรใช้michaelgmccarthy.com
2016/08/24/ …

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เพื่อตรวจสอบว่ามันเป็นองค์กรใหม่หรือไม่ตรวจสอบคำตอบของ Ladislav Mrnka


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

คำตอบของ Ladislav นั้นใกล้เคียงกัน แต่ฉันต้องทำการแก้ไขสองสามครั้งเพื่อให้มันทำงานใน 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 ดังนั้นถ้าฉันเป็นคุณฉันจะหลีกเลี่ยงการใช้ชื่อวิธีการเดียวกันสำหรับวิธีการของคุณเอง
หยุด

2

ในความคิดของฉันมันคุ้มค่าที่จะบอกว่าด้วยEntityGraphOperations สำหรับ 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();
}

ฉันใช้วิธีการนี้ แต่และตรวจสอบ (หลังจากแรกหรือเริ่มต้น) ถ้า (query == null)
Patrick

2

ตรวจสอบแถวที่มีอยู่ด้วยใด ๆ

    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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.