2

I seem to remember (perhaps incorrectly) that DLinq offered automatic associations. I can't seem to find how to enable or accomplish this in EF.

Example:

public partial class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Clan Clan { get; set; }
}

public partial class Clan
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Person> People { get; set; }
}

If these were my entities, then I would like to perform an action like this:

var p = new Person() { Name = "Tom" };
var c = new Clan() { Name = "SomeClan" };
p.Clan = c;
ASSERT( c.People.First() == p )

or alternately

var p = new Person() { Name = "Tom" };
var c = new Clan() { Name = "SomeClan" };
c.People.Add(p);
ASSERT( p.Clan == c )

Further, should I have used the c.People.Add method, it should have checked to see if p.Clan already references a different Clan, and if so, removes it from that clan's People collection.

In DLINQ, I believed they used EntitySet and EntityRef to accomplish this. Does an equivalent exist in Entity Framework?

Many thanks!

Veldaeven
  • 319
  • 1
  • 13

2 Answers2

4

I tried your code in LINQ-toSQL (previously called DLINQ), but the assert will never pass. The obvious cause is that the context doesn't even know about the new objects, so it can't possibly associate them. However, even if you insert the objects into the context c.People will not be populated

var p = new Person() { Name = "Tom" };
var c = new Clan() { Name = "SomeClan" };
p.Clan = c;
context.People.InsertOnSubmit(p);
context.Clans.InsertOnSubmit(c);
ASSERT( c.People.First() == p ) // Still no items in c.People

And even after SubmitChanges, c.People has no items. Only when you re-fetch the new Clan from the database in a new context c.People will be loaded by lazy loading. So these "automatic associations" don't exist in LINQ-to-SQL.

In Entity Framework there is a process called relationship fixup that runs very frequently. In this process EF scans through primary and foreign key values and populates navigation properties (like c.People) when they match.

If you do the same thing in EF:

var p = new Person() { Name = "Tom" };
var c = new Clan() { Name = "SomeClan" };
p.Clan = c;
context.People.Add(p); // Also adds c to the context

...the Add method will trigger DetectChanges, which in turn triggers relationship fixup, and

ASSERT( c.People.First() == p )

...will now pass.

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • This is what I was afraid of, but its good to know that the Fixup occurs on the Add. Regarding DLINQ- I completely agree that using the "POCO" class would not have fixed-up anything. However, if you used the class generated by the model diagram, it would have used "EntitySet" and "EntityRef" instead of ICollection and Clan. So, at the expense of not having a POCO class anymore, I think that those two "proxy" classes were where the fixup occurred. But, regardless, your answer hits the nail on the head for what I need to do. THANK YOU! – Veldaeven Jan 29 '15 at 22:24
0

There would be many ways of doing this. The easiest would be to Add a ClanId property to your Person class and then use this:

public partial class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ClanId {get; set;}    // You might need to make it virtual. Not sure.
    public virtual Clan Clan { get; set; }
}

public partial class Clan
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Person> People { get; set; }
}

This would instruct EF to create a Foreign Key by the name of ClanId. Then you would be able to use the following syntax:

var p = new Person() { Name = "Tom" };
var c = new Clan() { Name = "SomeClan" };
p.ClanId = X;  // Replace X by a suitable Id.
ASSERT( c.People.First() == p )

The idea is to ensure that the Foreign Key relationship is created properly. EF is able to infer that many times, particularly in a one to many relationship but there are some cases where it isn't. Explicitly specifying it like this will make it work in all cases.

This also has additional benefits when you are trying to associate a new person to an existing clan. In some cases, EF thinks the clan is new and tries to insert that as well. With this approach, you will not face that issue.

Another way would be to explicitly specify the foreign key relationship in the overridden OnModelCreating method.

Yasir
  • 1,595
  • 5
  • 23
  • 42
  • This won't work. Your solution works great when you are within the scope of a DbContext and SaveChanges() is invoked. If I make the assignment `p.Clan = c` within the "using" scope of a DbContext, perform a `SaveChanges()` on the DbContext, open up a new scope using a new DbContext- The links are all there and fine. However, in your example, even with adding an Id and ClanId, the ASSERT fails _if the whole operation is performed outside the using scope of a DbContext_ – Veldaeven Jan 29 '15 at 20:54