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.