Linking multiple properties to the same table in Entity Framework - c#

Let me foreword this by saying this is my first real experience with both Entity Framework and relational databases in general. If I am doing it completely wrong, please tell me.
I want my data structured as something like this (Cut down on the "extra" code):
Indicators {
int SomeText1TranslationRef
List<Translation> SomeText1Translations
int SomeText2TranslationRef
List<Translation> SomeText2Translations
}
Measures {
int SomeText3TranslationRef
List<Translation> SomeText3Translations
int SomeText3TranslationRef
List<Translation> SomeText4Translations
}
Translation {
Int TranslationID
String LanguageCode
String Text
}
So in essence, the indicators table would have a list of SomeText1 Translations as well as SomeText2, all joined using the TranslationID through the "Ref" properties.
I have the translation properties annotated with [ForeignKey("....Ref")].
I expected this to work magically as the rest of the framework seems to, but instead the translation table gets columns named "SomeText1TranslationRef" and "SomeText2TranslationRef".
Am I doing this wrong?
I am looking at other features of Entity Framework and see an annotation for "InverseProperty". Is it something which may help?

I'm not 100% clear on your goal, but if an Indicator can have many Text1 translations and many Text2 translations, then that is 2 many-to-many relationships. Same for Measures. EF will need a join/bridge/junction table for this (IndicatorTranslation and MeasureTranslation). You can explicitly create this table, or let EF do it behind the scenes:
Indicator {
// other indicator fields
public virtual List<Translation> SomeText1Translations
public virtual List<Translation> SomeText2Translations
}
Measure {
// other measure fields
public virtual List<Translation> SomeText3Translations
public virtual List<Translation> SomeText4Translations
}
Translation {
Int TranslationID
String LanguageCode
String Text
// Use inverse attributes or fluent code to tell EF how to connect relationships
[InverseProperty("SomeText1Translations")]
public virtual ICollection<Indicator> TranslationForIndicatorText1 { get; set; }
[InverseProperty("SomeText2Translations")]
public virtual ICollection<Indicator> TranslationForIndicatorText2 { get; set; }
[InverseProperty("SomeText3Translations")]
public virtual ICollection<Measure> TranslationForMeasureText3 { get; set; }
[InverseProperty("SomeText4Translations")]
public virtual ICollection<Measure> TranslationForMeasureText4 { get; set; }
}

I'm happy to be corrected if I'm wrong since it's nothing I've tried for quite a while, but as far as I'm aware, EF is still not able to create relationships from one property on a type to two different other types, or vice versa, even with constraints that would make it valid.
In your case, you would end up with the 4 navigation properties being required on your translation. (int IndicatorRef1, int IndicatorRef2, int MeasureRef3, int MeasureRef4). Most wouldn't call it a dream scenario.
I asked a similar question a couple of years ago, and have since then sort of concluded that i was foolish trying to get EF to solve all my problems.
So here's an answer to what you're trying to achieve, and perhaps even a solution to 2 of your questions:
Don't rely on EF handle any scenario. Actually, pretty much don't rely on EF to handle relationships at all other than 1-1, 1-* or *-*. And some forms of inheritance.
In most other cases, you will end up with one navigation property for each type you're trying to reference, with data being populated with nulls for each navigation property but the one specifically targeted.
The good news? You don't have to rely on EF for it. The main advantage of EF is it's productivity. For certain cases, it's still worth leveraging EF, but providing your own methods of productivity. If you want to get a set of indicators with 2 collections of translations based on a ref, simply create a method that provides it.
Something like
public IQueryable<Indicators> SetOfIndicatorsWithTranslations()
{
// Untested query that might need some fixing in an actual implementation
return ctx.Set<Indicators>().Select(ind => new Indicators() {
Text1Ref= ind.Text1Ref, // whatever the property is
Text1RefList = ctx.Set<Translation>().Where(t => t.TranslationId == ind.Text1Ref),
Text2Ref= ind.Text2Ref,
Text2RefList = ctx.Set<Translation>().Where(t => t.TranslationId == ind.Text2Ref),
});
}
Now that's a query EF will handle for you gracefully.
There are of course a lot more elegant solutions to something like it. The important part is really that it's sometimes worth doing it yourself rather than restricting yourself to the capabilities of your tool of choice. (Well, at least that's the important part that I eventually learned :) )

