Làm cách nào để cập nhật bản ghi bằng Entity Framework 6?


245

Tôi đang cố gắng cập nhật hồ sơ bằng cách sử dụng EF6. Đầu tiên tìm bản ghi, nếu tồn tại, cập nhật nó. Đây là mã của tôi: -

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

Mỗi lần tôi cố cập nhật bản ghi bằng mã trên, tôi gặp lỗi này: -

{System.Data.Entity.Infr Hạ tầng.DbUpdateConcurrencyException: Lưu trữ cập nhật, chèn hoặc xóa câu lệnh ảnh hưởng đến số lượng hàng không mong muốn (0). Các thực thể có thể đã được sửa đổi hoặc xóa kể từ khi các thực thể được tải. Làm mới các mục nhập ObjectStateManager


7
Lưu ý bên: catch (Exception ex){throw;}là dư thừa và bạn hoàn toàn có thể loại bỏ nó.
Sriram Sakth Xoay

thử bắt khối chỉ để tìm ra lý do thất bại. Nhưng vẫn không hiểu tại sao mã này bị lỗi?
user1327064

2
Không phải là chuyên gia trong chủ đề này, tôi không thể trả lời câu hỏi này. nhưng không cần thử bắt, bạn cũng có thể sử dụng break khi ngoại lệ được ném ra để phá vỡ trình gỡ lỗi khi có ngoại lệ.
Sriram Sakth Xoay

1
Bạn đã không thay đổi bất cứ điều gì. Chơi với trạng thái Thực thể sẽ không thay đổi thực tế rằng đối tượng chưa thực sự được sửa đổi.
Jonathan Allen

1
Vâng, tôi đã làm giống như bạn và không nhận được lỗi. Ngoại lệ cho biết DbUpdateConcurrencyException. Làm thế nào bạn xử lý đồng thời? Bạn đã sử dụng dấu thời gian, bạn đã sao chép và sau đó hợp nhất các đối tượng lại hay bạn đã sử dụng các thực thể tự theo dõi? (3 cách tiếp cận được sử dụng nhiều nhất). Nếu bạn không xử lý đồng thời, tôi đoán đó là vấn đề.
El Mac

Câu trả lời:


344

Bạn đang cố cập nhật bản ghi (theo tôi nghĩa là "thay đổi giá trị trên bản ghi hiện có và lưu lại"). Vì vậy, bạn cần lấy lại đối tượng, thực hiện thay đổi và lưu nó.

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
Gán giá trị không cập nhật cơ sở dữ liệu, gọi db.SaveChanges()với các đối tượng đã sửa đổi trong ngữ cảnh cập nhật cơ sở dữ liệu.
Craig W.

6
Tuy nhiên, nó vẫn hấp dẫn tôi ... vì vậy, kết quả var, thực sự được kết nối với dbcontext ... vì vậy điều này có nghĩa là bất kỳ biến nào được khởi tạo bởi bất kỳ thành viên dbcontext nào sẽ thực sự có sự kết hợp đó với cơ sở dữ liệu để bất kỳ thay đổi nào được áp dụng cho biến đó , nó cũng được áp dụng hay tồn tại?
WantIt

6
Bởi vì bối cảnh tạo ra đối tượng, bối cảnh có thể theo dõi đối tượng, bao gồm các thay đổi đối tượng. Khi bạn gọi SaveChangesbối cảnh sẽ đánh giá tất cả các đối tượng mà nó đang theo dõi để xác định xem chúng có được thêm, thay đổi hoặc xóa hay không và đưa ra SQL thích hợp cho cơ sở dữ liệu được kết nối.
Craig W.

3
tôi đang đối mặt với cùng một vấn đề - sử dụng EF6, cố gắng cập nhật một thực thể. Đính kèm + EntityState.Modified không hoạt động. Điều duy nhất hoạt động là - bạn cần truy xuất đối tượng, thực hiện các thay đổi mong muốn và lưu nó thông qua db.SaveChanges ();
Gurpreet Singh

7
Bạn KHÔNG nên truy xuất đối tượng trước để cập nhật nó. Tôi đã có cùng một vấn đề cho đến khi tôi nhận ra rằng tôi đang cố gắng thay đổi một trong các giá trị khóa chính (khóa tổng hợp). Miễn là bạn cung cấp khóa chính chính xác, bạn có thể đặt EntityState thành Modified và SaveChanges () sẽ hoạt động, miễn là bạn không phá vỡ một số ràng buộc toàn vẹn khác được xác định trên bảng.
adrianz

165

Tôi đã xem xét mã nguồn của Entity Framework và tìm thấy một cách để thực sự cập nhật một thực thể nếu bạn biết thuộc tính Key:

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

Nếu không, hãy kiểm tra việc triển khai AddOrUpdate để biết ý tưởng.

Hy vọng điều này giúp đỡ!


12
Đẹp! Không cần liệt kê tất cả các thuộc tính. Tôi giả sử SaveChanges()cuộc gọi là bắt buộc sau khi thiết lập giá trị.
Jan Zahradník

3
Có, các thay đổi sẽ được duy trì trên SaveChanges ()
Miguel

1
Câu trả lời tuyệt vời, không quá rõ ràng với IntelliSense rằng làm điều gì đó như thế này sẽ KHÔNG hoạt động: _context.MyObj = newObj; sau đó SaveChanges () hoặc .... _context.MyObj.Update (newObj) rồi SaveChanges (); Giải pháp của bạn cập nhật toàn bộ đối tượng mà không cần phải lặp qua tất cả các thuộc tính.
Adam

7
Điều này phàn nàn với tôi rằng tôi đang cố gắng chỉnh sửa trường ID
Vasily Hall

3
@VasilyHall - điều này xảy ra nếu các trường ID (hoặc bất cứ điều gì bạn đã xác định Khóa chính là) khác nhau giữa các mô hình (bao gồm null / 0 trong một trong các mô hình). Hãy chắc chắn rằng ID trùng khớp giữa hai mô hình và nó sẽ cập nhật tốt.
Gavin Coates

51

Bạn có thể sử dụng AddOrUpdatephương pháp:

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

1
Giải pháp tốt nhất cho IMO
Norgul

112
.AddOrUpdate()được sử dụng trong quá trình di chuyển cơ sở dữ liệu, rất không khuyến khích sử dụng phương thức này bên ngoài việc di chuyển, do đó tại sao nó lại nằm trong Entity.Migrationskhông gian tên.
Adam Vincent

1
Như @AdamVincent đã nói, AddOrUpdate()phương thức này dành cho việc di chuyển và nó không phù hợp với tình huống khi bạn chỉ cần cập nhật hàng hiện có. Trong trường hợp khi bạn không có sách có tham chiếu tìm kiếm (ví dụ ID), nó sẽ tạo một hàng mới và nó có thể là một vấn đề trong các trường hợp đến (ví dụ: bạn có API cần trả về phản hồi 404-NotFound nếu bạn thử gọi phương thức PUT cho hàng không tồn tại).
Marko

4
Đừng sử dụng cái này trừ khi bạn biết bạn đang làm gì !!!!!!!!!!!!!!!! đọc: michaelgmccarthy.com/2016/08/24/...
Yusha

4
Tôi đã trở lại vấn đề này một lần nữa hôm nay, tôi có thể cảnh báo tất cả các bạn rằng đây không phải là một giải pháp tốt cho trường hợp sử dụng mong muốn
Yusha

23

Vì vậy, bạn có một thực thể được cập nhật và bạn muốn cập nhật nó trong cơ sở dữ liệu với số lượng mã ít nhất ...

Đồng thời luôn luôn khó khăn, nhưng tôi giả định rằng bạn chỉ muốn cập nhật của mình để giành chiến thắng. Đây là cách tôi đã làm nó cho trường hợp tương tự của tôi và sửa đổi tên để bắt chước các lớp của bạn. Nói cách khác, chỉ cần thay đổi attachthành addvà nó hoạt động với tôi:

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

Bạn nên sử dụng phương thức Entry () trong trường hợp bạn muốn cập nhật tất cả các trường trong đối tượng của mình. Ngoài ra, hãy nhớ rằng bạn không thể thay đổi id trường (khóa) do đó trước tiên hãy đặt Id giống như khi bạn chỉnh sửa.

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
Ít nhất bạn nên cố gắng trả lời câu hỏi, không chỉ đăng mã
StaceyGirl

Vui lòng đưa ra một số lời giải thích cho câu hỏi thay vì chỉ để lại một đoạn mã để giúp người hỏi câu hỏi tốt hơn.
feanor07

9

Mã này là kết quả của một thử nghiệm để chỉ cập nhật một tập hợp các cột mà không thực hiện truy vấn để trả về bản ghi trước. Nó sử dụng mã Entity Framework 7 trước tiên.

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

Đây là mã hoàn chỉnh:

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

7

Đối với lõi .net

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();

5

Đây là giải pháp tốt nhất cho vấn đề này: Trong Xem thêm tất cả ID (Khóa). Xem xét có nhiều bảng được đặt tên (Thứ nhất, Thứ hai và Thứ ba)

@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)

