gpt4 book ai didi

entity-framework - Entity Framework 5按类型更新表,更改子类型,但保持相同的基本类型

转载 作者:行者123 更新时间:2023-12-04 08:10:32 27 4
gpt4 key购买 nike

我有一个简单的层次结构

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个表,一个表和两个子表使用与表相同的PK。

我的问题是我希望能够通过更改其类型来更新CommunicationSupport。
假设我创建了一个TelecomSupport,将其保存,然后将其类型更改为PostalSupport,然后再次保存(更新)。我期望的结果是EF保留相同的基本记录(CommunicationSupport Id),但删除TelecomSupport表中的记录并在PostalSupport中创建一个新记录。
因此,TelecomSupport和PostalSupport是互斥的,不能共享相同的基础CommunicationSupport。

如何使用EntityFramework 5做到这一点?

谢谢你的帮助!

最佳答案

我没有很好的答案,但是我可以想到四个真正的解决方法:


不要为主键使用DBMS计算的值(如果您已经使用自然键,那就很好了)。
使用DBMS计算的代理键。
遵循state pattern之类的内容。
object state manager做一些邪恶的伏都教。


更新:似乎已经有一个普遍的共识,即尝试甚至不值得。因此,大多数人只是使用存储过程来解决此问题。


Changing Inherited Types in Entity Framework
Entity Framework: Inheritance, change object type
Changing the type of an (Entity Framework) entity that is part of an inheritance hierarchy
Changing the type of an entity that is part of an inheritance hierarchy


使用自然键

首先,请记住,EF跟踪的对象是DAL的一部分,而不是域模型的一部分(无论您是否使用POCO)。有些人不需要领域模型,但请记住它,因为我们现在可以将这些对象视为表记录的表示形式,我们可以通过不使用域对象的方式进行操作。

在这里,我们使用IDbSet.Remove删除实体的记录,然后使用IDbSet.Add使用相同的主键添加新记录,所有这些都在单个事务中进行。请参见下面的示例代码中的ChangeType方法。

从理论上讲,完整性是可以的,从理论上讲,EF可以检测到您要尝试做的事情并优化了事情。实际上,它目前没有(我分析了SQL接口以验证这一点)。结果是它看起来很丑陋(DELETE + INSERT而不是UPDATE),因此,如果系统美观和性能成为问题,则可能无法通过。如果可以接受,那么它相对简单。

这是一些我用来测试的示例代码(如果您想进行实验,只需创建一个新的控制台应用程序,添加对EntityFramework程序集的引用,然后粘贴代码)。

A是基类,XY是子类。我们认为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();
}
}
}
}


输出:

Type:   EntitySubTypeChange.X
Id: 0
Foo: Base Value
Bar: SubType X Value

Type: EntitySubTypeChange.Y
Id: 0
Foo: Base Value
Baz:


注意:如果要使用自动生成的自然键,则不能让EF要求DBMS计算它,否则EF将阻止您以所需的方式进行操作(请参阅下文)。实际上,EF仍然将具有计算值的所有键都视为代理键,即使它仍然很乐意将其泄漏(两全其美)。

注意:我用 Table注释了子类,因为您提到了TPT设置,但是问题实际上与TPT无关。

使用代理键

如果您认为代理密钥是真正的内部密钥,那么只要您仍然可以以相同的方式访问数据(例如,使用二级索引),它就不会在您的鼻子下改变。

注意:实际上,许多人都会泄漏代理密钥(域模型,服务接口等)。不要这样

如果您使用先前的示例,只需在子类型的副本构造函数中删除 DatabaseGenerated属性和 Id的赋值即可。

注意:使用由DBMS生成的值, Id属性将被EF完全忽略,并且除了由模型构建器进行分析以在SQL模式中生成 Id列之外,它没有任何实际用途。那并被不良的程序员泄漏。

输出:

Type:   EntitySubTypeChange.X
Id: 1
Foo: Base Value
Bar: SubType X Value

Type: EntitySubTypeChange.Y
Id: 2
Foo: Base Value
Baz:


使用状态模式(或类似模式)

大多数人都认为这种解决方案是“适当的解决方案”,因为您无法在大多数面向对象的语言中更改对象的固有类型。包含C#的 CTS兼容语言就是这种情况。

问题在于,这种模式已在域模型中正确使用,而不是像EF实施的DAL那样正确使用。我并不是说这是不可能的,您可能能够使用复杂的类型或TPH构造来破解事物,从而避免创建中间表,但很可能您会在河上游走,直到您放弃。希望有人能证明我错了。

注意:您可以决定让您的关系模型看起来不同,在这种情况下,您可以完全绕开此问题。但是,这不会回答您的问题。

使用内部EF伏都教

我已经相当快地浏览了 DbContextObjectContextObjectStateManager的参考文档,但我无法立即找到任何更改实体类型的方法。如果您比我幸运,则可以使用DTO和 DbPropertyValues进行转换。

重要的提示

使用前两种解决方法,您可能会遇到一些导航属性和外键问题(由于 DELETE + INSERT操作)。这将是一个单独的问题。

结论

当您做一些琐碎的事情时,EF并没有那么灵活,但是它一直在进步。希望这个答案将来不会有用。还有可能我不知道现有的杀手级功能会使您想要的成为可能,因此请不要根据此答案做出任何决定。

关于entity-framework - Entity Framework 5按类型更新表,更改子类型,但保持相同的基本类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18100849/

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