Entity Framework Code First : Setting up One-To-One foreign key association using Annotations
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.
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, 2020Comments
-
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.RetryLazy
2.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.InternalSet
1.Initialize() at System.Data.Entity.Internal.Linq.InternalSet1.GetEnumerator() at System.Data.Entity.Infrastructure.DbQuery
1.System.Collections.Generic.IEnumerable.GetEnumerator() at System.Collections.Generic.List1..ctor(IEnumerable
1 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 about 12 yearsArialdo, @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 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 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 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 about 12 years@Slauma, I removed that. Thanks
-
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 about 12 yearsI 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 about 12 yearsAlso, note: in order to use lazy loading, you should add
virtual
to non-primitive properties. -
Mike Cole about 10 yearsNot supported by Entity Framework, or by Code First specifically? Pretty sure this works in edmx.
-
Michael Edenfield almost 10 yearsit'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 almost 10 years@MichaelEdenfield: What I meant is: It's not supported to define a property (like
StandardRack_Id
) other than the PK propertyId
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 almost 10 yearsAh, 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 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(...)
orHasRequired(...).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 almost 10 yearsThat 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 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 about 7 yearsNobody ever mentions
.Map
and.MapKey
, thank you for posting this! -
VikciaR about 7 yearsThanks, my time saver! :-)