Downcasting with Entity Framework

2019-01-20 15:22发布

问题:

I have a project where I've defined in EF an Employer as a derived class of User. In my process I create a user without knowing whether it will eventually be an employer (or other kinds of users) and later I need to convert it. At first I tried (Intellisense indicated an explicit conversion exists):

Employer e = (Employer) GetUser();

but at runtime I got:

Unable to cast object of type 'System.Data.Entity.DynamicProxies.User_7B...0D' to type 'Employer'.

so I tried to write a converter:

public partial class User
{
    public static explicit operator Employer(User u)
    {

but I get the error:

Error   21  'User.explicit operator Employer(User)': user-defined
conversions to or from a derived class are not allowed
C:\Users\..\Documents\Visual Studio 2010\Projects\..\Website\Models\EF.Custom.cs

fine. I then overloaded the constructor for Employer like this:

public partial class Employer
{
    public Employer(User u)
    {
        this.Id = u.Id;
        this.Claims = u.Claims;
        // etc.
    }
}

and figured I could then just do:

Employer e = new Employer(GetUser());

but when I run it I get the error:

System.InvalidOperationException was unhandled by user code
  Message=Conflicting changes to the role 'User' of the
  relationship 'EF.ClaimUser' have been detected.
  Source=System.Data.Entity
  StackTrace:
       [...]
       at Controllers.AuthController.Register(String Company, String GivenName, 
       String Surname, String Title, String Department) in C:\Users\..\Documents\
       Visual Studio 2010\Projects\..\Website\Controllers\AuthController.cs:line

as a last resort I tried writing this:

        Employer e = Auth.Claims("id")
            .Where(x => x.Value == Auth.NameIdentifier())
            .Select(x => x.User)
            .Cast<Employer>()
            .Single();

... GetUser() returns an object of type User which does not offer the .Cast<> so I used a direct query to get there... but I still get the casting of dynamic proxy objects exception.

so my question is: how can I downcast when the object has persistence through EF?

回答1:

It is not possible. You must always use final type. Once you create it as a User, EF will never allow you changing it to a derived entity type.

Btw. it is also not possible with object oriented approach as well. You cannot cast instance of parent class to the instance of derived class (unless it really is instance of derived class) - it will throw exception at runtime. Very simple example to reproduce the issue:

class X { } 

class Y : X { }

class Program 
{
    static void Main(string[] args) 
    {
        X x1 = new Y();
        Y y1 = (Y)x1;   // Works

        X x2 = new X();
        Y y2 = (Y)x2;   // InvalidCastException
    }
}

The only way to do that is overriding conversion operator which will internally create a new instance of derived class and copy all fields from the old parent instance to that new derived one.

Exactly the same approach is needed with entity framework. If you started with User entity and now you want to promote it to Employer entity you must delete old user and create new Employer.



回答2:

Supose that your Employer entity has only nullable properties then it is possible to go to the table at the database and change the Discriminator from User to Employer. All relationships will be kept. And also it is possible to do the opposite.