2009-05-22 15 views
6

Tôi cần đặt EntityKey của EntityObject. Tôi biết loại của nó và giá trị id của nó. Tôi không muốn truy vấn cơ sở dữ liệu một cách không cần thiết.EntityKey và ApplyPropertyChanges()

này hoạt động ...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    Model.EntityKey = (from Department d in db.Department 
         where d.Id == id 
         select d).FirstOrDefault().EntityKey; 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

này thất bại ...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    String EntitySetName = db.DefaultContainerName + "." + Model.GetType().Name; 
    Model.EntityKey = new System.Data.EntityKey(EntitySetName, "Id", Model.Id); 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

Các ApplyPropertyChanges() dòng không thành công với ngoại lệ này:

Các ObjectStateManager không chứa một ObjectStateEntry với tham chiếu đến một đối tượng thuộc loại 'Sample.Models.Department'.

Hai EntityKey là bằng nhau. Tại sao khối thứ hai của mã không thành công? Làm thế nào tôi có thể sửa chữa nó?

Trả lời

9

Lý do khối thứ hai của bạn bị lỗi là vì EF không thể tìm đối tượng trong ObjectStateManager - tức là khi nó kéo đối tượng từ db nó đặt chúng trong trình quản lý trạng thái để nó có thể theo dõi chúng - điều này tương tự như mẫu Identity Map. Mặc dù có một EntityKey, đối tượng của bạn không nằm trong trình quản lý trạng thái nên EF không thể tiếp tục thay đổi. Bạn có thể giải quyết vấn đề này bằng cách đặt đối tượng vào chính người quản lý nhà nước nhưng bạn có một chút lén lút về nó.

này hoạt động:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    var entitySetName = db.DefaultContainerName + "." + model.GetType().Name; 
    var entityKey = new System.Data.EntityKey(entitySetName, "Id", model.Id); 

    db.Attach(new Department{Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, model); 
    db.SaveChanges(); 
} 

... nhưng nó không phải là rất sạch sẽ. Về cơ bản, việc này gắn một đối tượng 'trống' chỉ với một khóa thực thể, chấp nhận tất cả các thay đổi và sau đó gọi hàm ApplyPropertyChanges với các giá trị được cập nhật thực tế thực tế.

Đây là điều tương tự được bao bọc trong một phương pháp mở rộng - điều này sẽ hoạt động đối với bất kỳ thứ gì đã sử dụng một cột db duy nhất cho khóa chính. Các chỉ là một phần thú vị của cuộc gọi phương pháp này là bạn cần phải nói cho nó làm thế nào để tìm key property thông qua một đại biểu như là đối số thứ hai để các phương pháp khuyến nông:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    db.ApplyDetachedPropertyChanges(model, x => x.Id); 
    db.SaveChanges(); 
} 

và phương pháp khuyến nông:

public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = getIdDelegate(entity); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    db.Attach(new Department {Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 

Khi phương thức mở rộng đang gọi AcceptAllChanges, bạn cần phải cẩn thận khi gọi điện thoại này nếu bạn đang cập nhật nhiều thực thể cùng một lúc - bạn có thể dễ dàng 'mất' các cập nhật nếu bạn không cẩn thận. Do đó, phương pháp này chỉ thực sự phù hợp với các tình huống cập nhật đơn giản - ví dụ: rất nhiều phương pháp hành động MVC :)

+0

+1 cho phương pháp mở rộng. Đó là khá! =) Phương thức này sẽ thay đổi như thế nào nếu bảng sử dụng PK nhiều cột? –

+0

Bạn sẽ cần phải thực hiện quá tải của phương pháp mở rộng đã mất một IEnumerable của KeyValuePair để xác định khóa, và sau đó gọi quá tải constructor tương ứng trên EntityKey. Để duy trì sự an toàn stype và tránh đi qua các chuỗi trong KeyValuePair của bạn, bạn có thể vượt qua một mảng các đại biểu 'tìm kiếm chính' cùng với các giá trị để đưa vào các khóa. –

+1

Dựa trên câu trả lời tuyệt vời này, tôi đã cải thiện nó một chút và tạo ra một phương pháp nhỏ của riêng tôi, và tôi cũng đăng tải câu trả lời. Nó hoạt động cho bất kỳ loại đối tượng thực thể nào và không sử dụng các đại biểu. –

