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.
Related
I have a problem with Entity Framework, I think I have a problem defining the one to many mapping, but I can't seem to find it.
When I try to save my changes and add my info to the database I get the following error:
Invalid object name 'Results1'
With Results being the name of one of my tables, for some reasons it's adding the 1 at the end.
I have a Results table where I need to store an Id and several other information regarding results of a test, then I have a Reasons table where I need to store several reasons for each Result.Id.
This is how I defined the Reasons class:
public class Reasons
{
public int Id { get; set; }
public int ResultId { get; set; }
public string Description { get; set; }
public Results Results { get; set; }
}
Then I have the Results class:
public class Results
{
public int Id { get; set; }
//Other properties
public ICollection<Reasons> Reasons { get; set; }
}
This is my Reasons configuration:
configurator.
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
configurator
.HasRequired<Results>(s => s.Results)
.WithMany(g => g.Reasons)
.HasForeignKey<int>(s => s.IdResults);
Solved: the only problem was that the "Reasons" and the "Results" class did not completely match the data model. Once that was repaired the problem disappeared.
Rename your foreign key in Reasons class from ResultId to ResultsId or explicitly use [ForeignKey("Results")] attribute above it. From here:
EF makes a property as foreign key property when its name matches with
the primary key property of a related entity
In your case it should be:
// Foreign key for Results
public int ResultsId { get; set; }
// Navigation property
public Results Results { get; set; }
Remove your configuration instructions. There is no need to use fluent api to configure your database scheme since entity framework will do it by self. Here are examples how to configure one to many relations via code first approach
As was answered in comments it is a good practice to use singular name for a model in order to use plural form for navigational properties.
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).
I`d like to add foreign key on unique not null attribute in second table. Here how it look like:
public class T_AlarmTresholds
{
[Key]
public int Id { get; set; }
....
....
public Guid MeasurementGuid { get; set; }
[ForeignKey("MeasurementGuid")]
public virtual T_Measurements Measurement { get; set; }
}
public partial class T_Measurements
{
public int Id { get; set; }
[Index("UC_Guid", IsUnique = true)]
public Guid GUID { get; set; }
}
Here is model builder:
modelBuilder.Entity<T_Measurements>()
.HasMany(x => x.T_AlarmTresholds)
.WithRequired(x => x.Measurement)
.HasForeignKey(x => x.MeasurementGuid);
Entity framework throws error while SQL Server accept this solution. Here is error in Visual Studio while debugging:
{"One or more validation errors were detected during model
generation:\r\n\r\nT_Measurements_T_AlarmTresholds_Source_T_Measurements_T_AlarmTresholds_Target:
: The types of all properties in the Dependent Role of a referential
constraint must be the same as the corresponding property types in the
Principal Role. The type of property 'MeasurementGuid' on entity
'T_AlarmTresholds' does not match the type of property 'ID' on entity
'T_Measurements' in the referential constraint
'T_Measurements_T_AlarmTresholds'.\r\n"}
You don't put the foreign key on the virtual object itself you need to create a new property in T_AlarmTresholds:
public int T_MeasurementsId{get; set;}
to act as the foreign key. The dependency property (the virtual one) will then link up automatically. You also need to remove the ForiegnKey attribute from the virtual property.
Edit:
Just spotted that your code doesn't use the Id column as the primary key but instead uses the Guid, so you'll instead want to add the foreign key attribute to the MeasurementGuid property.
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.
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?