gpt4 book ai didi

c# - Entity Framework ——如何缓存和共享只读对象

转载 作者:太空宇宙 更新时间:2023-11-03 15:12:23 25 4
gpt4 key购买 nike

我们有一个具有相当复杂实体模型的应用程序,其中高性能和低延迟是必不可少的,但我们不需要水平可扩展性。除了自托管的 ASP.NET Web API 2 之外,该应用程序还有许多事件源。我们使用 Entity Framework 6 从 POCO 类映射到数据库(我们使用出色的 Reverse POCO Generator 生成我们的类)。

每当事件到达时,应用程序必须对实体模型进行一些调整,并通过 EF 将此增量调整持久保存到数据库中。同时,读取或更新请求可能会通过 Web API 到达。

由于模型涉及到很多表和FK关系,而对一个事件的 react 通常需要加载主体实体下的所有关系,所以我们选择将整个数据集维护在内存缓存中,而不是加载每个事件的整个对象图。下图显示了我们模型的简化版本:-

enter image description here

在程序启动时,我们通过临时 DbContext 加载所有有趣的 ClassA 实例(及其关联的依赖图)并插入到字典(即我们的缓存)中.当事件到达时,我们在缓存中找到 ClassA 实例,并通过 DbSet.Attach() 将其附加到每个事件的 DbContext。程序自始至终使用 await-async 模式编写,可以同时处理多个事件。我们通过使用锁来保护缓存的对象不被并发访问,因此我们保证缓存的 ClassA 一次只能加载到一个 DbContext 中。到目前为止一切顺利,性能非常好,我们对这个机制很满意。 但是有一个问题。尽管实体图在 ClassA 下相当独立,但仍有一些 POCO 类表示我们认为是只读的静态数据(图中橙色阴影部分)。我们发现 EF 有时会报错

An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

当我们尝试同时 Attach() 两个不同的 ClassA 实例时(即使我们附加到不同的 Dbcontexts)因为它们共享对相同 ClassAType 的引用。下面的代码片段展示了这一点:-

    ConcurrentDictionary<int,ClassA> theCache = null;

using(var ctx = new MyDbContext())
{
var classAs = ctx.ClassAs
.Include(a => a.ClassAType)
.ToList();

theCache = new ConcurrentDictionary<int,ClassA>(classAs.ToDictionary(a => a.ID));
}

// take 2 different instances of ClassA that refer to the same ClassAType
// and load them into separate DbContexts

var ctx1 = new MyDbContext();
ctx1.ClassAs.Attach(theCache[1]);

var ctx2 = new MyDbContext();
ctx2.ClassAs.Attach(theCache[2]); // exception thrown here

有什么方法可以通知 EF ClassAType 是只读/静态的,我们不希望它确保每个实例只能加载到一个 DbContext?到目前为止,我发现解决这个问题的唯一方法是修改 POCO 生成器以忽略这些 FK 关系,因此它们不是实体模型的一部分。但这使编程变得复杂,因为 ClassA 中的处理方法需要访问静态数据。

最佳答案

我认为这个问题的关键是异常到底意味着什么:-

An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

我突然想到,也许这个异常是 Entity Framework 提示一个对象的实例在多个 DbContexts 中被更改,而不是简单地被多个 DbContexts 中的对象引用>。我的理论基于这样一个事实,即生成的 POCO 类具有反向 FK 导航属性,并且 Entity Framework 自然会尝试修复这些反向导航属性,作为将实体图附加到 DbContext< 的过程的一部分(参见 a description of the fix-up process)

为了检验这个理论,我创建了一个简单的测试项目,我可以在其中启用和禁用反向导航属性。令我非常高兴的是,我发现这个理论是正确的,并且 EF 非常乐意多次引用对象,只要对象本身不改变 - 这包括导航属性被改变 通过修复过程。

所以问题的答案只需遵循 2 个规则:-

  • 确保静态数据对象永远不会改变(理想情况下它们应该没有公共(public)setter属性)和
  • 不要包含任何指向引用类的 FK 反向导航属性。对于 Reverse POCO Generator 的用户我已向 Simon Hughes(作者)提出建议,添加一项增强功能,使其成为配置选项。

