Cập nhật hàng nếu nó tồn tại khác Chèn logic với khung thực thể


179

Có ai có đề xuất về cách hiệu quả nhất để triển khai logic "cập nhật hàng nếu nó tồn tại khác" bằng cách sử dụng Entity Framework không?


2
Đây là điều cần được thực hiện ở cấp độ công cụ cơ sở dữ liệu, trong một thủ tục được lưu trữ. Nếu không, bạn sẽ phải bọc phát hiện / cập nhật / chèn trong giao dịch.
Stephen Chung

1
@Stephen: Thực tế, đây là những gì tôi đã làm. Cảm ơn.
Jonathan Wood

Jonathan, câu hỏi của bạn rất hữu ích cho tôi. Tại sao bạn chuyển sang một thủ tục được lưu trữ?
anar khalilov

2
@Anar: Nó chỉ dễ dàng hơn và tôi mong đợi hiệu quả hơn nhiều.
Jonathan Wood

Bạn có phải viết một thủ tục lưu trữ cho mỗi bảng không?
tofutim

Câu trả lời:


174

Nếu bạn đang làm việc với đối tượng đính kèm (đối tượng được tải từ cùng một thể hiện của bối cảnh), bạn có thể chỉ cần sử dụng:

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

// Attached object tracks modifications automatically

context.SaveChanges();

Nếu bạn có thể sử dụng bất kỳ kiến ​​thức nào về khóa của đối tượng, bạn có thể sử dụng một cái gì đó như thế này:

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

context.SaveChanges();

Nếu bạn không thể quyết định sự tồn tại của đối tượng bằng Id của nó, bạn phải thực hiện truy vấn tra cứu:

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

Cảm ơn. Trông giống như những gì tôi cần. Tôi có thể hỏi bạn một câu mà đã làm phiền tôi một lúc không? Thông thường, tôi đặt bối cảnh của mình trong một usingkhối ngắn . Có thể để lại bối cảnh trong bộ nhớ trong một thời gian? Ví dụ, trong vòng đời của một mẫu Windows? Tôi thường cố gắng và dọn sạch các đối tượng cơ sở dữ liệu để đảm bảo tải tối thiểu trên cơ sở dữ liệu. Không có vấn đề chờ đợi để phá hủy bối cảnh EF của tôi?
Jonathan Wood

Kiểm tra điều này: stackoverflow.com/questions/3653009/ bối cảnh đối tượng nên sống càng ngắn càng tốt nhưng trong trường hợp winforms hoặc wpf, điều này có thể có nghĩa là bối cảnh đó tồn tại lâu như người trình bày. Câu hỏi được liên kết chứa liên kết đến bài viết msDN về việc sử dụng phiên nhibernate trong winforms. Cách tiếp cận tương tự có thể được sử dụng cho bối cảnh.
Ladislav Mrnka

1
Nhưng điều gì sẽ xảy ra nếu tôi cần làm điều này với một danh sách các đối tượng ... trong cơ sở dữ liệu của tôi có một danh sách các hàng có cùng id và tôi muốn thay thế nếu bạn tồn tại hoặc chèn nếu chúng không .. làm thế nào? cảm ơn!
Phoenix_uy

1
Câu trả lời này LOOKS tuyệt vời, nhưng tôi đang gặp phải vấn đề này khi cập nhật: Một đối tượng có cùng khóa đã tồn tại trong ObjectStateManager. ObjectStateManager không thể theo dõi nhiều đối tượng với cùng một khóa.
John Zumbrum

1
Có vẻ như tôi chỉ gặp một chút vấn đề với việc tìm nạp đối tượng hiện có để lấy khóa của nó trước khi thực hiện cập nhật; tách ra đối tượng tra cứu đầu tiên đã giúp sửa chữa nó.
John Zumbrum

33

Trong Entity Framework 4.3, có một AddOrUpdatephương thức tại không gian tên System.Data.Entity.Migrations:

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

mà theo tài liệu :

Thêm hoặc cập nhật các thực thể theo khóa khi SaveChanges được gọi. Tương đương với một hoạt động "nâng cấp" từ thuật ngữ cơ sở dữ liệu. Phương pháp này có thể hữu ích khi gieo dữ liệu bằng cách sử dụng Di chuyển.


Để trả lời nhận xét của @ Smashing1978 , tôi sẽ dán các phần có liên quan từ liên kết được cung cấp bởi @Colin

Công việc của AddOrUpdate là đảm bảo rằng bạn không tạo các bản sao khi bạn tạo dữ liệu trong quá trình phát triển.

Đầu tiên, nó sẽ thực thi một truy vấn trong cơ sở dữ liệu của bạn để tìm một bản ghi trong đó bất cứ thứ gì bạn cung cấp làm khóa (tham số đầu tiên) khớp với giá trị cột (hoặc giá trị) được ánh xạ được cung cấp trong AddOrUpdate. Vì vậy, đây là một chút lỏng lẻo để phù hợp nhưng hoàn toàn tốt cho dữ liệu thời gian thiết kế gieo hạt.

