EntityFramework、Azure ElasticScale 和 Table 每类型 (TPT) 继承
EntityFramework, Azure ElasticScale, and Table Per Type (TPT) Inheritance
如果在 Entity Framework 中使用 TPH 给定一个 table 结构。
class ContactLink {
Guid Contact_Link_ID { get; set;} //pk
Guid Tenant_ID { get; set;} //fk
Guid Contact_ID { get; set;} //fk
}
class ContactLinkCustomer : ContactLink {
Guid Contact_Link_ID { get; set;} //fk
Guid Customer_ID { get; set;} //fk
}
由于 Entity framework 在派生的 class 的 table 中不包含基础 class 属性,因此我应该如何为拆分合并操作配置弹性比例架构信息?具体来说Tenant_ID,也就是我的point map shard key
SchemaInfo schemaInfo = new SchemaInfo();
schemaInfo.Add(new ShardedTableInfo("dbo", "ContactLinkCustomer", ???));
smm.GetSchemaInfoCollection().Add("ShardName", schemaInfo);
更新:
ContactLink 不是抽象的。
更新二:
我应该注意到,ContactLink 也在我的 DbContext 中,并且独立于 ContactLinkCustomer 进行查询。
更新 3:
我没有使用 TPH,我们实际上使用的是 TPT。这就是导致多个 table 而不是带有鉴别器的单个 table 的原因。
如果您使用的是 TPH,并且 ContactLink 和 ContactLinkCustomer 都在同一个层次结构中,那么 EF 应该创建一个单一的非规范化 table 和 类 中的所有列。在那种情况下,ContactLink 将是 table 的分片,Tenant_ID 作为分片键。
但是,如果您真的打算使用多个 table,那么您必须在 table 中为 ContactLinkCustomer[=17= 添加 Tenant_ID 列],并将其分片到 Tenant_ID。 Elastic Scale 库和工具的当前版本要求分片键存在于参与拆分合并的所有分片 table 中。
下面对我有用,需要注意的是没有数据库级约束可以保持 Tenant_ID 同步,因此如果任何代码直接通过 T-[ 修改这些表,它们可能会不同步=27=](不是通过 EF)。
[Table("ContactLink")] // TPT inheritance
class ContactLink
{
public Guid Contact_Link_ID { get; set; } //pk
public Guid Tenant_ID { get; set; } //fk
public Guid Contact_ID { get; set; } //fk
}
[Table("ContactLinkCustomer")] // TPT inheritance
internal class ContactLinkCustomer : ContactLink
{
// Dummy property to trick EF into creating it as a column for sharding purposes
// Callers should just directly use the base Tenant_ID property
// It would be nice if we could set this to be public/protected, but then EF
// won't create it as a column. Maybe there is a workaround for this?
[Column("Tenant_ID")]
public Guid Tenant_ID_ContactLinkCustomer
{
get { return base.Tenant_ID; }
set { base.Tenant_ID = value; }
}
public Guid Contact_Link_ID { get; set; } //fk
public Guid Customer_ID { get; set; } //fk
}
下面是我用于测试的其他 类。
class Program
{
static void Main(string[] args)
{
string connStr = "Server=(local);Database=EfShardingTpt;Integrated Security=true";
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
// Drop and recreate database
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
}
// Create ContactLinkCustomer
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
ContactLinkCustomer clc = new ContactLinkCustomer
{
Contact_ID = Guid.Empty,
Contact_Link_ID = Guid.Empty,
Customer_ID = Guid.Empty,
Tenant_ID = Guid.Parse("00000000-0000-0000-0000-100000000000")
};
myDbContext.ContactLinkCustomers.Add(clc);
myDbContext.SaveChanges();
}
WriteTenantIds(connStr);
// Update through subtype
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
ContactLinkCustomer clc = myDbContext.ContactLinkCustomers.First();
clc.Tenant_ID = Guid.Parse("00000000-0000-0000-0000-200000000000");
myDbContext.SaveChanges();
}
WriteTenantIds(connStr);
// Update through supertype
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
ContactLink cl = myDbContext.ContactLinks.First();
cl.Tenant_ID = Guid.Parse("00000000-0000-0000-0000-300000000000");
myDbContext.SaveChanges();
}
WriteTenantIds(connStr);
}
private static void WriteTenantIds(string connectionString)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT Tenant_ID FROM ContactLink";
Guid contactLinkTenantId = (Guid) cmd.ExecuteScalar();
cmd.CommandText = "SELECT Tenant_ID FROM ContactLinkCustomer";
Guid contactLinkCustomerTenantId = (Guid)cmd.ExecuteScalar();
Console.WriteLine("{0} {1}", contactLinkTenantId, contactLinkCustomerTenantId);
}
}
}
class MyDbContext : DbContext
{
public MyDbContext(string connectionString) : base(connectionString)
{
}
public virtual DbSet<ContactLink> ContactLinks { get; set; }
public virtual DbSet<ContactLinkCustomer> ContactLinkCustomers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ContactLink>()
.HasKey(e => e.Contact_Link_ID);
modelBuilder.Entity<ContactLinkCustomer>()
.HasKey(e => e.Contact_Link_ID);
}
}
控制台输出:
00000000-0000-0000-0000-100000000000 00000000-0000-0000-0000-100000000000
00000000-0000-0000-0000-200000000000 00000000-0000-0000-0000-200000000000
00000000-0000-0000-0000-300000000000 00000000-0000-0000-0000-300000000000
也可能有某种基于映射的解决方案。我尝试了以下但它不起作用。也许通过更多的实验它可以工作,但上面的解决方案对我来说似乎已经足够好了所以我没有进一步探索它。
modelBuilder.Entity<ContactLinkCustomer>()
.Map(m =>
{
m.Properties(e => e.Tenant_ID);
m.ToTable("ContactLinkCustomer");
});
Unhandled Exception: System.NotSupportedException: The type 'ContactLinkCustomer' cannot be mapped as defined because it maps inherited properties from types th
at use entity splitting or another form of inheritance. Either choose a different inheritance mapping strategy so as to not map inherited properties, or change
all types in the hierarchy to map inherited properties and to not use splitting.
如果在 Entity Framework 中使用 TPH 给定一个 table 结构。
class ContactLink {
Guid Contact_Link_ID { get; set;} //pk
Guid Tenant_ID { get; set;} //fk
Guid Contact_ID { get; set;} //fk
}
class ContactLinkCustomer : ContactLink {
Guid Contact_Link_ID { get; set;} //fk
Guid Customer_ID { get; set;} //fk
}
由于 Entity framework 在派生的 class 的 table 中不包含基础 class 属性,因此我应该如何为拆分合并操作配置弹性比例架构信息?具体来说Tenant_ID,也就是我的point map shard key
SchemaInfo schemaInfo = new SchemaInfo();
schemaInfo.Add(new ShardedTableInfo("dbo", "ContactLinkCustomer", ???));
smm.GetSchemaInfoCollection().Add("ShardName", schemaInfo);
更新: ContactLink 不是抽象的。
更新二: 我应该注意到,ContactLink 也在我的 DbContext 中,并且独立于 ContactLinkCustomer 进行查询。
更新 3: 我没有使用 TPH,我们实际上使用的是 TPT。这就是导致多个 table 而不是带有鉴别器的单个 table 的原因。
如果您使用的是 TPH,并且 ContactLink 和 ContactLinkCustomer 都在同一个层次结构中,那么 EF 应该创建一个单一的非规范化 table 和 类 中的所有列。在那种情况下,ContactLink 将是 table 的分片,Tenant_ID 作为分片键。
但是,如果您真的打算使用多个 table,那么您必须在 table 中为 ContactLinkCustomer[=17= 添加 Tenant_ID 列],并将其分片到 Tenant_ID。 Elastic Scale 库和工具的当前版本要求分片键存在于参与拆分合并的所有分片 table 中。
下面对我有用,需要注意的是没有数据库级约束可以保持 Tenant_ID 同步,因此如果任何代码直接通过 T-[ 修改这些表,它们可能会不同步=27=](不是通过 EF)。
[Table("ContactLink")] // TPT inheritance
class ContactLink
{
public Guid Contact_Link_ID { get; set; } //pk
public Guid Tenant_ID { get; set; } //fk
public Guid Contact_ID { get; set; } //fk
}
[Table("ContactLinkCustomer")] // TPT inheritance
internal class ContactLinkCustomer : ContactLink
{
// Dummy property to trick EF into creating it as a column for sharding purposes
// Callers should just directly use the base Tenant_ID property
// It would be nice if we could set this to be public/protected, but then EF
// won't create it as a column. Maybe there is a workaround for this?
[Column("Tenant_ID")]
public Guid Tenant_ID_ContactLinkCustomer
{
get { return base.Tenant_ID; }
set { base.Tenant_ID = value; }
}
public Guid Contact_Link_ID { get; set; } //fk
public Guid Customer_ID { get; set; } //fk
}
下面是我用于测试的其他 类。
class Program
{
static void Main(string[] args)
{
string connStr = "Server=(local);Database=EfShardingTpt;Integrated Security=true";
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
// Drop and recreate database
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
}
// Create ContactLinkCustomer
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
ContactLinkCustomer clc = new ContactLinkCustomer
{
Contact_ID = Guid.Empty,
Contact_Link_ID = Guid.Empty,
Customer_ID = Guid.Empty,
Tenant_ID = Guid.Parse("00000000-0000-0000-0000-100000000000")
};
myDbContext.ContactLinkCustomers.Add(clc);
myDbContext.SaveChanges();
}
WriteTenantIds(connStr);
// Update through subtype
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
ContactLinkCustomer clc = myDbContext.ContactLinkCustomers.First();
clc.Tenant_ID = Guid.Parse("00000000-0000-0000-0000-200000000000");
myDbContext.SaveChanges();
}
WriteTenantIds(connStr);
// Update through supertype
using (MyDbContext myDbContext = new MyDbContext(connStr))
{
ContactLink cl = myDbContext.ContactLinks.First();
cl.Tenant_ID = Guid.Parse("00000000-0000-0000-0000-300000000000");
myDbContext.SaveChanges();
}
WriteTenantIds(connStr);
}
private static void WriteTenantIds(string connectionString)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT Tenant_ID FROM ContactLink";
Guid contactLinkTenantId = (Guid) cmd.ExecuteScalar();
cmd.CommandText = "SELECT Tenant_ID FROM ContactLinkCustomer";
Guid contactLinkCustomerTenantId = (Guid)cmd.ExecuteScalar();
Console.WriteLine("{0} {1}", contactLinkTenantId, contactLinkCustomerTenantId);
}
}
}
class MyDbContext : DbContext
{
public MyDbContext(string connectionString) : base(connectionString)
{
}
public virtual DbSet<ContactLink> ContactLinks { get; set; }
public virtual DbSet<ContactLinkCustomer> ContactLinkCustomers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ContactLink>()
.HasKey(e => e.Contact_Link_ID);
modelBuilder.Entity<ContactLinkCustomer>()
.HasKey(e => e.Contact_Link_ID);
}
}
控制台输出:
00000000-0000-0000-0000-100000000000 00000000-0000-0000-0000-100000000000
00000000-0000-0000-0000-200000000000 00000000-0000-0000-0000-200000000000
00000000-0000-0000-0000-300000000000 00000000-0000-0000-0000-300000000000
也可能有某种基于映射的解决方案。我尝试了以下但它不起作用。也许通过更多的实验它可以工作,但上面的解决方案对我来说似乎已经足够好了所以我没有进一步探索它。
modelBuilder.Entity<ContactLinkCustomer>()
.Map(m =>
{
m.Properties(e => e.Tenant_ID);
m.ToTable("ContactLinkCustomer");
});
Unhandled Exception: System.NotSupportedException: The type 'ContactLinkCustomer' cannot be mapped as defined because it maps inherited properties from types th
at use entity splitting or another form of inheritance. Either choose a different inheritance mapping strategy so as to not map inherited properties, or change
all types in the hierarchy to map inherited properties and to not use splitting.