Nieuws
Foto's
Artikelen
Componenten
Applicaties
Kleinkunst

.NET - Eerst kijk op WCF RIA Services

Introduction

A few months ago Microsoft released the first preview of .NET RIA Services. This new framework promises to simplify building n-tier Line of Business (LoB) applications by providing patterns, components and tools to build services, query data, handle CRUD operations, resolve concurrency, manage data validation, … The March 2009 and the new May 2009 CTP's only support Silverlight 3 but the final version will also offer support for ASP.NET/MVC/AJAX and WPF.

I have been exploring RIA services for a few weeks now and I must admit that I’m really excited. The architectural design looks quite good, these first CTP's work fine and it really boosts productivity. The RIA Services framework handles all the plumbing to move entities from the server to the client, it offers advanced change-tracking and caching, several techniques for data validation are provided, there is support for authentication and roles, the integration with Silverlight UI controls like DataForm and DataPager works great and the current support for the ADO.NET Entity Framework really simplifies certain tasks.

If you want to start exploring RIA Services then you should definitely start reading the PDF document that Microsoft has published. This document gives a very good overview of all features of RIA Services.

While using RIA Services I found several great features that I wanted to blog about. I will not explain the details about its design, but I will try to give a lot of code samples, tips and ideas. I also stumbled upon some limitations and shortcomings of the current preview. So in this first article I will focus on the current limitations and I will offer you some of my workarounds and tips.


Multiple server side projects

To summarize the key concepts of RIA services very shortly:

  • On server side you will have to create entities (Entity Framework, LINQ to SQL, POCO, …) and validation logic and decorate these classes with some metadata attributes.
  • Furthermore you have to create DomainService classes which expose operations. You will need to follow some name conventions or add metadata attributes.
  • When compiling the project, the framework will automatically generate corresponding proxy classes and copy the shared sources into the linked Silverlight client project. On client side DomainContext classes will be generated and they will enable you to invoke operations exposed by the domain services.

This process works very good and it surely increases productivity. But the current preview has a few problems when you separate entities, validation, shared source and services into different projects/libraries. First of all, if you want to use an LINQ to SQL DataContext or an Entity Framework ObjectContext, first build all your projects, otherwise the DomainService wizard will not be able to found your domain models.

The code generation will not generate shared objects if they are not included in the ASP.NET web application. And if all the partial classes are not included in the same namespace or assembly you will get "Cannot implicitly convert type ObjectQuery to IQueryable" or "There is no implicit reference conversion from YourDomainEntity to 'System.Data.Objects.DataClasses.IEntityWithRelationships" errors.

Therefore it is better to put everything in one ASP.NET web application project. The best approach I found is to create folders and to use different namespaces for all domain entities related stuff and for the domain services. When a future RIA services version will provide better support for multiple projects, you will be able to simply move your sources to new projects.

 

Multiple client side projects

In a Silverlight application/library you have to specify an ASP.NET server project link. The result is that all proxy classes will be generated in each Silverlight application/ library project. So when you are using several client projects, with or without remote downloading or Prism modules, all proxy classes will be duplicated in each client project. Everything will work fine but of course the file size of the XAP files will increase.

I’m sure that this is one of the things which will be improved in future beta versions. Hopefully we will be able not only to specify a ASP.NET server project but also one or more server side assemblies which contain the corresponding domain entities and domain services.

 

ReSharper and Intellisense

When compiling, all proxy classes will be generated and stored in the client project(s) in a subfolder called Generated_Code. These generated files will not be included in the project.

In spite of the fact that the files are not included in the project, you will still be able to use Intellisense once you add a using statement with the namespace of the domain contexts.

Unfortunately if you are using ReShaper this will not work. ReShaper will highlight all proxy classes because they are unknown and Intellisense will not work. The JETBrains team is working on this and it will surely be solved in the future version for Visual Studio 2010.

For now you should include the generated files into your project. So make all files visible and use the Include in Project option. Be aware that you sometimes have to build your project twice to use the latest generated sources. Also be careful with source control! These generated files should not be in source control.

 

Metadata files

When the code-generator of RIA Services creates the client proxy classes for the entities, it will automatically merge validation attributes that are present in the associated metadata class. When you use the Domain Service Wizard, you will have an option called "Generate associated classes for metadata". This will generate 2 new files: one generated DomainService and an accompanying DomainService metadata class.

This metadata file will contain a partial class with metadata for a domain object/entity but the filename will become MyDomainService.metadata.cs. In my opinion this is wrong and it should be MyDomainEntity.metadata.cs. The metadata is related to the domain object/entity and it is a partial class so it definitely should have the same namespace as the domain entity.

 

