As far as i know, i have two way to implement many-to-many relation in asp.net mvc using code-first.
1- Fluent Api
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrPersonTitle> HrPersonTitle { get; set; }
}
public class HrPersonTitle
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<HrPerson> HrPerson { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<HrPerson>()
.HasMany(s => s.HrPersonTitle)
.WithMany(c => c.HrPerson)
.Map(t =>
{
t.MapLeftKey("HrPersonId")
.MapRightKey("HrPersonTitleId")
.ToTable("HrMapPersonTitle");
});
}
2-Custom Mapping Table
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrMapPersonTitle> HrMapPersonTitle { get; set; }
}
public class HrPersonTitle
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<HrMapPersonTitle> HrMapPersonTitle { get; set; }
}
public class HrMapPersonTitle
{
public int Id { get; set; }
public int HrPersonId { get; set; }
public int HrPersonTitleId { get; set; }
public virtual HrPerson HrPerson { get; set; }
public virtual HrPersonTitle HrPersonTitle { get; set; }
public string Note { get; set; }
public bool Deleted { get; set; }
}
My questions:
If i choose second way, i am not able to reach HrPersonTitle.Name property from HrPerson model in the view. How can i reach the properties ?
If i choose the first way i can reach the HrPersonTitle.Name but i am not able to add more property in the map file ? How can i add more properties?
Regards.
When you create a M2M without a payload (just the foreign key relationships, no extra data), EF collapses the relationship so that you can query directly without having to explicitly go through the join table. However, if you need a payload, then EF can no longer manage the relationship in this way.
So, if you want to get the title, you have to go through HrMapPersonTitle:
#foreach (var title in Model.HrMapPersonTitle)
{
#title.HrPersonTitle.Name
}
Both these methods seem overkill maybe. I don't know your full intentions however I implement this all the time at work and I use the following:
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrPersonTitle> HrPersonTitles { get; set; }
}
public class HrPersonTitle
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<HrPerson> HrPersons { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<HrPerson>()
.HasMany(s => s.HrPersonTitles)
.WithMany(c => c.HrPersons);
}
If you are using code first and you try and access either mapping within the DbContext it should Lazy Load your information and every property should be accessible.
I do have one question though. Are you sure it should be many to many, do they really have multiple titles?
Related
This is done using MVC .net framework and entity framework "database first" approach. There is a many to many relationship between two tables. They are connected through third table that has combined key as id from first table and id from second table.
public class ManyToManyTable
{
[Required]
[Key, Column(Order=0)]
public int firsttableid { get; set; }
[Required]
[Key, Column(Order=1)]
public int secondtableid { get; set; }
public int something { get; set; }
[ForeignKey("firsttableid")]
public virtual FirstTable firstTable { get; set; }
[ForeignKey("secondtableid")]
public virtual SecondTable secondTable { get; set; }
}
First and Second table have some id which is primary key.
I want to create View and Controller method that enables master detail entry form for this ManyToManyTable. that would have FirstTable in Master and SecondTAble in details, and all to be saved in ManyToManyTable when button Save is pressed.
Of course, both First and Second Table have this property:
public virtual ICollection<ManyToManyTable> ManyToManyTables { get; set; }
What is the easiest way to implement cases like this one?
Thank you!
EF has a default conventions for many-to-many relationships. No need to create specific
mapping class. You have to include navigation properties in both "FirstTable" and "SecondTable" Class as shown below.
public class FirstTable
{
public FirstTable()
{
secondTableProperties = new HashSet<SecondTable>();
}
public int Id { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
public virtual ICollection<SecondTable> secondTableProperties { get; set; }
}
public class SecondTable
{
public SecondTable()
{
FirstTableProperties = new HashSet<FirstTable>();
}
public int Id { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
public virtual ICollection<FirstTable> FirstTableProperties { get; set; }
}
Remove mapping class from DBContext , only include above two classes. Build and run the application , EF will automatically create a Mapping table in SQL server. Usually the Mapping table contains only the primary keys of other two tables.
You can use Fluent API to take some control on the created mapping table
modelBuilder.Entity<FirstTable>()
.HasMany<SecondTable>(s => s.FirstTableProperties)
.WithMany(c => c.secondTableProperties)
.Map(cs =>
{
cs.MapLeftKey("FirstTableId");
cs.MapRightKey("SecondTableId");
cs.ToTable("ManyToManyTable");
});
If you want to work with a join table with additional properties, above mentioned many-to-many relationship won't work . In that case you will have to create two one-to-many relationships as shown below.
public class FirstTable
{
public int Id { get; set; }
public int MyProperty2 { get; set; }
public virtual ICollection<ManyToManyTable> manytomany { get; set; }
}
public class SecondTable
{
public int Id { get; set; }
public int MyProperty2 { get; set; }
public virtual ICollection<ManyToManyTable> manytomany { get; set; }
}
public ManyToManyTable
{
[Required]
[Key, Column(Order=0)]
public int firsttableid { get; set; }
[Required]
[Key, Column(Order=1)]
public int secondtableid { get; set; }
public int AdditionalProperty { get; set; }
public virtual FirstTable first { get; set; }
public virtual SecondTable Second { get; set; }
}
I have a model and i want to put an extra field which can be populated form the same model. IE: Categories and and sub-categories.
In my example, visitor can add an filetype but if file type is under an another file type, he can choose,
But i cant work it out. Below you can see my model.
public class HrFileType
{
[Key]
public int Id { get; set; }
[Display(Name = "Dosya Adı")]
public int Name { get; set; }
public int? HrFileTypeId { get; set; }
public virtual HrFileType HrFileType2 { get; set; }
}
You just need to add a ForeignKeyAttribute like below:
public class HrFileType
{
[Key]
public int Id { get; set; }
[Display(Name = "Dosya Adı")]
public int Name { get; set; }
public int? HrFileTypeId { get; set; }
[ForeignKey("HrFileTypeId")]
public virtual HrFileType HrFileType2 { get; set; }
}
You can also use fluent API to achieve this:
public class HrFileType
{
[Key]
public int Id { get; set; }
[Display(Name = "Dosya Adı")]
public int Name { get; set; }
public int? HrFileTypeId { get; set; }
public virtual HrFileType HrFileType2 { get; set; }
}
public class YourDbContext : DbContext
{
public DbSet<HrFileType> HrFileTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//
modelBuilder.Entity<HrFileType>()
.HasOptional(c => c.HrFileType2)
.WithMany()
.HasForeignKey(c => c.HrFileTypeId);
}
}
Have you tried listing the other file types?
public class HrFileType
{
[Key]
public int Id { get; set; }
[Display(Name = "Dosya Adı")]
public int Name { get; set; }
public List<HrFileType> RelatedTypes { get; set; }
}
then using Entity Frameworks fluent API in the DbContext, try explicitly declaring a many to many map.
modelbuilder.Entity<HrFileType>().HasMany(x => x.RelatedTypes).WithMany();
I'd be very interested to see if this works. It's the only logical solution I can think of without having some kind of parent class.
I'm trying to create a list of train journeys (among other things) in MVC, using code first Entity Framework and wondered how I could map foreign keys for the stations. The Journey model/table will have a DepartureStationID and an ArrivalStationID which will be foreign keys linking to one table/model, called Station.
Here is the code for both these models:
public class Station
{
public int StationID { get; set; }
public string StationName { get; set; }
public string StationLocation { get; set; }
}
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
public virtual Operator Operator { get; set; }
public virtual Station DepartureStation { get; set; }
public virtual Station ArrivalStation { get; set; }
}
There is another foreign key value in there, namely Operator and that has mapped successfully, but the departure and arrivals haven't, and return null values in the view: (#Html.DisplayFor(modelItem => item.DepartureStation.StationName).
When I looked in the database, there had been two additional fields created by EF:
DepartureStation_StationID
ArrivalStation_StationID
And the SQL relationship was between the station table and the two fields above, rather than DepartureID and ArrivalID
So, my question is - Do I need to do something different in the model when referencing the same table for two fields? I don't know why those additional fields were added so I presume I've set up the model incorrectly.
Thanks
For completeness, here's the same thing with fluent configuration.
public class MyDb : DbContext
{
public DbSet<Journey> Journeys { get; set; }
public DbSet<Operator> Operators { get; set; }
public DbSet<Station> Stations { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Journey>()
.HasRequired(j => j.DepartureStation)
.WithMany()
.HasForeignKey(j => j.DepartureID);
builder.Entity<Journey>()
.HasRequired(j => j.ArrivalStation)
.WithMany()
.HasForeignKey(j => j.ArrivalId);
// ... Same thing for operator ...
base.OnModelCreating(builder);
}
}
Edit: To address your above comment about the cascade delete, you can add .WillCascadeOnDelete(false) after .HasForeignKey() and that might help (although you'll then have to delete Journey records manually)
Add the folowing attributes on your navigation properties :
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
[ForeignKey("OperatorID")]
public virtual Operator Operator { get; set; }
[ForeignKey("DepartureID")]
public virtual Station DepartureStation { get; set; }
[ForeignKey("ArrivalID")]
public virtual Station ArrivalStation { get; set; }
}
And of course you need to regenerate your database in order to apply the new configuration.
Hope this will help.
public class ParikshaContext :DbContext
{
public ParikshaContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ParikshaContext>());
}
public DbSet<UserDetail> UserDetails { get; set; }
public DbSet<Standard> Standards { get; set; }
public DbSet<Subject> Subjects { get; set; }
public DbSet<QuestionDescriptor> QuestionDescriptors { get; set; }
public DbSet<QuestionBrief> QuestionBriefs { get; set; }
public DbSet<QuestionCustom> QuestionCustoms { get; set; }
public DbSet<QuestionChoice> QuestionChoices { get; set; }
public DbSet<QuestionMatch> QuestionMatches { get; set; }
public DbSet<Test> Tests { get; set; }
public DbSet<Test_Question> Test_Questions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<QuestionCustom>().ToTable("Custom");
modelBuilder.Entity<QuestionBrief>().ToTable("Brief");
modelBuilder.Entity<QuestionMatch>().ToTable("Match");
modelBuilder.Entity<QuestionChoice>().ToTable("Choice");
}
}
public class QuestionDescriptor
{
public int QuestionDescriptorId { get; set; }
public int StandardId { get; set; }
[ForeignKey("StandardId")]
public virtual Standard Standard { get; set; }
public int SubjectId { get; set; }
[ForeignKey("SubjectId")]
public virtual Subject Subject { get; set; }
public int Rating { get; set; }
public int Difficulty { get; set; }
public DateTime DateOfCreation{get;set;}
public int UserDetailId { get; set; }
[ForeignKeyAttribute("UserDetailId")]
public virtual UserDetail Creator { get; set; }
}
public class QuestionBrief : QuestionDescriptor
{
public String QuestionText { get; set; }
public String Answer { get; set; }
//true for fill in the blanks and false for a loing answers
public bool Short { get; set; }
}
public class Standard
{
public int StandardId { get; set; }
public String StandardName { get; set; }
}
public class Subject
{
public int SubjectId { get; set; }
public String SubjectName { get; set; }
public String SubjectCategory { get; set; }
// public int StandardId { get; set; }
// [ForeignKey("StandardId")]
// public virtual Standard Standard { get; set; }
}
public class Test
{
public int TestID { get; set; }
public DateTime DateOfCreation { get; set; }
public String StandardName { get; set; }
public String SubjectName { get; set; }
public String SubjectCategory { get; set; }
// public int UserDetailId { get; set; }
// [ForeignKey("UserDetailId")]
// public virtual UserDetail Creator { get; set; }
}
public class Test_Question
{
public int Test_QuestionID { get; set; }
public int TestId { get; set; }
[ForeignKey("TestId")]
public virtual Test Test { get; set; }
public int QuestionDescriptorId { get; set; }
[ForeignKey("QuestionDescriptorId")]
public virtual QuestionDescriptor Question { get; set; }
}
public class UserDetail
{
public int UserDetailId { get; set; }
[Required]
[MaxLength(10, ErrorMessage = "UserName must be 10 characters or less"), MinLength(5)]
public String Name { get; set; }
[Required]
public String Password { get; set; }
public String UserRole { get; set; }
public DateTime DateOfCreation{ get; set;}
}
//Match,Custom,Choice classes have been omitted for lack of space (which sounds stupid when i look at the amount of code i have pasted )
I have two problems:-
I cant get a foreign key relation between standard and subjects,it says the relation will cause several cascade delete paths...
if I make a foreign key rlation between test and usedetail it gives me the above problem for mapping the tst_question table .
Also since I am new to EF code first ,please point out my mistakes.all help and disccussion is welcome.
By default EF will create foreign keys will cascade delete. In your model if you delete a Standard there are multiple paths to delete the QuestionDescriptor.
Standard -> QuestionDescriptor
and
Standard -> Subject -> QuestionDescriptor
That is why SQL server does not allow you to do this. See this answer for more details
What you can do is explicitly tell EF to create foreign keys without cascade delete. But this may create data integrity problems. So make sure you understand the consequences.
What you can do is configure the relationships using fluent API with WillCascadeOnDelete(false).
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//other mappings
modelBuilder.Entity<Subject>()
.HasRequired(subject => subject.Standard).WithMany()
.HasForeignKey(subject => subject.StandardId)
.WillCascadeOnDelete(false);
}
I am using EF4 CTP5. Here are my POCOs:
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public decimal Total { get; set; }
public Address ShippingAddress { get; set; }
public Address BillingAddress { get; set; }
}
Is there a way to get Address to be a ComplexType for the Order class? After playing around with this, I'm guessing not, but maybe there's a way I haven't seen.
EDIT: In response to Shawn below, I gave it my best shot:
//modelBuilder.Entity<Order>().Ignore(o => o.BillingAddress);
//modelBuilder.Entity<Order>().Ignore(o => o.ShippingAddress);
modelBuilder.Entity<Order>()
.Property(o => o.BillingAddress.City).HasColumnName("BillingCity");
Fails at runtime with error "The configured property 'BillingAddress' is not a declared property on the entity 'Order'." Trying to use Ignore() doesn't work. Next, the Hanselman article is CTP4, but the CTP5 equivalent is:
modelBuilder.Entity<Order>().Map(mapconfig =>
{
mapconfig.Properties(o => new {
o.Id
, o.Total
, o.BillingAddress.City
});
mapconfig.ToTable("Orders");
});
Fails with error "Property 'BillingAddress.City' of type 'Order' cannot be included in its mapping."
I give up. Maybe the final release will have something like this. Or maybe I need to switch to NHibernate =)
All you need to do is to place ComplexTypeAttribute on Address class:
[ComplexType]
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
Alternatively, you can achieve this by fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Address>();
}
But you cannot have Address type as to be both an Entity and a Complex Type, it's one way or another.
Take a look at this blog post where I discuss this at length:
Associations in EF Code First CTP5: Part 1 – Complex Types
If you want Address to be in the same table as Order, you're going to have to tell EF that in the DbContext OnModelCreating override.
Take a look here: http://weblogs.asp.net/scottgu/archive/2010/07/23/entity-framework-4-code-first-custom-database-schema-mapping.aspx