Bob Tway
Bob Tway

Reputation: 9603

Entity framework two-way relationship not loading includes

This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.

Three code-first objects, related to one another.

public class Schedule
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }

    public virtual ICollection<Charge> Charges { get; set; }
}

public class Charge
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public decimal Rate { get; set; }
    public Type Type { get; set; }
}

public class Type
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public string TypeName { get; set; }
}

When I query this, I want all related types, so:

        Schedule currentSchedule = _Context.Schedules
                .Include("Charges.Type")
                .Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
                .First();

In C#, this has been working fine.

The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.

So I change it to this:

public class Type
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public string TypeName { get; set; }
    public virtual Charge Charge { get; set; }
}

Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:

Unable to determine the principal end of an association between the types 'Core.Charge' and 'Core.Type'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations

Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:

public class Type
{
    [Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public string TypeName { get; set; }
    public virtual Charge Charge { get; set; }
}

And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.

What's going on, and what have I done wrong?

Upvotes: 2

Views: 1899

Answers (2)

Slauma
Slauma

Reputation: 177133

You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.

Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.

If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:

public class Type
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public string TypeName { get; set; }
    public virtual ICollection<Charge> Charges { get; set; }
}

Upvotes: 2

J.W.
J.W.

Reputation: 18181

Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this

public class Type
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid RowId { get; set; }
    public string TypeName { get; set; }
    public int ChargeId { get; set; }
    [ForeignKey("ChargeId")]
    public virtual Charge Charge { get; set; }
}

Upvotes: 0

Related Questions