Entity framework conventions - entity-framework

I have just started to work with the Entity framework and have created my Repository and context class and interfaces and defined my POCO and I am now trying to understand how the conventions working but am having difficulties and thus far have been unable to do so.
I have the following 2 POCO classes:
Gameweek
public class Gameweek
{
[Key][Required]
public int GameweekID { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime Deadline { get; set; }
[Required]
public int NumberFixtures { get; set; }
public virtual ICollection<Fixture> Fixtures { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
Fixture
public class Fixture
{
[Key][Required]
public int FixtureID { get; set; }
[Required]
public int GameweekID { get; set; }
[Required]
public string HomeTeam { get; set; }
[Required]
public string AwayTeam { get; set; }
public virtual Result Result { get; set; }
}
Result
public class Result
{
[Key][Required]
public int FixtureID { get; set; }
[Required]
public int HomeGoals { get; set; }
[Required]
public int AwayGoals { get; set; }
}
In my model a Gameweek could contain between 0-20 fixtures. Each fixture in the gameweek has one result.
Am I modelling this correctly with the classes and the following convetions:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove < PluralizingTableNameConvention>();
//Table relationships defined here
modelBuilder.Entity<Gameweek>()
.HasMany(g => g.Fixtures);
modelBuilder.Entity<Fixture>()
.HasOptional(f => f.Result);
}
Any tips would be greatly appreciated.

Apparently you want to have a one-to-one relationship between Fixture and Result (a Result entity cannot be shared between multiple Fixtures), right? In this case your mapping is not 100% correct. You need:
modelBuilder.Entity<Fixture>()
.HasOptional(f => f.Result)
.WithRequired();
If you don't add WithRequired EF conventions will assume a WithMany, i.e. a one-to-many instead of one-to-one relationship.
Speaking of conventions, you can actually remove modelBuilder.Entity<Gameweek>().HasMany(g => g.Fixtures);, all [Required] attributes and the [Key] attribute in Gameweek. Only the one-to-one mapping above and the [Key] attribute on Result.FixtureID is needed. Everything else will be mapped automatically based on conventions.

Related

EF core 2.0 one to many share the same table

How to resolve "Navigation properties can only participate in a single relationship." error on below case?
1 company has many Milestone and MissionValueStory, where Milestone and MissionValueStory share same table with different typeId, and each of those has many translation where link up with companyInfoId only
Or BETTER break the relationship between companyInfo and company, and just another query to fetch companyInfo is much easy?
public class Company
{
[key]
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<CompanyInfo> Milestone { get; set; } //multi
public virtual ICollection<CompanyInfo> MissionValueStory { get; set; } //multi
}
public class CompanyInfo
{
[key]
public long Id { get; set; }
public long typeId { get; set; }
[Required]
public long CompanyId { get; set; }
public string Title { get; set; }
public string Text { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public ICollection<Translation> Translation { get; set; }
}
public class Translation
{
[key]
public long Id { get; set; }
public string Title { get; set; }
[Required]
public long CompanyInfoId { get; set; }
public string Language { get; set; }
[ForeignKey("CompanyInfoId")]
public virtual CompanyInfo CompanyInfo { get; set; }
}
modelBuilder.Entity<Company>()
.HasMany(e => e.Milestone)
.WithOne(t => t.Company)
.HasForeignKey(m => m.CompanyId).IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Company>()
.HasMany(e => e.MissionValueStory)
.WithOne(t => t.Company)
.HasForeignKey(m => m.CompanyId).IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CompanyInfo>()
.HasMany(e => e.Translation)
.WithOne(t => t.CompanyInfo).IsRequired();
What you're trying to do is legitimately not supported. At least in the way you're going about this. Fortunately there's a fairly painless solution for you. Use Table Per Hierarchy.
Change the class CompanyInfo to be an abstract class called CompanyInfoBase, and let it be an abstract type. Make typeId abstract on CompanyInfoBase.
Create two new classes that implement CompanyInfoBase:
public class MilestoneCompanyInfo : CompanyInfoBase
{
public override long typeId { get; set; } = MILESTONE_TYPE_ID;
}
public class MissionValueStoryCompanyInfo : CompanyInfoBase
{
public override long typeId { get; set; } = MISSION_VALUE_STORY_TYPE_ID;
}
where MILESTONE_TYPE_ID and MISSION_VALUE_STORY_TYPE_ID are some sort of predefined constants.
Then, in your DbContext's OnModelCreating, use typeId as your discriminator.
It'll look something like this:
modelBuilder.Entity<CompanyInfoBase>()
.HasDiscriminator<long>(nameof(CompanyInfoBase.typeId))
.HasValue<MilestoneCompanyInfo>(MILESTONE_TYPE_ID)
.HasValue<MissionValueStoryCompanyInfo>(MISSION_VALUE_STORY_TYPE_ID);
Since you're changing the name of the entity, it's worth setting the table name to accommodate your existing db. Something like:
modelBuilder.Entity<CompanyInfoBase>().ToTable("CompanyInfos");
Note to other readers: It's only required to define the discriminator like this due to his decision to use a long. If he had just left it undefined then EF Core automagically handles this (by creating a column named discriminator that contains the concrete class names).
Here's a link to the inheritance reference page: https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/inheritance

How can I name a navigation property a different name from it's entity name in my EF POCO?

I have a POCO Entity named Employee.
And then I have a second POCO Entity named Case.
I want a navigation property that looks like instead this:
public class Case : BaseEntity
{
public long EmployeeId { get; set; }
public virtual Employee Employee{ get; set; }
like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
public virtual Employee InitialContact { get; set; }
I want to name my property InitialContact. Not Employee.
But I get this error when EF tries to create the Database:
Unable to determine the relationship represented by navigation property 'Case.InitialContact' of type 'Employee'. Either manually configure the relationship, or ignore this property from the model.
Update 1:
I got it to work like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
}
The primary key is ID in my BaseEntity. Not EmployeeId.
But I have second part to my question.
Here is my Complete Employee POCO:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Hrsa.Core.Generic.Model.Framework.Concrete;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Hrsa.Core.Generic.Model.Lerd
{
public class Employee : BaseEntity
{
[BindNever]
public string Email { get; set; }
[BindNever]
public long OrganizationId { get; set; }
[BindNever]
public string Supervisor { get; set; }
[BindNever]
public string SupervisorEmail { get; set; }
[BindNever]
public string FirstName { get; set; }
[BindNever]
public string LastName { get; set; }
public string Notes { get; set; }
[BindNever]
public long BargainingUnitId { get; set; }
[BindNever]
public long PayPlanId { get; set; }
[BindNever]
public long GradeRankId { get; set; }
[BindNever]
public long PositionTitleId { get; set; }
[BindNever]
public long SeriesId { get; set; }
public bool IsUnionEmployee { get; set; }
public virtual Organization Organization { get; set; }
public virtual BargainingUnit BargainingUnit { get; set; }
public virtual PayPlan PayPlan { get; set; }
public virtual GradeRank GradeRank { get; set; }
public virtual PositionTitle PositionTitle { get; set; }
public virtual Series Series { get; set; }
public virtual ICollection<UnionHours> UnionHours { get; set; }
public virtual ICollection<Case> Cases { get; set; }
[NotMapped]
public string UnionEmployeeYesNo => (IsUnionEmployee) ? "Yes" : "No";
}
}
I want my Employee to have many Cases:
public virtual ICollection<Case> Cases { get; set; }
Here is my complete Cases POCO:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
}
So now my DB looks like this:
So I have my InitialContactId in Cases ok.
But now I need my Case to have many Employees.
So I add this in to my Case POCO:
public virtual ICollection<Employee> Employees { get; set; }
Now it looks like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now when I run it, I get this error again:
Unable to determine the relationship represented by navigation property 'Case.InitialContact' of type 'Employee'. Either manually configure the relationship, or ignore this property from the model.
Update 2:
I found this article for a Many-Many relationship in .Net Core 1:
http://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
So now I have a bridge lookup entity:
public class EmployeeCase
{
[ForeignKey("Id")]
public long EmployeeId { get; set; }
public Employee Employee { get; set; }
[ForeignKey("Id")]
public long CaseId { get; set; }
public Case Case { get; set; }
}
Employee POCO:
Changed:
public virtual ICollection<Case> Cases { get; set; }
to:
// Mapping - Collection of Cases
public virtual ICollection<EmployeeCase> EmployeeCases { get; set; }
Case POCO:
Changed:
public virtual ICollection<Employee> Employees { get; set; }
to:
// Mapping - Collection of Employees
public virtual ICollection<EmployeeCase> EmployeeCases { get; set; }
In my AppDbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region Many-to-Many Employees Cases
modelBuilder.Entity<EmployeeCase>()
.HasKey(ec => new { ec.EmployeeId, ec.CaseId });
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Employee)
.WithMany(e => e.EmployeeCases)
.HasForeignKey(ec => ec.EmployeeId);
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Case)
.WithMany(c => c.EmployeeCases)
.HasForeignKey(ec => ec.CaseId);
#endregion
}
Now when I run I get this error:
An exception of type 'System.Data.SqlClient.SqlException' occurred in Microsoft.EntityFrameworkCore.Relational.dll but was not handled in user code
Additional information: Introducing FOREIGN KEY constraint 'FK_EmployeeCase_Employees_EmployeeId' on table 'EmployeeCase' 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 or index. See previous errors.
Update 3:
Finally got my tables the way I want with this piece of code from:
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Get rid of Cascading Circular error on ModelBuilding
foreach (var relationShip in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationShip.DeleteBehavior = DeleteBehavior.Restrict;
}
#region Many-to-Many Employees Cases
modelBuilder.Entity<EmployeeCase>()
.HasKey(ec => new { ec.EmployeeId, ec.CaseId });
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Employee)
.WithMany(e => e.EmployeeCases)
.HasForeignKey(ec => ec.EmployeeId);
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Case)
.WithMany(c => c.EmployeeCases)
.HasForeignKey(ec => ec.CaseId);
#endregion
base.OnModelCreating(modelBuilder);
}
Update 4:
This did not work after all.
Remvoving the delete behavior for everything messes up my other relationships and I get errors.
How can I fix this?
This is disgusting.
So wishing I did not go Core.
Entity Framework uses conventions to guess how to map your C# model to database objects.
In your case you violate convention by custom name, so you should explain Entity Framework how to map this stuff.
There are two possible ways: attributes and fluent API. I'd suggest to use the latter one.
See section "Configuring a Foreign Key Name That Does Not Follow the Code First Convention" here: Entity Framework Fluent API - Relationships
I have made it a habit of explicitly defining my relationships as EF does not always get them the way I want. I like to create a Mapping folder that contains my entity maps. The fluent api works great for this and inherits from EntityTypeConfiguration.
Try this.
public class CaseMap : EntityTypeConfiguration<Case>
{
public CaseMap()
{
HasKey(m => m.Id)
HasRequired(m => m.InitialContact)
.WithMany(e => e.Cases)
.HasForeignKey(m => m.InitialContactId);
}
}
Almost forgot. You need to tell your DbContext where to find these mappings. Add this to your DbContexts OnModelCreating method.
modelBuilder.Configurations.AddFromAssembly(typeof(MyContext).Assembly);
This is what worked finally for the Cascading Delete circular references on the many-to-many in EF Core:
// Get rid of Cascading Delete Circular references error.
var type = modelBuilder.Model.GetEntityTypes().Single(t => t.Name == "Hrsa.Core.Generic.Model.Lerd.EmployeeCase");
foreach (var relationship in type.GetForeignKeys())
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
You have to get the Entity representing the many to many lookup only.
And from there restrict the DeleteBehavior.

