2009-03-31 30 views
31

Tôi đang sử dụng khung thực thể và tôi đang gặp sự cố với các đối tượng "tìm lại" tôi vừa tạo ... về cơ bản nó như sau:Entity Framework: Tìm lại các đối tượng gần đây được thêm vào ngữ cảnh

string theId = "someId"; 

private void Test() 
{ 
    using(MyEntities entities = new MyEntities()) 
    { 
    EntityObject o = new EntityObject(); 
    o.Id = theId; 
    entities.AddToEntityObject(o); 
    CallSomeOtherMethod(entities); 
    } 
} 

void CallSomeOtherMethod(MyEntities ents) 
{ 
    EntityObject search = ents.EntityObject.FirstOrDefault(o => o.Id == theId); 
    if(search == null) 
    { 
    Console.WriteLine("wha happened???"); 
    } 
} 

(không đảm bảo mã hoạt động btw - tất cả đều nằm trong đầu của tôi)

Tại sao truy vấn "tìm" EntityObject vừa được tạo?

Nếu tôi gọi SaveChanges() sau khi AddToEntityObject nó hoạt động (điều này không làm tôi ngạc nhiên) nhưng tại sao nó không kéo từ bộ nhớ cache đúng cách?

Tôi vẫn xanh trên công cụ này vì vậy tôi hy vọng rằng có một số điều thực sự dễ dàng mà tôi chỉ nhìn ...

Cảm ơn

Trả lời

18

Điều này xảy ra bởi vì ents.EntityObject.WhatEver luôn truy vấn nguồn dữ liệu. Đây là một quyết định thiết kế. Họ làm theo cách này, bởi vì họ sẽ phải thực hiện truy vấn đối với nguồn dữ liệu, dựa vào bộ nhớ cache cục bộ và sau đó hợp nhất các kết quả. Là một trong những nhà phát triển được chỉ ra trong một blog (không thể nhớ chính xác) họ không thể xử lý điều này một cách nhất quán.

Như bạn có thể tưởng tượng có rất nhiều trường hợp góc cạnh bạn phải xử lý đúng cách. Bạn chỉ có thể tìm thấy một id bạn đã tạo cục bộ, được tạo bởi người khác trong cơ sở dữ liệu. Điều này sẽ buộc bạn phải chuẩn bị để xử lý các xung đột trên (gần như) mọi truy vấn. Có lẽ họ có thể tạo ra các phương thức để truy vấn bộ nhớ cache cục bộ và các phương thức để truy vấn nguồn dữ liệu, nhưng điều đó cũng không thông minh.

Bạn có thể xem Transparent Lazy Loading for Entity Framework. Điều này thay thế trình tạo mã thông thường và bạn nhận được các thực thể cư trú các bộ sưu tập thực thể liên quan và các tham chiếu thực thể của họ một cách tự động khi truy cập. Điều này tránh tất cả các đoạn mã

if (!Entity.ReleatedEntities.IsLoaded) 
{ 
    Entity.RelatedEntities.Load(); 
} 

mã. Và bạn có thể truy vấn các bộ sưu tập vì chúng luôn được tải ngầm. Nhưng giải pháp này cũng không hoàn hảo. Có một số vấn đề. Ví dụ, nếu bạn tạo một thực thể mới và truy cập vào một tập hợp các thực thể liên quan, bạn sẽ nhận được một ngoại lệ vì mã không thể truy xuất các thực thể liên quan từ cơ sở dữ liệu. Ngoài ra còn có một vấn đề liên quan đến ràng buộc dữ liệu và có thể là một số chi tiết mà tôi không biết.

Điều tốt là bạn lấy mã nguồn và có thể tự khắc phục sự cố và tôi sẽ xem xét vấn đề đầu tiên nếu tôi tìm thấy thời gian. Nhưng tôi khá chắc chắn rằng nó sẽ không được dễ dàng để sửa chữa, bởi vì tôi mong đợi một số trường hợp đã không chỉ cần nhấn cơ sở dữ liệu nếu thực thể vừa được tạo ra không phải là hành vi mong đợi.

+0

cảm ơn mẹo/cập nhật ... tôi sẽ để câu hỏi này mở trong một thời gian với hy vọng ai đó có thể cung cấp thêm thông tin cho tôi ... nhưng cảm ơn – dovholuk

1

Bạn có một số tùy chọn. Bạn có thể mở rộng ObjectContext bằng một lớp khác để tạo cơ chế riêng của bạn để truy xuất thông tin được thêm gần đây. Hoặc bạn có thể chỉ cần đặt một phương pháp mở rộng trên ObjectContext mà nhìn qua các ObjectContext.ObjectStateManager tìm kiếm 'thêm' ObjectStateEntries, và sau đó sử dụng LINQ to Objects để tìm thấy những gì bạn đang tìm kiếm.

