Working with Associations in ADO.NET Data Services

Working with Associations in ADO.NET Data Services

Rate This
  • Comments 13

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.

Leave a Comment
  • Please add 6 and 5 and type the answer here:
  • Post
  • Where did TestContext suddenly come from?

  • 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")

    );

  • 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

    -Vince

  • Hi Vince,

    Thanks for the feedback.

    For V1 , we wanted to give you POCO access to the data classes. Although we could infer the relation based on the type of the navigation property , this would break scenarios where we have more than 1 navigation properties of the same type and belonging to the same entity Set.

    Consider this entity ,

    public class Customer{

    public List<Orders> CashPurchases{get;set;}

    public List<Orders> CreditCardPurchases{get;set;}

    }

    if you do an AddLink with just an instance of Orders, we would have to guess which navigation property we have to add the link to .

    Customer newCustomer = new Customer();

    Orders cashPurchase = new Orders();

    Orders creditCardPurchase = new Orders();

    context.AddLink( newCustomer , "??", cashPurchase);

    //In the above case , trying to guess the name of the association will land us in hot water as we dont know which navigation property you intended .

    we cant go the other way around and try to use the instance of Orders to guess the name of the association

    as we Orders may have other associations of type Customers and we dont know which one you intended.

    If you do not wish to use the string to remember the association name , you can move that to a separate

    resource file and have all your strings localized in one place.

    Consider this ..

    public class StoreConstants{

    public static string Customers_To_CashPurchases  ="CashPurchases";

    public static string Customers_To_CreditCardPurchases  ="CreditCardPurchases";

    }

    and then in your code ,

    Customer newCustomer = new Customer();

    Orders cashPurchase = new Orders();

    Orders creditCardPurchase = new Orders();

    context.AddLink( newCustomer , StoreConstants.Customers_To_CashPurchases , cashPurchase);

    context.AddLink( newCustomer , StoreConstants.Customers_To_CreditCardPurchases , creditCardPurchase );

    Hope this helps

  • Hi Phani,

    Nice clear exposition of the topic.

    However, I would think that, given that we are working with a client-side proxy, the separation of object tracking and association tracking is a bit too abstract for the most common scenarios where this client class may be used, ie with out-of-the-box databinding to UI components etc.

    I understand the nice separation of concerns etc, but surely if the Customer or Contact properties are set in code, the user intends them to be set.

    I guess it is about user intentions and usability. Could you provide an overload such as

    Context.AddObject( String property,Object value, Boolean shouldTrackAssociations)

    Just a thought/suggestion.

    Cheers

    Simon

  • 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.

  • Hola ! I am interrupting my regularly scheduled blog post to&#160; answer some really interesting questions

  • Thank you soooo much! You are a god! :) Ive spent ages trying to figure out the FK constraint errors I was getting trying to add a child object to a parent but there was no good documentation for this, even the MS example did not mention this!

    Thanks again!

  • Last post I described one way to build a smart client in WPF against ADO.NET Data Services. In this example

  • In my last few posts we've been building a simple Office Business Application (OBA) for the new Northwind

  • In my last few posts we&#39;ve been building a simple Office Business Application (OBA) for the new Northwind

  • 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?

  • 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

Page 1 of 1 (13 items)