Trong mã 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

Attaching một thực thể sẽ đặt trạng thái theo dõi của nó thành Unchanged. Để cập nhật một thực thể hiện có, tất cả những gì bạn cần làm là đặt trạng thái theo dõi thành Modified. Theo các tài liệu của EF6 :

Nếu bạn có một thực thể mà bạn biết đã tồn tại trong cơ sở dữ liệu nhưng những thay đổi có thể đã được thực hiện thì bạn có thể cho biết bối cảnh để đính kèm thực thể đó và đặt trạng thái của nó thành Sửa đổi. Ví dụ:

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

Tôi tìm thấy một cách làm việc tốt.

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

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


1

Đây là phương pháp cập nhật thực thể sau RIA của tôi (cho khung thời gian 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();
}

Lưu ý rằng đó FrameworkTypeUtility.SetProperties()là một chức năng tiện ích nhỏ mà tôi đã viết từ lâu trước AutoMapper trên 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);
        }
    }
}

Lưu ý: Chỉ hoạt động nếu các thuộc tính của bạn giống hệt trong mô hình của bạn với đối tượng ViewModel đang được lưu vào mô hình.
vapcguy

1

Giống như Renat đã nói, xóa: db.Books.Attach(book);

Ngoài ra, thay đổi truy vấn kết quả của bạn để sử dụng "AsNoTracking", bởi vì truy vấn này đang loại bỏ trạng thái mô hình của khung thực thể. Nó nghĩ rằng "kết quả" là cuốn sách để theo dõi ngay bây giờ và bạn không muốn điều đó.

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

1

Thử nó....

UpdateModel (sách);

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

Tôi biết nó đã được trả lời tốt vài lần rồi, nhưng tôi thích cách làm dưới đây. Tôi hy vọng nó sẽ giúp được ai đó.

//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

Điều này nếu cho Entity Framework 6.2.0.

Nếu bạn có một mục cụ thể DbSetvà một mục cần được cập nhật hoặc tạo:

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

Tuy nhiên, điều này cũng có thể được sử dụng cho một chung DbSetvới một khóa chính duy nhất hoặc một khóa chính tổng hợp.

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.