Entity Framework Many to Many Relation on same entity with additional parameters - entity-framework

I have City entity:
public class City
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<CityDistance> CityDistances { get; set; }
}
and i would like to keep distances between cities.
1. How can achieve this in code first entity framework 6?
Here are my other classes:
public class CityDistance
{
[Key, Column(Order = 0)]
public int CityAID { get; set; }
[Key, Column(Order = 1)]
public int CityBID { get; set; }
public virtual City CityA { get; set; }
public virtual City CityB { get; set; }
public double Distance { get; set; }
}
Is this correct design?
When i run "add-migration Distances" here is the result.
3. Why is it adding another foreign key column named "City_ID"?
CreateTable(
"dbo.CityDistances",
c => new
{
CityAID = c.Int(nullable: false),
CityBID = c.Int(nullable: false),
Distance = c.Double(nullable: false),
City_ID = c.Int(),
})
.PrimaryKey(t => new { t.CityAID, t.CityBID })
.ForeignKey("dbo.Cities", t => t.City_ID)
.ForeignKey("dbo.Cities", t => t.CityAID, cascadeDelete: true)
.ForeignKey("dbo.Cities", t => t.CityBID, cascadeDelete: true)
.Index(t => t.CityAID)
.Index(t => t.CityBID)
.Index(t => t.City_ID);