Entity Framework Code first creates unexpected Tables and Relationships

Using EntityFramework 6.1.3, I've got the following
public class RacesContext:DbContext
{
public DbSet<Race> Races { get; set; }
public DbSet<Sailboat> Sailboats { get; set; }
public DbSet<VenueParticipation> VenueParticipations { get; set; }
}
public class Crew
{
public int CrewId { get; set; }
public string Name { get; set; }
}
public class Sailboat
{
[Key]
public int SailboatId { get; set; }
public string Name { get; set; }
public string Skipper { get; set; }
public virtual ICollection<Crew> BoatCrew { get; set; }
}
public class VenueParticipation
{
[Key]
public int Id { get; set; }
public virtual ICollection<Sailboat> Boats { get; set; }
public virtual ICollection<Race> Races { get; set; }
}
public class Race
{
[Key]
public int RaceId { get; set; }
public string Venue { get; set; }
public DateTime Occurs { get; set; }
}
EF creates the Creates the Crews table with the proper PK and FK as I would expect. But creates the Races Sailboats, VenueParticipations tables in an unexpected way. Sailboats get's the expected PK but the unexpected FK VenueParticipation_Id as does Races. I was expecting the VenueParticipations table to get FKs to the others allowing a many to many relationship.. I'm sure I'm missing something here. Any advice would be great.
You can either configure the joining tables VenueParticipationSailboat, VenueParticipationRace with the proper FKs or you can use the fluent API:
modelBuilder.Entity<VenueParticipation>()
.HasMany(t => t.Sailboats)
.WithMany(t => t.VenueParticipations)
.Map(m =>
{
m.ToTable("VenueParticipationSailboat");
m.MapLeftKey("VenueParticipationID");
m.MapRightKey("SailboatID");
});
https://msdn.microsoft.com/en-us/data/jj591620.aspx#ManyToMany

