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

前端 未结 4 1074
执笔经年
执笔经年 2020-12-11 19:43

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 abs

相关标签:
4条回答
  • 2020-12-11 19:47

    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". 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.

    Because of that we tried using TPT inheritance, instead of TPH - but TPH would work somehow too.

    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. In TPT it would be:

    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.

    When using TPH the query would only change from INSERT ... VALUES ... to UPDATE ... SET ... WHERE [Id] = .... Dont forget to change Discriminator column too.

    After next call dbcontext.Users.OfType<Customer> there is our original user, "converted" to 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(). That didn't work for me (neither TPH nor TPT). Either because using separate DBContexts per module, or because EntityFramework 6(.1) ignores this. It can be found here.

    0 讨论(0)
  • 2020-12-11 19:47

    Yes, you got it all right. EF inheritance does not support this scenario. 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

    0 讨论(0)
  • 2020-12-11 19:47

    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

    0 讨论(0)
  • 2020-12-11 20:01

    EDIT: APOLOGIES, THIS IS AN EF 6.x ANSWER

    I'm posting example code for completeness. In this scenario, I have a base Thing class. Then, sub-classes: ActiveThing and DeletedThing

    My OData ThingsController, has a main GetThings which I intend to only expose ActiveThings, but, it's GetThing(ThingId) can still return either type of object. 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. I'm using inline SQL (parameterized)

    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

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题