That's because EF is acting like a simple bookkeeper: CityA, CityB, that's two foreign keys, CityDistances, that's another foreign key, that makes three foreign keys.
EF doesn't know that you intend CityDistances to be the other (inverse) end of the association for either CityA or CityB. You have to indicate this explicitly, either by data annotations:
public class City
{
...
[InverseProperty("CityA"]
public virtual ICollection<CityDistance> CityDistances { get; set; }
}
or by fluent mapping:
modelBuilder.Entity<City>()
.HasMany(c => c.CityDistances)
.WithRequired(cd => cd.CityA)
.HasForeignKey(cd => cd.CityAID);
(By which I implicitly say that, yes, this is correct design, at least technically).

Related

EF 6 Code First relationships using Fluent API. How to set relation between first and third tables or get grouped collection

I have three related entities, here is a structure and relations declared using Fluent API
An event, participating many persons (actually a group of persons) so there is a possibility to collect them all by GroupID. So how to do this?
public class Event
{
public int EventID { get; set; }
public string DocID { get; set; }
public string GroupID { get; set; }
public virtual Person Person { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Person> GroupPerson {get; set}
}
Person entity, here I have all information about person, such as name, surname, birthdate...
public class Person
{
public string PersonID { get; set; }
public string PersonName { get; set; }
public string PersonSurName { get; set; }
public string PersonCode { get; set; }
}
Group entity, here is an information about the group
public class Group
{
public string GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> EventGroup { get; set; }
}
Now I describe relations using Fluent API. Primary keys first of all:
modelBuilder.Entity<Event>().HasKey(e => e.EventID);
modelBuilder.Entity<Person>().HasKey(e => e.PersonID);
modelBuilder.Entity<Group>().HasKey(e => e.GroupID);
Here I will have person related to event
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany()
.HasForeignKey(fk=> fk.PersonID);
Here I will have PersonGroup
modelBuilder.Entity<Group>()
.HasKey(e => e.GroupID)
.HasMany(e => e.EventGroup)
.WithOptional()
.HasForeignKey(f => f.GroupID);
And my question is how to set a relation to get that list of persons in group?
PersonGroup is an Event type and I need list of persons type: Person => ICollection<Person> GroupPerson in Event class.
Given that your relationships are like this:
One event has (is related to) exactly one group (required)
One group has (is related to) zero to many events
One group has (is related to) zero to many people
One person has (is related to) zero to many groups
That is, your relationship Events-Groups is one-to-many, and your relationship Groups-People is many-to-many (I'm assuming that the same person can be in more than one group). There is no direct relationship between Events and People, but a transitive relationship Event -> Group -> People.
Then it can be modelled like this:
public class Event
{
public int EventID { get; set; }
public string DocID { get; set; }
public string GroupID { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Person> People { get { return Group.People; } }
}
public class Person
{
public string PersonID { get; set; }
public string PersonName { get; set; }
public string PersonSurName { get; set; }
public string PersonCode { get; set; }
}
public class Group
{
public string GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> Events { get; set; }
public virtual ICollection<Person> People { get; set; }
}
With these DbSets in the DbContext:
public DbSet<Person> People { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Event> Events { get; set; }
And this EF configuration:
modelBuilder.Entity<Event>()
.HasKey(e => e.EventID)
.Ignore(e => e.People)
.HasRequired(e => e.Group)
.WithMany(g => g.Events);
modelBuilder.Entity<Group>()
.HasKey(g => g.GroupID)
.HasMany(g => g.People)
.WithMany();
modelBuilder.Entity<Person>()
.HasKey(p => p.PersonID);
Note that there is an explicit Ignore() for Event.People. This is because the relationship between Event and Person is transitive, you don't need extra columns in your database for it. If you don't see why, try commenting out the Ignore() line and regenerating the migration, and see that an extra column for the Event ID is generated in the People table (this column doesn't make much sense).
As a consequence the People property in Events is not populated by EF, you have to do it yourself:
public virtual ICollection<Person> People { get { return Group.People; } }
To add people to an Event you should use the Group navigation property, something like this:
public class Event
{
...
public void AddPerson(Person p)
{
this.Group.People.Add(p);
}
}
With this code the migration is generated like this, with four tables: Events, Groups, People and and extra table PeopleGroups for the many-to-many relationship between Person and Group.
public override void Up()
{
CreateTable(
"dbo.Events",
c => new
{
EventID = c.Int(nullable: false, identity: true),
DocID = c.String(),
GroupID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.EventID)
.ForeignKey("dbo.Groups", t => t.GroupID, cascadeDelete: true)
.Index(t => t.GroupID);
CreateTable(
"dbo.Groups",
c => new
{
GroupID = c.String(nullable: false, maxLength: 128),
GroupName = c.String(),
})
.PrimaryKey(t => t.GroupID);
CreateTable(
"dbo.People",
c => new
{
PersonID = c.String(nullable: false, maxLength: 128),
PersonName = c.String(),
PersonSurName = c.String(),
PersonCode = c.String(),
})
.PrimaryKey(t => t.PersonID);
CreateTable(
"dbo.GroupPersons",
c => new
{
Group_GroupID = c.String(nullable: false, maxLength: 128),
Person_PersonID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => new { t.Group_GroupID, t.Person_PersonID })
.ForeignKey("dbo.Groups", t => t.Group_GroupID, cascadeDelete: true)
.ForeignKey("dbo.People", t => t.Person_PersonID, cascadeDelete: true)
.Index(t => t.Group_GroupID)
.Index(t => t.Person_PersonID);
}
If you don't like the names of the columns in the relationship table GroupPersons you can add a .Map() configuration (but you don't really need to do this, as this table isn't directly used, there is no model entity for it, and it doesn't even have a DbSet property in the DbContext).

Entity framework 6, same column for many foreign keys

I need help with this issue, because after many hours of investigation I am stuck.
I created a datamodel from an existing old database, using Entity Framework 6 (I am using Code First approach). This database was multi-company oriented, so most of its tables has a column "Company" that its used as a part of almost all primary keys and foreign keys.
The datamodel creation created all the foreign keys using Fluent API. But this don't helps and when I try to select data from any table I received errors "invalid columna name 'TABLE_COLUMN'. Because in this database usually the columns has different name in every table and the Entity framework can't determine the relation, so its required to map the column names.
So, I can solve the issue using DataAnnotations, and I can do, for example:
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[ForeignKey("BLOQHOR"), InverseProperty("CODHOR")]
public int NUMHOR { get; set; }
[Key]
[Column(Order = 2)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[ForeignKey("BLOQHOR"), InverseProperty("DISTAINIC")]
public int DISTAINIC { get; set; }
[Key]
[Column(Order = 3)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[ForeignKey("BLOQHOR"), InverseProperty("COMPANY")]
public int COMPANY{ get; set; }
What happends now?
The table has another foreign key that also needs the column COMPANY. Because data annotations don't allow me to use the column twice, I can't make the table to work.
I repeat, in the data model, it created a fluent api definition for the second foreign key, but it don't works.
modelBuilder.Entity<CABAJUSTES>()
.HasMany(e => e.AJUSBLOQ)
.WithRequired(e => e.CABAJUSTES)
.HasForeignKey(e => new { e.NUMAJUST, e.COMPANY})
The fact its that everytime I try to get data I received errors like "Invalid column name CABAJUSTES_CODAJUSTE" and "Invalid column name CABAJUSTES_COMPANY". And I am unable to map this second foreign key.
What can I do?
Thanks in advance.
Its a bit hard to follow your table structure, so I've tried to set up a comprehensive example using some common entities anyone should be able to follow. Please comment if this does not fully describe your problem.
Note that I've deliberately used pretty shitty foreign keys to make sure the helping automapping in Entity Framework doesn't help me, and to show that this works with any legacy database design you may have.
First the expected structure in the example
One Company holds many Articles and many Invoices.
One Invoice holds many InvoiceRows.
Each InvoiceRow may optionally refer to an Article.
The actual Entities
class Company
{
public int TheCompanyKey { get; set; }
public virtual ICollection<Invoice> Its_Invoices { get; set; }
public virtual ICollection<Article> Its_Articles { get; set; }
}
class Invoice
{
public int Its_CompanyKey { get; set; }
public int TheInvoiceKey { get; set; }
public string InvoiceNumber { get; set; }
public DateTime InvoiceDate { get; set; }
public virtual Company Its_Company { get; set; }
public virtual ICollection<InvoiceRow> Its_Rows { get; set; }
}
class InvoiceRow
{
public int Rows_Company_Key { get; set; }
public int Its_InvoiceID { get; set; }
public int RowNumber { get; set; }
public int? Its_Articles_ID { get; set; }
public string Text { get; set; }
public double Price { get; set; }
public virtual Invoice Its_Invoice { get; set; }
public virtual Article Its_Article { get; set; }
}
class Article
{
public int TheArticleCompany_Key { get; set; }
public int TheArticleKey { get; set; }
public string ArticleNumber { get; set; }
public double Cost { get; set; }
public double TargetPrice { get; set; }
public virtual Company Its_Company { get; set; }
}
The DbContext with OnModelCreating()
There are multiple ways to generate the required structure, depending on if you think top-down or bottom-up. My take on modelling is to start with the base tables and the describe how children relate to them.
class MyContext : DbContext
{
public MyContext() : base("name=MyContext")
{
}
public virtual IDbSet<Company> Companies { get; set; }
public virtual IDbSet<Invoice> Invoices { get; set; }
public virtual IDbSet<InvoiceRow> InvoiceRows { get; set;}
public virtual IDbSet<Article> Articles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>()
.HasKey(e => e.TheCompanyKey);
modelBuilder.Entity<Article>()
.HasKey(e => new { e.TheArticleCompany_Key, e.TheArticleKey })
.HasRequired(e => e.Its_Company).WithMany(e => e.Its_Articles).HasForeignKey(e => e.TheArticleCompany_Key);
modelBuilder.Entity<Invoice>()
.HasKey(e => new { e.Its_CompanyKey, e.TheInvoiceKey })
.HasRequired(e => e.Its_Company).WithMany(e => e.Its_Invoices).HasForeignKey(e => e.Its_CompanyKey);
modelBuilder.Entity<InvoiceRow>()
.HasKey(e => new { e.Rows_Company_Key, e.Its_InvoiceID, e.RowNumber });
modelBuilder.Entity<InvoiceRow>()
.HasRequired(e => e.Its_Invoice).WithMany(e => e.Its_Rows)
.HasForeignKey(e => new { e.Rows_Company_Key, e.Its_InvoiceID }).WillCascadeOnDelete();
modelBuilder.Entity<InvoiceRow>()
.HasOptional(e => e.Its_Article)
.WithMany()
.HasForeignKey(e => new { e.Rows_Company_Key, e.Its_Articles_ID });
}
}
Finally the generated migration
Running add-migration multikeys in the Package Manager Console window results in the following migration:
public partial class multikeys : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Articles",
c => new
{
TheArticleCompany_Key = c.Int(nullable: false),
TheArticleKey = c.Int(nullable: false),
ArticleNumber = c.String(),
Cost = c.Double(nullable: false),
TargetPrice = c.Double(nullable: false),
})
.PrimaryKey(t => new { t.TheArticleCompany_Key, t.TheArticleKey })
.ForeignKey("dbo.Companies", t => t.TheArticleCompany_Key, cascadeDelete: true)
.Index(t => t.TheArticleCompany_Key);
CreateTable(
"dbo.Companies",
c => new
{
TheCompanyKey = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.TheCompanyKey);
CreateTable(
"dbo.Invoices",
c => new
{
Its_CompanyKey = c.Int(nullable: false),
TheInvoiceKey = c.Int(nullable: false),
InvoiceNumber = c.String(),
InvoiceDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => new { t.Its_CompanyKey, t.TheInvoiceKey })
.ForeignKey("dbo.Companies", t => t.Its_CompanyKey, cascadeDelete: true)
.Index(t => t.Its_CompanyKey);
CreateTable(
"dbo.InvoiceRows",
c => new
{
Rows_Company_Key = c.Int(nullable: false),
Its_InvoiceID = c.Int(nullable: false),
RowNumber = c.Int(nullable: false),
Its_Articles_ID = c.Int(),
Text = c.String(),
Price = c.Double(nullable: false),
})
.PrimaryKey(t => new { t.Rows_Company_Key, t.Its_InvoiceID, t.RowNumber })
.ForeignKey("dbo.Articles", t => new { t.Rows_Company_Key, t.Its_Articles_ID })
.ForeignKey("dbo.Invoices", t => new { t.Rows_Company_Key, t.Its_InvoiceID }, cascadeDelete: true)
.Index(t => new { t.Rows_Company_Key, t.Its_Articles_ID })
.Index(t => new { t.Rows_Company_Key, t.Its_InvoiceID });
}
public override void Down()
{
DropForeignKey("dbo.Articles", "TheArticleCompany_Key", "dbo.Companies");
DropForeignKey("dbo.InvoiceRows", new[] { "Rows_Company_Key", "Its_InvoiceID" }, "dbo.Invoices");
DropForeignKey("dbo.InvoiceRows", new[] { "Rows_Company_Key", "Its_Articles_ID" }, "dbo.Articles");
DropForeignKey("dbo.Invoices", "Its_CompanyKey", "dbo.Companies");
DropIndex("dbo.InvoiceRows", new[] { "Rows_Company_Key", "Its_InvoiceID" });
DropIndex("dbo.InvoiceRows", new[] { "Rows_Company_Key", "Its_Articles_ID" });
DropIndex("dbo.Invoices", new[] { "Its_CompanyKey" });
DropIndex("dbo.Articles", new[] { "TheArticleCompany_Key" });
DropTable("dbo.InvoiceRows");
DropTable("dbo.Invoices");
DropTable("dbo.Companies");
DropTable("dbo.Articles");
}
}
Summary
I believe this describes the OP problem and with a little study gives a good understanding of how Fluent can be used to map entities.
Good luck!

Creating a 1:1 relationship using EF CodeFirst

I am battling with EF code first and trying to map a 1:1 relationship with no joy.
Basically a user can have a userdetail.
I am setting primary keys on both tables.On my UserDetail table has field UserId I am trying to use as the FK.
public class User:BaseModel
{
public virtual UserDetail UserDetail { get; set; }
public string UserName { get; set; }
}
public class UserDetail:BaseModel
{
public virtual User User { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public string UserDetailName { get; set; }
}
public class BaseModel{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
When I try using the add-migration command I get this error
UserDetail_User_Source: : Multiplicity is not valid in Role 'UserDetail_User_Source' in relationship 'UserDetail_User'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
What do I need to do to get this working? Surely this shouldnt be that difficult?
Update below based on comment from #steve-green
I configured the fluentapi like steve suggested
modelBuilder.Entity<User>()
.HasRequired(t => t.UserDetail)
.WithRequiredPrincipal(t => t.User);
however the generated migration step looks wrong to me
CreateTable(
"dbo.Users",
c => new
{
UserId = c.Int(nullable: false, identity: true),
UserName = c.String(),
})
.PrimaryKey(t => t.UserId);
CreateTable(
"dbo.UserDetails",
c => new
{
UserDetailId = c.Int(nullable: false, identity: true),
UserId = c.Int(nullable: false),
UserDetailName = c.String(),
})
.PrimaryKey(t => t.UserDetailId)
.ForeignKey("dbo.Users", t => t.UserDetailId)
.Index(t => t.UserDetailId);
The FK is configured as UserDetailId shouldnt it be UserId?
Slightly modified model
public class UserDetail
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserDetailId { get; set; }
public virtual User User { get; set; }
public int UserId { get; set; }
public string UserDetailName { get; set; }
}
Get rid of the attribute
[ForeignKey("User")
in UserDetail. Also, i would add
int UserDetailId
to User
That should then work.
If you are going to use a base table, then you can't do it with annotations because (as you mention) the key on the required dependent needs to be a foreign key. You will need fluent api code:
modelBuilder.Entity<User>()
.HasRequired(t => t.UserDetail)
.WithRequiredPrincipal(t => t.User);
https://msdn.microsoft.com/en-us/data/jj591620.aspx#RequiredToRequired

EF Code First cascade delete doesn't work

I have 4 tables:
User table
public enum SEX { Male, Female }
public abstract class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public SEX Sex { get; set; }
}
Doctor table inherites from User
[Table("Doctor")]
public class Doctor : User
{
public string Department { get; set; }
public string Occupation { get; set; }
public string CabinetNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
Patient table inherites from User
[Table("Patient")]
public class Patient : User
{
public int InsuranceNumber { get; set; }
public int CardNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
public class Treat
{
public int TreatId { get; set; }
public int DoctorUserId { get; set; }
public int PatientUserId { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual Patient Patient { get; set; }
}
public class HospitalContext: DbContext
{
public HospitalContext() : base("DBConnectionString") {
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HospitalContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Doctor)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.DoctorUserId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Patient)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.PatientUserId)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Treat> Treats { get; set; }
}
I have found much answers here but no one from them works. I have spend a few hours trying to make it work. I know that Entity Framework must enable cascade delete when there is one-to-many relation, but it didn't
Entity Framework doesn't apply cascade deletion with TPT (Table Per Type) inheritance. You can solve this with Code Fist migrations:
CreateTable(
"dbo.Treats",
c => new
{
TreatId = c.Int(nullable: false, identity: true),
DoctorUserId = c.Int(nullable: false),
PatientUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.TreatId)
.ForeignKey("dbo.Doctor", t => t.DoctorUserId, cascadeDelete: true)
.ForeignKey("dbo.Patient", t => t.PatientUserId, cascadeDelete: true)
.Index(t => t.DoctorUserId)
.Index(t => t.PatientUserId);
The important part is cascadeDelete: true. You have to manually add it after migration code generation. After that you will have cascade deletion in your database:
FOREIGN KEY ([DoctorUserId]) REFERENCES [dbo].[Doctor] ([UserID]) ON DELETE CASCADE,
FOREIGN KEY ([PatientUserId]) REFERENCES [dbo].[Patient] ([UserID]) ON DELETE CASCADE

How to set up a hierarchy via Fluent API in Entity Framework

I have a hierarchy of employees, which have boss and subordinates.
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Job { get; set; }
public virtual Employee Boss { get; set; }
public virtual ICollection<Employee> Subordinates { get; set; }
}
I tried to configure it as follows:
internal class EmployeeConfiguracao : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguracao()
{
HasOptional(p => p.Boss).WithMany(p => p.Subordinates).WillCascadeOnDelete(false);
}
}
But the result of Add-Migration Init
Note that Boss is not-null but the is optional (president has no boss)
CreateTable(
"dbo.Employees",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(nullable: false, maxLength: 90),
Job = c.String(),
Boss_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Employees", t => t.Boss_Id)
.Index(t => t.Boss_Id);
How to set up this hierarchy so that it can be employed without boss (as President)
If Boss should be nullable it should be defined like this:
Boss_Id = c.Int(nullable: false)
If it is setup like this:
Boss_Id = c.Int()
then default value for nullable will be used which is true, so it is as you wanted it to be.