Entity framework foreign key constraint - c#

I'm still getting my head around using EF. I using a code first approach in my project and stumbled upon the following issue.
I have the following objects:
public class Employee
{
public int EmployeeId { get; set; }
public int BusinessUnitId { get; set; }
public virtual BusinessUnit BusinessUnit { get; set; }
}
public class Quote
{
public int QuoteId { get; set; }
[DisplayName("Business Unit")]
public int BusinessUnitId { get; set; }
[DisplayName("Responsible Employee")]
public int EmployeeId { get; set; }
[DisplayName("Date Issued")]
[DataType(DataType.Date)]
public DateTime DateIssued { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public virtual Employee Employee { get; set; }
public virtual BusinessUnit BusinessUnit { get; set; }
}
Both include a BusinessUnit property, and it seems that EF doesn't want to allow this. Seeing that I get the following error below on the Index() method when a Linq query with a bunch of includes are executed.
Introducing FOREIGN KEY constraint
'FK_dbo.Quotes_dbo.BusinessUnits_BusinessUnitId' on table
'Quotes' may cause cycles or multiple cascade paths. Specify ON DELETE
NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints. Could not create constraint. See previous errors.
Can someone please explain to me why I get this error and how I might go about fixing it. Thanks.
EDIT:
This is definitly caused by including the BusinessUnit property in both the Quote object and the Employee object. I just dont understand why.
EDIT 2:
The code for the BusinessUnit class:
public class BusinessUnit
{
public int BusinessUnitId { get; set; }
public string Name { get; set; }
}

Right now, if you try to delete an Employee, it will attempt to delete the Quote, the BusinessUnit for that Quote, and then the Employees for that BusinessUnit, etc.
Either make some relationships optional, or turn off cascading conventions as below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Either entirely
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// or for one model object
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
}

Adding comment as answer: It's a conflict between the BusinessUnitId properties and the BusinessUnit properties
Remove the BusinessUnitId property from Quote and Employee classes

At the core of this issue is SQL Server's refusal to allow circular referenced cascading deletes (as the SO link from #Oskar discusses). EF is relaying an exception from SQL Server to you. It is being triggered by code first's default behavior: "If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship. If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null."
You should be able to overcome this issue by making at least one of your BusinessUnitId properties nullable, either on Quote or Employee. Alternatively, you can use EF's fluent api to specify cascade delete rules.

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.

How do I configure Entity Framework cascade delete to work properly with a one to many relationship?

After researching cascade deletes and browsing issues here, I'm under the impression that the following scenario will work
Entity with many:
public partial class master
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public master()
{
analysis = new HashSet<analysis>();
}
[Key]
public int id { get; set; }
[StringLength(50)]
public string description { get; set; }
public virtual ICollection<analysis> analysis { get; set; }
}
Entity with one:
public partial class analysis
{
[Key]
public int id { get; set; }
[StringLength(50)]
public string description { get; set; }
public int? master_id { get; set; }
public virtual master master { get; set; }
}
The foreign key on analysis entity is nullable, so that cascade deletion will automagically set the FK to null when it tries to delete the master record.
That configuration is in my Context:
public class Context : DbContext
{
public Context() : base("Context")
{
this.Configuration.LazyLoadingEnabled = true;
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
}
public virtual DbSet<master> master { get; set; }
public virtual DbSet<analysis> analysis { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<master>()
.HasMany(e => e.analysis)
.WithOptional(e => e.master)
.HasForeignKey(e => e.master_id)
.WillCascadeOnDelete(true);
}
}
Then when I attempt to delete a master record by passing the id into the following (generic repo snippet, this works fine when deleting a master record with no analyses):
public void Delete(int id)
{
var entity = FindByKey(id);
_dbSet.Remove(entity);
_context.SaveChanges();
}
I get an error
Cannot delete or update a parent row: a foreign key constraint fails
on this record. What am I missing here?
The use of CascaseOnDelete should mirror cascade rules on the database. With CodeFirst when you set a relationship as Required, EF maps this both in the mapping and the schema as a delete cascade. With Optional relationships this isn't the case, it assumes deleting a parent will leave the child orphaned since the child accepts an existence without a parent due to it's null-able FK. However, without knowledge of the child records it cannot clear the ID out of the FK or delete the data so it relies on whatever the DB is set up to do.
You can still use .WillCascadeOnDelete(), though for it to work, the context needs to know about the children. For instance, on a new context:
var master = _db.Masters.Find(id);
_db.Masters.Remove(master);
This typically fails with a FK constraint error. (SQL Server) The error message differs from yours so I suspect like Tetsuya that is a different provider?
To resolve this you can use:
var master = _db.Masters.Include(x=>x.analysis).Find(id);
_db.Masters.Remove(master);
though that can be tedious for objects with larger graphs to include all children.
As a general rule if you want to use cascade deletes, you need to ensure that the database schema is set up with a cascade on delete behaviour. With a null-able FK and a cascade delete rule the original code should behave as expected. Alternatively, Setting a cascade rule to "Set to Null" will leave the child records orphaned with a null FK. (no error from EF)

