donderdag 1 september 2011

Linking an entity between web domain services in RIA services


I’m not the biggest fan of RIA services, but I was pretty surprised the other day when I saw how easily RIA handled the sharing and linking of entities trough different domain services.
So suppose we have the following table structure in our database:


As you can see we have 2 tables: Person and Function.  Then we can generate our web domain services by adding an edmx and then adding a domain service class to the solution.



So I told you nothing new at this moment.  
Now imagine evil reincarnated into a person.  We’ll call this person the customer. One day he comes to you and says: “We’ll need to split the Person table to a centralized database for reuse”.  As soon as you realize he is serious your face will probably be going this way:

But have no fear (RIA is here) because it’s actually pretty easy to accomplish this in RIA services!

At first you’ll need to create a new edmx.  The reason why is because the edmx is basically wrapped around one connection string so for each separate connection you’ll need to create a new edmx.

So the 2 databases will look like this:



And the edmx’s will look like this:



Then build the solution.  Before you can build it you’ll have to remove the person operations from the first domain context.  The reason why you have to do this is because your first domain context inherits from LinqToEntitiesDomainService<FirstDatabaseEntities>. The underlying ObjectContext won’t have any reference any more to the Person entityset that you’ve just deleted.

If you have navigation properties defined in the metadata file then you should delete these also to be able to build.

If you are receiving build errors in your Silverlight projects (which will be very likely), then just ignore these.  It is only essential that you’re able to build the web projects.

Then add a new RIA web domain service class generated from the second database and select the tables you want.  



So now you’ll need to tell RIA services that the Function class in the first entity model is linked to the Person class in the second entity model.  To do this you’ll have to create a partial class of the Function entity.  Now add a property there that will return the Person.  The property must be decorated like so:

public partial class Function
{
    [ExternalReference]
    [Association("Person_Function", "PersonId", "ID")]
    public Person Person { get; set; }
          
}

The External reference attribute will tell the current domain service that the entity this property is returning is from an external domain context.  

The Association attribute will tell the domain service how to link the entities together.  What this will actually do is generate code on the client side that will filter the appropriate entity out.

When you’re finished doing this, build the solution to trigger the code generation client side.  Then you can open the generated file and see that it generated the following code:

public global::DomainService.Web.Person Person
{
    get
    {
        if ((this._person == null))
        {
            this._person = new EntityRef<global::DomainService.Web.Person>(this, "Person", this.FilterPerson);
        }
        return this._person.Entity;
    }
}

private bool FilterPerson(global::DomainService.Web.Person entity)
{
    return (entity.ID == this.PersonId);
}

Notice the generation of the filter depending on the parameters we gave to the Association attribute.

If you’re not interested in coupling the entities server side, then you can always manually couple the entities on the client side with a partial class that has the same code as the generated file.  Either way the client side code will be practically the same.

Now we still need to tell our context object where to find the associated Person entity.  We can do this by calling the AddReference method and supply the Person type and the corresponding context in which the Person entity resides.

_context1 = new DomainService1();
_context2 = new DomainService2();

_context1.AddReference(typeof(Person),_context2);

When this is done you just need to retrieve the data from the server and tadaaaa:

var personQuery = _context2.GetPersonQuery();
_context2.Load(personQuery, PersonLoadCompleted, true);


void PersonLoadCompleted(LoadOperation<Person> loadOperation)
{
    var nrOfEntitiesReturned = loadOperation.AllEntities.Count();
    MessageBox.Show(nrOfEntitiesReturned.ToString());

    var functionQuery = _context1.GetFunctionQuery();
    _context1.Load(functionQuery, FunctionLoadCompleted, true);
}

void FunctionLoadCompleted(LoadOperation<Function> loadOperation)
{
    var personOfFunction = loadOperation.Entities.First().Person;
    MessageBox.Show(personOfFunction.Name);
}

Some special notes to pay attention to:
  • Name the relation the same as the entity you are returning
    I find this a very strange implementation but if you name the navigation property different then for some strange reason RIA services won’t generate the relation properly
  • Make sure you don’t have any include attributes above the external reference in you metadata class
    When you do this RIA services will generate an objectset within the service context of that type.  This will generate errors when you’re trying to couple the entity type between the two service contexts.
That’s it, till next time!

Geen opmerkingen:

Een reactie posten