【问题标题】:Entity Framework 4 TPH inheritance, how to change one type into another?Entity Framework 4 TPH继承,如何将一种类型更改为另一种类型?
【发布时间】:2011-05-04 17:06:08
【问题描述】:

我找到了一些关于此的信息,但不足以让我了解此场景的最佳实践。我有一个带有抽象基类“公司”的典型 TPH 设置。我有几个孩子“小公司”,“大公司”等从公司继承。实际上,我实际上对公司有不同的现实分类,但在这个例子中我试图保持简单。在根据 TPH 的数据库中,我有一个带有 FirmTypeId 列(int)的单一公司表,用于区分所有这些类型。一切都很好,除了我需要允许用户将一种类型的公司更改为另一种类型的公司。例如,用户在添加公司时可能出错,并希望将其从 Big Firm 更改为 Small Firm。因为实体框架不允许将区分数据库列公开为属性,所以我不相信有一种方法可以通过 EF 将一种类型更改为另一种类型。如果我错了,请纠正我。在我看来,我有两个选择:

  1. 不要使用 TPH。只需拥有一个公司实体,然后返回使用 .Where(FirmTypeId == something) 来区分类型。
  2. 直接使用 context.ExecuteStoreCommand 执行 SQL 更新数据库的 FirmTypeId 列。

我看过一篇帖子,其中有人建议 OOP 的原则之一是实例不能更改其类型。尽管这对我来说很有意义,但我似乎无法将这些点联系起来。如果我们要遵循这个规则,那么使用任何类型继承(TPH/TPT)的唯一时间是当人们确定一种类型永远不会被转换为另一种类型时。所以小公司永远不会变成大公司。我看到建议应该改用组合。即使这对我来说没有意义(这意味着我看不到公司如何拥有大公司,对我来说大公司就是公司),我可以看到如果数据在 EF 中如何建模多个表。但是,在我在数据库中有一个表的情况下,它似乎是 TPH 或我在上面的 #1 和 #2 中描述的。

【问题讨论】:

  • 那个“某人”可能就是我。在基于类的 OO 中,对象不能更改类型。曾经。 C# 不允许这样做。 EF 当然不会在 C# 中添加这样的功能。 EF 中唯一不同的是对象的生命周期更长——实际上是永久的。
  • 克雷格,可能是你。我刚刚编辑了这篇文章,以包含更多关于此的信息。如果您不介意查看我帖子中的最后一段并了解正确的方法。
  • “小公司”和“大公司”到底有什么区别?顺便说一句,除非您将@Craig 放入其中,否则我不会收到您的回复通知。
  • @Craig,在我的示例中,在拥有额外列方面实际上没有区别。另一个例子是说我有一个带有 FlavorId 的 Icecreame 表,它是表中具有不同风味的表的外键。我认为在我的场景中,不必不断地编写 .Where(FlavorId == 1) 或 .Where(FlavorId == 2) 我只需通过使用 TPH 的继承来强类型化它们。当然有人可能会决定在管理网页上改变冰淇淋的味道,所以我的设计是 SOL。
  • 我同意对象不应在 OO 中更改其类型,但如果有业务需求,我认为这样做没有任何问题。 @Craig:您能否举一个问题的例子,说明有人可能会通过更改对象类型来解决问题?

标签: c# asp.net asp.net-mvc entity-framework entity-framework-4


【解决方案1】:

编辑:道歉,这是一个 EF 6.x 答案

为了完整起见,我发布了示例代码。在这种情况下,我有一个基础 Thing 类。然后,子类:ActiveThingDeletedThing

我的 OData ThingsController,有一个主要的 GetThings,我打算只公开 ActiveThings,但是,它的 GetThing(ThingId) 仍然可以返回任一类型的对象。 Delete 操作执行从 ActiveThingDeletedThing 的转换,其方式与 OP 请求的方式非常相似,并且与其他答案中描述的方式非常相似。我正在使用内联 SQL(参数化)

public class myDbModel:DbContext
{
    public myDbModel(): base("name=ThingDb"){}

    public DbSet<Thing> Things { get; set; }  //db table

    public DbSet<ActiveThing> ActiveThings { get; set; } // now my ThingsController 'GetThings' pulls from this

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
       //TPH (table-per-hierarchy):
      modelBuilder.Entity<Ross.Biz.ThingStatusLocation.Thing>()
        .Map<Ross.Biz.ThingStatusLocation.ActiveThing>(thg => thg.Requires("Discriminator").HasValue("A"))
        .Map<Ross.Biz.ThingStatusLocation.DeletedThing>(thg => thg.Requires("Discriminator").HasValue("D"));
    }

}

这是我更新后的 ThingsController.cs

public class ThingsController : ODataController
{
    private myDbModel db = new myDbModel();

    /// <summary>
    /// Only exposes ActiveThings (not DeletedThings)
    /// </summary>
    /// <returns></returns>
    [EnableQuery]
    public IQueryable<Thing> GetThings()
    {
        return db.ActiveThings;
    }

