Entity Framework Foreign Key For Circular Relation - c#

I am converting an application to Entity Framework Core and am running into trouble getting a Foreign Key relationship between two of my model classes. The classes are setup like so (Note that a Guid Id field is declared on BaseEntity):
public class Crt : BaseEntity
{
[Required]
public Guid FacId { get; set; }
[Required]
public string Code { get; set; }
[ForeignKey("ActiveCrtChk")
public Guid? ActiveCrtChkId { get; set; }
public string Description { get; set; }
public string Device { get; set; }
#region navigation properties
public CrtChk ActiveCrtChk;
public List<CrtChk> CartChecks;
#endregion
}
public class CrtChk : BaseEntity
{
[Required]
public Guid CrtId { get; set; }
[Required]
public string Device { get; set; }
[Required]
public Guid OutSysUsrId { get; set; }
[Required]
public DateTime OutSysDateTime { get; set; }
public Guid? InSysUsrId { get; set; }
public DateTime? InSysDateTime { get; set; }
[Required]
public string Type { get; set; }
#region navigation properties
public Crt Cart { get; set; }
public Usr OutSysUsr { get; set; }
public Usr InSysUsr { get; set; }
public List<CrtEvt> CartEvents { get; set; }
#endregion
}
The idea behind the relationship is that one Crt can have many CrtChk records, but Crt also stores the Id of the active CrtChk record.
When I run the migration, it generates all of the foreign key relationships I would expect between Crt and CrtChk except there is no foreign key generated for the ActiveCrtChkId field.
It is my understanding from reading this post that having the ForeignKey attribute on the ActiveCrtChkId property with the name of the ActiveCrtChk navigation property, that I should get a Foreign Key constraint in my migration.
What am I missing here?
Edit
After fixing my mistake of declaring the Crt navigation properties as fields, I have stumbled on a new error when I try to create the migration.
Unable to determine the relationship represented by navigation property 'Crt.ActiveCrtChk' of type 'CrtChk'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I thought the ForeignKey attribute was manually configuring the relationship? Do I need to use the Fluent API do create the relationship? If so, how can I use the Fluent API to make a relationship that can be both one-to-one (Crt to ActiveCrtChk) and one to many (all CrtChks associated with Crt)?

It's possible, but since this design creates circular dependency between the two entities, it would cause you a lot of problems. For instance, not only one of the relationships (let say from CrtChk to Crt) cannot use cascade delete, but also you cannot simply delete the Crt without first updating the ActiveCrtChkId to null (and calling SaveChanges).
Anyway, here is how you configure the desired relationships. Usually it would be enough to use InverseProperty attribute to resolve navigation property mapping ambiguity, but one-to-one unidirectional (i.e. with navigation property only at one of the ends) requires fluent configuration (otherwise it will be mapped by convention to one-to-many). Also specially for relationships, I find explicit fluent configuration much clear than considering all EF conventional assumptions and data annotations like where to put ForeignKey attribute (on FK property or navigation property), what string to put there is the first or later case etc.
Shortly, here is the full explicit configuration of the relationships in question:
// Crt 1 - 0..N CrtChk
modelBuilder.Entity<Crt>()
.HasMany(e => e.CartChecks)
.WithOne(e => e.Cart)
.HasForeignKey(e => e.CrtId)
.OnDelete(DeleteBehavior.Cascade);
// CrtChk 1 - 0..1 Crt
modelBuilder.Entity<Crt>()
.HasOne(e => e.ActiveCrtChk)
.WithOne()
.HasForeignKey<Crt>(e => e.ActiveCrtChkId)
.OnDelete(DeleteBehavior.Restrict);
Note that Cart property cannot be used in both relationships. First, because each navigation property can be mapped only to one relationship. Second, because the relational model cannot enforce that CrtChk record referenced by ActiveCrtChkId FK has the same CrtId as the Id of the Crt referencing it - it could be any other (although logically the intent is different).

Related

Entity Framework : Invalid column name *_ID even with fluent api or data annotations

Odd issue that I've been looking at all day. I am working with Entity Framework 6. The issue I have is that I have three entities:
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
// other properties and relationships here
public int? OrderProfileId { get; set; }
public int OrderTemplateId { get; set; }
public virtual OrderProfile Profile { get; set; } // optional property
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderProfile
{
public int Id { get; set; }
// other properties
// added here 6/15/2021
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderTemplate : EntityMetaData
{
public int Id { get; set; }
// other properties
public int? OrderProfileId{ get; set; }
public OrderProfile OrderProfile { get; set; }
}
In our model builder, we have these definitions:
modelBuilder.Entity<Order>()
.HasOptional(x => x.OrderProfile)
.WithMany(x => x.Orders)
.HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
But even with the above fluent api model, we get the error
Invalid column name 'OrderProfile_Id'
Throughout various testing I was unable to find why this issue was occurring, so I looked at our logs and found when this error started popping it's head up and then was able to find the changes associated to OrderProfile and found that the only change that was made was adding the relationship from OrderProfile to OrderTemplate.
When I removed that fluent api relationship OrderProfile to OrderTemplate, it worked as expected... I don't need that relationship to OrderTemplate, but would like it to be there, how can I establish a optional 1 to optional 1 relationship without breaking other relationships? Also, why would additional relationships be effected by this?
UPDATE 6/15/2021
So I found I had a reverse navigation property in the OrderProfile model:
public virtual OrderTemplate OrderTemplate{ get; set; }
removing that and the associated fluent relationship
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
Doing the above resolved the issue, but for some reason, the issue seems to have cascaded down to another relationship that has a circular reference like the above. The Order class is involved with this cascaded issue. I guess this is a pretty big cause for concern since this application worked fine for the last 4 years and for these relationships to be decaying like this is worrisome. Does anyone know why this is happening?
if you use the right naming convention, EF will do magic. in this sample, you don't need fluent API to relate entities.
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
public int? OrderProfileId { get; set; } //means HasOptional (nullable) and ForeignKey
//variable name must be OrderProfile not Profile
public virtual OrderProfile OrderProfile { get; set; }
}
public class OrderProfile
{
public OrderProfile()
{
Orders = new HashSet<Order>();
}
public int Id { get; set; }
//be aware circular reference at any conversion or mapping
public virtual ICollection<Order> Orders {get; set;} //means WithMany
}
I've got an error like this too. It's caused by unmatching OrderProfileId property in OrderTemplate class with the fluent api model
If I'm not wrong, you want the OrderProfile model a many to many relation between Order and OrderTemplate. Then if it was the case, add the nvaigation property in OrderProfile.
public class OrderProfile
{
public int Id { get; set; }
// other properties
public virtual ICollection<Order> Orders { get; set; }
public virtual OrderTemplate OrderTemplate { get; set; }
}
Then change the fluent api model to be like this
// the EF has modelled the relation for normal 1 to many relation
// modelBuilder.Entity<Order>()
// .HasOptional(x => x.OrderProfile)
// .WithMany(x => x.Orders)
// .HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderTemplate>()
.HasOptional(x => x.OrderProfile)
.WithOptional(x => x.OrderTemplate);
You're working database-first, which always leaves room for a mismatch between the actual database model and the model EF infers from class and property names and mapping code (= conceptual model). If this happens, it may help to make EF generate a database from the conceptual model and see where it creates the column it expects, OrderProfile_Id.
This is what you'll see when logging the SQL statements:
CREATE TABLE [dbo].[OrderTemplates] (
[Id] [int] NOT NULL IDENTITY,
[OrderProfileId] [int],
[OrderProfile_Id] [int],
CONSTRAINT [PK_dbo.OrderTemplates] PRIMARY KEY ([Id])
)
...
ALTER TABLE [dbo].[OrderTemplates]
ADD CONSTRAINT [FK_dbo.OrderTemplates_dbo.OrderProfiles_OrderProfile_Id]
FOREIGN KEY ([OrderProfile_Id]) REFERENCES [dbo].[OrderProfiles] ([Id])
There you see the expected nullable column OrderProfile_Id which is the FK to OrderProfiles. It's noteworthy to see that EF does not use OrderProfileId as a foreign key field. It's just a field that could be used for anything.
That's because EF6 doesn't support 1:1 associations as foreign key associations (reference property and primitive FK property).
Knowing this, the remedy is simple: remove the property OrderTemplate.OrderProfileId and tell EF to use the field OrderTemplate.OrderProfileId in the database:
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile)
.Map(m => m.MapKey("OrderProfileId"));
That said, I wonder why Order has a foreign key to OrderProfile. Isn't its OrderProfile determined by its OrderTemplate? If it's a redundant relationship it may be better to remove it.

