จะอัปเดตระเบียนโดยใช้ Entity Framework 6 ได้อย่างไร


245

ฉันกำลังพยายามอัปเดตบันทึกโดยใช้ EF6 ก่อนอื่นให้ค้นหาระเบียนหากมีอยู่ให้อัปเดต นี่คือรหัสของฉัน: -

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

ทุกครั้งที่ฉันพยายามอัปเดตบันทึกโดยใช้รหัสด้านบนฉันได้รับข้อผิดพลาดนี้: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: อัปเดตที่เก็บแทรกหรือลบคำสั่งมีผลต่อจำนวนแถวที่ไม่คาดคิด (0) อาจมีการแก้ไขหรือลบรายการเนื่องจากมีการโหลดรายการ รีเฟรช ObjectStateManager entrie


7
หมายเหตุด้านข้าง: catch (Exception ex){throw;}ซ้ำซ้อนและคุณสามารถลบออกได้อย่างสมบูรณ์
Sriram Sakthivel

ลองบล็อก catch เป็นเพียงการหาสาเหตุของความล้มเหลว แต่ยังไม่เข้าใจว่าทำไมรหัสนี้จึงล้มเหลว
user1327064

2
ฉันไม่ใช่ผู้เชี่ยวชาญในหัวข้อนี้ฉันไม่สามารถตอบคำถามนี้ได้ แต่โดยไม่ต้องลอง catch คุณสามารถใช้break เมื่อมีการโยนคุณสมบัติเพื่อแยกตัวดีบักเมื่อมีข้อยกเว้น
Sriram Sakthivel

1
คุณยังไม่ได้เปลี่ยนแปลงอะไรเลย การเล่นด้วยสถานะเอนทิตีจะไม่เปลี่ยนความจริงที่ว่าวัตถุนั้นไม่ได้รับการแก้ไข
Jonathan Allen

1
ฉันทำเช่นเดียวกับคุณและไม่ได้รับข้อผิดพลาด ข้อยกเว้นพูดว่า DbUpdateConcurrencyException คุณจัดการกับการเกิดพร้อมกันได้อย่างไร? คุณใช้การประทับเวลาคุณโคลนแล้วผสานวัตถุอีกครั้งหรือคุณใช้เอนทิตีติดตามด้วยตนเอง (3 วิธีที่ใช้มากที่สุด) หากคุณไม่ได้จัดการปัญหาที่เกิดขึ้นพร้อมกันฉันคิดว่านั่นเป็นปัญหา
El Mac

คำตอบ:


345

คุณกำลังพยายามที่จะปรับปรุงบันทึก (ซึ่งฉันหมายถึง "เปลี่ยนค่าในบันทึกที่มีอยู่และบันทึกกลับ") ดังนั้นคุณต้องดึงข้อมูลวัตถุทำการเปลี่ยนแปลงและบันทึก

using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue = "Some new value";
        db.SaveChanges();
    }
}

16
การกำหนดค่าจะไม่อัปเดตฐานข้อมูลการโทรdb.SaveChanges()ด้วยออบเจ็กต์ที่ถูกดัดแปลงในบริบทจะอัพเดตฐานข้อมูล
Craig W.

6
ถึงกระนั้นมันก็ทำให้ฉันประทับใจ ... ดังนั้นผลลัพธ์ var ก็จะเชื่อมต่อกับ dbcontext ... ดังนั้นนี่หมายความว่าตัวแปรใด ๆ ที่อินสแตนซ์โดยสมาชิก dbcontext ใด ๆ จะมีการเชื่อมโยงนั้นกับฐานข้อมูลเพื่อให้การเปลี่ยนแปลงใด ๆ ถูกนำไปใช้กับตัวแปรนั้น มันยังถูกนำไปใช้หรือคงอยู่?
WantIt

6
เนื่องจากบริบทสร้างวัตถุบริบทจึงสามารถติดตามวัตถุรวมถึงการเปลี่ยนแปลงวัตถุ เมื่อคุณเรียกSaveChangesบริบทประเมินวัตถุทั้งหมดที่มีการติดตามเพื่อตรวจสอบว่าพวกเขาจะเพิ่มเปลี่ยนหรือลบและออก SQL ที่เหมาะสมไปยังฐานข้อมูลที่เชื่อมต่อ
Craig W.

