Entity Framework Code First : Setting up One-To-One foreign key association using Annotations

31,969

Solution 1

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();
        }
    }
}

Solution 2

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.)

Solution 3

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.

Share:
31,969
Jatin
Author by

Jatin

Self employed software developer. Develops applications for Not-For-Profit organizations. Has introductory knowledge of designing signalling circuits for Railways using Route Relay Interlocking logic. Areas of Interest - C#, WPF, ASP.net MVC, Entity Framework, AutoCAD.Net.

Updated on July 14, 2020

Comments

  • Jatin
    Jatin almost 4 years

    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:

  • Jatin
    Jatin about 12 years
    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.
  • Slauma
    Slauma about 12 years
    +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?
  • Jatin
    Jatin about 12 years
    @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.
  • Slauma
    Slauma about 12 years
    @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.
  • Jatin
    Jatin about 12 years
    @Slauma, I removed that. Thanks
  • Arialdo Martini
    Arialdo Martini about 12 years
    @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
    Arialdo Martini about 12 years
    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
    Arialdo Martini about 12 years
    Also, note: in order to use lazy loading, you should add virtual to non-primitive properties.
  • Mike Cole
    Mike Cole about 10 years
    Not supported by Entity Framework, or by Code First specifically? Pretty sure this works in edmx.
  • Michael Edenfield
    Michael Edenfield almost 10 years
    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
  • Slauma
    Slauma almost 10 years
    @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.
  • Michael Edenfield
    Michael Edenfield almost 10 years
    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.
  • Slauma
    Slauma almost 10 years
    @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).
  • Michael Edenfield
    Michael Edenfield almost 10 years
    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...)
  • Slauma
    Slauma almost 10 years
    @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").
  • Langdon
    Langdon about 7 years
    Nobody ever mentions .Map and .MapKey, thank you for posting this!
  • VikciaR
    VikciaR about 7 years
    Thanks, my time saver! :-)