Foreign Key issue with Code first from Existing Database

tl;dr Do I need to have a foreign key id field as a property in the related class with EF code-first?
Following ScottGu's advice, i have created a model to reflect an existing database. Two of the tables in the db are: Project and ProjectType with a foreign key relationship. Each Project has a ProjectType.
I have added the necessary virtual fields to the model classes to reflect the relationship:
public class Project {
public int ProjectID { get; set; }
public string ProjectName { get; set; }
public DateTime CreationDate { get; set; }
...
public virtual ProjectType ProjectType {get; set; }
...
}
public class ProjectType {
public int ProjectTypeID { get; set; }
...
public virtual ICollection<Project> Projects { get; set;}
...
}
According to Scott (as shown in the image below), there is no need for the actual (foreign key) ID field to be exposed in the dependent class, ie, I don't need a public int ProjectTypeID { get; set; } property in the Project class.
However, when I try a call to retrieve the data, I hit an EntityCommandExecutionException with an InnerException of: Invalid column name 'ProjectType_ProjectTypeID'
Initial googling suggested adding a [ForeignKey] annotation to the ProjectType property. I tried both [ForeignKey("ProjectType")] and [ForeignKey("ProjectTypeID")] but neither worked.
Further googling suggested using FluentAPI with a call to:
modelBuilder.Entity<Project>().HasRequired<ProjectType>(p => p.ProjectType)
.WithMany(pt => pt.Projects)
in an OnModelCreating method, but this falls over with the same Invalid column name error.
So, do I need to have the ProjectTypeID field as a property in the Project class? If not, how do I tell EF to use the ProjectTypeID as the foreign key?
What is the foreign key column name in the existing database? You don't need to add a foreign key field but you do need to configure your modelBuilder so that foreign key names match.
modelBuilder.Entity<Project>()
.HasRequired<ProjectType>(p => p.ProjectType)
.WithMany(pt => pt.Projects)
.Map(p => p.MapKey("FK_NAME_IN_EXISTING_DB"));
You can also choose the option to have EF generate code first from database.

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.

Entity Framework 5: Code-First Cyclical Relationship Issues

I understand why EF does not allow "cyclical references" in the PK/FK relationships. I am looking for advice on how to alter my model to make the following scenario work.
Scenario
Three entities: Employee, Agency, WorkRecord. Their purpose is to log Employee time spent doing work. Employee then contains reference to the Agency he/she is employed by, and his/her WorkRecord contain reference to the Agency the work was done for.
public class Employee
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int AgencyId { get; set; }
public virtual Agency Agency { get; set; }
public virtual IEnumerable<WorkRecord> WorkRecords { get; set; }
}
public class Agency
{
[Key]
public int Id { get; set; }
}
public class WorkRecord
{
[Key]
public int Id { get; set; }
public int Hours { get; set; }
public int AgencyId { get; set; }
public virtual Agency Agency { get; set; }
public int EmployeeId { get; set; }
public virtual Employee { get; set; }
}
Like this, it bitches: FK_dbo.WorkRecords_dbo.Employees_EmployeeId causes a cyclical reference.
Experiments
My first thought was because of the bi-directional virtual properties, so I decided to designate one of the two a top-level entity with a 1-way relationship:
First, I designated WorkRecord as a top-level entity and remove the virtual WorkRecords reference reference from the Employee entity... the same message is produced.
Second, I made Employee the top-level entity, leaving its virtual WorkRecords collection, and removing the virtual Employee reference property from the WorkRecord entity... works fine but does not achieve my goal.
After more investigation, I find it is the Agency virtual reference property on both entities that causes the circular reference. If one entity removes this, the Employee/WorkRecord entity relationships work in all directions.
Question:
So, clear as i can ask - how can I express this business model, using WorkRecord as my top-level entity, without making EF5 upset?
It sounds like you just want to get EF off your back, but I think it's actually expressing a valid problem in the coupling of your data. If you bind AgencyId to both WorkRecord and Employee then updating the AgencyId on WorkRecord, for example, will cascade to Employee. Which will then cascade to WorkRecord etc. Hence "circular reference". You really should designate which of those data objects will "own" the relationship to Agency.
Personally, I suspect that the most natural binding is to reference the Agency from the WorkRecord. I can see a scenario where an Employee might move from one agency to another but it'd be much harder for a WorkRecord to move from one Agency to another. It's also the case that an Employee without a WorkRecord can't really be termed much of an Employee, really. If you determine this to be the case, then I'd remove the Agency reference from Employee. If you need to get to the Agency from the Employee then you probably should go through a WorkRecord anyway.
All of that is merely conceptual, however. I suspect that if you make it possible for AgencyId to be null on the Employee that EF won't complain any longer (and you might want it optional on both). That should make it valid for an Employee to be updated without requiring a circular update with WorkRecord. I'd have to test that to verify, but I suspect it'd hold true.
public class Employee
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? AgencyId { get; set; }
public virtual Agency Agency { get; set; }
public virtual IEnumerable<WorkRecord> WorkRecords { get; set; }
}
You probably get an exception from SQL Server, not Entity Framework, like:
Introducing FOREIGN KEY constraint 'ABC' on table 'XYZ' may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
This exception basically says what you need to do to fix the problem: "Specifying ON DELETE NO ACTION" means disabling cascading delete for at least one of the relationships. The problem is that all three relationships are required because your foreign key properties AgencyId and EmployeeId are non-nullable. In this case EF will create the relationships in the database with enabled delete. The result is a multiple delete path when you would delete an Agency: It would delete the WorkRecords and the Employees, but the Employees will delete the Workrecords as well, so you have two multiple delete paths on WorkRecords.
You can disable cascading delete only with Fluent API:
modelBuilder.Entity<Employee>()
.HasRequired(e => e.Agency)
.WithMany()
.HasForeignKey(e => e.AgencyId);
modelBuilder.Entity<WorkRecord>()
.HasRequired(w => w.Agency)
.WithMany()
.HasForeignKey(w => w.AgencyId)
.WillCascadeOnDelete(false); // or for one or more of the other relationships
modelBuilder.Entity<WorkRecord>()
.HasRequired(w => w.Employee)
.WithMany(e => e.WorkRecords)
.HasForeignKey(w => w.EmployeeId);
Deleting an Agency now causes the related employees to be deleted and the deleted employees will cause the related workrecords to be deleted. But the agency won't directly delete the workrecords anymore, thus removing the second delete path.
You can alternatively make one of relationships optional which disables cascading delete automatically by convention (see Jacob Proffitt's answer).
BTW: You can't use an IEnumerable<T> for a navigation property, you must use ICollection<T> or a derived interface or implementation.

Categories

Resources