3
ฉันกำลังเผชิญกับปัญหาเดียวกัน - โดยใช้ EF6 พยายามอัปเดตเอนทิตี แนบ + EntityState.Modified ไม่ทำงาน สิ่งเดียวที่ทำงานคือ - คุณต้องดึงวัตถุทำการเปลี่ยนแปลงที่ต้องการและบันทึกผ่าน db.SaveChanges ();
Gurpreet Singh

7
คุณไม่ควรเรียกวัตถุก่อนเพื่ออัพเดท ฉันมีปัญหาเดียวกันจนกระทั่งฉันรู้ว่ากำลังพยายามเปลี่ยนหนึ่งในค่าคีย์หลัก (คีย์ผสม) ตราบใดที่คุณระบุคีย์หลักที่ถูกต้องคุณสามารถตั้งค่า EntityState เป็น Modified และ SaveChanges () จะทำงานได้หากคุณไม่ทำลายข้อ จำกัด ด้านความสมบูรณ์อื่น ๆ ที่กำหนดไว้ในตาราง
adrianz

166

ฉันได้ตรวจสอบซอร์สโค้ดของ Entity Framework แล้วและพบวิธีในการอัพเดตเอนทิตีจริง ๆ ถ้าคุณรู้จักคุณสมบัติของคีย์:

public void Update<T>(T item) where T: Entity
{
    // assume Entity base class have an Id property for all items
    var entity = _collection.Find(item.Id);
    if (entity == null)
    {
        return;
    }

    _context.Entry(entity).CurrentValues.SetValues(item);
}

มิฉะนั้นตรวจสอบการใช้งานAddOrUpdateสำหรับแนวคิด

หวังว่าจะช่วยได้!


12
ดี! ไม่จำเป็นต้องระบุคุณสมบัติทั้งหมด ฉันถือว่าการSaveChanges()โทรเป็นสิ่งจำเป็นหลังจากตั้งค่า
Jan Zahradník

3
ใช่การเปลี่ยนแปลงจะยังคงอยู่ใน SaveChanges ()
มิเกล

1
คำตอบที่ดีมันไม่ชัดเจนเกินไปกับ IntelliSense ที่การทำสิ่งนี้จะไม่ทำงาน: _context.MyObj = newObj; จากนั้น SaveChanges () หรือ .... _context.MyObj.Update (newObj) จากนั้น SaveChanges (); โซลูชันของคุณอัพเดตวัตถุทั้งหมดโดยไม่ต้องวนลูปผ่านคุณสมบัติทั้งหมด
Adam

7
สิ่งนี้บ่นกับฉันว่าฉันกำลังพยายามแก้ไขช่อง ID
Vasily Hall

3
@VasilyHall - สิ่งนี้เกิดขึ้นหากฟิลด์ ID (หรืออะไรก็ตามที่คุณกำหนดคีย์หลักเป็น) จะแตกต่างกันระหว่างโมเดล (รวมถึง null / 0 ในหนึ่งในโมเดล) ตรวจสอบให้แน่ใจว่า ID ตรงกันกับทั้งสองรุ่นและจะอัปเดตได้ดี
Gavin Coates

51

คุณสามารถใช้AddOrUpdateวิธีการ:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();

1
ทางออกที่ดีที่สุด IMO
Norgul

112
.AddOrUpdate()ถูกใช้ในระหว่างการย้ายฐานข้อมูลซึ่งไม่แนะนำให้ใช้วิธีนี้นอกเหนือจากการย้ายข้อมูลดังนั้นทำไมจึงอยู่ในEntity.Migrationsเนมสเปซ
Adam Vincent