Metadata attributes

The May CTP provides new features concerning the metadata. LinqToEntities and LinqToSql domain services will automatically infer and propagate DAL level validation like StringLength, Required, ConcurrencyCheck and TimeStamp. So automatically not-null fields will become required in the entities on client side. And all string properties will have a StringLength attribute.

On an hand this is great. On the other hand this raises a problem because this behavior cannot be overridden. In some cases you only want to fill not-null fields on server side but this can't be done anymore because the validation on client side will fail. I hope that a future version will have a feature to enable/disable this automatic propagating and that overriding attributes will become possible.

 

Operation parameters

At this moment operation/query parameters can only be primitive types (string, int, bool, …) and some complex types like (IEnumerable, array, …). Entity types which are exposed by domain operations can be used, but other arbitrary types are not supported. So e.g. GetPersons(Criteria criteria) is not allowed. This is a important limitation but I assume that a future version will be able to generate the corresponding proxy classes if the classes are serializable.  For now, you should use primitive type arguments.

 

Validation exceptions

When client side validation fails Visual Studio will break its execution. So every time you are entering invalid data on the client side, Visual Studio will be focused. This is not handy and in my opinion not needed. To disable this behavior you have to add the System.ComponentModel.DataAnnotations.ValidationException to the list of Exceptions and uncheck the User-unhandled checkbox.

 

Loading one entity

Even if you only want to retrieve one entity and update it, the best approach is to return a collection with one entity. When using LINQ you can accomplish this by adding a where clause with the key property.

public IEnumerable<Employee> GetEmployees(int employeeId)
{
    return Context.Employees.Where(e => e.EmployeeID == employeeId);
}

Or use the yield statement after calling the LINQ First() or Single() method or when you want to return a single POCO object.

public IEnumerable<Employee> GetEmployees(int employeeId)
{
    yield return Context.Employees.FirstOrDefault(e => e.EmployeeID == employeeId);
}

 

Server side problems

When errors occur on server side, Visual Studio will not always break its execution. Therefore it is important to check the returned arguments in the Loaded, Submitted and Completed events on client side. The Loaded event will return an exception if errors occur on server side when executing a query.

private void ...Loaded(object sender, LoadedDataEventArgs e)
{
    if (e.Error == null)
    {
    }
}

After calling SubmitChanges(), the Submitted event will return a SubmittedChangesEventArgs object which contains all information about validation errors, concurrency problems, …

private void ...Submitted(object sender, SubmittedChangesEventArgs e)
{
    if (e.Error != null)
    {
        if (e.EntitiesInError != null && e.EntitiesInError.Count() >0)
        {
        }
    }
}

And after calling a service operation you should check the Error property of the InvokeEventArgs object.

private void ...Completed(object sender, InvokeEventArgs e)
{
    if (e.Error == null)
    {
    }
}


Client side problems

I stumbled upon some minor issues on the client side which can easily be solved once you know where to look.

If you get a "System.InvalidOperationException: Editing items is not supported by the IEditableCollection" error, then you are trying to start editing an entity while you forgot to declare an Update method in your DomainService.

If paging doesn’t seem to work, you have to check if the collection is sorted. The Silverlight DataPager control can’t work with unsorted collections because the Skip() and Take() extension method expects an OrderBy clause. So just add an OrderBy clause in your query at server side or define a SortDescriptor in the DomainDataSource on client side in XAML or in the code behind file.

public IQueryable<Employee> GetEmployees()
{
    return Context.Employees.OrderBy(e => e.FirstName);
}
<ria:DomainDataSource
  LoadMethodName="LoadEmployees"
  AutoLoad="True"
  <ria:DomainDataSource.SortDescriptors>
    <Data:SortDescriptor PropertyPath="FirstName" Direction="Ascending" />
  </ria:DomainDataSource.SortDescriptors>
EmployeesDomainDataSource.SortDescriptors.Add(new SortDescriptor("FirstName", SortDirection.Ascending));

 

Associations in Entity Framework entities

The first few times I tried to use associations in my EF entities together with RIA Services it was quite confusing. It was difficult to find out what were features and what were limitations. So finally I created a small sample application to test many-to-one and many-to-many associations and self referencing tables.

Many-to-one

I created a table Person with a many-to-one relation to Place. Once the Entity Framework entities are generated my Person entity will have a navigation property Place. It also provides an EntityReference property (called PlaceReference) which has a EntityKey property that holds the foreign key value.

