Linq to SQL doing it manually: Part 3 - Relationships

Beside column mapping we did in Part 1 of the "Linq to SQL doing it manually" series. We also want to have relationships, this part will be all about relationships. The series contains the following parts:

When we take a look at the generated code for a one-to-many relation it has the following looks.

For the one-side:

1 private EntityRef<Post> _Post; 2 [Association(Name="Post_PostTagRelation", Storage="_Post", ThisKey="PostId", OtherKey="Id", IsForeignKey=true)] 3 public Post Post 4 { 5 get 6 { 7 return this._Post.Entity; 8 } 9 set 10 { 11 Post previousValue = this._Post.Entity; 12 if (((previousValue != value) 13 || (this._Post.HasLoadedOrAssignedValue == false))) 14 { 15 this.SendPropertyChanging(); 16 if ((previousValue != null)) 17 { 18 this._Post.Entity = null; 19 previousValue.PostTagRelations.Remove(this); 20 } 21 this._Post.Entity = value; 22 if ((value != null)) 23 { 24 value.PostTagRelations.Add(this); 25 this._PostId = value.Id; 26 } 27 else 28 { 29 this._PostId = default(System.Guid); 30 } 31 this.SendPropertyChanged("Post"); 32 } 33 } 34 } 35 36 private System.Guid _PostId; 37 [Column(Storage="_PostId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)] 38 public System.Guid PostId 39 { 40 get 41 { 42 return this._PostId; 43 } 44 set 45 { 46 if ((this._PostId != value)) 47 { 48 if (this._Post.HasLoadedOrAssignedValue) 49 { 50 throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException(); 51 } 52 this.OnPostIdChanging(value); 53 this.SendPropertyChanging(); 54 this._PostId = value; 55 this.SendPropertyChanged("PostId"); 56 this.OnPostIdChanged(); 57 } 58 } 59 }

For the many-side:

1 private EntitySet<PostTagRelation> _PostTagRelations; 2 [Association(Name="Post_PostTagRelation", Storage="_PostTagRelations", ThisKey="Id", OtherKey="PostId")] 3 public EntitySet<PostTagRelation> PostTagRelations 4 { 5 get 6 { 7 return this._PostTagRelations; 8 } 9 set 10 { 11 this._PostTagRelations.Assign(value); 12 } 13 }

I think this is a lot of code. So let's try to minimize this.

For the one-side:

1 [Column(IsPrimaryKey = true)] 2 private Guid PostId { get; set; } 3 4 [Association(IsForeignKey = true, ThisKey = "PostId")] 5 public Post Post { get; set; }

For the many-side:

1 private IList<PostTagRelation> postTagRelations = new List<PostTagRelation>(); 2 [Association(Storage = "postTagRelations", OtherKey = "PostId")] 3 public IList<PostTagRelation> PostTagRelations 4 { 5 get { return postTagRelations; } 6 set { postTagRelations = value; } 7 }

A lot less code I would guess. From 72 lines to just 12 lines. But we are not there yet. After some testing I found out the generated relation is bi-directional, and the relation I created is just uni-directional. What does this mean? This means that if you manipulated either post.PostTagRelations or postTagRelation.Post the other will be changed as well in a bi-directional relation. I remember in the past you had to do something special to make this work with NHibernate. I have no solution for this right now, but you can take a look at how other people did something similar on CodePlex. One of the troubles I find in the solution they propose is the usage of EntityRef and EntitySet. Those are both parts of Linq to SQL and would kind of interfere with my other code-parts, if used.

Another feature that is lost is lazy loading. I must admit I don't hate it being lost. I think people must think about persistence before doing everything automatically. So if we want to load only the tags we don't have to anything special. But if we also want to load the PostTagRelations and the Posts with the tags we will have to add some DataLoadOptions. The following code example show how it works.

1 using (var context = new Context("")) 2 { 3 var dataLoadOptions = new DataLoadOptions(); 4 dataLoadOptions.LoadWith<Tag>(t => t.PostTagRelations); 5 dataLoadOptions.LoadWith<PostTagRelation>(ptr => ptr.Post); 6 //dataLoadOptions.LoadWith<PostTagRelations>(ptr => ptr.Tag); 7 context.LoadOptions = dataLoadOptions; 8 9 var dotNetTag = (from tag in context.Tags 10 where tag.Name.Equals(".NET") 11 select tag).Single(); 12 13 Assert.Equal(3, dotNetTag.PostTagRelations.Count()); 14 15 var posts = from postTagRelation in dotNetTag.PostTagRelations 16 where postTagRelation.Post != null 17 select postTagRelation.Post; 18 Assert.Equal(3, posts.Count()); 19 20 var tags = from postTagRelation in dotNetTag.PostTagRelations 21 where postTagRelation.Tag != null 22 select postTagRelation.Tag; 23 Assert.Equal(3, tags.Count()); 24 }

The only thing I must admit, line 23 fails. The tags aren't loaded in the PostTagRelations. I tried adding line 6 but this did throw an exception: "System.InvalidOperationException : Cycles not allowed in LoadOptions LoadWith type graph." I have no solution for this yet, but it has something to do with the bi-directional issue I also found.

There are some areas of improvement on the relationships with Linq to SQL.

  • Gravatar roman January 22nd, 2009 at 16:57
    Hi Mark

    i'm just getting in contact with LINQ and stumbled upon your awesome blog.

    i found the following construct in all the examples:

    11 Post previousValue = this._Post.Entity;

    15 this.SendPropertyChanging();
    16 if ((previousValue != null))
    17 {
    18 this._Post.Entity = null;
    19 previousValue.PostTagRelations.Remove(this);
    20 }
    21 this._Post.Entity = value;

    what is the idea behind that you assign null to this._Post.Entity in line 18 and then assign the value to the entity in line 21?


    cheers
    Roman
  • Gravatar Mark Monster January 23rd, 2009 at 08:37
    Hi Roman,

    It's important to know that this was part of the generated code, like I mention in the article. You do not necessary need this implementation.

    But let me explain what's happening.

    Basically it's first removing the old reference that's not longer valid. And after that the new reference is created, this is line 21 to 31. Removing the reference is done by removing both ends. The current Post relation (one end) and also remove "this" from the PostTagRelation (the other end).

    -
    Mark Monster
Gravatar