1
ดังที่ @AdamVincent กล่าวว่าAddOrUpdate()วิธีการนี้มีไว้สำหรับการย้ายข้อมูลและไม่เหมาะสำหรับสถานการณ์เมื่อคุณต้องการอัปเดตแถวที่มีอยู่เท่านั้น ในกรณีที่คุณไม่มีหนังสือที่มีการอ้างอิงการค้นหา (เช่น ID) มันจะสร้างแถวใหม่และอาจเป็นปัญหาในกรณีที่มา (ตัวอย่างเช่นคุณมี API ที่ต้องการคืนการตอบสนอง 404- หากคุณ ลองเรียกวิธี PUT สำหรับแถวที่ไม่มีอยู่)
Marko

4
อย่าใช้สิ่งนี้จนกว่าคุณจะรู้ว่าคุณกำลังทำอะไร !!!!!!!!!!!!!!!! อ่าน: michaelgmccarthy.com
2016/08/24/…

4
ฉันกลับมาที่นี่อีกครั้งวันนี้ฉันขอเตือนคุณได้ไหมว่านี่ไม่ใช่วิธีแก้ปัญหาที่ดีสำหรับกรณีการใช้งานที่ต้องการ
Yusha

23

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

การเกิดขึ้นพร้อมกันนั้นยุ่งยากเสมอ แต่ฉันคิดว่าคุณแค่ต้องการให้การอัพเดทของคุณชนะ นี่คือวิธีที่ฉันทำสำหรับกรณีเดียวกันของฉันและแก้ไขชื่อเพื่อเลียนแบบคลาสของคุณ ในคำอื่น ๆ เพียงเปลี่ยนattachไปaddและมันทำงานได้สำหรับฉัน:

public static void SaveBook(Model.Book myBook)
{
    using (var ctx = new BookDBContext())
    {
        ctx.Books.Add(myBook);
        ctx.Entry(myBook).State = System.Data.Entity.EntityState.Modified;
        ctx.SaveChanges();
    }
}

10

คุณควรใช้เมธอด Entry () ในกรณีที่คุณต้องการอัปเดตฟิลด์ทั้งหมดในวัตถุของคุณ โปรดทราบว่าคุณไม่สามารถเปลี่ยนรหัสฟิลด์ (คีย์) ดังนั้นก่อนอื่นให้ตั้งค่า Id เป็นแบบเดียวกับที่คุณแก้ไข

using(var context = new ...())
{
    var EditedObj = context
        .Obj
        .Where(x => x. ....)
        .First();

    NewObj.Id = EditedObj.Id; //This is important when we first create an object (NewObj), in which the default Id = 0. We can not change an existing key.

    context.Entry(EditedObj).CurrentValues.SetValues(NewObj);

    context.SaveChanges();
}

2
อย่างน้อยคุณควรพยายามที่จะตอบคำถามไม่ใช่แค่โพสต์รหัส
StaceyGirl

โปรดอธิบายคำถามแทนการปล่อยให้ข้อมูลโค้ดเพื่อช่วยให้ผู้ถามคำถามดีขึ้น
feanor07

9

รหัสนี้เป็นผลของการทดสอบเพื่ออัปเดตชุดของคอลัมน์เท่านั้นโดยไม่ต้องทำการสืบค้นเพื่อส่งคืนเรกคอร์ดก่อน ใช้รหัส Entity Framework 7 ก่อน

// This function receives an object type that can be a view model or an anonymous 
// object with the properties you want to change. 
// This is part of a repository for a Contacts object.

public int Update(object entity)
{
    var entityProperties =  entity.GetType().GetProperties();   
    Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

    if (con != null)
    {
        _context.Entry(con).State = EntityState.Modified;
        _context.Contacts.Attach(con);

        foreach (var ep in entityProperties)
        {
            // If the property is named Id, don't add it in the update. 
            // It can be refactored to look in the annotations for a key 
            // or any part named Id.

            if(ep.Name != "Id")
                _context.Entry(con).Property(ep.Name).IsModified = true;
        }
    }

    return _context.SaveChanges();
}

public static object ToType<T>(object obj, T type)
{
    // Create an instance of T type object
    object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