By default RIA Services will not generate a property Place in the client side entity. It will only generate a property PlaceId which holds the FK value. The coming version of the Entity Framework (EF4) will also provide an option to include "FK properties" and RIA Services already does the same on client side.

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ScipBe.Demo.RiaEf.Domain")]
public sealed partial class Person : Entity
{
    private Nullable<int> _placeId;

    [DataMember()]
    public Nullable<int> PlaceId
    {
        get
        {
            return this._placeId;
        }
        set
        {
            if ((this._placeId != value))
            {
                this.ValidateProperty("PlaceId", value);
                this.RaiseDataMemberChanging("PlaceId");
                this._placeId = value;
                this.RaiseDataMemberChanged("PlaceId");
            }
        }
    }

If you want to include the Place object property then you have to create a metadata class and add the Include and Association attributes and specify the foreign key property and the primary key property in the destination entity.

[MetadataType(typeof(Person.PersonMetadata))]
public partial class Person
{
    internal sealed class PersonMetadata
    {
        private PersonMetadata()
        {
        }
 
        [Include]
        [Association("PersonPlace", "PlaceId", "Id")]
        public Place Place;
    }
}
Now RIA Services will also generate a Place object property:
[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ScipBe.Demo.RiaEf.Domain")]
public sealed partial class Person : Entity
{
    private EntityRef<Place> _place;
 
    [Association("PersonPlace", "PlaceId", "Id")]
    public Place Place
    {
        get
        {
            if ((this._place == null))
            {
                this._place = new EntityRef<Place>(this, "Place", this.FilterPlace);
            }
            return this._place.Entity;
        }
        set
        {
            Place previous = this.Place;
            if ((previous != value))
            {
                this.ValidateProperty("Place", value);
                this._place.Entity = value;
                if ((value != null))
                {
                }
            }
        }
    }

Of course do not forget to call the Include() method of the ObjectContext if you want to load the associated data. If you want to avoid to use hardcoded strings in the Include version, take a look at the great lambda-enabled Include version of Rudi Breedenraedt.

public IEnumerable<PersonManyToOne> GetPersons()
{
    //return Context.Persons.Include(p => p.Place);
    return Context.Persons.Include("Place");
}

If you call the SubmitChanges method of the domain context to insert or update data, the client side entities will be converted back to server side entities. In the Update or Insert method you will notice that the Place property is always null. When sending data to the server only the EntityKey of the EntityReference will be filled with the value of the FK property. So changing the Place property on client side won’t have any effect. You really need to modify the FK PlaceId property.

public void UpdatePerson(PersonManyToOne currentPerson, PersonManyToOne originalPerson)
{
    // currentPerson.Place is always null
    var newPlaceId = currentPerson.PlaceReference.EntityKey.EntityKeyValues
       .First(k => k.Key == "Id").Value;

 

Many-to-many

Adding many-to-many associations in a POCO class can also be done by adding the Include and Association attributes.

[Include]
[Association("OrderLines", "Id", "OrderId")]
public IEnumerable<OrderLine> OrderLines { get; set; }
 
[Include]
[Association("OrderLines", "Id", "OrderId")]
public EntityCollection<OrderLine> OrderLines { get; set; }

However this approach will not work with Entity Framework entities. In the database you will have 3 tables with 2 many-to-one relations. In the OO world, many-to-many relationships can be mapped easily between objects using navigation relations. So the Entity Framework will drop the junction/link table and only create two entities which will have many-to-many navigation properties to each other. Therefore the child entity does not have a foreign key property which contains the ID of the parent and this is a required argument in the metadata Association attribute. So for now, many-to-many associations with EF entities are not supported.

I tried a lot of things and finally I ended up with 2 possible solutions.

DTO

The first solution is to create 2 new DTO’s (data transfer objects). PhoneDto will have a PersonId and PersonDto will have a collection of PhoneDtos. All other properties will be just the same.

public class PersonDto
{
    [Key]
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [Include]
    [Association("Phones", "Id", "PersonId")]
    public IEnumerable<PhoneDto> Phones { get; set; }
}
public class PhoneDto
{
    [Key]
    public int PersonId { get; set; }
    [Key]
    public int PhoneId { get; set; }
    public string Number { get; set; }
}

When you want to fill these DTO’s you will have to run through the data and convert it.

[EnableClientAccess()]
public class PersonDomainService : LinqToEntitiesDomainService<RiaDbEntities>
{
    public IEnumerable<PersonDto> GetPersons()
    {
        var persons = Context.Persons.Include("Phones").OrderBy(p => p.Id);
 
        var result = new Collection<PersonDto>();
 
        foreach (var person in persons)
        {
            var phones = new Collection<PhoneDto>();
 
            foreach (var phone in person.Phones)
            {
                phones.Add(new PhoneDto()
                           {
                               PersonId = person.Id,
                               PhoneId = phone.Id,
                               Number = phone.Number
                           });
            }
 
            result.Add(new PersonDto()
                       {
                           Id = person.Id,
                           FirstName = person.FirstName,
                           LastName = person.LastName,
                           Phones = phones
                       });
        }
 
        return result;
    }

If you are using a lot of DTO’s, you should definitely take a look at AutoMapper. This is a great open-source convention-based object-object mapper. Converting Person to PersonDto and Phone to PhoneDto can be done with just 3 simple lines of code. Unfortunately I couldn’t find a way to fill the PersonId property in the PhoneDto so that is why two iterations are required after the mapping to fill the PersonId property.

[EnableClientAccess()]
public class PersonDomainService : LinqToEntitiesDomainService<RiaDbEntities>
{
    public IEnumerable<PersonDto> GetPersons()
    {
        var persons = Context.Persons.Include("Phones").OrderBy(p => p.Id);
 
        Mapper.CreateMap<Phone, PhoneDto>().ForMember(dest => dest.PhoneId, 
            opt => opt.MapFrom(p => p.Id));
        Mapper.CreateMap<PersonManyToMany, PersonDto>();
        var result = Mapper.Map<IEnumerable<PersonManyToMany>, IEnumerable<PersonDto>>(persons);
 
        foreach (var person in result)
        {
            foreach (var phone in person.Phones)
            {
                phone.PersonId = person.Id;
            }
        }
 
        return result;
    }


Junction/link entity

Another conceivable solution for many-to-many associations is to force the Entity Framework to create the junction/link entity. This can be done by adding a dummy field to this table. Now the Entity Framework will generate 3 entities with many-to-one navigation properties.

Now you have to add the Include and Association attribute to the metadata of both entities.

[MetadataType(typeof(Person.PersoMetadata))]
public partial class Person
{
    internal sealed class PersonMetadata
    {
        private PersonMetadata()
        {
        }
 
        [Include]
        [Association("PersonPhone", "Id", "PersonId")]
        public IEnumerable<PersonPhone> PersonPhones;
    }
}
[MetadataType(typeof(PersonPhone.PersonPhoneMetadata))]
public partial class PersonPhone
{
    internal sealed class PersonPhoneMetadata
    {
        private PersonPhoneMetadata()
        {
        }
 
        [Include]
        [Association("PersonPhonePhone", "PhoneId", "Id")]
        public Phone Phone;
    }
}

The query in the domain service will look like this:

[EnableClientAccess()]
public class PersonMomDomainService : LinqToEntitiesDomainService<RiaDbEntities>
{
    public IEnumerable<PersonTwoManyToOne> GetPersons()
    {
        return Context.PersonTwoManyToOne
            .Include("PersonPhones")
            .Include("PersonPhones.Phone")
            .OrderBy(p => p.Id);
    }
}

I haven’t tried to use CRUD operations together with many-to-many associations yet. I assume that you will need to create the corresponding Insert, Update, Delete methods for each entity type. Maybe I will try to cover this in a following article. As you can see, there are some workarounds but I really hope that the RIA Services team will come up with a feasible solution to many-to-many associations.


Self referencing entities

Building hierarchies with self referencing tables is quite easy with RIA Services. Because the client side proxy entities have FK properties, you can easily call my AsHierarchy() extension method to build a hierarchy. No metadata association attributes are needed because you only need to return a flat list to the client.

void PersonContextLoaded(object sender, LoadedDataEventArgs e)
{
    var hierarchy = context.Persons.AsHierarchy(p => p.Id, p => p.ParentId);
}

 

May 2009 CTP changes

At this moment there is no official list of bugfixes, changes or improvements for the May 2009 Preview. The announcement only covers the main changes. Some other issues I noticed are:

  • The guid conversion bug (The type of the key field 'Id' is expected to be 'System.Guid', but the value provided is actually of type 'System.String') has been solved. But there is still a problem with guid types and the DataFormComboBox.
  • A lot of code from LinqToEntitiesDomainServices has been moved to the generic version. Consequently some types of properties and methods have been changed to the generic type. (e.g. CreateContext, Context, ...)


I hope that this article has clarified some issues and that it provides some workarounds and tips for some current limitations. Despite these limitations I really like RIA Services and the final version will surely help us developers to build great n-tier Line of Business (LoB) applications with Silverlight.