4
public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
     var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 

     T newEntity = Activator.CreateInstance<T>(); 
     newEntity.EntityKey = db.CreateEntityKey(entitySetName, entity); 

     Type t = typeof(T); 
     foreach(EntityKeyMember keyMember in newEntity.EntityKey.EntityKeyValues) { 
      PropertyInfo p = t.GetProperty(keyMember.Key); 
      p.SetValue(newEntity, keyMember.Value, null); 
     } 

     db.Attach(newEntity); 
     //db.AcceptAllChanges(); 

     db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 
+0

Cảm ơn, tôi đã tìm kiếm một cái gì đó như thế này, và bạn cung cấp sự cần thiết! +1 – camainc

+1

Tôi đã phải thêm phương thức mở rộng chuỗi "ToPlural()" vào "entity.GetType(). Name" vì các thực thể của tôi được nhiều máy tạo mã đa dạng. Ngoài ra, mã của bạn hoạt động hoàn hảo. – camainc

2

Hãy thử sử dụng mã bên dưới và cho tôi biết nếu nó hoạt động cho bạn.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    using (var context = new EntityContext()) 
    { 
     try 
     { 
      Object entity = null; 
      IEnumerable<KeyValuePair<string, object>> entityKeyValues = 
       new KeyValuePair<string, object>[] { 
        new KeyValuePair<string, object>("DepartmentID", id) }; 

      // Create the key for a specific SalesOrderHeader object. 
      EntityKey key = new EntityKey("EntityContext.Deparment", 
                    entityKeyValues); 

      // Get the object from the context or the persisted store by its key. 
      if (context.TryGetObjectByKey(key, out entity)) 
      { 
       context.ApplyPropertyChanges(key.EntitySetName, Model); 
       context.SaveChanges(); 
      } 
      else 
      { 
       // log message if we need 
       //"An object with this key could not be found." 
      }     
     } 
     catch (EntitySqlException ex) 
     { 
      // log message 
     } 
    } 
}  
1

Tôi đã gặp vấn đề tương tự, trong khi nó đang hoạt động trên các trang khác. Tôi không biết nếu bình luận này sẽ hữu ích ... Nhưng cuối cùng tôi đã phát hiện ra rằng, trong objet của tôi (tương đương với Departement của bạn), "Id" của tôi đã không xuất hiện trong các hình thức ...

Chỉ cần thực tế thêm nó (trong khi tôi đã xóa nó ...) trên biểu mẫu của tôi đã giải quyết được sự cố của tôi. (với style = "visibility: hidden", để không nhìn thấy anh ta ...)

2

Cải thiện khi triển khai tuyệt vời của Steve Willcock, đây là đề xuất của tôi.

Nó sử dụng cách phản chiếu (một phần của .NET) nhiều hơn ví dụ ban đầu, để giúp bạn tiết kiệm một số mã. Nó cũng tự động hỗ trợ bất kỳ loại lớp thực thể nào, và không chỉ là một "Bộ".

Ngoài ra, nó sẽ loại bỏ phương pháp lỗi thời ApplyPropertyChanges và sử dụng phương thức ApplyCurrentValues mới.

Phương pháp

Phương pháp này về cơ bản chỉ sử dụng phản ánh để có được giá trị của "Id" sở hữu động, và đặt nó quá. Điều này tiết kiệm tất cả các rắc rối với một đại biểu.

public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity) where T : EntityObject 
{ 
    PropertyInfo idProperty = typeof(T).GetProperty("Id"); 

    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = idProperty.GetValue(entity, null); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    Type type = entity.GetType(); 
    EntityObject obj = (EntityObject)Activator.CreateInstance(type); 

    idProperty.SetValue(obj, id, null); 
    obj.EntityKey = entityKey; 

    db.Attach(obj); 
    db.AcceptAllChanges(); 

    db.ApplyCurrentValues(entitySetName, entity); 
} 

Cách sử dụng

Sử dụng nó là khá đơn giản là tốt.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    db.ApplyDetachedPropertyChanges(Model); 
    db.SaveChanges(); 
} 
Các vấn đề liên quan