我在下面包含了测试类:-

class Program
{
static void Main(string[] args)
{
ConcurrentDictionary<int,ClassA> theCache = null;

try
{
using(var ctx = new MyDbContext())
{
var classAs = ctx.ClassAs
.Include(a => a.ClassAType)
.ToList();

theCache = new ConcurrentDictionary<int,ClassA>(classAs.ToDictionary(a => a.ID));
}

// take 2 instances of ClassA that refer to the same ClassAType
// and load them into separate DbContexts
var classA1 = theCache[1];
var classA2 = theCache[2];

var ctx1 = new MyDbContext();
ctx1.ClassAs.Attach(classA1);

var ctx2 = new MyDbContext();
ctx2.ClassAs.Attach(classA2);

// When ClassAType has a reverse FK navigation property to
// ClassA we will not reach this line!

WriteDetails(classA1);
WriteDetails(classA2);

classA1.Name = "Updated";
classA2.Name = "Updated";

WriteDetails(classA1);
WriteDetails(classA2);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
System.Console.WriteLine("End of test");
}

static void WriteDetails(ClassA classA)
{
Console.WriteLine(String.Format("ID={0} Name={1} TypeName={2}",
classA.ID, classA.Name, classA.ClassAType.Name));
}
}

public class ClassA
{
public int ID { get; set; }
public string ClassATypeCode { get; set; }
public string Name { get; set; }

//Navigation properties
public virtual ClassAType ClassAType { get; set; }
}

public class ClassAConfiguration : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<ClassA>
{
public ClassAConfiguration()
: this("dbo")
{
}

public ClassAConfiguration(string schema)
{
ToTable("TEST_ClassA", schema);
HasKey(x => x.ID);

Property(x => x.ID).HasColumnName(@"ID").IsRequired().HasColumnType("int").HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
Property(x => x.Name).HasColumnName(@"Name").IsRequired().HasColumnType("varchar").HasMaxLength(50);
Property(x => x.ClassATypeCode).HasColumnName(@"ClassATypeCode").IsRequired().HasColumnType("varchar").HasMaxLength(50);

//HasRequired(a => a.ClassAType).WithMany(b => b.ClassAs).HasForeignKey(c => c.ClassATypeCode);
HasRequired(a => a.ClassAType).WithMany().HasForeignKey(b=>b.ClassATypeCode);
}
}

public class ClassAType
{
public string Code { get; private set; }
public string Name { get; private set; }
public int Flags { get; private set; }


// Reverse navigation
//public virtual System.Collections.Generic.ICollection<ClassA> ClassAs { get; set; }
}

public class ClassATypeConfiguration : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<ClassAType>
{
public ClassATypeConfiguration()
: this("dbo")
{
}

public ClassATypeConfiguration(string schema)
{
ToTable("TEST_ClassAType", schema);
HasKey(x => x.Code);

Property(x => x.Code).HasColumnName(@"Code").IsRequired().HasColumnType("varchar").HasMaxLength(12);
Property(x => x.Name).HasColumnName(@"Name").IsRequired().HasColumnType("varchar").HasMaxLength(50);
Property(x => x.Flags).HasColumnName(@"Flags").IsRequired().HasColumnType("int");

}
}

public class MyDbContext : System.Data.Entity.DbContext
{
public System.Data.Entity.DbSet<ClassA> ClassAs { get; set; }
public System.Data.Entity.DbSet<ClassAType> ClassATypes { get; set; }

static MyDbContext()
{
System.Data.Entity.Database.SetInitializer<MyDbContext>(null);
}

const string connectionString = @"Server=TESTDB; Database=TEST; Integrated Security=True;";

public MyDbContext()
: base(connectionString)
{
}

protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ClassAConfiguration());
modelBuilder.Configurations.Add(new ClassATypeConfiguration());
}
}

关于c# - Entity Framework ——如何缓存和共享只读对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40548991/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com