When you use WCF RIA Services in your Silverlight projects, Visual Studio usually generates all kinds of classes in your server project (based on an .EDMX or .DMBL file) which are translated to Entity classes in your Silverlight client project. These serverside classes get annotated with all kinds of attributes, which RIA Services needs to function properly. If you find yourself in a somewhat more advanced scenario (no Entity Framework / LINQ To SQL for example), Visual Studio can’t generate these server side classes for you and you’ll have to create them manually. This is when it pays off to know the different attributes WCF RIA Services uses and this post will guide you through them.
The attributes
I’m not going to focus on the data validation attributes in this post as they are really self explanatory and if you have trouble with them, you can find an explanation here. The attributes which will get covered are:
- [KnownType]
- [Association]
- [Include]
- [Key]
- [ExternalReference]
- [Composition]
- [Exclude]
- [Timestamp]
- [RoundTripOriginal]
- [ConcurrencyCheck]
The Data Model
The Data model used is for a fictional garage. People can bring their cars in for maintenance or repairs and those jobs are performed by mechanics. A job can consist of multiple activities and a person can own multiple cars. This means that there are relationships (associations) between owners and cars and between jobs and activities. There is also a form of inheritance. An owner can be a car lease firm, a human owner or even a mechanic, if a mechanic brings his own car in for maintenance. A car can also be owned by the garage itself in case of a loan car. This translates to the following data model:
As you can see, the super class of all owners is the GarageEntity class. A GarageEntity can be the Garage, a LeaseFirm or a Person. A Person can be a Mechanic who has a relation with the Jobs he performed, or a NormalOwner. Each GarageEntity has a collection of Cars he / she / it owns. A Car has an Owner which is the GarageEntity, but also has a Driver, which is of course a Person. Finally, each Job consists of multiple Activity objects. Next up, the attributes!
[Key] and [KnownType]
Let’s take a look at the GarageEntity class:
1: [KnownType(typeof(Garage))]
2: [KnownType(typeof(LeaseFirm))]
3: [KnownType(typeof(Person))]
4: public partial class GarageEntity
5: {
6: #region Primitive Properties
7:
8: [Key]
9: public int GarageEntityId
10: {
11: get;
12: set;
13: }
14:
15: public string GarageEntityFirstName
16: {
17: get;
18: set;
19: }
Note that above example is not complete, I intentionally left out the rest of the properties. On line 8 you can find the [Key] attribute. In WCF RIA Services each entity must have a primary key defined and you designate a property as the primary key with the [Key] attribute. You can decorate multiple properties if the class has a composite primary key.
If you take a look at lines 1-3, you’ll notice the [KnownType] attribute. Perhaps you can imagine that a DomainService has a method “GetAllOwners()” which essentially returns all GarageEntity objects. These objects can be of different subclasses. If you want those objects to keep the correct sub classes on the client, you’ll have to let RIA Services know what the subclasses are. You might have noticed that I have added a [KnownType] for Person, but not for Mechanic and NormalOwner. That’s because the Person class defines these [KnownType] attributes:
1: [KnownType(typeof(Mechanic))]
2: [KnownType(typeof(NormalOwner))]
3: public partial class Person : GarageEntity
4: {
5: #region Primitive Properties
6:
7: public string PersonLastName
8: {
9: get;
10: set;
11: }
Note that the Person class doesn’t define a primary key, since Person inherits the primary key from GarageEntity.
The “Association” Attributes
Next up are the attributes that kind of belong together, since they all have something to do with associations or relationships. What you first need to know about associations in WCF RIA Services is that RIA Services currently only supports foreign key associations. This means that in one of the objects participating in the relationship, there must be a property which holds the primary key of the related object. Direct many to many relationships are because of this currently not supported. Let’s take a look at the Job class:
1: public partial class Job
2: {
3:
4:
5: [Key]
6: public int JobId
7: {
8: get;
9: set;
10: }
11:
12:
13:
14: public int JobKilometerDistance
15: {
16: get;
17: set;
18: }
19:
20: public string JobType
21: {
22: get;
23: set;
24: }
25:
26: [Association("FK_CA_JO", "JobId", "ActivityJobId")]
27: public ICollection<Activity> Activities
28: {
29: get
30: {
31:
32: return _activities;
33: }
34: set
35: {
36:
37: _activities = value;
38:
39: }
40: }
41: private ICollection<Activity> _activities;
42:
43: [Include]
44: [Association("FK_CAR_JOB", "JobCarId", "CarId", IsForeignKey = true)]
45: public Car Car
46: {
47: get { return _car; }
48: set
49: {
50:
51: _car = value;
52:
53: }
54: }
55: private Car _car;
56:
57:
58:
59: }
As you can see in the data model, an Activity has a foreign key to the Job it belongs to. In other words, Activity is in this case what I call the foreign key class and Job the primary key class. This means that an Activity has one Job, but a Job can have multiple Activities. Those Activities are defined on line 26 with the [Association] attribute. This attribute takes three mandatory arguments:
- First is the relationship name, for bi directional relationships (In this case: If the Activity has a Job property) this name bust be the same on both sides.
- Second is the “thisKey”. If you are in the primary key class, it’s the name (or comma separated names) of the propert(y)(ies) with the [Key] attribute.
- Third is the “otherKey”. If you are in the primary key class, it’s the name of the property in the foreign key class which holds the primary key of the related object.
For bi directional associations you’ll also need this attribute in the foreign key class, in this case Activity:
1: public partial class Activity
2: {
3:
4: [Key]
5: public int ActivityId
6: {
7: get;
8: set;
9: }
10:
11: [Association("FK_CA_JO", "ActivityJobId", "JobId",IsForeignKey=true)]
12: public Job ActivityJob { get; set; }
13:
14: public int ActivityJobId
15: {
16: get { return _activityJobId; }
17: set
18: {
19: _activityJobId = value;
20: }
21: }
22: private int _activityJobId;
23:
24: public string ActivityDescription
25: {
26: get;
27: set;
28: }
You’ll notice on line 11 that the attribute looks slightly different. In the foreign key class the “thisKey” is the property which holds the primary key of the related object and the “otherKey” is the property in the related object with the [Key] attribute. On top of that, when you are in the foreign key class you need to set “IsForeignKey” to “true”. Note that the association name must be the same on both sides of the association.
Now take a look at the Job class on lines 43 and 44. Here the association between a Job and the Car it belongs to is defined. On line 43 you can spot a new attribute, namely the [Include] attribute. If you use the [Association] attribute by itself, a couple of things will happen on the client side:
- A navigation property will be generated.
- A class for the type of the navigation property will be generated.
- When related objects are fetched from the server to the client, RIA Services will automatically fix the associations between the new objects and the previously loaded related object. You can use this for easy lazy loading.
What won’t happen, in this case, is if I fetch a Job from the server which already had its Activities loaded on the server, is that those Activity objects get serialized back to the client together with the related Job. With other words, if you only use the [Association] attribute, you can’t eagerly load related objects on the server, as they will never be sent back to the client. If you specify the [Include] attribute for an association, RIA Services will serialize the related objects to the client, but you’re still responsible for loading these related objects prior to serialization.
When you use the [Association] attribute alone, or in combination with the [Include] attribute, RIA Services generate a class for the type of the navigation property, even if there isn’t any other query method in the domain service which returns that type. This means that you can run into a conflict if you create another domain service which does have query methods with the return type of the navigation property, as sharing of entities between domain services is currently not supported (it’s coming in the new release). If you run into this, you can replace the [Include] attribute above the [Association] attribute with the [ExternalReference] attribute. This means that RIA Services will generate the navigation property, but won’t generate the type of the navigation property in the client project. Thus, you will get a compiler error in the client project because of the missing type, until you add a DomainService with a query method which returns that type in the server project. Eager loading on the server isn’t possible anymore and automatic relationship fixup is only possible if you associate the two DomainContext objects on the client with each other, using the “AddReference()” method of one of these DomainContext objects.
There is one attribute left that has to do with associations, namely the [Composition] attribute. We could change the Activities property in the Job class to this:
1: [Composition]
2: [Include]
3: [Association("FK_CA_JO", "JobId", "ActivityJobId")]
4: public ICollection<Activity> Activities
5: {
6: get
7: {
8:
9: return _activities;
10: }
11: set
12: {
13:
14: _activities = value;
15:
16: }
17: }
This will change a couple of things. A composition means a very tight coupling between a parent entity (Job) and child entities. If you don’t use the [Include] attribute, eager loading is still disabled. The [Composition] attribute means that the Update, Delete and Insert operations of the parent entity, in this case the Job, is also responsible for Updating, Deleting or Inserting the activities. Only in a composition, will WCF Ria Services send the related objects from the client back to the server when performing a data modification operation. Thus, if you know that an Activity in your client application is always created, updated or deleted together with a Job, [Composition] would have been a good choice.
I deliberately say “would have been”, because in practice I rarely use this attribute. I usually lazily load related data, most certainly when dealing, in this case with a large number of Job objects on the client. When I’m done with a Job and want to load the related Activity objects of the next Job, I’d like to detach the Activity objects of the previous Job, so that they can be garbage collected. If you use a composition, RIA Services won’t generate an EntitySet on the DomainContext for the related objects, this means that you can’t detach them without detaching the parent entity.
That are all “association” attributes, next up are the concurrency attributes.
The “Concurrency” attributes
Let’s take a look at a part of the Car class:
1: public partial class Car
2: {
3:
4: [Key]
5: public int CarId
6: {
7: get;
8: set;
9: }
10:
11: public string CarLicensePlateNumber
12: {
13: get;
14: set;
15: }
16:
17: [Timestamp]
18: public byte[] Version { get; set; }
On line 17 you’ll find the [Timestamp] attribute. Normally when sending an updated object from the client to the server, only the current values are sent to the server. When using optimistic concurrency you’ll also want the original value, which hasn’t been modified by the client. If you want original values of properties to roundtrip between the client and the server you have three attributes you can use:
- [RoundTripOriginal]: This attribute makes sure that the original value of a property get’s sent back to the server. You’ll usually use this attribute if you want that original value for another purpose than optimistic concurrency.
- [ConcurrencyCheck]: Functionally the same as [RoundTripOriginal], but this attribute also documents for other developers that you want the original value to roundtrip because you use it for a concurrency check. You’ll use this attribute when using other properties than a timestamp property for optimistic concurrency.
- [Timestamp]: You use this for a property which contains a timestamp or rowversion value, in other words, a property that solely exists for optimistic concurrency. It does the same as the first two attributes, with the added benefit that some designers / code generators won’t show this property. For example, when generating a form in Visual Studio 2010 for a RIA Service entity, it won’t generate any input fields for properties decorated with the [Timestamp] attribute as they are of no interest to the end user.
Since my entities usually have a rowversion or a timestamp property, I find myself using the [Timestamp] attribute the most.
Concluding
Well, that’s all of them…..Wait! There is one attribute left I didn’t cover. It’s the [Exclude] attribute. Don’t worry, it’s easy. It just means that when a property in a RIA entity on the server is decorated with this attribute, that property won’t get generated on the client. That’s all! Happy coding and let me know whether I missed some non-validation attributes that need an explanation.