Entity Framework Core 2.0 many-to-many relationships same table [duplicate]

This question already has an answer here:
Many-to-many self referencing relationship
(1 answer)
Closed 3 years ago.
I'm using Entity Framework Core 2.0 to map exist DB, the DB has two tabels: Teams and SupportTeam.
Team Fields: ID, Name
TeamSupport: TeamID (ForeignKey on Team Table), SupportTeamID (ForeignKey on Team Table)
I tried to map them as following:
public class Team
{
public int Id { get; set; }
public string name { get; set; }
public List<TeamSupport> SupportTeams { get; set; }
}
public class TeamSupport
{
public int TeamId { get; set; }
public virtual Team Team { get; set; }
public int SupportTeamId { get; set; } // In lack of better name.
public virtual Team SupportTeam { get; set; }
}
But I had the follwing error when i run "add-migration":
Unable to determine the relationship represented by navigation
property 'Team.SupportTeams' of type 'List'. Either
manually configure the relationship, or ignore this property using the
'[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'.
The TeamSupport entity has two reference navigation properties to Team (which define two many-to-one relationships between TeamSupport and Team), but the Team entity has only one collection navigation property, hence EF does not know how to map it (to Team.Team or Team.SupportTeam) and throws the exception in question.
In such scenarios you have to resolve the mapping explicitly. Usually it's enough to use [InverseProperty] data annotation, but multiple references to the same table always lead to multiple cascade paths problem, which requires turning the delete cascade off for one or more of the relationships. And the later can be done only with fluent configuration, so it's better to do the whole mapping with fluent configuration as well.
The minimal configuration needed by your model the way it is now is:
modelBuilder.Entity<TeamSupport>()
.HasOne(e => e.Team)
.WithMany(e => e.SupportTeams);
modelBuilder.Entity<TeamSupport>()
.HasOne(e => e.SupportTeam)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
Note that since there is no corresponding collection navigation property, the second relationships configuration uses the parameterless WithMany overload to indicate that. In case you decide to add such collection to the model
public class Team
{
public int Id { get; set; }
public string name { get; set; }
public List<TeamSupport> SupportTeams { get; set; }
public List<TeamSupport> SupportOfTeams { get; set; } // <--
}
don't forget to specify that in the corresponding mapping
.WithMany(e => e.SupportOfTeams)
otherwise EF will create a third relationship.
For more info, see Relationhips section of the EF Core documentation.

