2013-02-01 21 views
3

這裏原來加載的一些測試代碼演示我的問題:實體框架 - 避免「查找」實體獲取添加狀態時,在另一種情況下

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 
using NUnit.Framework; 

namespace EFGraphInsertLookup 
{ 
    public class GraphLookup 
    { 
     public int ID { get; set; } 
     public string Code { get; set; } 
    } 

    public class GraphChild 
    { 
     public int ID { get; set; } 
     public virtual GraphRoot Root { get; set; } 
     public virtual GraphLookup Lookup { get; set; } 
    } 

    public class GraphRoot 
    { 
     public int ID { get; set; } 
     public virtual ICollection<GraphChild> Children { get; set; } 
    } 

    public class TestDbContext : DbContext 
    { 
     public DbSet<GraphRoot> GraphRoots { get; set; } 
     public DbSet<GraphChild> GraphChildren { get; set; } 
     public DbSet<GraphLookup> GraphLookups { get; set; } 

     public TestDbContext() 
     { 
      GraphLookups.ToList(); 
     } 
    } 

    public class TestDbInit : DropCreateDatabaseAlways<TestDbContext> 
    { 
     protected override void Seed(TestDbContext context) 
     { 
      base.Seed(context); 
      context.GraphLookups.Add(new GraphLookup { Code = "Lookup" }); 
      context.SaveChanges(); 
     } 
    } 

    [TestFixture] 
    public class Tests 
    { 
     [Test] 
     public void MainTest() 
     { 
      Database.SetInitializer<TestDbContext>(new TestDbInit()); 

      var lookupCtx = new TestDbContext(); 
      var firstLookup = lookupCtx.GraphLookups.Where(l => l.Code == "Lookup").Single(); 

      var graph = new GraphRoot 
      { 
       Children = new List<GraphChild> { new GraphChild { Lookup = firstLookup } } 
      }; 
      var ctx = new TestDbContext(); 
      ctx.GraphRoots.Add(graph); // Creates a new lookup record, which is not desired 
      //ctx.GraphRoots.Attach(graph); // Crashes due to dupe lookup IDs 
      ctx.SaveChanges(); 

      ctx = new TestDbContext(); 
      graph = ctx.GraphRoots.Single(); 
      Assert.AreEqual(1, graph.Children.First().Lookup.ID, "New lookup ID was created..."); 
     } 
    } 
} 

我的願望是有GraphLookup充當查找表,其中記錄鏈接到其他記錄,但記錄不會通過應用程序創建。

我遇到的問題是查找實體在不同的上下文中加載時,例如,當它被緩存。因此,保存記錄的上下文沒有跟蹤該實體,並且在調用GraphRoot DbSet時調用Add時,查找最終會添加一個EntityState,但實際上它應該是未更改的。

如果我反而嘗試使用attach,則由於重複鍵導致崩潰,因爲兩個查找實體在上下文中結束。

解決此問題的最佳方法是什麼?請注意,我已經簡化了很多實際問題。在我的實際應用中,這是通過位於EF DBContext之上的幾個不同層的存儲庫,工作單元和業務服務類來實現的。所以我可以在DBContext中以某種方式應用一個通用的解決方案,這將是非常受歡迎的。

回答

3

如果您將現有實體(例如從緩存中)帶入另一個DbContext,則必須明確地管理實體狀態。這導致了兩個簡單的結論:不會混合來自多個上下文的實體,除非您真的需要,並且當您這樣做時,明確設置您附加的所有內容的實體狀態

緩存你可能會嘗試的一種方法是這樣的。創建一個簡單的緩存管理器類,也許是靜態的對於你希望緩存每個實體類型,有一個GetMyEntity(int myEntityId, DbContext context)方法看起來是這樣的:

