locked
DataServiceContext.SaveChanges not sending ForeignKey/Navigation Property updates RRS feed

  • Question

  • Hi,
    I am using the Dyanmic Data Futures samples to try and get a simple updatable Data Service.
    My problem is that I cannot seem to get any foreign key updates to the server.
    I started out thinking this was a UI issue but this code still fails to update the Customer property while it does update the Ship Country.

    NorthwindClientEntities entities = new NorthwindClientEntities(new Uri("http://localhost:59486/Northwind.svc/"));

                var order = (from o in entities.Orders.Expand("Customers")
                             where (o.OrderID == 10248)
                             select o).First();

              //get a different customer 
    var cust = (from Customers c in entities.Customers
                            where (c.CustomerID == "VINET")
                            select c).First();

    //Show old customer
                Console.WriteLine(order.OrderID + " --- " +
                    order.Customers.CompanyName);

                //System.InvalidOperationException: The context is already tracking the entity.
                //entities.AttachTo("Orders", order);

    //change foreignkey/ navigation property
    //set new customer
                order.Customers = cust;
    //set another simple property
                order.ShipCountry = "Fr";

    //correctly shows new customer
                Console.WriteLine(order.OrderID + " --- " +
                    order.Customers.CompanyName);


                //mark object as changed
                entities.UpdateObject(order);
                System.Data.Services.Client.DataServiceResponse dataServiceResponse = entities.SaveChanges();


    However the request that I receive on the Data Service is missing the customer information.
    Request Info:
    MERGE /Northwind.svc/Orders(10248) HTTP/1.1
    Content-Length: 1129
    Content-Type: application/atom+xml
    Accept: application/atom+xml,application/xml
    Accept-Charset: UTF-8
    Expect: 100-continue
    Host: localhost:59486
    User-Agent: Microsoft ADO.NET Data Services
    DataServiceVersion: 1.0;NetFx
    MaxDataServiceVersion: 1.0;NetFx

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <entry xmlnsBig Smile="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
      <title />
      <updated>2008-10-09T03:16:11.8948Z</updated>
      <author>
        <name />
      </author>
      <id>http://localhost:59486/Northwind.svc/Orders(10248)</id>
      <content type="application/xml">
        <mStick out tongueroperties>
          <d:Freight m:type="Edm.Decimal">0.0000</d:Freight>
          <dSurpriserderDate m:type="Edm.DateTime">1996-07-04T00:00:00</dSurpriserderDate>
          <dSurpriserderID m:type="Edm.Int32">10248</dSurpriserderID>
          <d:RequiredDate m:type="Edm.DateTime">1996-08-01T00:00:00</d:RequiredDate>
          <dTongue TiedhipAddress>59 rue de l'Abbaye</dTongue TiedhipAddress>
          <dTongue TiedhipCity>bb</dTongue TiedhipCity>
          <dTongue TiedhipCountry>Fr</dTongue TiedhipCountry>
          <dTongue TiedhipName>Vins et alcools Chevalier</dTongue TiedhipName>
          <dTongue TiedhipPostalCode>cc</dTongue TiedhipPostalCode>
          <dTongue TiedhipRegion>cc</dTongue TiedhipRegion>
          <dTongue TiedhippedDate m:type="Edm.DateTime">2008-11-01T00:00:00</dTongue TiedhippedDate>
        </mStick out tongueroperties>
      </content>
    </entry>

    Note my simple scalar value change is received on the Data Service site but not the new customer.

    Have spent most of the day on this, and I am still at sea, can anyone help me find land

    Cheers
    Simon
    Thursday, October 9, 2008 3:44 AM

Answers

  • You will need to call entities.SetLink(order, "Customers", cust) to let the context know you have set the Link.

     

     

     

    Sunday, October 12, 2008 3:46 PM
    Moderator
  • Having an overload of SetLink which takes a key value assumes thats how the back end data source maintains the relationship between the entities (PK-FK relationship).  We tried to avoid that assumption in Astoria.

     

    That said, you can new up the related object and just set the key value.  Then attach it and set the reference.  That should work.

     

    The Astoria client does a limited amount of folding operations on the wire for newly inserted entities.  In general, if one needs to rely on all the changes being sent to the server as a Unit of Work, one should specify batch changes when calling SaveChanges.

     

     

     

    Wednesday, October 15, 2008 8:09 PM
    Moderator

