16

I have following two Entities that I am trying to relate (one to one) using foreign key associations.

public class StandardRack {
    public int Id {get;set}
    public StandardRelay StandardRelay {get;set} 
}

public class StandardRelay {
    public int Id {get;set} 

    public int StandardRack_Id {get;set;}
    [Required][ForeignKey("StandardRack_Id")]
    public StandardRack StandardRack { get; set; }
}

This throws ModelValidationException. Any ideas why such a seemingly simple one-to-one bidirectional relationship cannot be configured.

Edit:

Here is the Exception:

System.Data.Entity.ModelConfiguration.ModelValidationException was caught Message=One or more validation errors were detected during model generation:

System.Data.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'StandardRelay_StandardRack_Source' in relationship 'StandardRelay_StandardRack'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be �*�.

Source=EntityFramework StackTrace: at System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateAndSerializeCsdl(EdmModel model, XmlWriter writer) at System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateCsdl(EdmModel model) at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo) at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection) at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext) at System.Data.Entity.Internal.RetryLazy2.GetValue(TInput input) at System.Data.Entity.Internal.LazyInternalContext.InitializeContext() at System.Data.Entity.Internal.InternalContext.Initialize() at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) at System.Data.Entity.Internal.Linq.InternalSet1.Initialize() at System.Data.Entity.Internal.Linq.InternalSet1.GetEnumerator() at System.Data.Entity.Infrastructure.DbQuery1.System.Collections.Generic.IEnumerable.GetEnumerator() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) at TestApplication.MainWindow.Window_Loaded(Object sender, RoutedEventArgs e) in D:\RailwayProjects\RelayAnalysis\TestApplication\MainWindow.xaml.cs:line 33 InnerException:

Masoud
  • 8,020
  • 12
  • 62
  • 123
Jatin
  • 4,023
  • 10
  • 60
  • 107

4 Answers4

30

One-to-one foreign key associations are not supported by Entitiy Framework. You must remove the foreign key and use shared primary keys (primary key of the dependent is its foreign key to the principal at the same time):

public class StandardRack {
    public int Id {get;set}
    public StandardRelay StandardRelay {get;set} 
}

public class StandardRelay {
    public int Id {get;set} 
    public StandardRack StandardRack { get; set; }
}

Mapping in Fluent API:

modelBuilder.Entity<StandardRack>()
    .HasOptional(rack => rack.StandardRelay)
    .WithRequired(relay => relay.StandardRack);

