Héritage Entity Framework 4 TPH, comment changer un type en un autre?

J’ai trouvé quelques informations à ce sujet, mais pas assez pour que je comprenne quelle est la meilleure pratique pour ce scénario. J’ai votre configuration typique de TPH avec une classe de base abstraite “Firm”. J’ai plusieurs enfants “Small Firm”, “Big Firm” etc. héritant de Firm. En réalité, j’ai des classifications réalistes différentes pour les entresockets, mais j’essaie de garder les choses simples dans cet exemple. Dans la firebase database selon TPH, j’ai une seule table Firm avec une colonne FirmTypeId (int) qui différencie tous ces types. Tout fonctionne très bien, sauf que je demande à un utilisateur de modifier un type de société en un autre. Par exemple, un utilisateur peut avoir commis une erreur lors de l’ajout de la société et souhaiter la changer de grande entreprise à petite entreprise. Étant donné que le cadre de l’entité ne permet pas d’exposer la colonne de firebase database discriminante en tant que propriété, je ne pense pas qu’il soit possible de changer un type en un autre via EF. S’il vous plait corrigez moi si je me trompe. La façon dont je le vois, j’ai deux options:

  1. Ne pas utiliser TPH. Créez simplement une entité de la société et recommencez à utiliser .Where (FirmTypeId == quelque chose) pour différencier les types.
  2. Exécutez SQL directement à l’aide de context.ExecuteStoreCommand pour mettre à jour la colonne FirmTypeId de la firebase database.

J’ai vu un article dans lequel des personnes suggèrent qu’un des principes de la programmation orientée object est que les instances ne peuvent pas changer de type. Bien que cela me semble tout à fait logique, je ne semble tout simplement pas être capable de relier les points. Si nous suivions cette règle, le seul moment pour utiliser un type d’inheritance (TPH / TPT) est quand on est certain qu’un type ne serait jamais converti en un autre. Ainsi, une petite entreprise ne deviendra jamais une grande entreprise. Je vois des suggestions que la composition devrait être utilisée à la place. Même si cela n’a aucun sens pour moi (c’est-à-dire que je ne vois pas comment une entreprise a une grande entreprise, pour moi, une grande entreprise est une entreprise), je peux voir comment la composition peut être modélisée dans EF si les données sont stockées. plusieurs tables. Cependant, dans une situation où il n’y a qu’une seule table dans la firebase database, il semble que ce soit TPH ou ce que j’ai décrit aux n ° 1 et n ° 2 ci-dessus.

Oui, tout va bien. L’inheritance EF ne prend pas en charge ce scénario. Le meilleur moyen de changer un type d’ entreprise pour une entreprise existante est d’utiliser une procédure stockée.

S’il vous plaît jeter un oeil à ce post pour plus d’informations:
Modification des types hérités dans Entity Framework

J’ai rencontré ce problème dans notre projet, où nous avons le kernel DBContext et quelques modules “enfichables” avec leurs propres DBContexts , dans lesquels “l’utilisateur du module” hérite de “l’utilisateur principal (de base)”. J’espère que c’est compréhensible.

Nous avions également besoin de la capacité de changer (appelons-le) User en Customer (et si nécessaire également en un autre utilisateur “hérité” en même temps, afin que cet utilisateur puisse utiliser tous ces modules.

À cause de cela, nous avons essayé d’utiliser l’inheritance TPT, au lieu de TPH – mais TPH fonctionnerait aussi.

Une solution consiste à utiliser une procédure stockée personnalisée, comme suggéré par de nombreuses personnes …

Une autre façon qui m’est venue à l’esprit est d’envoyer une requête d’insertion / mise à jour personnalisée à DB. En TPT, ce serait:

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

Dans ce scénario, le Customer hérite de l’ User et n’a qu’une ssortingng Email .

Lors de l’utilisation de TPH, la requête ne ferait que passer de INSERT ... VALUES ... à UPDATE ... SET ... WHERE [Id] = ... Ne pas oublier de changer la colonne Discriminator aussi.

Après l’appel suivant, dbcontext.Users.OfType , notre utilisateur d’origine est “converti” en client.


Bottomline: J’ai également essayé la solution d’une autre question ici, qui comprenait le détachement de l’entité d’origine (utilisateur) de ObjectStateManager et la ObjectStateManager de l’ ObjectStateManager nouvelle entité (client), puis l’enregistrement de dbcontext.SaveChanges() . Cela n’a pas fonctionné pour moi (ni TPH ni TPT). Soit parce que vous utilisez des DBContexts distincts par module, soit parce que EntityFramework 6 (.1) l’ignore. Il peut être trouvé ici.

À moins que vous souhaitiez explicitement utiliser la fonctionnalité polymorphe de l’inheritance relationnel, pourquoi ne pas envisager une stratégie de fractionnement?

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

EDIT: APOLOGIES, CECI EST UNE REPONSE EF 6.x

Je poste exemple de code pour l’exhaustivité. Dans ce scénario, j’ai un cours de base Thing . Ensuite, sous-classes: ActiveThing et DeletedThing

Mon OData ThingsController a un GetThings principal que je ne ActiveThing exposer ActiveThing , mais GetThing (ThingId) peut toujours renvoyer l’un ou l’autre type d’object. L’action Delete effectue une conversion d’ ActiveThing en DeletedThing de la manière demandée par le PO et de la manière décrite dans d’autres réponses. J’utilise inline SQL (paramétré)

 public class myDbModel:DbContext { public myDbModel(): base("name=ThingDb"){} public DbSet Things { get; set; } //db table public DbSet ActiveThings { get; set; } // now my ThingsController 'GetThings' pulls from this protected override void OnModelCreating(DbModelBuilder modelBuilder) { //TPH (table-per-hierarchy): modelBuilder.Entity() .Map(thg => thg.Requires("Discriminator").HasValue("A")) .Map(thg => thg.Requires("Discriminator").HasValue("D")); } } 

Voici mes mises à jour ThingsController.cs

 public class ThingsController : ODataController { private myDbModel db = new myDbModel(); ///  /// Only exposes ActiveThings (not DeletedThings) ///  ///  [EnableQuery] public IQueryable GetThings() { return db.ActiveThings; } public async Task 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); } }