How to map two properties in one code first object to the same parent type

I've been at this for hours and have tried many suggestions I found searching but no luck. I'm using code first EF 5.
The situation is that I have a class Employee. Then I have another class that has two properties on it, both are of type Employee. I want these both to be foreign key constraints but the requirements allow many of the same requests to and from the same users so I can't just use them as keys. I don't really care about Employee having the two collections for navigation but in my working through the problem that seemed a requirement. If it simplifies the problem I can remove those.
I get this message.
System.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'Employee_RequestsForEmployee_Target' in relationship 'Employee_RequestsForEmployee'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
I've tried this using the Fluent API in the OnModelCreation method of the context;
modelBuilder.Entity()
.HasRequired(u => u.ForEmployee)
.WithMany()
.HasForeignKey(u => u.ForEmployeeId);
modelBuilder.Entity<RevenueTransferRequest>()
.HasRequired(u => u.FromEmployee)
.WithMany()
.HasForeignKey(u => u.FromEmployeeId);
The classes in conflict are (I've removed some properties for clarity);
public class Employee : IEmployee
{
[Key]
public string Id { get; set; }
[InverseProperty("ForEmployee")]
public ICollection<RevenueTransferRequest> RequestsForEmployee { get; set; }
[InverseProperty("FromEmployee")]
public ICollection<RevenueTransferRequest> RequestsFromEmployee { get; set; }
}
public class RevenueTransferRequest : IRevenueTransferRequest
{
[Key]
public Guid Id { get; set; }
[Required]
[ForeignKey("ForEmployee")]
public String ForEmployeeId { get; set; }
[InverseProperty("RequestsForEmployee")]
public Employee ForEmployee { get; set; }
[Required]
[ForeignKey("FromEmployee")]
public String FromEmployeeId { get; set; }
[InverseProperty("RequestsFromEmployee")]
public Employee FromEmployee { get; set; }
}
Any help would be much appreciated. Thanks in advance.
I never did figure out how to do it using data annotations but using the Fluent API I was able to do it. What I was missing was that I had to specify in the HasMany() method what the relationship on the other side was which I assumed was understood through the data annotations and conventions.
This is called in the DbContext OnModelCreating override (The WillCascadeOnDelete(false) is related to another issue).
modelBuilder.Entity<RevenueTransferRequest>()
.HasRequired(e => e.FromEmployee)
.WithMany(x=>x.RequestsFromEmployee)
.WillCascadeOnDelete(false);
modelBuilder.Entity<RevenueTransferRequest>()
.HasRequired(e => e.ForEmployee)
.WithMany(x => x.RequestsForEmployee)
.WillCascadeOnDelete(false);
With the classes:
[Key]
public String Id { get; set; }
public String BusinessUnitLeaderId { get; set; }
public Employee BusinessUnitLeader { get; set; }
[Required]
[MaxLength(150)]
public String DisplayName { get; set; }
public ICollection<Project> BusinessUnitLeaderProjects { get; set; }
public ICollection<RevenueTransferRequest> RequestsForEmployee { get; set; }
public ICollection<RevenueTransferRequest> RequestsFromEmployee { get; set; }
public class RevenueTransferRequest
{
[Key]
public Guid Id { get; set; }
[Required]
public String ForEmployeeId { get; set; }
public Employee ForEmployee { get; set; }
[Required]
public String FromEmployeeId { get; set; }
public Employee FromEmployee { get; set; }
[Required]
public String ProjectId { get; set; }
public Project Project { get; set; }
[Required]
public Double? TransferAmount { get; set; }
public int WorkflowState { get; set; }
}

Defining foreign key constraints with Entity Framework code-first

I have following entity class called Code. It stores categories of different kinds - the data for which I would have otherwise needed to create many small tables e.g. User Categories, Expense Categories, Address types, User Types, file formats etc.
public class Code
{
public int Id { get; set; }
public string CodeType { get; set; }
public string CodeDescription { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
:
: // many more
}
The class Expense looks like this:
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public double Amount { get; set; }
public DateTime ExpenseDate { get; set; }
}
With the above class definitions, I have established 1:many relation between Code and Expense using the CategoryId mapping.
My problem is, I want to map the SourceId field in Expense to the Code object. Which means, Expense object would contain
public Code Source { get; set; }
If I use this, at runtime I get an error about cyclic dependencies.
Can someone please help?
You will need to disable cascading delete on at least one of the two relationships (or both). EF enables cascading delete by convention for both relationships because both are required since the foreign key properties are not nullable. But SQL Server doesn't accept multiple cascading delete paths onto the same table that are introduced by the two relationships. That's the reason for your exception.
You must override the convention with Fluent API:
public class Code
{
public int Id { get; set; }
//...
public virtual ICollection<Expense> Expenses { get; set; }
//...
}
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public virtual Code Source { get; set; }
//...
}
Mapping with Fluent API;
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Category)
.WithMany(c => c.Expenses)
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Source)
.WithMany()
.HasForeignKey(e => e.SourceId)
.WillCascadeOnDelete(false);