(I'm assuing here that a StandardRack has an optional Relay.)

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • I personally prefer this approach to to one I posted above: I like database-agnostic entities, hence I always try to avoid annotations. +1 – Arialdo Martini Apr 13 '12 at 09:52
  • 2
    Also, note: in order to use lazy loading, you should add `virtual` to non-primitive properties. – Arialdo Martini Apr 13 '12 at 09:54
  • Not supported by Entity Framework, or by Code First specifically? Pretty sure this works in edmx. – Mike Cole Mar 07 '14 at 18:49
  • 1
    it's supported by Code First (and you really should be using `WithPrincipal`, that's what it's for). It's just not supported by the code first conventions -- you HAVE to tell EF how to set up the relationship – Michael Edenfield Jul 13 '14 at 19:35
  • @MichaelEdenfield: What I meant is: It's not supported to define a property (like `StandardRack_Id`) other than the PK property `Id` itself as FK in a one-to-one relationship. That's what he tried with annotations and because it's not supported he gets an exception. I'm not aware how you could make it work with Fluent API. If you know a way maybe you could provide your own answer here. – Slauma Jul 14 '14 at 23:30
  • Ah, that is true, you cannot do that. However, my understanding is that you need to use `WithPrincipal` for one-to-one relationships because EF cannot determine which side is the principal and which is the dependent -- which is needs to know because it has to pick a table to have the FK constraint on it. – Michael Edenfield Jul 14 '14 at 23:50
  • 3
    @MichaelEdenfield: You only need (and only have the option) to choose `With...Principal` if both sides are optional or both are required. If one side is optional and one is required you can't choose it because then the optional side is always the principal. `HasOptional(...).WithRequired(...)` or `HasRequired(...).WithOptional(...)` are the only valid combinations then. It's not abvious from the question if he wants an optional-required relationship, I was just assuming that in my answer. For a required-required relationship the mapping would be different (but still without the FK property). – Slauma Jul 15 '14 at 00:10
  • That I knew but... if one side is optional than it's not 1:1 anymore, right? it's 0:1? (I'm actually working through this exact issue right now so I'm sincerely trying to figure it out...) – Michael Edenfield Jul 15 '14 at 00:11
  • @MichaelEdenfield: Ah, I see, you interprete the term "one-to-one" in the question title as a strict "1:1"? I've read the "one" just in the less precise sense of "not many", so either "0:1" or "0:0" or "1:1", so to speak :) There could certainly be more details and possible solutions discussed in my answer. I just picked the one that is easiest to understand (in my opinion the "0:1"). – Slauma Jul 15 '14 at 00:27
10

Here is how you can specify one-to-one relationship with FK using fluent api.

Note that FK is not explicitly defined in Enitity, but it's defined using fluent api.

public class StandardRack {
    public int Id {get;set}
    public StandardRelay StandardRelay {get;set} 
}

public class StandardRelay {
    public int Id {get;set} 
    public StandardRack StandardRack { get; set; }
}


modelBuilder.Entity<StandardRack>()
            .HasOptional(x => x.StandardRelay)
            .WithOptionalPrincipal(y => y.StandardRack)
            .Map(configurationAction: new Action<ForeignKeyAssociationMappingConfiguration>(x => x.MapKey("StandardRack_Id")));

fluent api will add column StandardRack_Id in StandardRelay.

Note that method name WithOptionalPrincipal() is quite ironic. msdn documentation of WithOptionalDependent shall make it clear.

Sudarshan_SMD
  • 2,549
  • 33
  • 23
8

I think the foreignKey should be Id, not StandardRack_id. Also, you should use virtual, in order to be able to use lazy loading.

This works for me

using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace Racks
{

    public class StandardRack
    {
        public int Id { get; set; }
        public virtual StandardRelay StandardRelay { get; set; }
    }

    public class StandardRelay
    {
        public int Id { get; set; }

        public int StandardRack_Id { get; set; }

        [ForeignKey("Id")]
        [Required]
        public virtual StandardRack StandardRack { get; set; }
    }

    public class Context : DbContext
    {
        static Context()
        {
            Database.SetInitializer<Context>(null);
        }

        public DbSet<StandardRack> StandardRacks { get; set; }
        public DbSet<StandardRelay> StandardRelays { get; set; }

    }

    class Program
    {
        static void Main(string[] args)
        {
            var context = new Context();
            context.Database.Delete();
            context.Database.Create();

            var standardRack = new StandardRack();
            standardRack.StandardRelay = new StandardRelay();

            context.StandardRacks.Add(standardRack);
            context.SaveChanges();
        }
    }
}
Arialdo Martini
  • 4,427
  • 3
  • 31
  • 42
  • Arialdo, @Slauma, the approaches suggested by both of you results in same Database structure, namely, the Id column of StandardRack serves as target of foreign key (Id) of StandardRelay. I will go with Arialdo's approach and mark his answer as correct, although both the answers work. – Jatin Apr 11 '12 at 15:25
  • +1 Good idea! I didn't expect that one-to-one mapping would also work with annotations. I'm wondering if it also works if you remove the `[ForeignKey]` attribute altogether (but leave the `[Required]` attribute in place). Did you test that? – Slauma Apr 11 '12 at 15:27
  • 1
    @Slauma, Even if I remove the Foreignkey, it works and does create the same Database structure. So Foreignkey can be left out altogether. Thanks very much to both of you for helping me out. – Jatin Apr 11 '12 at 15:38
  • @Nirvan: Great! Good to know. Just a remark: I think you can remove the `StandardRack_Id` completely from DB table and from the model class. It is just an ordinary scalar property now which doesn't participate in the association anymore. – Slauma Apr 11 '12 at 15:40
  • @Slauma, I removed that. Thanks – Jatin Apr 11 '12 at 15:44
  • @Slauma Actually, the ForeignKey attribute is not needed. I leaved it for clarity. To be honest, since I want that my entities are database-agnostic, I don't like annotations and I prefere maps. – Arialdo Martini Apr 13 '12 at 09:44
0

You might want to adapt the annotation of ID in StandardRelay. Also see this related question:

What does principal end of an association means in 1:1 relationship in Entity framework

public class Foo
{
    public string FooId{get;set;}
    public Boo Boo{get;set;}
}

public class Boo
{
    [Key, ForeignKey("Foo")]
    public string BooId{get;set;}
    public Foo Foo{get;set;}
}
Community
  • 1
  • 1
Stefan
  • 10,010
  • 7
  • 61
  • 117