    // Loop through the properties of the object you want to convert
    foreach (PropertyInfo pi in obj.GetType().GetProperties())
    {
        try
        {
            // Get the value of the property and try to assign it to the property of T type object
            tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
        }
        catch (Exception ex)
        {
            // Logging.Log.Error(ex);
        }
    }
    // Return the T type object:         
    return tmp;
}

นี่คือรหัสที่สมบูรณ์:

public interface IContactRepository
{
    IEnumerable<Contacts> GetAllContats();
    IEnumerable<Contacts> GetAllContactsWithAddress();
    int Update(object c);
}

public class ContactRepository : IContactRepository
{
    private ContactContext _context;

    public ContactRepository(ContactContext context)
    {
        _context = context;
    }

    public IEnumerable<Contacts> GetAllContats()
    {
        return _context.Contacts.OrderBy(c => c.FirstName).ToList();
    }

    public IEnumerable<Contacts> GetAllContactsWithAddress()
    {
        return _context.Contacts
            .Include(c => c.Address)
            .OrderBy(c => c.FirstName).ToList();
    }   

    //TODO Change properties to lambda expression
    public int Update(object entity)
    {
        var entityProperties = entity.GetType().GetProperties();

        Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

        if (con != null)
        {
            _context.Entry(con).State = EntityState.Modified;
            _context.Contacts.Attach(con);

            foreach (var ep in entityProperties)
            {
                if(ep.Name != "Id")
                    _context.Entry(con).Property(ep.Name).IsModified = true;
            }
        }

        return _context.SaveChanges();
    }

    public static object ToType<T>(object obj, T type)
    {
        // Create an instance of T type object
        object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

        // Loop through the properties of the object you want to convert
        foreach (PropertyInfo pi in obj.GetType().GetProperties())
        {
            try
            {
                // Get the value of the property and try to assign it to the property of T type object
                tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
            }
            catch (Exception ex)
            {
                // Logging.Log.Error(ex);
            }
        }
        // Return the T type object
        return tmp;
    }
}    

public class Contacts
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Company { get; set; }
    public string Title { get; set; }
    public Addresses Address { get; set; }    
}

public class Addresses
{
    [Key]
    public int Id { get; set; }
    public string AddressType { get; set; }
    public string StreetAddress { get; set; }
    public string City { get; set; }
    public State State { get; set; }
    public string PostalCode { get; set; }  
}

public class ContactContext : DbContext
{
    public DbSet<Addresses> Address { get; set; } 
    public DbSet<Contacts> Contacts { get; set; } 
    public DbSet<State> States { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = "Server=YourServer;Database=ContactsDb;Trusted_Connection=True;MultipleActiveResultSets=true;";
        optionsBuilder.UseSqlServer(connString);
        base.OnConfiguring(optionsBuilder);
    }
}


5

นี่คือทางออกที่ดีที่สุดสำหรับปัญหานี้: ในมุมมองเพิ่ม ID (คีย์) ทั้งหมด พิจารณาการตั้งชื่อตารางหลายตาราง (แรกสองและสาม)

@Html.HiddenFor(model=>model.FirstID)
@Html.HiddenFor(model=>model.SecondID)
@Html.HiddenFor(model=>model.Second.SecondID)
@Html.HiddenFor(model=>model.Second.ThirdID)
@Html.HiddenFor(model=>model.Second.Third.ThirdID)

ในรหัส C #

[HttpPost]
public ActionResult Edit(First first)
{
  if (ModelState.Isvalid)
  {
    if (first.FirstID > 0)
    {
      datacontext.Entry(first).State = EntityState.Modified;
      datacontext.Entry(first.Second).State = EntityState.Modified;
      datacontext.Entry(first.Second.Third).State = EntityState.Modified;
    }
    else
    {
      datacontext.First.Add(first);
    }
    datacontext.SaveChanges();
    Return RedirectToAction("Index");
  }

 return View(first);
}

5

AttachUnchangedไอเอ็นจีกิจการจะตั้งรัฐในการติดตาม Modifiedการอัปเดตเป็นองค์กรที่มีอยู่ทั้งหมดที่คุณต้องทำคือการตั้งรัฐติดตาม อ้างอิงจากเอกสาร EF6 :

