I have a simple 2 object inheritance defined in an EF model, Person <- User.
Person is the base entity/class, it is not abstract, and it contains fields like firstname, lastname, email.
User is the derived entity/class, and contains fields like username, lastlogin, islockedout.
The database uses a table-per-type schema, so there is 1 table for Person, and another table for User. Both use the same primary key field, PersonID. PersonID is an IDENTITY column, but in the EDM, StoreGeneratedPattern for PersonID is set to None.
Because this is a table-per-type inheritance schama, there can be a Person row/object that doesn't have a corresponding User row/object. This is so that the system can contain data about people who are not users. However, a Person may eventually become a User, and this is where I'm having problems.
I have a test case for this scenario, where the system finds a Person that it wants to tu开发者_开发问答rn into a User. I've tried setting the PersonID field on a new User object, adding it to the ObjectContext, and saving changes. What happens is the database creates a new Person row along with the new User row, ignoring the value I set for PersonID.
How can I get around this? Do I have to create sprocs to handle the EDM crud operations?
Here is your problem:
I have a test case for this scenario, where the system finds a Person that it wants to turn into a User
Objects can't change types, in C# or any other class-based OOPL I know of. The EF does not change this.
You need to change your design. It's not OO. I wrote about this last year:
One of the mental barriers that you have to get over when designing a good object relational mapping is the tendency to think primarily in object oriented terms, or relational terms, whichever suits your personality. A good object relational mapping, though, incorporates both a good object model and a good relational model. For example, let’s say you have a database with a table for People, and related tables for Employees and Customers. A single person might have a record in all three tables. Now, from a strictly relational point of view, you could construct a database VIEW for employees and another one for customers, both of which incorporate information from the People table. When using a one VIEW or the other, you can temporarily think of an individual person as "just" an Employee or "just" a Customer, even though you know that they are both. So someone coming from this worldview might be tempted to do an OO mapping where Employee and Customer are both (direct) subclasses of Person. But this doesn’t work with the data we have; since a single person has both employee and customer records (and since no Person instance can be of the concrete subtype Employee and Customer simultaneously), the OO relationship between Person and Employee needs to be composition rather than inheritance, and similarly for Person and Customer.
I was able to solve this problem, though I'm not entirely happy with the solution. I ended up writing stored procedures to map the insert, update, and delete operations for both Person and User (since they're in the same Entity set, both have to be mapped with all 3 sprocs). The insert sproc for User takes a PersonID parameter, and uses it to decide whether to create a new Person row, or attach to an existing Person row.
The catch is that the PersonID has to be mapped as both sproc input parameter and as a Result Column binding. When the db must generate a new PersonID, we have to get it back out using the result binding in order for other tables to get the correct value for foreign key columns referencing PersonID. Except.... you can't map User.PersonID as both an input parameter and a result column binding at the same time. This causes a runtime exception "Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements or store generated values."
I ended up adding a new column to the User table (and to the corresponding conceptual model) which is a nullable int (I called it BasePK). In the EDM, I made the getters and setters for this property protected, to hide them from the public interface. Then, I mapped BasePK as the sproc input parameter, and mapped PersonID as a result column binding to receive the identity value if one was generated. One overload for the User constructor goes like this:
public User(int personID) : base() { this.BasePK = personID; }
So now, whenever I want to turn an existing Person into a User, I just have to construct the user using this overload.
int personID = [some method to search for person and return his/her PersonID]; var User = new User(personID);
When the ObjectContext goes to insert the entity, it passes the PersonID to the sproc via the BasePK property. When the sproc is done, it returns the PersonID to the actual primary key property.
I didn't like the idea of having to add a column & property to accomplish this, but it does jive with my comment to Craig's answer: I want to be able to plug into Person with new specializations without having to change / recompose the Person entity each time. This way, at least I can keep all of the changes in the derived entity types, even if it means those changes involve adding meaningless columns which will always be null in the physical store.
精彩评论