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 结束,但实际上它应该是未更改的。

如果我改为尝试使用附加,则会由于重复键而导致崩溃,因为两个查找实体最终出现在上下文中。

解决这个问题的最佳方法是什么?请注意,我已经相当简化了实际问题。在我的实际应用程序中,这是通过位于 EF DBContext 之上的几个不同的存储库层、工作单元和业务服务类来实现的。因此,我可以在 DBContext 中以某种方式应用的通用解决方案将是更可取的。

4

2 回答 2

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

编辑2:

这是显示预加载参考数据如何解决您的问题的代码。

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 的 fluent api 片段

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

或者

如果您想获得当前的工作方法,您可以尝试将查找项附加到上下文第一。确保它没有被标记为然后添加图表;)

于 2013-02-01T15:30:44.173 回答