I got into some trouble with EntityFramework and the following datamodel (see simplified diagram).
The Matter object can be thinked as the "main container". There are Bill and BillRecord. There is a one-to-many association from Bill to BillRecord. Precisely, a Bill can reference many BillRecord (possibly 0) and a BillRecord can be referenced to at most one bill.
1) I want to be able to delete a BillRecord but it should not delete the Bill, if there is an association (that is why I did not set a OnCascadeDelete For Bill on BillRecords entity). Similarly, if I delete a Bill I do not want to delete the BillRecord that may be associated with.
2) However, when I delete a Matter I want everything to disappear: the Matter, Bill and BillRecords.
With the following code, I manage to have 1) right and 2) works if there is no BillRecord associated to a Bill, if there is one I get the following error.
System.Data.SqlServerCe.SqlCeException: The primary key value cannot be deleted because references to this key still exist. [ Foreign key constraint name = FK_dbo.BillRecordDboes_dbo.BillDboes_BillId ]
Here is my entities and my logic for OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MatterDbo>().HasMany<BillRecordDbo>(s => s.BillRecordDbos)
.WithRequired(s => s.Matter).HasForeignKey(s => s.MatterId).WillCascadeOnDelete(true);
modelBuilder.Entity<MatterDbo>().HasMany<BillDbo>(s => s.BillDbos)
.WithRequired(s => s.Matter).HasForeignKey(s => s.MatterId).WillCascadeOnDelete(true);
}
public class MatterDbo
{
public MatterDbo()
{
BillDbos = new List<BillDbo>();
BillRecordDbos = new List<BillRecordDbo>();
}
public Guid Id { get; set; }
public virtual List<BillDbo> BillDbos { get; set; }
public virtual List<BillRecordDbo> BillRecordDbos { get; set; }
}
public class BillRecordDbo
{
public Guid Id { get; set; }
public Guid MatterId { get; set; }
public virtual MatterDbo Matter { get; set; }
public Guid? BillId { get; set; }
public virtual BillDbo Bill { get; set; }
}
public class BillDbo
{
public BillDbo()
{
BilledRecords = new List<BillRecordDbo>();
}
public Guid Id { get; set; }
public virtual List<BillRecordDbo> BilledRecords { get; set; }
public Guid MatterId { get; set; }
public virtual MatterDbo Matter { get; set; }
}
Of course, when deleting a Matter I could check and remove all the associations of Bill and BillRecords manually but I think it would be a wrong usage of EF.
I am using EntityFramework 6.0 and SQL CE targetting .NET 4.0
Thank you very much.
If you really need BillRecord to remain in the database if its parent, Bill entity, is deleted, then you have to set the parent property to null before deleting the parent. However, having a nullable FK in the database is often a sign there might be a better db design solution.
Related
I have a .NET Core Razor Pages proyect in Visual Studio 2022 with Entity Framework Core 6 and Postgres 13 that models companies and cars.
A Car belongs to only one Company and a Company can have many Cars.
In turn, a Company may or may not have an active Car.
My model is:
public class Company {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Car>? Cars { get; set; }
public int? ActiveCarId { get; set; }
public Car? ActiveCar { get; set; }
}
public class Car {
public int Id { get; set; }
public string LicensePlate { get; set; }
public int CompanyId { get; set; }
public Company? Company { get; set; }
}
In the context I have:
public class CompanyCarsContext : DbContext {
public CompanyCarsContext(DbContextOptions<CompanyCarsContext> options)
: base(options) {
}
protected override void OnModelCreating(ModelBuilder builder) {
builder.Entity<Company>().HasMany<Car>(e => e.Cars).WithOne(p => p.Company);
builder.Entity<Company>().HasOne<Car>(e => e.ActiveCar);
}
public DbSet<CompanyCars.Models.Company> Companys { get; set; } = default!;
public DbSet<CompanyCars.Models.Car> Cars { get; set; }
}
When I do the migration it gives me the warning:
Microsoft.EntityFrameworkCore.Model.Validation[10625]
The foreign key property 'Company.ActiveCarId1' was created in shadow state because a conflicting property with the simple name
'ActiveCarId' exists in the entity type, but is either not mapped,
is already used for another relationship, or is incompatible with the
associated primary key type. See
https://aka.ms/efcore-relationships for information on mapping
relationships in EF Core.
But when I remove the property ActiveCarId in class Company (removing the migrations folder as well) and do the migration again, it gives me error:
Both relationships between 'Car.Company' and 'Company.Cars' and
between 'Car' and 'Company.ActiveCar' could use {'CompanyId'} as the
foreign key. To resolve this, configure the foreign key properties
explicitly in 'OnModelCreating' on at least one of the relationships.
What is the correct way to do it?
Courses have many prerequisites, and simultaneously a particular course can be a prerequisite for many courses. I've tried to establish the many-to-many relationship (in OnModelBCreating) using EF code-first with the following:
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany(e => e.Postrequisites)
.Map(m => m.ToTable("CourseRequisiteMappings")
.MapLeftKey("CourseId").MapRightKey("CourseId")); // EDIT: THIS LINE IS THE PROBLEM. SEE MARKED ANSWER AND MY COMMENT ON IT.
Also, here is the Course class:
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<Course> Prerequisites { get; set; }
public virtual ICollection<Course> Postrequisites { get; set; }
}
When I implemented this and went to update the database, it gave me the following errors:
CourseId: Name: Each property name in a type must be unique. Property
name 'CourseId' is already defined.
ModuleId: Name: Each property name in a type must be unique. Property
name 'ModuleId' is already defined.
CourseCourse: EntityType: EntitySet 'CourseCourse' is based on type
'CourseCourse' that has no keys defined.
ModuleModule: EntityType: EntitySet 'ModuleModule' is based on type
'ModuleModule' that has no keys defined.
I could not find an example of doing this which leads me to believe one of the following three are true:
There's a different way of accomplishing this that I don't see
I'm on the right track but overlooking something due to my lack of knowledge with EF
I'm the first one to try and EF doesn't support this (very unlikely)
First, does anyone know how I can set up this relationship, i.e., what do these errors mean (responding to #2)? For bonus points, is there another way of doing this that might be better or worse (kinda #1)? Thanks in advance.
Your mapping is nearly correct. But you have to understand that under the hood the Entity Framework wil create a so callled junction table that stores the many to many relationship.
This junction table wil just have two fields, containing the foreign keys which together make up the primary key. Obviously these foreign keys cannot have the same name.EF is smart enough to all figure it out by itself and no maping is necessary. Below a working example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace ManyToManyUnderTheHoodSpike
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<CourseContext>());
using (CourseContext context=new CourseContext())
{
context.Courses.Add(new Course("Top of the bill")
{
PrerequisiteCourses = new List<Course>()
{
new Course("My two cents"),
new Course("Counting to two")
}
});
context.SaveChanges();
}
}
}
public class CourseContext : DbContext
{
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class Course
{
public Course() { }
public Course(string name)
{
Name = name;
}
public string Name {get;set;}
public int CourseId{get;set;}
public ICollection<Course> PrerequisiteCourses{get;set;}
public ICollection<Course> FollowUpCourses{get;set;}
}
}
If you run this code you get a database with two tables: Courses and CourseCourses with as the only fields Course_Id and Course_Id1.
But that is not very readable, so let's make the mapping to make it more readable:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>().HasMany(course => course.PrerequisiteCourses)
.WithMany(course => course.FollowUpCourses)
.Map(data => data.ToTable("Prerequisites")
.MapLeftKey("FollowUpId")
.MapRightKey("PrerequisiteId"));
}
Presto!
I would model like this. I know you wanted only 1 table. But Ef will create the many to many table if you dont. Not sure what you didnt get right without testing. So anyway, here is another option.
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
//nav elements
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<PreReqCourse> Prerequisites { get; set; }
// You can Find follow on courses, by accessing PreReqCourse table, but if you felt this navigation offered enough value, create a post req table too. Using same approach.
// public virtual ICollection<Course> Postrequisites { get; set; }
}
public class PreReqCourse
{
public virtual int Id {get; set;}
public virtual int CourseId { get; set; }
public virtual Course PreReqForCourse { get; set; } //Nav prop
}
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany();
// Leave WithMany empty. You can define in PreReqCourse Table model, you dont need to model from both directions.
modelBuilder.Entity<PreReqCourse>()
.HasRequired(e => e.PreReqForCourse)
.HasForeignKey(f => f.CourseId)
.WithMany(p=>p.PreRequisites);
My problem looks simple. I need to implement a relationships between items in the database. For example: relationship between entities like computer and software shows users that computer stores a specific software and similarly - a software is installed in the specific computer. I think I should implement an entity with source id and target id or something similar. I wrote some code using code first in EntityFramework 6. Here are two classes:
public class ConfigurationItem
{
public int Id { get; set; }
public String Name { get; set; }
public String DeploymentState { get; set; }
public String IncidentState { get; set; }
[DataType(DataType.MultilineText)]
public String Description { get; set; }
[DataType(DataType.MultilineText)]
public String Note { get; set; }
public virtual ICollection<Relationship> Relationship { get; set; }
}
public class Relationship
{
[Key]
public int RelationshipId { get; set; }
[ForeignKey("ConfigurationItem")]
public int SourceId { get; set; }
[ForeignKey("ConfigurationItem")]
public int TargetId { get; set; }
public String Type { get; set; }
public virtual ConfigurationItem Source { get; set; }
public virtual ConfigurationItem Target { get; set; }
}
This solution doesn't work. I need a tip or something what should I try to make it work properly. EF throws an error about foreign key:
The ForeignKeyAttribute on property 'SourceId' on type 'cms_1.Models.Relationship' is not valid. The navigation property 'ConfigurationItem' was not found on the dependent type 'cms_1.Models.Relationship'. The Name value should be a valid navigation property name.
When I try to resolve it EF throws an error about cascade deleting. I know how to disable it but I just don't want to. I need a proper solution with that feature but I think I don't know how to do a model representing given scenario.
Simply - I need to store two foreign keys from entity "A" in the entity "B". How is it possible?
from a quick review , I can tell that you need 3 tables :
first : Computer
second : Software
third : a table , lets call it ComputerSoftware which tell which software has in what computer ( or you can also see it - which computer use what software ), which has ComputerID column and SoftwareID column.
example (source)
class Country
{
public int Id { get; set; }
public virtual ICollection<CountryCurrency> CountryCurrencies { get; set; }
}
class Currency
{
public int Id { get; set; }
}
class CountryCurrency
{
[Key, Column(Order=0)]
public virtual int CountryId { get; set; }
[Key, Column(Order=1)]
public virtual int CurrencyId { get; set; }
public virtual Country Country { get; set; }
public virtual Currency Currency { get; set; }
}
Your issue could be that in the migration file creating those tables, it will have something like
.ForeignKey("dbo.Relationship", t => t.Id, cascadeDelete: true)
This will be set on both tables, ConfigurationItem and Relationship of their Primary Key fields. When you delete one, that config tells SQL Server to delete the relationships as well and the relationship probably has a cascadeDelete: true to the parent. This will cause the cyclical cascading delete issue you are experiencing.
After the migration has been generated, go in and change one or all to cascadeDelete: false and this will fix that issue. This is what EF generates by default if I recall.
I have the entities:
public class Plugin
{
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual Plugin ParentPlugin { get; set; }
public virtual IEnumerable<Setting> Settings { get; set; }
}
public class Setting
{
public virtual int Id { get; set; }
public virtual Plugin Plugin { get; set; }
public virtual int Version { get; set; }
}
The problems is that when I added ParentPlugin, it added a ParentPlugin_Id to the Setting table in the DB. I can't figure out why it did this, however I'd like for Setting to only have a Plugin_Id not both Plugin_Id and ParentPlugin_Id in the database.
So, I would like to know 2 things to receive bounty:
1. How can I remove the ParentPlugin_Id reference from Setting?
2. And why did it do this in the first place (link to doc is ok, I could not find one explaining this)?
I'm using Fluent Nhibernate's automapping, mostly all defaults.
By specifying the exact FK name I was able to remove the extra ParentPlugin_Id. A bit counter intuitive to have to add an FK instead of removing one but...
.Override<Plugin>(m => m.HasMany(c => c.Settings).KeyColumn("Plugin_Id"))
This was figured out by a deeper understanding of the Foreign Key Conventions (https://github.com/jagregory/fluent-nhibernate/wiki/Auto-mapping)
I need help creating the relationship in entity framework as everything I have tried gives me errors when trying to add the migration or if I get passed that then I try to update the database and get an error about indexes with the same name.
public class Profile
{
public Profile()
{
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string VersionCreated { get; set; }
public string DiskLocation { get; set; }
public string Name { get; set; }
public DateTime DateTime { get; set; }
public virtual Product Product { get; set; }
public virtual Instance OriginalInstance { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
public class Instance
{
public Instance()
{
TestResults = new HashSet<TestResult>();
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string UserFriendlyName { get; set; }
public virtual Product Product { get; set; }
public virtual Profile LastKnownProfile { get; set; }
public virtual Computer Computer { get; set; }
public virtual ICollection<TestResult> TestResults { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
The problem with the above classes is that the OrginalInstance property on the Profile class and the LastKnownProfile in the Instance class are supposed to just be foreign keys to those specific tables and they probably won't be the same very often. They can also both possibly be null.
I have tried:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile);
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance);
This gave me an Unable to determine the principal end of an association between the types 'EcuWeb.Data.Entities.Instance' and 'EcuWeb.Data.Entities.Profile'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. error.
and with:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile).WithOptional();
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance).WithOptional();
The database adds a foreign key reference back to itself.
...that the OrginalInstance property on the Profile class and the
LastKnownProfile in the Instance class are supposed to just be foreign
keys to those specific tables and they probably won't be the same very
often. They can also both possibly be null.
In this case you actually want two one-to-many relationships between Profile and Instance if I don't misunderstand your quote above. It would mean that many Profiles can have the same OriginalInstance and that many Instances can have the same LastKnownProfile. The correct mapping would look like this then:
modelBuilder.Entity<Profile>()
.HasOptional(p => p.OriginalInstance)
.WithMany()
.Map(m => m.MapKey("OriginalInstanceId"));
modelBuilder.Entity<Instance>()
.HasOptional(i => i.LastKnownProfile)
.WithMany()
.Map(m => m.MapKey("LastKnownProfileId"));
The lines with MapKey are optional. Without them EF will create a foreign key with a default name.
Also note that you must use HasOptional (instead of HasRequired) if "both can possibly be null".