Working with associations in ADO.NET Data Services , Part 2

In part 2 ,we will talk about how to use the client library to deal with :

1..N Associations

This is the case of entities associated with each other 1..N . An example from Northwind is the relation between Categories and Products Entities.

An instance of the Categories entity type should have associated Products entities, this is another way of saying “ A row in the Products table should have links to a row in the Categories table”.
In case of 1..N relations , the left( child ) ends of the association are expressed as Collection properties on the right ( parent ) end of the relation.
This is shown in the metadata of the service as :

 <EntityType Name="Categories">
<!-- Primary Key-->
<Key>
  <PropertyRef Name="CategoryID" /> 
</Key>
 <!--Primitive Properties-->
  <Property Name="CategoryID" Type="Edm.Int32" Nullable="false" /> 
  <Property Name="CategoryName" Type="Edm.String" Nullable="false" /> 
  <Property Name="Description" Type="Edm.String" Nullable="true" /> 
  <Property Name="Picture" Type="Edm.Binary" Nullable="true" /> 
 <!-- Child Entity Set-->
  <NavigationProperty 
        Name="Products" 
        Relationship="northwind.FK_Products_Categories" 
        FromRole="Categories" 
        ToRole="Products" /> 
</EntityType>

This leads the client utility( DataSvcUtil.exe ) to generate a type Individual which has instances of types Customer and Contact as Properties.

The generated class on the client looks like this :

 public class Categories {
  public global::System.Collections.ObjectModel.Collection<Products> Products {
            get {
                return this._Products;
            }
            set {
                if ((value != null))
                {
                    this._Products = value;
                }
            }
        }
}

Now that we have set the stage, lets look at how one would use the client library to interact with the related types.

You have new instances of Individual , Contact and Customer and you want to persist the new entities and their relation to the store.

 DataServiceContext dsc = new DataServiceContext(new Uri("http://ServiceEndpoint"));
Categories freeProductsCategory = new Categories()
{
    CategoryName = "Free Products",
    Description = "These products are free",
};
Products freeHeadPhones = Products.CreateProducts(0, "Free Headphones", false);
Products freeZune = Products.CreateProducts(1, "Free Zune", false);

dsc.AddObject("Categories", freeProductsCategory);
dsc.AddObject("Products", freeHeadPhones);
dsc.AddObject("Products", freeZune);

Now what ?

Since the entities are created and added to the store can’t I just set the Customer and Contact Property to the Individual object and that will save the relation ?

 freeProductsCategory.Products.Add(freeHeadPhones);
freeProductsCategory.Products.Add(freeZune);
dsc.SaveChanges();

Shouldn’t this be enough ?

Nope , that’s not enough to save the relation to the store.

Why ?

Remember that the client context only gives you POCO access to entities in the store and any new entities that you create.

The Client context does not track any relations unless you explicitly ask it to do so !

Neat , now how do I ask it to do that ?

You use the AddLink method defined on the context.

 dsc.AddLink(freeProductsCategory, "Products", freeHeadPhones);
dsc.AddLink(freeProductsCategory, "Products", freeZune);

The signature and the intent of the SetLink method is lucid , it binds 2 entities into a relation , its kinda like a priest at a wedding,

This is what it looks like ..

AddLink ( Parentinstance ,”ChildPropertyName”,ChildInstance)

Deleting the parent entity

Deleting the parent entity means that the relations with the child entities are also removed.

Think of this as the argument of “How do I delete an entity that has 1..1 links with other   entities?”

What happens if I delete the parent entity without deleting the links ?

It depends on your store . If your database is configured to do a cascade delete on deletion of the parent entity ,you might get away with deleting the entity without removing the links

The right way to delete the entity is to remove all the links it has with the child entities and then delete the entity itself.

The code would look like this..

 DataServiceContext dsc = new DataServiceContext(new Uri("http://ServiceEndpoint"));
//Load an existing parent
Categories freeProductsCategory = dsc
                                  .CreateQuery<Categories>("Categories")
                                  .Where(cat => cat.CategoryName == "Free Products")
                                  .First();
//Load all the related child Entities
dsc.LoadProperty(freeProductsCategory, "Products");

