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をルックアップテーブルとして機能させることです。この場合、レコードは他のレコードにリンクされますが、アプリケーションを介してレコードが作成されることはありません。

私が抱えている問題は、ルックアップエンティティが別のコンテキストで読み込まれる場合、たとえばキャッシュされている場合です。したがって、レコードの保存を行うコンテキストはそのエンティティを追跡していません。GraphRootDbSetでAddが呼び出されると、ルックアップはEntityStateがAddedになりますが、実際にはUnchangedである必要があります。

代わりにattachを使用しようとすると、2つのルックアップエンティティがコンテキストに含まれるため、キーが重複しているためにクラッシュが発生します。

これを解決するための最良の方法は何ですか?実際の問題をかなり単純化したことに注意してください。私の実際のアプリケーションでは、これは、EF DBContextの上にあるリポジトリ、作業単位、およびビジネスサービスクラスのいくつかの異なるレイヤーを通じて発生しています。したがって、DBContextに何らかの形で適用できる一般的なソリューションの方がはるかに望ましいでしょう。

4

2 に答える 2

3

既存のエンティティを(たとえばキャッシュから)別のエンティティに持ち込む場合はDbContext、エンティティの状態を明示的に管理する必要があります。これにより、2つの簡単な結論が導き出されます。本当に必要な場合を除いて、複数のコンテキストのエンティティを混在させないでください。必要な場合は、アタッチするすべてのエンティティの状態を明示的に設定してください。

あなたが試みるかもしれないキャッシングへの1つのアプローチはこれです。単純なキャッシュマネージャークラスを作成します。おそらく静的です。キャッシュするエンティティタイプごとに、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;
    }
}

タイプミスは許してください。でも、うまくいけば、あなたはその考えを理解するでしょう。これは一般化できるので、エンティティタイプごとに1つのメソッドを用意する必要はありません。

編集:

次のコードは、実際に追加したいエンティティ以外のすべてを切り離す方法を示すのに役立つ場合があります。

// 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);
于 2013-02-01T15:28:29.487 に答える
1

ルックアップは外部キーとして定義されていますか?このコードが最初ですか?その場合は、ナビゲーションプロパティだけでなく、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; }
}

エンティティGraphCHILDの流暢なAPIスニペット

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

また

現在のアプローチを機能させたい場合は、最初にルックアップアイテムをコンテキストにアタッチしてみてください。マークされていないことを確認してから、グラフを追加してください;)

于 2013-02-01T15:30:44.173 に答える