C# 实体框架4 TPH继承,如何将一种类型更改为另一种类型?

C# 实体框架4 TPH继承,如何将一种类型更改为另一种类型?,c#,asp.net,asp.net-mvc,entity-framework,entity-framework-4,C#,Asp.net,Asp.net Mvc,Entity Framework,Entity Framework 4,我已经找到了一些关于这方面的信息,但还不足以让我理解这个场景的最佳实践是什么。我有一个抽象基类“Firm”的典型TPH设置。我有几个孩子“小商行”、“大商行”等继承自商行。实际上,我对公司有不同的实际分类,但在这个例子中,我尽量保持简单。在TPH数据库中,我有一个单独的FirmTypeId列(int)的firmTable,它区分所有这些类型。一切都很好,除了我有一个要求,允许用户改变一种类型的公司到另一种。例如,用户可能在添加公司时出错,并希望将其从大公司更改为小公司。因为实体框架不允许将区分数

我已经找到了一些关于这方面的信息,但还不足以让我理解这个场景的最佳实践是什么。我有一个抽象基类“Firm”的典型TPH设置。我有几个孩子“小商行”、“大商行”等继承自商行。实际上,我对公司有不同的实际分类,但在这个例子中,我尽量保持简单。在TPH数据库中,我有一个单独的FirmTypeId列(int)的firmTable,它区分所有这些类型。一切都很好,除了我有一个要求,允许用户改变一种类型的公司到另一种。例如,用户可能在添加公司时出错,并希望将其从大公司更改为小公司。因为实体框架不允许将区分数据库列作为属性公开,所以我认为没有办法通过EF将一种类型更改为另一种类型。如果我错了,请纠正我。在我看来,我有两个选择:

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

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

    是的,您完全正确。EF继承不支持此方案。为现有公司更改公司类型的最佳方法是使用存储过程

    有关更多信息,请查看此帖子:

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


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

    我们还需要将
    用户
    更改为
    客户
    (如果需要,还需要同时更改为另一个“继承的”
    用户
    ,以便用户可以使用所有这些模块

    正因为如此,我们尝试使用TPT继承,而不是TPH——但TPH也会以某种方式起作用

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

    我想到的另一种方法是向DB发送自定义插入/更新查询

    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;
            }
        }
    
    在此场景中,
    客户
    继承
    用户
    ,并且只有
    字符串电子邮件

    使用TPH时,查询只会从
    INSERT…VALUES…
    更改为
    UPDATE…SET…,其中[Id]=…
    不要忘记更改
    鉴别器列。

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


    底线:我还尝试了另一个问题的解决方案,其中包括从
    ObjectStateManager
    中分离原始实体(用户)并修改新实体(客户)状态,然后保存
    dbcontext.SaveChanges()
    。这对我不起作用(TPH和TPT都不起作用)。这可能是因为每个模块使用单独的DBContexts,或者是因为EntityFramework 6(.1)忽略了这一点。
    编辑:抱歉,这是EF 6.x的答案

    为了完整性,我发布了示例代码。在这个场景中,我有一个基本的
    Thing
    类。然后,子类:
    ActiveThing
    DeletedThing

    我的OData
    ThingsController
    ,有一个主
    GetThings
    ,我只想公开
    ActiveThing
    s,但它正在GetThing(ThingId)仍然可以返回任意类型的对象。
    Delete
    操作执行从
    ActiveThing
    DeletedThing
    的转换,转换方式与OP要求的方式相同,也与其他答案中描述的方式相同。我使用的是内联SQL(参数化)

    公共类myDbModel:DbContext
    {
    public myDbModel():base(“name=ThingDb”){}
    公共DbSet Things{get;set;}//db table
    public DbSet ActiveThings{get;set;}//现在我的ThingsController'GetThings'从这个
    模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
    {
    //TPH(每个层次结构的表):
    modelBuilder.Entity()
    .Map(thg=>thg.Requires(“鉴别器”).HasValue(“A”))
    .Map(thg=>thg.Requires(“鉴别器”).HasValue(“D”);
    }
    }
    
    这是我更新的东西controller.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);
        }
    }
    
    公共类ThingsController:ODataController
    {
    private myDbModel db=new myDbModel();
    /// 
    ///仅公开ActiveThings(而不是DeletedThings)
    /// 
    /// 
    [启用查询]
    公共IQueryable GetThings()
    {
    返回db.ActiveThings;
    }
    公共异步任务删除([FromODataUri]长键)
    {
    使用(var context=new myDbModel())
    {
    使用(var transaction=context.Database.BeginTransaction())
    {
    等待
    
    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);
        }
    }