Quan trọng hơn, nếu một trận đấu được tìm thấy thì bản cập nhật sẽ cập nhật tất cả và loại bỏ mọi thứ không có trong AddOrUpdate của bạn.

Điều đó nói rằng, tôi có một tình huống tôi đang lấy dữ liệu từ một dịch vụ bên ngoài và chèn hoặc cập nhật các giá trị hiện có bằng khóa chính (và dữ liệu cục bộ của tôi cho người tiêu dùng là chỉ đọc) - đã được sử dụng AddOrUpdatetrong sản xuất hơn 6 tháng nay và vì vậy không có vấn đề gì


7
Không gian tên System.Data.Entity.Migations chứa các lớp liên quan đến di chuyển dựa trên mã và cấu hình của chúng. Có bất kỳ lý do tại sao chúng ta không nên sử dụng điều này trong kho của chúng tôi cho thực thể không di chuyển AddOrUpdates?
Matt Lengenfelder

10
Hãy cẩn thận với phương pháp AddOrUpdate
Colin

1
Bài viết này mô tả lý do tại sao không nên sử dụng
AddOrUpdate

11

Phép thuật xảy ra khi gọi SaveChanges()và phụ thuộc vào hiện tại EntityState. Nếu thực thể có một EntityState.Added, nó sẽ được thêm vào cơ sở dữ liệu, nếu có EntityState.Modified, nó sẽ được cập nhật trong cơ sở dữ liệu. Vì vậy, bạn có thể thực hiện một InsertOrUpdate()phương pháp như sau:

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

        context.SaveChanges(); 
    } 
}

Tìm hiểu thêm về EntityState

Nếu bạn không thể kiểm tra Id = 0để xác định xem đó có phải là một thực thể mới hay không, hãy kiểm tra câu trả lời của Ladislav Mrnka .


8

Nếu bạn biết rằng bạn đang sử dụng cùng một bối cảnh và không tách rời bất kỳ thực thể nào, bạn có thể tạo một phiên bản chung như thế này:

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 Tất nhiên có thể là một trường lớp hoặc phương thức có thể được tạo tĩnh và mở rộng, nhưng đây là điều cơ bản.


4

Câu trả lời của Ladislav rất gần nhưng tôi đã phải thực hiện một vài sửa đổi để làm cho nó hoạt động trong EF6 (cơ sở dữ liệu đầu tiên). Tôi đã mở rộng bối cảnh dữ liệu của mình bằng phương thức AddOrUpdate và cho đến nay nó dường như hoạt động tốt với các đối tượng tách rời:

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 cũng tồn tại như một phương thức mở rộng trong System.Data.Entity.Migations, vì vậy nếu tôi là bạn, tôi sẽ tránh sử dụng lại tên phương thức tương tự cho phương thức của riêng bạn.
Thu hút

2

Theo tôi, điều đáng nói là với EntityGraphOperations cho Entity Framework Code Trước tiên, bạn có thể tự cứu mình khỏi việc viết một số mã lặp đi lặp lại để xác định trạng thái của tất cả các thực thể trong biểu đồ. Tôi là tác giả của sản phẩm này. Và tôi đã xuất bản nó trong github , code-project ( bao gồm trình diễn từng bước và một dự án mẫu đã sẵn sàng để tải xuống)nuget .

Nó sẽ tự động đặt trạng thái của các thực thể thành Addedhoặc Modified. Và bạn sẽ chọn thủ công những thực thể phải bị xóa nếu nó không còn tồn tại nữa.

Ví dụ:

Hãy nói rằng tôi có một Personđối tượng. Personcó thể có nhiều điện thoại, một Tài liệu và có thể có người phối ngẫu.

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

Tôi muốn xác định trạng thái của tất cả các thực thể được bao gồm trong biểu đồ.

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

Ngoài ra, như bạn biết các thuộc tính khóa duy nhất có thể đóng vai trò trong khi xác định trạng thái của thực thể Điện thoại. Đối với các mục đích đặc biệt như vậy, chúng tôi có ExtendedEntityTypeConfiguration<>lớp, kế thừa từ EntityTypeConfiguration<>. Nếu chúng ta muốn sử dụng các cấu hình đặc biệt như vậy thì chúng ta phải kế thừa các lớp ánh xạ từ đó ExtendedEntityTypeConfiguration<>chứ không phải EntityTypeConfiguration<>. Ví dụ:

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

Đó là tất cả.


2

Chèn khác cập nhật cả hai

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

Tôi đã sử dụng phương pháp này nhưng và đã kiểm tra (sau lần đầu tiên hoặc mặc định) nếu (truy vấn == null)
Patrick

2

Kiểm tra hàng hiện có với 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();

        }

    }

1

Thay thế cho câu trả lời @LadislavMrnka. Đ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);
    }
}

-1

Đã sửa

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
Vui lòng sử dụng chỉnh sửa thay vì đăng câu trả lời khác
Suraj Rao
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.