Long story short, the caveat is the "Core" part of .Net Core. EF Core does not support convention-over-configuration many-to-many relationships yet (See here).
The only way to achieve this is to manually create the junction tables as Steve suggested. Here is all the information needed: https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
In previous versions of Entity Framework, this model definition was sufficient for EF to imply the correct type of relationship and to generate the join table for it. In EF Core 1.1.0, it is necessary to include an entity in the model to represent the join table, and then add navigation properties to either side of the many-to-many relations that point to the join entity instead:
The above link will most likely be updated with time so for context purposes, here is the code which goes along with it:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class BookCategory
{
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Alternatively, using Fluent:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookCategory>()
.HasKey(bc => new { bc.BookId, bc.CategoryId });
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Book)
.WithMany(b => b.BookCategories)
.HasForeignKey(bc => bc.BookId);
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Category)
.WithMany(c => c.BookCategories)
.HasForeignKey(bc => bc.CategoryId);
}

Related

How to use Fluent API for creating a simple one-to-many relationship

Let me first give some background: I'm creating an application, which should handle a DB. That DB might evolve (extra tables/columns/constraints might be added, but nothing gets removed, in fact the DB gets more and more elaborated).
I started with a "Database First" approach and as a result, I have created an Entity Framework diagram, with according classes in *.cs files. Two of those files are (only some interesting fields):
Area.cs:
public partial class Area
{
public Area() { }
public string Name { get; set; }
public int Id { get; set; }
}
Location.cs:
public partial class Location
{
public Location() { }
public string Name { get; set; }
public int Id { get; set; }
...
public Nullable<int> AreaId { get; set; }
}
This is generated from a version of the DB, which does not cover constraints, and now I would like to add a ForeignKeyConstraint to the corresponding Entity Framework model:
Location.AreaId is a foreign key towards Area.Id
There are many Location objects for one Area object
It's the idea to prevent deletion of Area objects, being referred to by Location objects).
I believe this should be done as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Area>().HasKey(t => t.Id); // Creation of primary key
modelBuilder.Entity<Location>().HasKey(t => t.Id); // Creation of primary key
modelBuilder.Entity<Location>().HasRequired(n => n.AreaId)
.WithMany(...)
.HasForeignKey(n => n.AreaId);
...
This, obviously, does not work. I'm missing following information:
My "Area.cs" file does not contain a reference to the Location object (as this version of the DB does not contain constraints, this has not been added by the "database first" wizard), should I add this or can I solve my issue without?
What do I need to fill in instead of the ellipsis .WithMany(...)?
Extra question: I'm aware of the ForeignKey directive. Should I replace public Nullable<int> AreaId { get; set; } in "Location.cs" by [ForeignKey("AreaId")], followed by public virtual Area Area { get; set; }?
Edit
Important remark: as "Location.cs" and "Area.cs" are auto-generated, I like to minimise changes in those files.
Next edit
Meanwhile I've updated my "Location.cs" file as follows:
...
// public Nullable<int> AreaId { get; set; }
[ForeignKey("AreaId")]
public Area Area { get; set;}
....
My OnModelCreating() has been changed into:
modelBuilder.Entity<Location>().HasRequired(n => n.Area)
.WithMany(...)
.HasForeignKey(n => n.Area);
That leaves only the ellipsis problem to be solved.
Another edit
Since it takes such a long time for an answer (even for a comment), I've decided to add following line of source code to my "Area.cs" file:
public virtual ICollection<Location> Locations { get; set; }
I've then filled in the ellipsis as follows:
modelBuilder.Entity<Location>().HasRequired(l => l.Area)
.WithMany(a => a.Locations)
.HasForeignKey(l => l.Area);
Now just one question: how can I mention that the link between the Area and the Location should be handled by Location.AreaId and Area.Id (I know that Location.AreaId is the foreign key, but how can I know that it refers to Area.Id)?
Thanks in advance
The simple answer to your last question. EF is recognizing that Area.Id is a primary key so connects Location.AreaId to Area.Id
Also, here is a simple guide on how to do it.

Using AutoMapper when having a LINQ statement with multiple .Include