//Delete the links with each of the child entities
foreach (Products freeProduct in freeProductsCategory.Products) {
    dsc.DeleteLink(freeProductsCategory, "Products", freeProduct);
}
//Delete the parent entity
dsc.DeleteObject(freeProductsCategory);
dsc.SaveChanges();

Wait !! what’s with the LoadProperty there ?
Well, if you need to delete the relation , the way to identify the relation or the link is to have both the right and the left end of the relations to be materialized. without the expand , the Contact and the Customer property are null , and we don’t know which relation to delete.In case of 1..1 relations it might be easy to predict based on the relation name ,

but wouldn’t be easy in case of 1..n relations.

Deleting the child entity

 //Load an existing parent
Categories freeProductsCategory = dsc
                                   .CreateQuery<Categories>("Categories")
                                   .Where(cat => cat.CategoryName == "Free Products")
                                   .First();
//Load all the related child Entities
dsc.LoadProperty(freeProductsCategory, "Products");

//Delete the links with each of the child entities
foreach (Products freeProduct in freeProductsCategory.Products) {
    dsc.DeleteLink(freeProductsCategory, "Products", freeProduct);
    //Delete the child entity
    dsc.DeleteObject(freeProduct);
}
dsc.SaveChanges();
Special Consideration for Navigation properties that are named differently than the Entity Type

This is a special case because of a bug in the client library at the time of writing this post.

If you have a navigation property  which is named differently than the EntityType and try to delete the link between the source

and the navigation property , we produce an invalid URI for the DELETE of the link.

ex:

Entity Type : Customers , EntitySet Name : Customers

Navigation Properties :
                      EntityType : Orders , Navigation Property Name : CashOrders

                      EntityType : Orders , Navigation Property Name : CreditCardOrders

Upon calling ,

dataContext.DeleteLink(customerInstance,”CashOrders”,orderInstance);

We generate a DELETE to the URI

/Customers(Key)/$links/Orders(Key)

This is wrong , the DELETE should go to :

/Customers(Key)/$links/CashOrders(Key)

We will address this issue in a future release .In the meantime ,  use the workaround below ,

 public partial class YourDataContextClass {

    public void DeleteLinkManual(object source, string associationName, object target) {
            System.Uri UriOfParent;
            System.Uri UriOfChild;
            this.TryGetUri(source, out UriOfParent);
            this.TryGetUri(target, out UriOfChild);

            //Get the Segment of the URI with the Key 
            string strChildAssociationURI = UriOfChild.Segments.Last();
            strChildAssociationURI = strChildAssociationURI.Substring(strChildAssociationURI.IndexOf('('));

            //ServiceEndPoint/ParentEntitySet/$links/NavigationPropertyName(ChildEntitySetKey)
            string requestUriForLinkDelete = System.String.Format("{0}/{1}/$links/{2}{3}",
                                             this.BaseUri.OriginalString,
                                             UriOfParent.Segments.Last(),
                                             associationName,
                                             strChildAssociationURI);

            System.Net.WebRequest request = System.Net.WebRequest.Create(requestUriForLinkDelete);
            request.Method = "DELETE";
            System.Net.HttpWebResponse response = request.GetResponse() as System.Net.HttpWebResponse;
            if (response.StatusCode != System.Net.HttpStatusCode.NoContent)
            {
                throw (new System.Data.Services.Client.DataServiceClientException("Delete Failed"));
            }
            //Detach this link as the link no longer exists and keep the context consistent
            this.DetachLink(source, associationName, target);
      }
}

and then , in your code ,

 YourDataContext dataContext = new YourDataContext(<Uri>);
dataContext.DeleteLinkManual(customerInstance,”CashOrders”,orderInstance);

This will produce the right URI :

 ServiceEndPoint/Customers(Key)/$links/CashOrders(Key) 

Note that this doesnt work in Silverlight , I will create a separate sample for Silverlight later .

If you have any questions  , leave a comment . If you have any issues with code and need help , please post your query on the astoria forums as I can’t promise that I will be able to reply to emails sent to me directly .The whole team is very active on the forums and the more eyes on a problem the better.