Working with Associations in ADO.NET Data Services
I’ve seen that users are having some question about how to deal with certain types of relations between entities and how to deal with them in the client library.I will outline the a couple of common scenarios of using the client library among a few relationship types. I'll be using the AdventureWorks Database for examples of relations.
1..1 Associations
This is the case of entities associated with each other 1..1 . An example from Adventureworks is the relation between Individual and Contact,Customer Entities.
An instance of the Individual entity type should have associated Contact and Customer entities, this is another way of saying “ A row in the Individual table should have links to a row in the Contact and Customer table”.
In case of 1..1 relations , the left( child ) ends of the association are expressed as properties on the right ( parent ) end of the relation.This is shown in the metadata of the service as :
<EntityType Name="Individual">
<Key>
<PropertyRef Name="CustomerID" />
</Key>
<!—Primitive & Complex Properties -->
<Property Name="CustomerID" Type="Edm.Int32" Nullable="false" />
<Property Name="Demographics" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="true" FixedLength="false" />
<Property Name="ModifiedDate" Type="Edm.DateTime" Nullable="false" />
<!-- Navigation Properties , Signifying related entities of an association -->
<NavigationProperty Name="Contact"
Relationship="AdventureWorks.FK_Individual_Contact_ContactID"
FromRole="Individual" ToRole="Contact" />
<NavigationProperty Name="Customer" Relationship="AdventureWorks.FK_Individual_Customer_CustomerID"
FromRole="Individual" ToRole="Customer" />
</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 Individual {
//Other properties removed for brevity
public Contact Contact {
get {
return this._Contact;
}
set {
this._Contact = value;
}
}
public Customer Customer {
get {
return this._Customer;
}
set {
this._Customer = value;
}
}
}
Trivia : Properties of a type P in an entity type E which signify that the type E has a 1..1 relation with the type P of the property are called as Reference Properties.
Now that we have set the stage, lets look at how one would use the client library to interact with the related types.
Creating the link between the entities
You have new instances of Individual , Contact and Customer and you want to persist the new entities and their relation to the store.
Individual individual = new Individual();
//Initialize all its necessary properties
Customer newCustomer = new Customer();
//Initialize all its necessary properties
Contact contact = new Contact();
//Initialize all its necessary properties
//Add the newly created entities to be tracked by the client context
TestContext.AddObject("Individual", individual);
TestContext.AddObject("Customer", newCustomer);
TestContext.AddObject("Contact", contact);
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 ?
individual.Customer = newCustomer;
individual.Contact = contact;
TestContext.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 ?
Beta 1 Code :
You use the AddLink method defined on the context.
TestContext.AddLink(individual, "Contact", contact);
TestContext.AddLink(individual, "Customer", newCustomer);
The signature and the intent of the AddLink method is lucid , it binds 2 entities into a relation , its kinda like a priest at a wedding,
“I now bind you into the holy bond of 1..1 relations. You may now change your facebook status“
okay , jokes aside , this is what it looks like ..
AddLink ( Parentinstance ,”ChildPropertyName”,ChildInstance)
RTM Code :
You use the SetLink method defined on the context.
TestContext.SetLink(individual, "Contact", contact);
TestContext.SetLink(individual, "Customer", newCustomer);
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 ..
SetLink ( 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..
Individual existingIndividual = TestContext.Individual.Expand("Customer,Contact").Take<Individual>(1).First<Individual>();
TestContext.DetachLink(existingIndividual, "Contact", existingIndividual.Contact);
TestContext.DetachLink(existingIndividual, "Customer", existingIndividual.Customer);
TestContext.DeleteObject(existingIndividual);
TestContext.SaveChanges();
Wait !! what’s with the expand 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.
Well, that’s it from me , In the next part , we shall discuss entities with 1..N relations and how to work with them using the astoria context.
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.
I will update the code used in this sample and upload it later.
Comments
Anonymous
July 14, 2008
Where did TestContext suddenly come from?Anonymous
July 14, 2008
Hi Matt, TestContext is an instance of the AdventureWorks' client library . I put it in there for demonstration purpose . It will be clear once I upload the code for the sample. AdventureWorksContext TestContext = new AdventureWorksContext( new Uri("http://serviceendpoint/service.svc") );Anonymous
July 28, 2008
Hi, I find bad to be forced to use the Addlink to allow us to save relational object in the DB. We have a pretty full object model but the Addlink method needs string argument. As each entity knows all navigation properties, why to not have something like entity.NavigationObject rather than string SourceProperty. At least, myObject.MyNavigationProperty.Name or ToStringName would be better. Thanks Nice blogs Regards -VinceAnonymous
July 28, 2008
The comment has been removedAnonymous
October 13, 2008
The comment has been removedAnonymous
October 15, 2008
Hi Simon, Thanks for the feedback. I agree that the concept of treating associations as first class resources is somewhat new to the application scenarios where the client library is being used. At the same time , we dont want to make assumptions on how the relation exists on the server as the client does not have enough information about the server schema. Sure , we have the CLR types reflect the associations on the server i.e Reference for 1..1 relations and Collection types for 1..N relations. But we really dont want to make assumptions about this. Associations are first class resources on the server too , which means that any changes to associations should happen as separate requests or as part of another request. That making the assumption of a given relation causes an additional HTTP Request , which uses bandwidth you will be paying for . Even if we added some magic to cause newProduct.Categories.Add(newCategory) ; to be Context.AddLink(newProduct,"Categories",newCategory); then the entity classes Products and Category would have to know about the DataServiceContext , newProduct.Context = myDataServiceContext; and this would just look ugly ,IMHO.Anonymous
October 15, 2008
Hola ! I am interrupting my regularly scheduled blog post to  answer some really interesting questionsAnonymous
December 04, 2008
The comment has been removedAnonymous
January 20, 2009
Last post I described one way to build a smart client in WPF against ADO.NET Data Services. In this exampleAnonymous
February 12, 2009
In my last few posts we've been building a simple Office Business Application (OBA) for the new NorthwindAnonymous
February 12, 2009
In my last few posts we've been building a simple Office Business Application (OBA) for the new NorthwindAnonymous
July 31, 2009
Going back to the original code. What if I had an individual that was created without a customer or a contact. 1 day later I want to create a customer and a contact and link them to the individual that is already in the database. Do I have to get the individual from the database before I can create the link? If I already have the "Key" for the idividual... say it is 10, can I do it without selecting the individual from the database?Anonymous
August 01, 2009
Hi Ryan, Yes , if you alread know the key property , you can link the individual without querying for the entity . Ex: Individual keyKnown = new Individual(); keyKnown.KeyProperty = KeyValue; context.AttachTo("IndividualsSet",keyKnown); and then use the keyKnown instance to setup links