So I am new to using AutoMapper and have been able to get basic mapping of items no problem with using LINQ statements that do not use the .Include("blah"), however when I have a statement for example like this;
var courses = dc.Courses.Include("Students")
.Include("CourseTimes")
.OrderBy(n=>n.CourseSemester.courseStart);
AutoMapper doesnt seem to pull any of the information from ("Students") or ("CourseTimes"). My objects are posted below and to give a quick breakdown, Courses contain a List of Students(I need Students so I can count the number of people in each course), Courses also contain a List of CourseTimes(so I can display the times of each class for the given course). Here is my ViewModel that I am using.
public class UserIndexCourseList
{
[Key]
public int courseId { get; set; }
public string courseCode { get; set; }
public string courseName { get; set; }
// this simply stored a count when I did Students.Count without using AutoMapper
public int size { get; set; }
public string room { get; set; }
public List<CourseTime> courseTimeSlot { get; set; }
}
Here are some of the AutoMapper statements I tried to used but had no luck with it working.
//to the viewmodel
Mapper.CreateMap<Models.Course, ViewModels.UserIndexCourseList>();
Mapper.CreateMap<Models.CourseTime, ViewModels.UserIndexCourseList>();
Mapper.CreateMap<Models.Student, ViewModels.UserIndexCourseList>();
//from the viewmodel
Mapper.CreateMap<ViewModels.UserIndexCourseList, Models.Course>();
Mapper.CreateMap<ViewModels.UserIndexCourseList, Models.CourseTime>();
Mapper.CreateMap<ViewModels.UserIndexCourseList, Models.Student>();
So essentially how can I create a Map which will also pull all of that information so I can use it with my ViewModel that was posted above ? I have tried numerous options but no luck.
I apologize for a similar post I made ahead of time but I don't think I explained myself well enough the first time. Thanks again!
By convention automapper maps properties with same names, so in your case you can do this:
public class UserIndexCourseList
{
...
//rename field so it has same name as reference
public List<CourseTime> CourseTimes{ get; set; }
}
or you can rename reference in EF so it's name is courseTimeslot.
Another solution if you don't want to rename your property is to add options to map, for example:
Mapper.CreateMap<Models.Course, ViewModels.UserIndexCourseList>()
.ForMember(d => d.courseTimeSlot,
opt => opt.MapFrom(src => src.CourseTime));
Edit: also they have great documentation, your case is described here: https://github.com/AutoMapper/AutoMapper/wiki/Projection
"Because the names of the destination properties do not exactly match up to the source property (CalendarEvent.Date would need to be CalendarEventForm.EventDate), we need to specify custom member mappings in our type map configuration..."

Entity Framework Code First Relationships; what am I missing?

