简体   繁体   English

实体框架4 TPH继承,如何将一种类型转换为另一种?

[英]Entity Framework 4 TPH inheritance, how to change one type into another?

I have found some information regarding this but not enough for me to understand what the best practice for is for this scenario. 我已经找到了一些有关这方面的信息,但还不足以让我理解这种情况的最佳实践。 I have your typicaly TPH setup with an abstract base class "Firm". 我有你的典型TPH设置与抽象基类“公司”。 I have several children "Small Firm", "Big Firm" etc inheriting from Firm. 我有几个孩子“小公司”,“大公司”等继承自公司。 In reality I actually have different realistic classifications for firms but I am trying to keep it simple in this example. 实际上,我实际上对公司有不同的现实分类,但我试图在这个例子中保持简单。 In the database as per TPH I have a single Firm table with a FirmTypeId column (int) that differentiates between all these types. 在根据TPH的数据库中,我有一个具有FirmTypeId列(int)的Firm表,用于区分所有这些类型。 Everything works great except I have a requirement to allow a user to change one type of firm into another. 一切都很好,除了我要求允许用户将一种类型的公司改为另一种。 For example a user might have made a mistake when adding the firm, and would like to change it from Big Firm to Small Firm. 例如,用户在添加公司时可能会犯错,并希望将其从Big Firm更改为Small Firm。 Because entity framework does not allow exposing the discriminating database column to be exposed as a property, I don't believe there is a way to change one type into another via EF. 因为实体框架不允许将区分数据库列暴露为属性,所以我不相信有一种方法可以通过EF将一种类型更改为另一种类型。 Please correct me if I am wrong. 如果我错了,请纠正我。 The way I see it I have two options: 我看到它的方式我有两个选择:

  1. Don't use TPH. 不要使用TPH。 Simply have a Firm Entity and go back to using .Where(FirmTypeId == something) to differentiate between the types. 只需拥有一个公司实体并返回使用.Where(FirmTypeId == something)来区分类型。
  2. Execute SQL directly using context.ExecuteStoreCommand to update the FirmTypeId column of the database. 使用context.ExecuteStoreCommand直接执行SQL以更新数据库的FirmTypeId列。

I've seen a post where people suggest that One of the tenets of OOP is that instances cannot change their type. 我看过一篇文章,人们认为OOP的一个原则是实例不能改变它们的类型。 Although that makes perfect sense to me, I just don't seem to be able to connect the dots. 虽然这对我来说很有意义,但我似乎无法连接点。 If we were to follow this rule, then the only time to use any kind of inheritance (TPH/TPT) is when one is sure that one type would never be converted into another. 如果我们遵循这个规则,那么唯一一次使用任何类型的继承(TPH / TPT)就是确定一种类型永远不会转换为另一种类型。 So a Small Firm will never become a Big Firm. 因此,小公司永远不会成为一家大公司。 I see suggestions that composition should be used instead. 我看到应该使用构图的建议。 Even though it doesn't make sense to me (meaning I don't see how a Firm has a Big Firm, to me a Big Firm is a Firm), I can see how composition can be modeled in EF if the data is in multiple tables. 即使它对我来说没有意义(意思是我没有看到公司如何拥有一家大公司,对我来说,一家大公司就是一家公司),我可以看到如果数据存在,如何在EF中建模组合。多个表。 However in a situation where I have a single table in the database it seems it's TPH or what I've described in #1 and #2 above. 但是,在我在数据库中有一个表的情况下,它似乎是TPH或我在上面#1和#2中描述的内容。

I've ran into this problem in our project, where we have core DBContext and some "pluggable" modules with their own DBContexts , in which "module user" inherits "core (base) user". 我在我们的项目中遇到了这个问题,我们有核心DBContext和一些带有自己DBContexts “可插拔”模块,其中“模块用户”继承了“核心(基础)用户”。 Hope that's understandable. 希望这是可以理解的。

We also needed the ability to change (let's call it) User to Customer (and if needed also to another "inherited" Users at the same time, so that user can use all those modules. 我们还需要改变(我们称之为)的能力, UserCustomer (如果需要还的另一个“继承” Users在同一时间,使用户可以使用所有这些模块。

Because of that we tried using TPT inheritance, instead of TPH - but TPH would work somehow too. 因为我们尝试使用TPT继承,而不是TPH - 但TPH也会以某种方式工作。

One way is to use custom stored procedure as suggested by many people... 一种方法是使用许多人建议的自定义存储过程 ...

Another way that came to my mind is to send custom insert/update query to DB. 我想到的另一种方法是向DB发送自定义插入/更新查询 In TPT it would be: 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;
        }
    }

In this scenario Customer inherits User and has only string Email . 在此方案中, Customer继承User并且只有string Email

When using TPH the query would only change from INSERT ... VALUES ... to UPDATE ... SET ... WHERE [Id] = ... . 使用TPH时 ,查询只会从INSERT ... VALUES ...更改为UPDATE ... SET ... WHERE [Id] = ... Dont forget to change Discriminator column too. 别忘了改变Discriminator专栏。

After next call dbcontext.Users.OfType<Customer> there is our original user, "converted" to customer. 下次调用dbcontext.Users.OfType<Customer> ,我们的原始用户“转换”为客户。


Bottomline: I also tried solution from another question here, which included detaching original entity (user) from ObjectStateManager and making new entity (customer) state modified , then saving dbcontext.SaveChanges() . 底线:我还尝试了另一个问题的解决方案,其中包括从ObjectStateManager 分离原始实体(用户)并修改新的实体(客户)状态,然后保存dbcontext.SaveChanges() That didn't work for me (neither TPH nor TPT). 这对我没用(TPH和TPT都没有)。 Either because using separate DBContexts per module, or because EntityFramework 6(.1) ignores this. 因为每个模块使用单独的DBContexts,或者因为EntityFramework 6(.1)忽略了这一点。 It can be found here. 在这里能找到它。

Yes, you got it all right. 是的,你做得很好。 EF inheritance does not support this scenario. EF继承不支持此方案。 The best way to change a Firm type for an existing Firm is to use a stored procedure. 更改现有公司公司类型的最佳方法是使用存储过程。

Please take a look at this post for more info: 请查看这篇文章了解更多信息:
Changing Inherited Types in Entity Framework 在实体框架中更改继承的类型

Unless you explicitly want to use the polymorphic functionality of the relational inheritance, then why not look at a splitting strategy? 除非您明确要使用关系继承的多态功能,否则为什么不查看拆分策略?

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

EDIT: APOLOGIES, THIS IS AN EF 6.x ANSWER 编辑:APOLOGIES,这是EF 6.x答案

I'm posting example code for completeness. 我发布了完整性的示例代码。 In this scenario, I have a base Thing class. 在这种情况下,我有一个基础Thing类。 Then, sub-classes: ActiveThing and DeletedThing 然后,子类: ActiveThingDeletedThing

My OData ThingsController , has a main GetThings which I intend to only expose ActiveThing s, but, it's GetThing(ThingId) can still return either type of object. 我的OData ThingsController有一个主要的GetThings ,我打算只公开ActiveThing ,但是,它的GetThing(ThingId)仍然可以返回任何类型的对象。 The Delete action performs a conversion from ActiveThing to DeletedThing much in the way requested by the OP, and much in the manner described in other answers. Delete操作以OP请求的方式执行从ActiveThingDeletedThing的转换,并且以其他答案中描述的方式执行。 I'm using inline SQL (parameterized) 我正在使用内联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"));
    }

}

Here's my updated ThingsController.cs 这是我更新的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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM