The Elephant and the Silverlight

Thoughts and analysis of Silverlight for business applications

Page List

    Meet the EntityRef and the AssociationAttribute

    Today I am going to cover the EntityRef and the AssociationAttribute, but first some cleanup on the EntityCollection. In the Advanced EntityCollection post I had part of the constructor explained incorrectly, it has been corrected as well as some cleanup on the code to make it more understandable.

    The EntityRef

    The EntityRef is the one-to-one relationship version of the EntityCollection, which means it is probably even less well known. Like the EntityCollection, the EntityRef has two different jobs depending on if the Entity is attached to an EntityList or not. However, the EntityRef is not directly exposed by the Entity, instead it is used to back a property with the type of the associated Entity. Here is the Book Entity’s Library property, the other side of the association covered in the EntityCollection posts.

    [Association("Library_Book", "LibraryId", "LibraryId", IsForeignKey=true)] 
    [XmlIgnore()] 
    public Library Library 
    { 
        get 
        { 
            if ((this._library == null)) 
            { 
                this._library = new EntityRef<Library>(this, "Library", this.FilterLibrary); 
            } 
            return this._library.Entity; 
        } 
        set 
        { 
            Library previous = this.Library; 
            if ((previous != value)) 
            { 
                this.ValidateProperty("Library", value); 
                if ((previous != null)) 
                { 
                    this._library.Entity = null; 
                    previous.Book.Remove(this); 
                } 
                this._library.Entity = value; 
                if ((value != null)) 
                { 
                    value.Book.Add(this); 
                    this.LibraryId = value.LibraryId; 
                } 
                else 
                { 
                    this.LibraryId = default(Nullable<Guid>); 
                } 
                this.RaisePropertyChanged("Library"); 
            } 
        } 
    }
    
    private bool FilterLibrary(Library entity) 
    { 
        return (entity.LibraryId == this.LibraryId); 
    }

    One important thing to note if you want to create your own properties backed by an EntityRef which are not read only, the this.ValidateProperty(“Library”, value) line is actually causing the getter to be called making sure that the _library field is getting populated. If you remove that line without replacing it you may get null reference exceptions.

     

    Meet the AssociationAttribute

    public AssociationAttribute( 
        string name, 
        string thisKey, 
        string otherKey 
    ) 

    Parameters
    name: Name of the association
    thisKey Comma separated list of the property names of the key values on this side of the association
    otherKey comma separated list of the property names of the key values on the other side of the association

    So, what does that pesky AssociationAttribute actually do? Both the EntityCollection and EntityRef require it but it is not obvious why that is. Also, what is the benefit of using EntityCollection and EntityRef over using a real CollectionView and a regular property?

    Both the EntityCollection and EntityRef monitor an EntityList for any changes to make sure that the associations are always kept up to date. However, if the various Filter method were called every time anything changed on the entities the performance issues would quickly get out of control. For example, lets say we have 10 libraries and we are entering 20 books with each book have 15 fields that we are changing. By the time we were done we would have called the FilterBook method 3,000 times (10 libraries * 20 books * 15 fields. This is not very efficient.

    To solve this problem, the EntityCollection and EntityRef only listen for changes made to those fields listed in the AssociationAttribute. This reduces us to 200 calls to FilterBook (10 libraries * 20 books * 1 field). By using the EntityRef and EntityCollection in our own code we are able to leverage this huge performance advantage ourselves. RIA Services probably has other uses for AssociationAttribute, but this is a really important one.


    Permalink | Comments (8) | Post RSSRSS comment feed

    Comments

    Elfdragore United States

    Thursday, September 10, 2009 10:18 AM

    Elfdragore

    Could you please elaborate on how to create an property that is backed by an EntityRef that is not read only?

    I'm using ria services July preview and it seems that the defaults behavior is to create them as read only. This is what I'm doing:

    namespace Testing {

       public partial class Location {
          [Key]
          public int Guid { get;set;}
          public string Name {get;set;}
       }

       public partial class Person {
          [Key]
          public int Guid { get;set;}
          public string Name {get;set;}
          [External, XmlIgnore, Association("PersonLocation","Guid","Guid", IsForeignKey=true)]
          public Location {get;set;}
       }
    }

    And the generated code (segment) is:

            private EntityRef<Testing.Location> _location;

            [Association("PersonLocation", "Guid", "Guid", IsForeignKey=true)]
            [External()]
            [XmlIgnore()]
            public Testing.Location Location
            {
                get
                {
                    if ((this._location == null))
                    {
                        this._location = new EntityRef<Testing.location>(this, "Location", this.FilterLocation);
                    }
                    return this._location.Entity;
                }
            }

    ColinBlair United States

    Thursday, September 10, 2009 11:00 AM

    ColinBlair

    @Elfdragore - Readonly isn't the default, but in this case you have made it impossible for the EntityRef to be editable.
    Remember, operations against the association itself can only break the association, they can't actually delete an object, at least not in the current version.
    Since in this case, you are using the same key in both tables so breaking the association would actually break the key so the codegen decided not to implement
    a setter. BTW, how in the heck did you end up with your keys being named Guid?

    Elfdragore United States

    Thursday, September 10, 2009 11:42 AM

    Elfdragore

    Colin,

    Thank you for your reply (and it was fast!!!)

    Since I'm using POCOs in my testing I think Guid is a nice name for my keys, unless there is something I am missing something about using that name. Anyway, I changed that.

    Unfortunately I do not understand your explanation.
    Why I can not do, on the client side, do Person.Location = ((Location) LocationDataGrid.SelectedItem) ?
    Or how can I change one Person from Location {Guid=1, Name="Seattle"} to Location {Guid=2, Name="NYC"} ?

    Same keys... I changed my keys as shown below, but without having any reflexes (no setter) in the generated code Frown, therefore I am totally missing the point here...

       public partial class Location {
          [Key]
          public int LocationSysId { get;set;}
          public string Name {get;set;}
       }

       public partial class Person {
          [Key]
          public int PersonSysId { get;set;}
          public string Name {get;set;}
          [External, XmlIgnore, Association("PersonLocation","PersonSysId","LocationSysID", IsForeignKey=true)]
          public Location {get;set;}
       }

    ColinBlair United States

    Friday, September 11, 2009 11:21 AM

    ColinBlair

    First, Guid is a type (System.Guid) so having an integer named Guid is like having decimal named String. Second, GUID is an acronym that stands for Globally Unique IDentifier and is usually implemented as a 128-bit number. No 32-bit integer is going to be a globally unique identifier.

    I am not sure what is going on with your code, you have the interaction with External there as well and I try to avoid External myself. Do you have CRUD methods for both Location and Person in your DomainServices?

    Elfdragore United States

    Wednesday, September 16, 2009 9:49 AM

    Elfdragore

    Hi Colin,

    Now I see why you were so mystified by my naming convention Smile

    Well, after so many hours of trying different approaches I was able to pin the problem, and I have to say, I think this is something that must be changed in the future of the RIA Services...

    Here is what was happening:

    I had two POCO classes, Location and Person, and two services classes LocationServices and PersonServices, in order to have the setter in the association of Person.Location property those two entites must be exposed in the same service class, that is, I had to create another service class (lets say GpsService) and move all my code from PersonService and LocationService to GpsService.

    I would love to hear the reasons behind this design choice in RIA Services.

    Best regards

    ColinBlair United States

    Wednesday, September 16, 2009 10:14 AM

    ColinBlair

    Generally speaking, the DomainService has a 1:1 relationship with a Model, not an entity. If you have entities that need to know about each other then they should be in the same DomainContext. Sometimes you get stuck in a design problem and the External option can help you get out of it but it should be the exception, not the rule. One simple example of the problem with External, transaction handling is really hard with two seperate DomainServices and really easy with a single DomainService.

    FRomano Portugal

    Thursday, September 24, 2009 12:23 PM

    FRomano

    I am having exactly the same issue as Elfdragore. I would like to have a Service for entity A and B, where entity B is referenced by entity A.
    Is there any other way to work around this issue?

    ColinBlair United States

    Friday, September 25, 2009 10:54 AM

    ColinBlair

    @FRomano I am not sure what other solutions you want. You can put both A and B in the same DomainService or you can have two seperate DomainServices and use the External to create a reference from A to B. What third solution would you like to see?

    Add comment


    (Will show your Gravatar icon)

      Country flag

    Click to change captcha
    biuquote
    • Comment
    • Preview
    Loading