I'm experimenting with EF5 Code First and I am using the models (show below).
When I look at the database that is created, I am confused because I do not see anything in the Track table that points to the Category table. Category has a FK pointing back to Track but that means that there are going to be duplicates of the categories?
A little background: I am trying to build a model that has tracks and every track can have 1 to N Categories. All of the categories are already defined, that is they are basically a lookup and I plan to create them in the seed method when database is created.
I think I am not understanding something obvious... When I query a track, how will I know what category it contains?
Thx
public class Track : IAuditInfo
{
public Int32 Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public String Data { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public ICollection<Category> Categories { get; set; }
public Track()
{
Categories = new List<Category>();
}
}
public class Category
{
public Int32 Id { get; set; }
public Boolean IsVisible { get; set; }
public String DisplayName { get; set; }
}
Your current model is a one-to-many relationship between tracks and categories.
This usually implemented, as you have noted that entity framework does, using a foreign key on the many side (category) to the one side (track).
If I understand you correctly, what you want is a many-to-many relationship. Many tracks can be related to the same category, and a single track can belong to many categories.
To let entity framework understand that you want a many-to-many relationship you can simply add a ICollection property to your category class.
So both your classes should have a collection of the other class.
I.e. tracks have many categories and categories have many tracks.
For more information you can also see: http://msdn.microsoft.com/en-us/data/hh134698.a.nospx
Olav is right, your data model at the moment is not telling Entity Framework that there is a many-to-many relationship in there.
The simplest way to resolve this is to add
public virtual ICollection<Track> Tracks { get; set; }
to your Category class.
However... You may not want to pollute your domain model with artefacts that are not relevant to your domain. More importantly, when you do it this way, it is up to Entity Framework to figure out what to call the binding table. Prior to EF6 this naming is non deterministic (see http://entityframework.codeplex.com/workitem/1677), which may mean that two different machines compiling the same code will decide on different names for that table and cause some interesting migration problems in your production system.
The answer to both problems is to always explicitly manage many-to-many relationships with Fluent Configuration.
In your Data Context class, override the OnModelCreating, something like this:
public class MyDb : DbContext
{
public IDbSet<Track> Tracks { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Track>()
.HasMany(t => t.Categories)
.WithMany()
.Map(c => c.ToTable("CategoriesForTracks"));
}
}
If you do this, you don't need to add a navigation property to your Category class, though you still can (if you do, you should use the overload for WithMany that allows you to specify a property).
Relationships between entities and how to map that to a relational database is inherently hard. For anything other than the simplest parent-child relationships you will want to use the fluent API to make sure you actually get what you want.
Morteza Manavi has a really good blog series describing relationships in EF Code First in exhaustive detail.
NOTE
You should usually make navigation properties virtual. So, you should change your Category class like this:
public virtual ICollection<Category> Categories { get; set; }
In theory, not making it virtual should just cause eager loading rather than lazy loading to happen. In practice I have always found lots of subtle bugs appearing when my navigation properties are not virtual.

Why do I need both the ID and the virtual collection in C# EF?

I'm a Java guy and I'm working on learning the .Net realm. One of the things I'm learning is EF4, and I noticed this interesting thing. When you declare an entity with a 1:n relationship to another entity, you have to do something like this:
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
My question is:
Is there a good reason that the framework requires both declarations?
In the Java world, the framework is smart enough to figure out what the primary key is and adds that record to the DB without having to have a separate field on the entity class. Why didn't .Net follow suit on this minor, but annoying, issue?
You don't have to do that. In fact you shouldn't because you can leave your object in an inconsistent state. Let's say you have:
public class Category {
public Int32 Id { get; set; }
}
public class SomeClass {
public Int32 Id { get; set; }
public virtual Category Category { get; set; }
}
This is valid. You just need to tell EF in its configuration how to find the foreign key. Based on the above it will try to use SomeClass.Category_Id, but you can change to it whatever you'd like.
EDIT: If you want to change the foreign key you can do so by adding a configuration class and adding it during the OnModelCreating event:
internal class ForSomeClassEntities : EntityTypeConfiguration<SomeClass> {
public ForSomeClassEntities(String schemaName) {
this.HasRequired(e => e.Category)
.WithMany()
.Map(map => map.MapKey("CategoryId"));
this.ToTable("SomeClass", schemaName);
}
}
In your overridden Context class:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations
.Add(new ForSomeClassEntities("SomeSchema"))
;
}
Using the same classes above this would tell EF to look for a foreign key property called SomeClass.CategoryId instead.
When you add a virtual Collection/Model to your model, Entity Framework automatically adds a <ClassName>Id field to it. Developers usually decide to explicitly declare the <ClassName>Id for readability purposes, so that other developers know explicitly that it is there (not relying on the convention over configuration). That is the case for tutorials, for example, because they want to make things clear.
Entity Framework is smart enough to use the existing <ClassName>Id if it has already been explicitly declared. Thus, addressing other responses to this topic, it doesn't lead to an inconsistent state. Also, from a performance perspective, it still fits the Lazy Loading pattern, because preloading an int (the ID, in this case) is still very fast (and not even remotely comparable to pre-loading a whole object).

Entity Framework - Determining if a Relationship Exists without a navigation property on one end

I have the following two entities (using Code First) in my application:
public class Note
{
public int NoteId { get; set; }
public string Text { get; set; }
}
public class Decision
{
// PK/FK
public int NoteId { get; set; }
// other fields ...
public virtual Note Note { get; set; }
}
I configured my relationship like this:
modelBuilder.Entity<Decision>().HasRequired(d => d.Note).WithOptional();
A Decision must have a note but a Note does not always have a decision. A 1:1 mapping with one side being optional.
I would like a property on my note that lets me know if there is a decision for it. Something like:
public bool HasDecision
{
get
{
// not sure what to do here
}
}
Is there a way to do this without having Decision be a lazy loaded property on Note?
You would need to do an explicite query. There is no such thing like "lazy loading proxies for scalar properties". Lazy loading is only supported for navigation properties. Your entity must have a reference to a context if you want to have HasDecision as a property on the entity. I would prefer to create a repository or service method like so:
public bool HasDecision(Note note)
{
return _context.Decisions.Any(d => d.NoteId == note.NoteId);
}

Categories

Resources