All replies

  • This appears to be specific to ado.net data services (aka astoria), so I'm moving the thread to that forum.

     

    - Danny

     

    Sunday, October 12, 2008 6:07 AM
  • You will need to call entities.SetLink(order, "Customers", cust) to let the context know you have set the Link.

     

     

     

    Sunday, October 12, 2008 3:46 PM
    Moderator
  • Thanks Andrew.  I am not sure why this call is required.  Should not the customer Setter, handle this automatically and do the internal link fixups?

    Sunday, October 12, 2008 7:39 PM
  • Thanks Daniel and Andrew,
    I guess I got a bit lost in the stack and it took me a while to figure out how far up the stack to look for a problem that shows itself in the UI Smile

    FYI William, I agree with you but perhaps not the Customer setter but the DataServiceContext.UpdateObject method should at least do the work. It seems to me if the change info is there, why ignore it. Certainly have private methods that update each object (order and customer link) but have a public method that calls both if necessary. My problem start when I was trying to find why everything in my object was being saved except my changed foreign key info.
    I looked into the Dynamic Data Futures samples and found there on the DataServiceLinqDataSourceView.UpdateObject only a call to UpdateObject and no mention of SetLink and there my confusion started.

    Cheers
    Simon
    Sunday, October 12, 2008 8:17 PM
  • Hi Simon,

     

      while not an exhaustive reference, I bolgged about how one deals with associations in the client library here :Working with Associations in ADO.NET Data Services
    Monday, October 13, 2008 4:38 PM
    Moderator
  • The current design of the client API assumes three things:  1)  That all operations should be explicit, 2) That Links are first class resources (as they are in REST) and that 3) Our context does not store any "before image" of a given entity.  Without this it would be very hard for us to support POCO scenarios.  For example, if an object is updated how do we interpret null for the Customer property.

     

    As a result, this means the developer has to explicitly manage Links with the DataServiceContext just like they need to do with their Entities.  This often seems overly verbose and low level - but means there is very little magic going on in the DataServiceContext. 

     

    That said, we acknowledge that perhaps it might be good to introduce some higher layer which does some magic and automcatically tracks Links, etc.  Currently we are looking at some design options for that and will get out to the team blog when we have something concrete.

     

    THanks -

    Andy

     

    Monday, October 13, 2008 9:34 PM
    Moderator
  • Thanks for the reply Andrew.
    I take your point. Simple is good.
    Could I make one suggestion on the SetLink front.
    What do you think about allowing the setting of a link using an unmaterialized object?

    Consider a materialized order with the customer field databound to a combobox.
    If the combo only contains a customerid and a customername for the value and text, and in order to get an update or insert back to the data service, all one has to have is a link element in the Merge or Post payload, could you consider having a set link overload such as:

    SetLink(Object materializedObject, String property, String objectPrimaryKey)

    eg you could call this

    SetLink(order,"Customers","MyCustomerID")

    and this would render an atom payload that contained the customerid link such as with this Insert request:


    POST /Northwind.svc/Orders HTTP/1.1
    Content-Length: 1223
    Content-Type: application/atom+xml
    Accept: application/atom+xml,application/xml
    Accept-Charset: UTF-8
    Expect: 100-continue
    Host: localhost:59486
    User-Agent: Microsoft ADO.NET Data Services
    DataServiceVersion: 1.0;NetFx
    MaxDataServiceVersion: 1.0;NetFx

    <?xml version="1.0" encoding="utf-8"?>
    <entry xmlnsBig Smile="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
    <title />
    <updated>2008-10-15T02:55:15.4920225Z</updated>
    <author>
    <name />
    </author>
    <id />
    <link href="http://localhost:59486/Northwind.svc/Customers('VINET')" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Customers" type="application/atom+xml;type=entry" />
    <content type="application/xml">
    <mStick out tongueroperties>
    <d:Freight m:type="Edm.Decimal" m:null="true" />
    <dSurpriserderDate m:type="Edm.DateTime">2008-10-15T15:55:15.4670225+13:00</dSurpriserderDate>
    <dSurpriserderID m:type="Edm.Int32">0</dSurpriserderID>
    <d:RequiredDate m:type="Edm.DateTime" m:null="true" />
    <dTongue TiedhipAddress m:null="true" />
    <dTongue TiedhipCity m:null="true" />
    <dTongue TiedhipCountry>Czechozlovakia</dTongue TiedhipCountry>
    <dTongue TiedhipName>Added without UI, ie Programatically</dTongue TiedhipName>
    <dTongue TiedhipPostalCode m:null="true" />
    <dTongue TiedhipRegion m:null="true" />
    <dTongue TiedhippedDate m:type="Edm.DateTime" m:null="true" />
    </mStick out tongueroperties>
    </content>
    </entry>


    Incidently:
    now that I use the SetLink and the UpdateObject in my code:



    //change an association prop
    order.Customers = cust;

    //change a scalar prop
    order.ShipCountry = "France2";

    //mark object and association as changed
    entities.SetLink(order, "Customers", cust);
    entities.UpdateObject(order);
    DataServiceResponse dataServiceResponse = entities.SaveChanges();



    I get 2 requests going to the server:
    PUT: http://localhost:59486/Northwind.svc/Orders(10248)/$links/Customers
    MERGE: http://localhost:59486/Northwind.svc/Orders(10248)

    Why would these not be combined into one call, such as happens with the insert/post above?
    Just thinking about reducing the trips to the server.


    Cheers
    Simon

    Wednesday, October 15, 2008 3:20 AM
  • Having an overload of SetLink which takes a key value assumes thats how the back end data source maintains the relationship between the entities (PK-FK relationship).  We tried to avoid that assumption in Astoria.

     

    That said, you can new up the related object and just set the key value.  Then attach it and set the reference.  That should work.

     

    The Astoria client does a limited amount of folding operations on the wire for newly inserted entities.  In general, if one needs to rely on all the changes being sent to the server as a Unit of Work, one should specify batch changes when calling SaveChanges.

     

     

     

    Wednesday, October 15, 2008 8:09 PM
    Moderator
  • Thanks for that Andrew,
    I did just find a similar solution to you 'minimally materialized object' suggestion.

    For anyone interested this would look something like..

    //Even though the Customer exists, create a new object to represent it. This saves getting it from the server
    Customer cust = new
    Customer();
    //minimally set the Key column
    cust.CustomerID= 'VINET';
    //use the object
    order.Customer=cust;

    //Let the Context know we are using the object so it tracks it
    // Don't use Add method, use AttachTo method.
    dataServiceContext.AttachTo("Customer",cust);

    //Set the link to the minimally materilized object. This may save you a trip to the server if you already have the CustomerID
    dataServiceContext.SetLink(order,"Customer",cust);

    Cheers
    Simon

    Friday, October 17, 2008 1:13 AM
  • SetLink method works for a 1-to-many relationship:  entities.SetLink(order, "Customers", cust)

    But what about a many-to-many relationship?  The SetLink method does not appear to accept a "Collection" as the second input parameter.  In other words, in the example above, lets say that that each order had multiple books, AND each book had multiple orders.  The following statement would not work:

    entities.SetLink(order, "Books", book)

    Is there any other way to account for a
    many-to-many relationship?
    Friday, October 17, 2008 5:46 PM
  • Hi ,

    "SetLink method works for a 1-to-many relationship:  entities.SetLink(order, "Customers", cust)"

    This is incorrect,  the SetLink API is used to specify a reference relation , i.e

     1..1 Mapping .

     

    AddLink is the API to be used for 1-to-Many or Many-To-Many relations.



    Tuesday, October 21, 2008 12:49 AM
    Moderator
  • Thanks Phani, your were correct, the "AddLink" method worked for 1-to-many and many-to-many relations.  I appreciate your help.  Robert
    Wednesday, October 22, 2008 4:59 AM
  •  

    This has cleared up a little bit of my confusion over AddLink, SetLink and AttachLink. From reading the MSDN help it is very unclear the differences between them. I would really appreciate if a clear explanation could be given for these.

     

    So far all I have is AddLink is for adding 1-to-many or many-to-many and SetLink is for 1-1 relationships.

     

    Thank you.

    Thursday, December 4, 2008 5:10 PM