Updating Entity Framework 5 for each type, changing the subtype, but keeping the same base type

I have a simple hierarchy

public abstract class CommunicationSupport { public SupportTypeEnum Type { get; set; } public Country Origin { get; set; } // National or Foreign support } public class TelecomSupport : CommunicationSupport { public string Number { get; set; } } public class PostalSupport : CommunicationSupport { public Address Address { get; set; } } 

I plan to use the Table-per-type hierarchy for my database. Thus, 3 tables will be created, one basic and two child, using the same PC as the base.

My problem is that I want to update CommunicationSupport by changing its type. Let me say that I create TelecomSupport, save it, and then change its type to PostalSupport and save it again (update). I expect EF to keep the same base record (CommunicationSupport Id), but delete the record in the TelecomSupport table and create a new one in PostalSupport. Therefore, TelecomSupport and PostalSupport are exclusive and cannot use the same basic CommunicationSupport.

How can I do this with EntityFramework 5?

Thank you for your help!

+8
entity-framework table-per-type
source share
1 answer

I do not have a good answer, but I can think of four “solutions” that are really workarounds:

  • Do not use DBMS-computed values ​​for your primary keys (if you are already using natural keys, this is normal).
  • Use surrogate keys with a DBMS.
  • Follow the example status template .
  • Make some evil voodoo using the object state manager .

Update:. There seems to be a popular belief that the attempt is not even worth it; therefore, most people simply use stored procedures to solve this problem.

Using natural keys

First, remember that objects monitored by EF are part of your DAL, not your domain model (whether you use POCOs or not). Some people do not need a domain model, but keep this in mind, because now we can think of these objects as representations of table records that we manage in ways that we will not use with domain objects.

Here we use IDbSet.Remove to delete object records and then add new ones with the same primary key using IDbSet.Add , all in one transaction. See the ChangeType method in the following code example.

Theoretically, integrity is fine, and in theory, EF can detect what you are trying to do and optimize. In practice, this is currently not the case (I have profiled the SQL interface to verify this). As a result, it looks ugly ( DELETE + INSERT instead of UPDATE ), so if system beauty and performance is a problem, it is probably not-go. If you can do this, it is relatively simple.

Here is an example of the code that I used for testing (if you want to experiment, just create a new console application, add a link to the EntityFramework assembly and paste the code).

A is the base class, X and Y are subclasses. We believe that Id is a natural key, so we can copy it into copy constructors for subclasses (here it is implemented only for Y ). The code creates a database and records it with a record of type X Then it starts and changes its type to Y , obviously losing X specific data in this process. Copy Designer is the place where you must convert data or archive it if data loss is not part of the business process. The only piece of "interesting" code is the ChangeType method, the rest is template.

 using System; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Linq; namespace EntitySubTypeChange { abstract class A { [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public string Foo { get; set; } public override string ToString() { return string.Format("Type:\t{0}{3}Id:\t{1}{3}Foo:\t{2}{3}", this.GetType(), Id, Foo, Environment.NewLine); } } [Table("X")] class X : A { public string Bar { get; set; } public override string ToString() { return string.Format("{0}Bar:\t{1}{2}", base.ToString(), Bar, Environment.NewLine); } } [Table("Y")] class Y : A { public Y() {} public Y(A a) { this.Id = a.Id; this.Foo = a.Foo; } public string Baz { get; set; } public override string ToString() { return string.Format("{0}Baz:\t{1}{2}", base.ToString(), Baz, Environment.NewLine); } } class Program { static void Main(string[] args) { Display(); ChangeType(); Display(); } static void Display() { using (var context = new Container()) Console.WriteLine(context.A.First()); Console.ReadKey(); } static void ChangeType() { using (var context = new Container()) { context.A.Add(new Y(context.A.Remove(context.X.First()))); context.SaveChanges(); } } class Container : DbContext { public IDbSet<A> A { get; set; } public IDbSet<X> X { get; set; } public IDbSet<Y> Y { get; set; } } static Program() { Database.SetInitializer<Container>(new ContainerInitializer()); } class ContainerInitializer : DropCreateDatabaseAlways<Container> { protected override void Seed(Container context) { context.A.Add(new X { Foo = "Base Value", Bar = "SubType X Value" }); context.SaveChanges(); } } } } 

Output:

 Type: EntitySubTypeChange.X Id: 0 Foo: Base Value Bar: SubType X Value Type: EntitySubTypeChange.Y Id: 0 Foo: Base Value Baz: 

Note. . If you want to create an automatically generated natural key, you cannot allow EF to request the DBMS to calculate it, or EF will not allow you to manipulate it the way you want (see below). In fact, EF treats all keys with calculated values ​​as surrogate keys, even though it still succeeds (the bad of the two worlds).

Note. I comment on the subclasses of Table because you mentioned the TPT setting, but the problem is not actually related to the TPT.

Using surrogate keys

If you think that the surrogate key is really internal, then it doesn’t matter if it changes under your nose, as long as you can still access your data the same way (using, for example, a secondary index).

Note. . In practice, many people come across surrogate keys around (domain model, service interface, ...). Do not do this.

If you are taking the previous sample, simply remove the DatabaseGenerated attribute and the Id assignment in the subtype copy constructor.

Note. . With its value generated by the DBMS, the Id property is completely ignored by EF and does not serve any real purpose, other than analysis by the model constructor for generating Id in the SQL schema. This is leaked by bad programmers.

Output:

 Type: EntitySubTypeChange.X Id: 1 Foo: Base Value Bar: SubType X Value Type: EntitySubTypeChange.Y Id: 2 Foo: Base Value Baz: 

Using a state template (or similar)

This solution is likely most people will consider the “right solution” since you cannot change the internal type of an object in most object-oriented languages. This applies to CTS- compatible languages, including C #.

The problem is that this template is used correctly in the domain model, and not in a DAL similar to that implemented with EF. I'm not saying this is not possible, you can hack things with complex TPH types or constructs to avoid creating an intermediate table, but most likely you will float down the river until you give up. Hope someone can prove that I'm wrong.

Note. . You can decide if you want your relational model to look different, in which case you can work around this problem altogether. That would not be the answer to your question.

Using Internal EF voodoo

I quickly looked through the reference documentation for DbContext , ObjectContext and ObjectStateManager , and I cannot immediately find a way to change the type of entity. If you're more fortunate than me, you can use DTO and DbPropertyValues to convert.

Important Note

With the first two workarounds, you are likely to encounter many problems with navigation properties and foreign keys (due to the DELETE + INSERT operation). This will be a separate issue.

Conclusion

EF is not so flexible when you do something non-trivial, but it continues to improve. I hope this answer will not be appropriate in the future. It is also possible that I do not know the existing function of the killer that would do what you want, so do not make any decisions based on this answer.

+6
source share

All Articles