    public async Task<IHttpActionResult> Delete([FromODataUri] long key)
    {
        using (var context = new myDbModel())
        {
            using (var transaction = context.Database.BeginTransaction())
            {
                Thing thing = await db.Things.FindAsync(key);
                if (thing == null || thing is DeletedThing) // love the simple expressiveness here
                {
                    return NotFound();//was already deleted previously, so return NotFound status code
                }

                //soft delete: converts ActiveThing to DeletedThing via direct query to DB
                context.Database.ExecuteSqlCommand(
                    "UPDATE Things SET Discriminator='D', DeletedOn=@NowDate WHERE Id=@ThingId", 
                    new SqlParameter("@ThingId", key), 
                    new SqlParameter("@NowDate", DateTimeOffset.Now)
                    );

                context.ThingTransactionHistory.Add(new Ross.Biz.ThingStatusLocation.ThingTransactionHistory
                {
                    ThingId = thing.Id,
                    TransactionTime = DateTimeOffset.Now,
                    TransactionCode = "DEL",
                    UpdateUser = User.Identity.Name,
                    UpdateValue = "MARKED DELETED"
                });
                context.SaveChanges();
                transaction.Commit();
            }
        }

        return StatusCode(HttpStatusCode.NoContent);
    }
}

【讨论】:

    【解决方案2】:

    我在我们的项目中遇到了这个问题,我们有核心DBContext 和一些带有自己的DBContexts 的“可插拔”模块,其中“模块用户”继承了“核心(基本)用户”。希望这是可以理解的。

    我们还需要能够将User 更改(我们称之为)Customer(如果需要,还需要同时更改为另一个“继承”Users,以便用户可以使用所有这些模块。

    因此,我们尝试使用 TPT 继承,而不是 TPH - 但 TPH 也会以某种方式工作。

    一种方法是使用许多人建议的自定义存储过程...

    我想到的另一种方法是将自定义插入/更新查询发送到数据库。在 TPT 中应该是:

    private static bool UserToCustomer(User u, Customer c)
        {
            try
            {
                string sqlcommand = "INSERT INTO [dbo].[Customers] ([Id], [Email]) VALUES (" + u.Id + ", '" + c.Email + "')";
                var sqlconn = new SqlConnection(ConfigurationManager.ConnectionStrings["DBContext"].ConnectionString);
                sqlconn.Open();
                var sql = new SqlCommand(sqlcommand, sqlconn);
                var rows = sql.ExecuteNonQuery();
                sqlconn.Close();
    
                return rows == 1;
            }
            catch (Exception)
            {
                return false;
            }
        }
    

    在这种情况下Customer 继承User 并且只有string Email

    使用 TPH 时,查询只会从 INSERT ... VALUES ... 更改为 UPDATE ... SET ... WHERE [Id] = ...不要忘记更改Discriminator 列。

    在下一次调用dbcontext.Users.OfType&lt;Customer&gt; 之后,我们的原始用户“转换”为客户。


    底线:我还尝试了此处另一个问题的解决方案,其中包括 ObjectStateManager 分离原始实体(用户)并使新实体(客户)状态修改,然后保存dbcontext.SaveChanges()。这对我不起作用(TPH 和 TPT 都不是)。要么是因为每个模块使用单独的 DBContext,要么是因为 EntityFramework 6(.1) 忽略了这一点。 It can be found here.

    【讨论】:

    • 一段时间后看到这一点,在查询中使用SQL参数而不是连接字符串会更干净。
    【解决方案3】:

    除非您明确想要使用关系继承的多态功能,否则为什么不考虑拆分策略?

    http://msdn.microsoft.com/en-us/data/ff657841.aspx

    【讨论】:

    • 我不确定这对我有什么帮助。在我的情况下,有一个类型列可以区分实体是 A、B 还是 C 类型。它们确实没有自己的属性。这对我来说是继承的味道,因为我可以为这些不同类型中的每一个添加更多属性,以更好地描述它们类型的细节。但是,在有业务要求允许将一种类型更改为另一种类型的情况下,似乎不应该使用继承。
    • 您可以使用其中一种方法在标志上拆分类型,然后通过切换标志本质上切换类型。就像我说的,只有当你想使用多态性时,继承才是强制性的。因此,您基本上有 2 种具有相似属性的不同类型,仅相差一两个属性。
    【解决方案4】:

    是的,你没问题。 EF 继承不支持这种情况。为现有 Firm 更改 Firm 类型的最佳方法是使用存储过程。

    请查看此帖子以获取更多信息:
    Changing Inherited Types in Entity Framework

    【讨论】:

    • 这就是我得出的结论。然而,在这一点上,我不确定这是解决这个问题的正确方法,似乎如果有人遇到这个问题,那么他们一开始就错误地解决了这个问题(根据克雷格)
    猜你喜欢
    • 1970-01-01
    • 2017-03-26
    • 2021-06-13
    • 2018-06-04
    • 1970-01-01
    • 2012-03-28
    • 1970-01-01
    • 2017-04-16
    • 2010-11-22
    相关资源
    最近更新 更多