Entity framework 6 fluent API, how to map navigation key to a view with composite key

I have 3 tables where Property has a foreign key to Dependent and Dependent has a foreign key to Main, causing a One-to-Many relationship for each of the tables. However I am only interested in the most recent record in Dependent and its Property records, thus i created a view v_Dependent which returns the most recent Dependant record grouped by MainId. This will enable a One-to-One relationship between Main and Dependant which is what I'm after, works with the code below.
I am eager loading all when loading Main objects, however after i switched to the view I may no longer eager load the records in the Properties collection of Dependent. The reason for this is that to map the view into a One-to-One relationship I had to add MainId to the composite key for Dependent. Now the foreign key from Property would have to contain the MainId as well to be able to load the collection, however I do not have MainId in the database table, nor do I want to.
My question is, do I have to create a view for Property as well to include the MainId and add this to the entity composite foreign key, or is there anything else I can do to map this using fluent API? Another option I'm currently using is explicitly loading the Property collection in my repository, however I was hoping fluent API could handle this for me. The commented out line is the config which worked for the whole graph while I treated the Dependent as a collection on Main. I am using the entities read only, so dont have to worry about storing back.
public class Main
{
public int MainId { get; set; }
public Dependent Dependent { get; set; }
}
public class Dependent
{
public int DependentId { get; set; }
public int MainId { get; set; }
public Main Main { get; set; }
public ICollection<Property> Properties { get; set; }
}
public class Property
{
public int PropertyId { get; set; }
public int DependentId { get; set; }
public Dependent Dependent { get; set; }
}
public class SomeContext : DbContext
{
public DbSet<Main> Mains { get; set; }
public DbSet<Dependent> Dependents { get; set; }
public DbSet<Property> Properties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Main>().ToTable("Main").HasKey(m => m.MainId);
//modelBuilder.Entity<Dependent>().ToTable("Dependent").HasKey(d => d.DependentId).HasRequired(d => d.Main).WithMany(m => m.Dependents).HasForeignKey(d => d.MainId);
modelBuilder.Entity<Dependent>().ToTable("v_Dependent").HasKey(d => new {d.DependentId, d.MainId}).HasRequired(d => d.Main).WithOptional(m => m.Dependent);
modelBuilder.Entity<Property>().ToTable("Property").HasKey(p => p.PropertyId).HasRequired(p => p.Dependent).WithMany(d => d.Properties).HasForeignKey(p => p.DependentId);
base.OnModelCreating(modelBuilder);
}
}
After alot of digging I found out that the underlying issue is that entity framework is unable to map a One to Zero or One relationship using a foreign key instead of primary key. See https://entityframework.codeplex.com/workitem/299
As a result I had to choose between changing the primary key of Dependent (which is currently a view and thus I could create an indexed view with all the limitations of such, but I have a subquery I cannot get rid of) or load the properties seperately. I chose the latter and changed the applcation to no longer have a navigation property and loading the properties when needed.

