Microsoft Developer Network > Forums Home > Data Platform Development (Pre-release) Forums > ADO.NET Entity Framework and LINQ to Entities (Pre-Release) > Working with FKs: How to create an EntityKey in an object with client-generated primary keys (Guid) ?
Ask a questionAsk a question
 

AnswerWorking with FKs: How to create an EntityKey in an object with client-generated primary keys (Guid) ?

  • Tuesday, October 27, 2009 11:20 PMTed Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Let's say we have two silly simple entity types: "Customer" (Id of type Guid, Name of type string) & "Order" (Id of type Guid, Value of type Int32 & CustomerId of type Guid)

    If I do new Customer() { Id = Guid.NewGuid(), Name = "John" }, then the EntityKey will be null.

    Try #1: What happens when I add the customer object to the context ? EntityKey becomes "EntitySet=Customers" (ie not a complete EntityKey).

    Try #2: newCustomer.EntityKey = Context.CreateEntityKey("Customers", newCustomer); creates a proper EntityKey for that object ("EntitySet=Customers;Id=242ca4b1-4e81-487c-a43c-96762af996fc") BUT when I add the object to the context the EntityKey is yet again turned into "EntitySet=Customers".

    Try #3: Create the customer, and then call Context.SaveChanges(); Hmm, it works, after the save the EntityKey is turned into a proper "EntitySet=Customers;Id=242ca4b1-4e81-487c-a43c-96762af996fc". But my problem is that I don't want to push the items to the datastore every time I create a new object. (it's too slow in my scenario)

    So why is this a problem at all?
    Well the problem is when I'm using ForeignKeys...
    So let's say we have a Customer created like above. Then we create an Order and add it to the context. If I now modify the CustomerId on the Order object. The order object's CustomerReference is correct BUT the Order.Customer relation is null! Why ? Ahh, because our Customer object doesn't have a proper EntityKey hence the order can't get set it's "Customer" property...

    I'm just starting out with EF, so maybe I'm going about this in the wrong way, but how am I supposed to do? Or is it a bug ? (I'm using EF4 beta2 btw)

    Thanks!
    Cheers,
    Ted

Answers

  • Wednesday, October 28, 2009 5:34 AMDiego B VegaMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Hi Ted,

    In Entity Framework, added objects always have temporary EntityKeys until they are saved. As you have already realized, a consequence of this design is that we cannot lookup an entity that is added by its key. So, when you establish a relationship using the EntityReference.EntityKey, the navigation property is set to null until the added entities are saved and we can finally convert the EntityKey to a regular one.

    Now, setting the EntityKey in the EntityReference (i.e. CustomerReference.EntityKey in this case) is probably the less natural way to establish the relationship anyway. Here are all the alternatives:

    1. Set the reference navigation property directly. For instance:
    order.Customer = customer;
    This will also immediately place the order in the customer.Orders collection and will set the corresponding FK property to the key property value of the customer. For instance:
    Assert(customer.OrderId == order.OrderId);

    2. Add the order to customer.Orders, for instance:
    customer.Orders.Add(order);
    This will also fix-up the reference and the FK values.

    3. Set the FK property value:
    order.CustomerId = customer.CustomerId;
    This will not by itself set the navigation properties, again because we cannot lookup an added entity by its key property value. But navigation properties will be aligned with the key values as soon as SaveChanges in invoked.

    4. Setting order.CustomerReference.EntityKey as you tried.

    In general, #1 and #2 are the recommended ways of setting up a relationship to an added object. Of course, #3 works as well as soon as you are ok with not having the graph fixed-up completely until SaveChanges. For cases in which you have foreign key properties in your entity (which is a new feature in .NET 4.0 beta 2), #4 is only provided for backwards compatibility.

    Hope this helps,
    Diego


    This posting is provided "AS IS" with no warranties, and confers no rights.

All Replies

  • Wednesday, October 28, 2009 5:34 AMDiego B VegaMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Hi Ted,

    In Entity Framework, added objects always have temporary EntityKeys until they are saved. As you have already realized, a consequence of this design is that we cannot lookup an entity that is added by its key. So, when you establish a relationship using the EntityReference.EntityKey, the navigation property is set to null until the added entities are saved and we can finally convert the EntityKey to a regular one.

    Now, setting the EntityKey in the EntityReference (i.e. CustomerReference.EntityKey in this case) is probably the less natural way to establish the relationship anyway. Here are all the alternatives:

    1. Set the reference navigation property directly. For instance:
    order.Customer = customer;
    This will also immediately place the order in the customer.Orders collection and will set the corresponding FK property to the key property value of the customer. For instance:
    Assert(customer.OrderId == order.OrderId);

    2. Add the order to customer.Orders, for instance:
    customer.Orders.Add(order);
    This will also fix-up the reference and the FK values.

    3. Set the FK property value:
    order.CustomerId = customer.CustomerId;
    This will not by itself set the navigation properties, again because we cannot lookup an added entity by its key property value. But navigation properties will be aligned with the key values as soon as SaveChanges in invoked.

    4. Setting order.CustomerReference.EntityKey as you tried.

    In general, #1 and #2 are the recommended ways of setting up a relationship to an added object. Of course, #3 works as well as soon as you are ok with not having the graph fixed-up completely until SaveChanges. For cases in which you have foreign key properties in your entity (which is a new feature in .NET 4.0 beta 2), #4 is only provided for backwards compatibility.

    Hope this helps,
    Diego


    This posting is provided "AS IS" with no warranties, and confers no rights.
  • Wednesday, October 28, 2009 6:07 AMTed Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi Diego,

    Thanks for your answer! That clears some things up. Sad to hear your point #3 though, since that's the way I was trying to do.

    Since I do know what the EntityKey should be, and can even create it myself, is there no way at all to set the EntityKey of my entity (which would solve my problems) ? Not even some "hackish" way?? :)

    Thanks again!
    Cheers,
    Ted


  • Wednesday, October 28, 2009 8:15 AMDiego B VegaMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi Ted,

    There a few things I believe I should clarify:

    1. As I said, the reason #3 above doesn't immediatelly fixup the reference navigation properties is that the target entity is added, and so it has a temporary EntityKey. If the target entity is not in the added state (i.e. it is Unchanged or Modified), then the navigation properties will be set.

    2. There is a difference between the EntityKey properties and the actual FK properties. EntityKey is a type that you need to construct, and it contains the FK values. the FK properties look like regular scalar properties and you can just set them as such.

    3. The alternative ways to setting EntityKeys are #1 and #2.

    4. POCO objects and graphs of POCO objects don't have EntityKeys at all.

    I am actually curious on why you want to use EntityKeys to manipulate the relationships. The alternatives are usually much easier.

    Thanks,
    Diego

    This posting is provided "AS IS" with no warranties, and confers no rights.
  • Wednesday, October 28, 2009 9:38 PMTed Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi,

    I've probably been too unclear in trying to explain what I'm trying to do. :) Probably because this is my first try with EF. I'm creating a WPF application, and EF sounded very convenient to use, it even had property change notifications in the base EntityObject class, which makes it a nice match for using the objects directly with WPF instead of going through the tedious extra work of creating wrapper viewmodels for the datamodels.

    So just to clarify, I'm not trying to use EntityKeys to manipulate relationships, I only noticed that my "Order.Customer" property became null, when I changed the FK (Order.CustomerID) to an ID of an object that I know is in the current Context, I found this very odd. So I followed the trail and noticed that this had to do with the fact that the Customer that I was referencing was newly added and that I hadn't called "SaveChanges", which in turn meant that the customer had a temporary EntityKey.
    Now I was hoping that I could set some property somewhere saying that the primary keys for my objects are NOT created in datastore but rather directly in the client. (I'm using SqlServerCe as a datastore, hence creating Guids directly on the client), and that this somehow wouldn't create temporary EntityKeys but rather proper EntityKeys, since the primarykey is not going to change after the objects have been inserted in the database.

    So what am I doing in my code that makes it harder to just set the relation using objects directly (ie Setting "Order.Customer = newCustomer;") ? Well, I'm using databinding, and I've extended EntityObject and created an "EditableEntityObject", edited the standard T4 script to generate entities based on this class instead. The "EditableEntityObject" is simply implementing "IEditableObject", to facilitate Master/Details scenarios with Apply/Cancel functionality. (Must have in any real application according to me. :) ) I'm simply overring the OnPropertyChanging/OnPropertyChanged and caching the changes if we are editing an object, and rolling them back if we cancel. and THIS is where the problem comes in, EntityObject are reporting changes to FKs but NOT to relations, therefore I'm setting the FKs, and this is where I get broken relations...

    Of course I could expand the code in the EditableEntityObject, and explicitly check for changed FKs and try to locate the proper object by their primary keys, and update the relations manually but this either seems like an oversight in the entity framework or that I'm doing things in a bad way. I think I was just assuming things were easier. :)

    Now all my problems would go away if I newly created object had their EntityKeys not made temporary (IF the framework knows that I'm creating objects with primarykeys generated on the clientside.) 

    Thanks for your help though Diego, much appreciated, as always one learns the most when things don't work the way you want right away! :)

    Cheers,
    Ted
     

     
  • Friday, October 30, 2009 6:23 AMDiego B VegaMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Hi Ted,

    Thanks for taking the time to write your detailed explanation. I think I understand now what you are trying to do and your frustration with the issues you are seeing.

    There are various technical and historical reasons for the current design around temporary keys and also for the lack of change notification on the navigation properties. All I can say is that I hope we will provide a simpler experience out of the box in this area in the future.

    Regarding the missing change notifications there is a known workaround that consists in handling the AssociationChanged event. Sample code is included in the following topic in the documentation:

    http://msdn.microsoft.com/en-us/library/cc716754(VS.100).aspx

    Hope this helps,

    Diego


    This posting is provided "AS IS" with no warranties, and confers no rights.
  • Saturday, October 31, 2009 9:11 PMTed Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thanks Diego, hooking up the AssociationChanged event and re-throw as PropertyChanged event was a simple solution that I incorporated in my EditableEntityObject class, easy and simple. Thanks again for all your help, much appreciated!

    Cheers,
    Ted