Entity framework 实体框架每种类型更新5个表,更改子类型但保持相同的基本类型
我有一个简单的层次结构Entity framework 实体框架每种类型更新5个表,更改子类型但保持相同的基本类型,entity-framework,table-per-type,Entity Framework,Table Per Type,我有一个简单的层次结构 public abstract class CommunicationSupport { public SupportTypeEnum Type { get; set; } public Country Origin { get; set; } // National or Foreign support } public class TelecomSupport : CommunicationSupport { public string Nu
public abstract class CommunicationSupport
{
public SupportTypeEnum Type { get; set; }
public Country Origin { get; set; } // National or Foreign support
}
public class TelecomSupport : CommunicationSupport
{
public string Number { get; set; }
}
public class PostalSupport : CommunicationSupport
{
public Address Address { get; set; }
}
我计划为我的数据库使用每个类型的表层次结构。因此,将创建3个表,一个基本表和两个子表,使用与基本表相同的主键
我的问题是,我希望能够通过更改通信支持的类型来更新它。
假设我创建了一个TelecomSupport,保存它,然后将其类型更改为PostalSupport并再次保存(更新)。我期望的结果是EF保留相同的基本记录(通信支持Id),但删除TelecomSupport表中的记录,并在PostalSupport中创建一个新记录。
因此,TelecomSupport和PostalSupport是互斥的,不能共享相同的基础通信支持
如何使用EntityFramework 5实现这一点
谢谢你的帮助 我没有一个好的答案,但我可以想出四个真正可行的“解决方案”:
ChangeType
方法
从理论上讲,诚信是可以的,从理论上讲,EF可以检测到你在做什么并优化事情。实际上,它目前没有(我分析了SQL接口以验证这一点)。结果是它看起来很难看(DELETE
+INSERT
而不是UPDATE
),因此如果系统美观和性能是问题,那么它可能是不可能的。如果你能接受,那就相对简单了
下面是我用来测试的一些示例代码(如果您想进行实验,只需创建一个新的控制台应用程序,添加对EntityFramework
程序集的引用,然后粘贴代码)
A
是基类,X
和Y
是子类。我们认为<代码> ID>代码>是一个自然键,所以我们可以将它复制到子类复制构造函数中(这里只针对<代码> y>代码>实现)。代码创建一个数据库,并使用X
类型的记录对其进行种子设定。然后,它运行并将其类型更改为Y
,显然在这个过程中丢失了X
特定的数据。复制构造函数是转换数据的地方,如果数据丢失不是业务流程的一部分,则可以对其进行归档。唯一“有趣”的代码是ChangeType
方法,其余的是样板文件
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace EntitySubTypeChange {
abstract class A {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Foo { get; set; }
public override string ToString() {
return string.Format("Type:\t{0}{3}Id:\t{1}{3}Foo:\t{2}{3}",
this.GetType(), Id, Foo, Environment.NewLine);
}
}
[Table("X")]
class X : A {
public string Bar { get; set; }
public override string ToString() {
return string.Format("{0}Bar:\t{1}{2}", base.ToString(), Bar, Environment.NewLine);
}
}
[Table("Y")]
class Y : A {
public Y() {}
public Y(A a) {
this.Id = a.Id;
this.Foo = a.Foo;
}
public string Baz { get; set; }
public override string ToString() {
return string.Format("{0}Baz:\t{1}{2}", base.ToString(), Baz, Environment.NewLine);
}
}
class Program {
static void Main(string[] args) {
Display();
ChangeType();
Display();
}
static void Display() {
using (var context = new Container())
Console.WriteLine(context.A.First());
Console.ReadKey();
}
static void ChangeType()
{
using (var context = new Container()) {
context.A.Add(new Y(context.A.Remove(context.X.First())));
context.SaveChanges();
}
}
class Container : DbContext {
public IDbSet<A> A { get; set; }
public IDbSet<X> X { get; set; }
public IDbSet<Y> Y { get; set; }
}
static Program() {
Database.SetInitializer<Container>(new ContainerInitializer());
}
class ContainerInitializer : DropCreateDatabaseAlways<Container> {
protected override void Seed(Container context) {
context.A.Add(new X { Foo = "Base Value", Bar = "SubType X Value" });
context.SaveChanges();
}
}
}
}
注意:如果您想要一个自动生成的自然密钥,您不能让EF要求DBMS计算它,否则EF将阻止您以您想要的方式操作它(见下文)。实际上,EF将所有具有计算值的键都视为代理键,尽管它仍然乐于泄漏它们(这两个世界都不好)
注意:我用注释子类,因为您提到了TPT设置,但问题实际上与TPT无关
使用代理键
如果你认为代理键真的是内部的,那么只要你能以同样的方式访问你的数据(使用一个辅助索引),那就没关系了。 注意:在实践中,许多人到处泄漏代理密钥(域模型、服务接口等)。不要这样做
如果使用上一个示例,只需删除子类型的复制构造函数中的DatabaseGenerated
属性和Id
的赋值
注意:使用DBMS生成的值,Id
属性被EF完全忽略,除了由模型生成器分析以在SQL模式中生成Id
列之外,它没有任何实际用途。而且被糟糕的程序员泄露
输出:
Type: EntitySubTypeChange.X
Id: 0
Foo: Base Value
Bar: SubType X Value
Type: EntitySubTypeChange.Y
Id: 0
Foo: Base Value
Baz:
Type: EntitySubTypeChange.X
Id: 1
Foo: Base Value
Bar: SubType X Value
Type: EntitySubTypeChange.Y
Id: 2
Foo: Base Value
Baz:
使用状态模式(或类似模式)
这个解决方案可能是大多数人所认为的“正确的解决方案”,因为你不能改变大多数面向对象语言中对象的内在类型。兼容语言(包括C#)就是这种情况
问题在于,这种模式在域模型中得到了正确的使用,而不是像用EF实现的DAL那样。我并不是说这是不可能的,您可以使用复杂类型或TPH构造来解决问题,以避免创建中间表,但最有可能的情况是,您将一直游弋到放弃为止。希望有人能证明我错了 注意:您可以决定希望您的关系模型看起来不同,在这种情况下,您可以完全绕过这个问题。但这并不能回答你的问题 使用内部EF巫毒 我很快查阅了和的参考文档,但我无法立即找到任何方法来更改实体的类型。如果你比我幸运,你也许可以使用DTO并进行转换 重要提示 在前两种解决方法中,您可能会遇到导航属性和外键方面的一系列问题(因为DELETE
+INSERT
操作)。这将是一个单独的问题
结论
EF i