หากคุณมีเอนทิตีที่คุณรู้ว่ามีอยู่แล้วในฐานข้อมูล แต่อาจมีการเปลี่ยนแปลงซึ่งคุณสามารถบอกบริบทให้แนบเอนทิตีและตั้งค่าสถานะเป็นโมดิฟายด์ ตัวอย่างเช่น:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Modified;

    // Do some more work...  

    context.SaveChanges();
}

4
using(var myDb = new MyDbEntities())
{

    user user = new user();
    user.username = "me";
    user.email = "me@me.com";

    myDb.Users.Add(user);
    myDb.users.Attach(user);
    myDb.Entry(user).State = EntityState.Modified;//this is for modiying/update existing entry
    myDb.SaveChanges();
}

4

ฉันพบวิธีที่ใช้งานได้ดี

 var Update = context.UpdateTables.Find(id);
        Update.Title = title;

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();


1

นี่คือวิธีการอัพเดตเอนทิตี post-RIA ของฉัน (สำหรับกรอบเวลา Ef6):

public static void UpdateSegment(ISegment data)
{
    if (data == null) throw new ArgumentNullException("The expected Segment data is not here.");

    var context = GetContext();

    var originalData = context.Segments.SingleOrDefault(i => i.SegmentId == data.SegmentId);
    if (originalData == null) throw new NullReferenceException("The expected original Segment data is not here.");

    FrameworkTypeUtility.SetProperties(data, originalData);

    context.SaveChanges();
}

โปรดทราบว่าFrameworkTypeUtility.SetProperties()เป็นฟังก์ชั่นยูทิลิตี้เล็ก ๆ ที่ฉันเขียนมานานก่อน AutoMapper บน NuGet:

public static void SetProperties<TIn, TOut>(TIn input, TOut output, ICollection<string> includedProperties)
    where TIn : class
    where TOut : class
{
    if ((input == null) || (output == null)) return;
    Type inType = input.GetType();
    Type outType = output.GetType();
    foreach (PropertyInfo info in inType.GetProperties())
    {
        PropertyInfo outfo = ((info != null) && info.CanRead)
            ? outType.GetProperty(info.Name, info.PropertyType)
            : null;
        if (outfo != null && outfo.CanWrite
            && (outfo.PropertyType.Equals(info.PropertyType)))
        {
            if ((includedProperties != null) && includedProperties.Contains(info.Name))
                outfo.SetValue(output, info.GetValue(input, null), null);
            else if (includedProperties == null)
                outfo.SetValue(output, info.GetValue(input, null), null);
        }
    }
}

หมายเหตุ: ใช้งานได้เฉพาะในกรณีที่คุณสมบัติของคุณเหมือนกันในแบบจำลองของคุณกับวัตถุ ViewModel ของคุณที่จะถูกบันทึกลงในมัน
vapcguy

1

เช่นเดียวกับ Renat กล่าวว่าลบ: db.Books.Attach(book);

นอกจากนี้เปลี่ยนคิวรีผลลัพธ์ของคุณเพื่อใช้ "AsNoTracking" เนื่องจากคิวรีนี้กำลังสลัดสถานะโมเดลของเอนทิตีเฟรมเวิร์ก มันคิดว่า "result" เป็นหนังสือที่จะติดตามตอนนี้และคุณไม่ต้องการมัน

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);

1

ลองมัน....

UpdateModel (หนังสือ);

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            UpdateModel(book);
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

1

ฉันรู้ว่ามันได้รับคำตอบที่ดีมาแล้วสองสามครั้ง แต่ฉันชอบวิธีการด้านล่างนี้ ฉันหวังว่ามันจะช่วยให้ใครบางคน

//attach object (search for row)
TableName tn = _context.TableNames.Attach(new TableName { PK_COLUMN = YOUR_VALUE});
// set new value
tn.COLUMN_NAME_TO_UPDATE = NEW_COLUMN_VALUE;
// set column as modified
_context.Entry<TableName>(tn).Property(tnp => tnp.COLUMN_NAME_TO_UPDATE).IsModified = true;
// save change
_context.SaveChanges();

1

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