Is there a way to model an optional many-to-many relationship in Entity Framework?

Is there a way in Entity Framework (and I assume it will be with fluent syntax as data annotations are somewhat limited) to model a many-to-many relationship in which both sides are optional (a 0..M to 0..N relationship)? The use case is this: I would like to allow users to add tags to entities. Tags to entities is a M:N relationship, but neither should be required. That is, a tag can exist that is not applied to any entities and an entity can be untagged. This seems fairly reasonable to me. I can't simply model this using:
public virtual ICollection<Tag> Tags { get; set; }
and
public virtual ICollection<Entity> Entities { get; set; }
because each class has other relationships, and I get a "foreign key constraint may cause cycles or multiple cascade paths." I was hoping maybe I could do something like:
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
but I am warned that EF is "Unable to determine the principal end of the association." From reading, it seems that such relationships HAVE to have a principal end, but in my case, that's undesirable.
I could add a class to represent the bridge table and handle the mapping manually, but I'd prefer not to clutter the code. I was wondering if there is another way to model this in EF.
To fill in a bit more detail, there is also an Author class (which amounts to Users). Authors and tags are 1:M and Authors to Entities are also 1:M. So of course, the problem is that the Entities class occurs twice in the cascade tree. Making the Tag/Entity relationship optional would fix this. I could also fix it if there was a way to get to Tags through Entities, but since Tags can exist without being connected to an entity, I figured that would be impossible.
Here's a summary of the related code:
public class Author
{
public Guid Id { get; set; }
public virtual List<Entity> Entities { get; set; }
public virtual List<Tag> Tags { get; set; }
}
public class Tag
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Entity> Entities { get; set; }
}
public class Entity
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
EDIT:
Using .HasMany().WithMany() as suggested below gives me this:
CREATE TABLE [dbo].[TagEntities] (
[Tag_Id] [uniqueidentifier] NOT NULL,
[Entity_Id] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_dbo.TagEntities] PRIMARY KEY ([Tag_Id], [Entity_Id])
)
but what I WANT is for Tag_Id and Entity_Id to be nullable on this table. Maybe this model doesn't make as much sense as I thought?? Can you have a bridge table where both sides are nullable?
Use
modelBuilder.Entity<Tag>().HasMany(t => t.Entities)
.WithMany(t => t.Tags);
Instead of
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
I don't know if this is the RIGHT answer, but I solved this by creating a base class called DbEntity that other classes inherited from. So now Author has just:
// Both entities and tags are part of this collection
public virtual List<DbEntity> Entities { get; set; }
Both "Entities" (which has special meaning in my code) and "Tags" subclass DbEntity. This eliminated the multiple cascade paths while preserving the navigation properties, although I do need to do this:
author.Entities.OfType<Tag>();
or
author.Entities.OfType<Entity>();
to get specific sets of entities.

How to make EF map NavigationProperty on one entity to Collection of the other

I want a straightforward foreign key relationship between two entities, with EF properties going both ways. However, at the moment EF is generating 2 foreign keys representing each direction of the relationship. How can I make EF treat them as a single property? The below generates two database foreign key constraints named Script_ScriptRuns and ScriptRun_Script, using foreign key field Script_Id and Script_Id1 respectively
public class Script
{
[Key]
public Guid Id { get; set; }
public virtual ICollection<ScriptRun> ScriptRuns { get; set; }
}
public class ScriptRun
{
[Key]
public Guid Id { get; set; }
[Required]
public virtual Script Script { get; set; }
}
Using the code you posted, it only created a single FK for me - Script_Id in the ScriptRun class.
Are you simply wanting to rename the FK that's getting created in that class? You can do that by mapping the column in your DbContext. This would map it to ScriptId:
modelBuilder.Entity<Script>()
.HasMany(s => s.ScriptRuns)
.WithRequired(sc => sc.Script)
.Map(x => x.MapKey("ScriptId"));
If not, can you post your entire model?

Categories

Resources