public MyEntity GetMyEntity(int entityId, MyContext context) 
{ 
    MyEntity entity; 

    // Get entity from context if it's already loaded. 
    entity = context.Set<MyEntity>().Loaded.SingleOrDefault(q => q.EntityId == entityId); 

    if (entity != null) 
    { 
     return entity; 
    } 
    else if (this.cache.TryGetValue("MYENTITY#" + entityId.ToString(), out entity) 
    { 
     // Get entity from cache if it's present. Adapt this to whatever cache API you're using. 
     context.Entry(entity).EntityState = EntityState.Unchanged; 
     return entity; 
    } 
    else 
    { 
     // Load entity if it's not in the context already or in the cache. 
     entity = context.Set<MyEntity>().Find(entityId); 

     // Add loaded entity to the cache. Adapt this to specify suitable rules for cache item expiry if appropriate. 
     this.cache["MYENTITY#" + entityId.ToString()] = entity; 
     return entity; 
    } 
} 

請原諒任何錯別字,但希望你的想法。你可能會看到這可能是泛化的,所以你不必爲每個實體類型都有一個方法。

編輯:

下面的代碼可能是有用的,以顯示你如何能脫離一切除非你真正想要添加的實體。

// Add a single entity. 
context.E1s.Add(new1); 

var dontAddMeNow = (from e in context.ChangeTracker.Entries() 
        where !object.ReferenceEquals(e.Entity, new1) 
        select e).ToList(); 

foreach (var e in dontAddMeNow) 
{ 
    e.State = System.Data.EntityState.Unchanged; // Or Detached. 
} 

EDIT2:

這裏是展示如何預加載的參考數據可以解決你的問題的代碼。

E2 child = new E2 { Id = 1 }; 

context.Entry(child).State = System.Data.EntityState.Unchanged; 

E1 new1 = new E1 
{ 
    Child = child 
}; 

// Add a single entity. 
context.E1s.Add(new1); 

Debug.Assert(context.Entry(new1.Child).State == System.Data.EntityState.Unchanged); 
Debug.Assert(context.Entry(new1).State == System.Data.EntityState.Added); 
+0

這很有道理,奧利。我希望找到一些可以在添加之後修改狀態的位置,但在SaveChanges之前。這樣消費者代碼就不會改變。另外,緩存並不是我遇到這個問題的唯一時間。當用戶界面加載查找時,也可能會發生這種情況,比如稱爲下拉菜單,然後將這些查找加入到添加中。 – RationalGeek

+0

@RationalGeek - 對。可能沒有解決這個問題的「靈丹妙藥」。這是EF無法解決你想要做的事情的情況;這是一個最好的猜測。 – Olly

+0

@RationalGeek - 概念上,如果您有類似於內部使用EF上下文的存儲庫的內容,呼叫者不必擔心遵守某些規則以避免EF錯誤。因此,使用EF的代碼必須負責適當地設置實體狀態。只有它能夠知道什麼是「參考」數據和什麼是「交易」數據。前者需要將其狀態設置爲未更改(或完全分離)。我喜歡將「參考」數據加載到高級環境中的想法,因此它永遠不會被標記爲添加。 – Olly

1

是定義爲外鍵的查找嗎?這個代碼優先嗎? 如果是這樣,請嘗試更改child以使LookupID不僅僅是導航屬性。
然後只提供GraphLookiD。 (更好的性能,因爲查找實體不需要先加載。)

public class GraphChild 
{ 
    public int ID { get; set; } 
    public int GraphLookupId { get; set; } //<<<<< add this an SET ONLY this 
    public virtual GraphRoot Root { get; set; } 
    public virtual GraphLookup Lookup { get; set; } 
} 

流利的API代碼段用於實體GraphCHILD

.HasRequired(x => x.Lookup).WithMany().HasForeignKey(x => x.graphlookupID); 

OR

,如果你想獲得當前的工作方式,你可以嘗試 裝上查找項目的上下文FIRST

。 確保它沒有標記,然後添加圖;)

+0

這是個好主意。它可能會工作。不幸的是,我不能保證我的商業服務的消費者只會設置ID。但我至少可以用它來解決確定的具體案例。 – RationalGeek

相關問題