2

Gần đây tôi đã gặp khó khăn với cùng một câu hỏi này. Tôi đăng câu trả lời này 2 năm sau khi câu hỏi được đặt ra với hy vọng rằng đoạn mã này có thể giúp ai đó tìm kiếm câu trả lời.Tôi đã cơ bản triển khai một phương pháp mở rộng (như đề xuất của Alex James) được gọi là "Tìm" hoạt động giống như "Nơi", nhưng "Tìm" cũng kiểm tra ObjectContext để xem có bất kỳ thực thể nào được thêm hay không thỏa mãn vị từ đã cho. Điều này cho phép bạn tìm một thực thể ngay cả khi nó chưa được lưu vào cơ sở dữ liệu.

Tìm trả về một IQueryable (của T) để bạn có thể sử dụng nó giống như bất kỳ toán tử LINQ nào khác.

<Extension()> 
Public Function Find(Of T As Class)(ByVal OSet As ObjectSet(Of T), _ 
     ByVal predicate As Expression(Of Func(Of T, Boolean))) _ 
     As System.Linq.IQueryable(Of T) 

    'Check the object context for Added objects first. 
    Dim AddedContextObjects = OSet.Context.ObjectStateManager _ 
         .GetObjectStateEntries(EntityState.Added) _ 
         .Select(Function(entity) entity.Entity).OfType(Of T)() 


    Dim Cpredicate = predicate.Compile 
    Dim MatchingObjects As New List(Of T) 

    For Each TObj As T In AddedContextObjects 
     If Cpredicate.Invoke(TObj) Then 
      MatchingObjects.Add(TObj) 
     End If 
    Next 

    'Now include a query to retrieve objects from the DB. 
    Dim DBObjects = OSet.Where(predicate) 

    If MatchingObjects.Count > 0 Then 
     'We found some added objects in the context. 
     'We want to return these objects as well as any Objects in DB 
     'that satisfy the predicate. 
     Return MatchingObjects.Union(DBObjects).AsQueryable 
    Else 
     'We didn't find any added objects in the context, 
     'so we just return the DB query. 
     Return DBObjects 
    End If 

End Function 
17

Tôi đã ở trong tình trạng tương tự. Tôi đã viết phương pháp mở rộng này có ít nhất đối với tôi giải quyết vấn đề (Tôi không có vấn đề với nghĩa là mâu thuẫn trong bối cảnh của tôi ...)

public static IEnumerable<T> WhereInclAdded<T>(this ObjectSet<T> set, Expression<Func<T, bool>> predicate) where T : class 
    { 
     var dbResult = set.Where(predicate); 

     var offlineResult = set.Context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(entry => entry.Entity).OfType<T>().Where(predicate.Compile()); 

     return offlineResult.Union(dbResult); 
    } 
+4

Đối với dbset tôi đã đổi thành: var offlineResult = set.Local.Where (predicate.Compile()); – John

+0

ý tưởng sạch sẽ và đẹp, thay vì WhereInclAdded, tôi sử dụng WhereAlsoAdded ... cổ vũ – Monah

+2

Lưu ý có một chi tiết tinh tế ở đây: .Where đang sử dụng IQueryable, trong khi địa phương ở đâu đang sử dụng IEnumerable. Nếu bạn sử dụng .Where (expression.Compile()) trong trường hợp đầu tiên, nó sẽ gọi cho IEnumerable thay vì làm cho toàn bộ bảng được nạp vào bộ nhớ. Chỉ cần một chi tiết nếu bạn đang sử dụng mã này làm cơ sở cho riêng bạn. –

17

đối tượng mới được bổ sung là trong nguồn dữ liệu địa phương như nó không tồn tại cơ sở dữ liệu chưa vì vậy bạn có thể nói EntityObject search = ents.EntityObject.FirstOrDefault(o => o.Id == theId) ?? ents.EntityObject.Local.FirstOrDefault(o => o.Id == theId);

+0

wow đang tìm kiếm lời cảm ơn này! – CMS

+0

cảm ơn ... giải pháp tốt – jstuardo

3

Các phương pháp khuyến nông dưới đây là DbSet <>

public static T TryAttach<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate) where T : class 
{ 
    T found = dbSet.Local.SingleOrDefault(predicate.Compile()); 
    if (found == null) dbSet.Attach(entity); 
    return found ?? entity; 
} 

Làm thế nào để sử dụng:

contextInstance.MyEntity.TryAttach(entityInstance, e => e.ID == entityInstance.ID); 

btw: Tôi thích Generics!

Các vấn đề liên quan