I am trying to build a model with Entity Framework - Code First in which I use a "Exam" class and a "Subject" class that have a many-to-many relationship.
A "Exam" contains a list of "Subject" (Subjects).
A "Subject" contains a list of "Exam" (Exams).
Both "Exams" and "Subjects" are virtual properties.
When I use context.Exams.Include("Subjects").ToList();, I get all the exams and all the subjects related to each exam, which is OK. The problem is I also get all the exams related to the subjects.
Result :
Exam 1
Subject 1
Exam 3
Exam 4
Subject 2
Exam 3
Exam 2
...
In this particular case, I don't need the exams related to the subjects. I just need the following data :
Exam 1
Subject 1
Subject 2
Exam 2
...
Is there a way to include "Subjects" but without the "Exams" property ?
Thank you.
Function
public List<Exam> GetAllExams()
{
using (var context = new PedagogieContext())
{
return context.Exams.Include("Subjects").ToList();
}
}
Classes
public class Exam
{
public int Id { get; set; }
public string ExamLabel { get; set; }
public virtual List<Subject> Subjects { get; set; }
}
public class Subject
{
public int Id { get; set; }
public string SubjectLabel { get; set; }
public virtual List<Exam> Exams { get; set; }
}
Mappings
class SubjectMap : EntityTypeConfiguration<Subject>
{
public SubjectMap()
{
this.HasKey(e => e.Id);
this.Property(e => e.Id).HasColumnName("KeyDiscipline");
this.Property(e => e.SubjectLabel).HasColumnName("DisciplineLib");
this.ToTable("vpDisciplines");
}
}
class ExamMap : EntityTypeConfiguration<Exam>
{
public ExamMap()
{
this.HasKey(e => e.Id);
this.Property(e => e.Id).HasColumnName("KeyExamen");
this.Property(e => e.ExamenLabel).HasColumnName("ExamenLib");
this.ToTable("vExamens");
this.HasMany(e => e.Subjects)
.WithMany(d => d.Exams)
.Map(m =>
{
m.ToTable("vrExamensDisciplines");
m.MapLeftKey("KeyExamen");
m.MapRightKey("KeyDiscipline");
});
}
}
Context
public class PedagogieContext : DbContext
{
public PedagogieContext()
: base(ConnectionStringManager.GetConnectionString())
{
this.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Exam> Exams { get; set; }
public DbSet<Subject> Subjects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ExamMap());
modelBuilder.Configurations.Add(new SubjectMap());
}
}
The "problem" is that Entity Framework executes relationship fixup whenever you get data from the database (and on many more occasions). This is the process where EF automatically populates navigation properties (like Subject.Exams) of entities in its cache.
You are fetching exams and subjects and EF kindly populates their Subjects and Exams, respectively. There is no way to stop EF from doing this (some may think that setting Configuration.AutoDetectChangesEnabled = false will do that, but no).
Note that you don't get more exams from the database than you get from the query, if that's what you're worried about. It's just that EF also creates the associations. In a debug view you could expand the collections endlessly without ever hitting the database.
The solution is not to display Subject.Exams. If this is for serialization, you have to block circular references. Some serializers (like Json.Net) have settings to do that.
Thank you all for your answers.
Indeed, entity framework doesn't load more exams than expected. It just populates the sub-exams with the exams already loaded.
My problem was actually a circular reference serializer issue.
I chose to use DTO (with automapper) to specify exactly the data I need in my view.
http://cpratt.co/using-automapper-getting-started/
Related
I have a set of models representing legal cases. One of the actions a user can do on a case is generate a document. This action is saved as a History entity, with an associated HistoryFile entity that contains the data about the file. Other actions may result in a History entity, with zero or multiple associated HistoryFile entities.
Cut-down versions of these two classes looks like this...
public class History {
public int Id { get; set; }
public ObservableCollection<HistoryFile> HistoryFiles { get; set; }
}
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
}
The next requirement is that a user can pick up on a document that was previously generated and continue working on it. The bit where I'm getting stuck is that the HistoryFile entity needs a reference back to the History entity that held the previous version. This means that I need to add two lines of code to the HistoryFile entity...
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
public int? PreviousHistoryId { get; set; }
public virtual History PreviousHistory { get; set; }
}
This means that there are two links from a HistoryFile to a History, one required one which is the parent History entity (via the History property) and an optional one via the PreviousHistory property.
I can't work out how to set this up for EF Core. As the code stands now, when I try to add a migration, I get the following error...
Cannot create a relationship between 'History.HistoryFiles' and 'HistoryFile.PreviousHistory' because a relationship already exists between 'History.HistoryFiles' and 'HistoryFile.History'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'HistoryFile.PreviousHistory' first in 'OnModelCreating'.
I tried adding the following to my DbContext...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
...but it didn't make any difference.
Anyone able to tell me how I configure this so that EF Core knows that there are two distinct links between the two entities?
I'm using EF Core 5.0.7 in a .NET5 project in case it makes a difference.
Thanks
Got it.
I needed to add the following two lines to the History class...
public virtual ICollection<HistoryFile> HistoryFilesParentHistory { get; set; }
public virtual ICollection<HistoryFile> HistoryFilesPreviousHistory { get; set; }
...and then change the code I added to the DbContext to look like this...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFilesParentHistory)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFilesPreviousHistory)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
This worked fine.
This question already has answers here:
EF Core returns null relations until direct access
(2 answers)
Closed 5 years ago.
I'm using .net core 2 mvc, I tried to build many-to-many relationship between Users and Steps.
the relationship is doen but when I query for the record I get user = null.
Hier is my code:
(applicationUser model):
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
(Steps model):
public class Steps
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
StepsUsers model:
public class StepsUsers : IAuditable
{
public int StepId { get; set; }
public Steps Step { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
In DbContext I did this :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<StepsUsers>()
.HasKey(s => new { s.StepId, s.UserId });
builder.Entity<StepsUsers>()
.HasOne(su => su.Step)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.StepId);
builder.Entity<StepsUsers>()
.HasOne(su => su.User)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.UserId);
}
public DbSet<MyApp.Models.StepsUsers> StepsUsers { get; set; }
Now, when I query for an instance of StepsUsers with specific StepId I get all de fields correct except the User field is null
var stepUsers = await _context.StepsUsers.Where(s => s.StepId == id).ToListAsync();
I did the same code for another two tables and it works fine, I don't know why it is like this, any suggestion 1?
The cause of your problems is that your forgot to declare your To-many relations as virtual. Another improvement would be to declare them as virtual ICollection instead of List. After all, what would ApplicationUser.StepUser[4] mean?
If you configure a many-to-many relationship according to the entity framework conventions for many-to-many, you don't need to mention the junction table (StepsUsers). Entity framework will recognize the many-to-many and will create the junction table for you. If you stick to the code first conventions you won't even need the fluent API to configure the many-to-many.
In your design every ApplicationUser has zero or more Steps and every Step is done by zero or more ApplicationUsers.
class ApplicationUser
{
public int Id {get; set;}
// every ApplicationUser has zero or more Steps:
public virtual ICollection<Step> Steps {get; set;}
public string Name {get; set;}
...
}
class Step
{
public int Id {get; set;}
// every Step is performed by zero or more ApplicationUsers:
public virtual ICollection<ApplicationUser> ApplicationUsers {get; set;}
public string Name {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<ApplicationUser ApplictionUsers {get; set;}
public DbSet<Step> Steps {get; set;}
}
This is all entity framework needs to know to recognize that you configured a many-to-many relationship. Entity framework will create the junction table for you and the foreign keys to the junction table. You don't need to declare the junction table.
But how am I suppose to do a join if I don't have the junction table?
The answer is: Don't do the join. Use the collections instead.
If you want all ApplicationUsers that ... with all their Steps that ... you would normally do an inner join with the junction table, and do some group by to get the Application users. Ever tried method syntax to join three tables? They look hideous, difficult to understand, error prone and difficult to maintain.
Using the collections in entity framework your query would be much simpler:
var result = myDbContext.ApplicationUsers
.Where(applicationUser => applicationUser.Name == ...)
.Select(applicationUser => new
{
// select only the properties you plan to use:
Name = applicationUser.Name,
Steps = applicationUser.Steps
.Where(step => step.Name == ...)
.Select(step => new
{
// again fetch only Step properties you plan to use
Name = step.Name,
...
})
.ToList(),
});
Entity framework will recognize that joins with the junction table is needed and perform them for you.
If you want Steps that ... with their ApplicationUsers who ... you'll do something similar:
var result = myDbContext.Steps
.Where(step => ...)
.Select(step => new
{
Name = step.Name,
... // other properties
ApplicationUsers = step.ApplicationUsers
.Where(applicationUser => ...)
.Select(applicationUser => new
{
...
})
.ToList(),
});
In my experience, whenever I think of performing a query with a of DbSets using entity framework, whether it is in a many-to-many, a one-to-many or a one-to-one relation, the query can almost always be created using the collections instead of a join. They look simpler, they are better to understand and thus better to maintain.
At first I created a Person model which only contained the basic properties:
[Table("SGDB_Persons")]
public class Person {
public int Id { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
[Required]
public Department Department { get; set; }
[Required]
public SourceType SourceType { get; set; }
After I noticed I'm missing something I've added a new PersonData Property:
[Required]
public PersonData PersonData { get; set; }
Unfortunately EF won't update the Database at all - PersonData which at first contained an object of type Person got updated so there is no Person property anymore. On the other hand, EF does not create a new Column for PersonData_Id.
Additionally the ID column is not auto-incrementing (all other table's Id Column do). What's confusing me is the following Constraing which gets created inside my Person table:
CONSTRAINT [FK_dbo.SGDB_Persons_dbo.SGDB_PersonData_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[SGDB_PersonData] ([Id])
I tried everything (at least I think so). I dropped all tables / the whole database manually, reinstalled EF, executed manual migrations but nothing seems to work.
I think it's this problem which causes me to not be able to seed my database with the following code:
protected override void Seed(PersonContext context) {
base.Seed(context);
var dep = new DepartmentContext().Departments.First();
var status = new Status("Test");
var persondata = new PersonData(status);
context.Status.Add(status);
context.PersonData.Add(persondata);
for (int i = 0; i < 10; i++) {
var person = new Person {
Firstname = $"TestPersonFirstname{i}",
Lastname = $"TestPersonLastname{i}",
SourceType = COM.SourceType.Manual,
Department = dep,
PersonData = persondata
};
context.Persons.Add(person);
}
context.SaveChanges();
}
Everytime this code get's executed I'm getting an Exception:
The member with identity 'SGDB.DAL.Contexts.Person_Department' does not exist in the metadata collection. Parameter name: identity.
I don't know if both problems are related to the same problem but both need to be resolved :)
Thanks in advance!
Update 1
My Solution is divided into a few different Projects:
BLL, DAL, COM, UI
DataContexts are located inside the DAL project, Models inside the COM project.
Department Model:
[Table("SGDB_Departments")]
public class Department {
public int Id { get; set; }
[Required]
public string Costcenter { get; set; }
[Required]
public string Abbreviation { get; set; }
public string Description { get; set; }
public string FullDepartmentName {
get {
return $#"{Division.Abbreviation}\{Abbreviation}";
}
}
[Required]
public virtual Division Division { get; set; }
}
PersonData Model:
[Table("SGDB_PersonData")]
public class PersonData {
public PersonData(Status status) {
Status = status;
}
public int Id { get; set; }
public DateTime Limit { get; set; }
public Person Responsible { get; set; }
[Required]
public Status Status { get; set; }
}
The Person table (as you can see) has got a Department_Id column (EF inserted automatically).
Clarification
A Person object contains a PersonData object as additional Information for this Person. A Person may / may not has a Responsible Person (so PersonData.Responsible is not a Navigation Property to the Parent Person).
Additionaly if possible I don't want to have a foreign Key inside the PersonData table.
As I figured out I'd have to modify
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal(e => e.Responsible)
.WillCascadeOnDelete(true);
to
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithMany()
.WillCascadeOnDelete(true);
I'll try this and report in if it solved my problem.
Update 2
The member with identity 'SGDB.DAL.Contexts.Person_Department' does not exist in the metadata collection.
Your model defines one-to-one relationship between Person and PersonData with later being required and the former - optional. EF always uses the required side of the one-to-one relationship as principal and optional part as dependent. Hence it thinks PersonaData is the principal and Person - dependent and reflects that in database table design.
You need the opposite and also both sides being required. When both sides are required or optional, EF cannot automatically derive the principal/dependent side and there is no way to specify that via data annotations (attributes), so you need a fluent API setup.
Override your DbContext OnModelCreating and add something like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal(e => e.Responsible)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
What it does is to tell EF that both sides of the Person->PersonData relationship are required and Person is the principal. This should make again your Person.Id column auto-increment and should resolve the person-data part of the problem.
The other thing I've noticed is this line:
var dep = new DepartmentContext().Departments.First();
while all other parts of the same procedure are using a variable called context. This might/might not be a problem, just check it out.
UPDATE: From the clarification in the updated question it turns out you have two relationships between Person and PersonData, so you need separate configuration for each of them like this:
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal()
.WillCascadeOnDelete(true);
modelBuilder.Entity<PersonData>()
.HasOptional(e => e.Responsible)
.WithOptionalDependent() // or WithMany()
.WillCascadeOnDelete(false);
Please note that there is no way to not introduce additional FK column in the PersonData table. It's needed to represent the Responsible relation, so you'll end up with a table column called Responsible_Id.
I would like to be able to add a collection of Notes to any of my main entities in my NHibernate application. I can see how you could do this with a seperate junction table per entity. However, I would like to be able to avoid this and only have one junction table - if this is possible.
Below is the code so far, however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity. What are the alternative approaches I need to take?
public class Entity
{
public virtual int Id { get; set; }
}
public class EntityType1 : Entity
{
public EntityType1()
{
Notes = new List<Note>();
}
public virtual string EntityTypeName { get; set; }
public virtual IList<Note> Notes {get;set;}
}
public class EntityType2 : Entity
{
public EntityType2()
{
Notes = new List<Note>();
}
public virtual string EntityType2Name { get; set; }
public virtual IList<Note> Notes { get; set; }
}
public class Note
{
public virtual int Id { get; set; }
public virtual IList<Entity> Entities { get; set; }
public virtual string NoteText { get; set; }
}
}
namespace FluentNHib.Mappings
{
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Id(m => m.Id);
}
}
public class EntityType1Map : ClassMap<EntityType1>
{
public EntityType1Map()
{
Id(m => m.Id);
Map(m => m.EntityTypeName1);
HasManyToMany(m => m.Notes).Table("EntityToNotes")
.ParentKeyColumn("EntityId")
.ChildKeyColumn("NoteId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class EntityType2Map : ClassMap<EntityType2>
{
public EntityType2Map()
{
Id(m => m.Id);
Map(m => m.EntityType2ame);
HasManyToMany(m => m.Notes).Table("EntityToNotes")
.ParentKeyColumn("EntityId")
.ChildKeyColumn("NoteId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class NoteMap : ClassMap<Note>
{
public NoteMap()
{
Id(m => m.Id);
Map(m => m.NoteText);
}
}
I am not sure what the real issue is:
...however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity...
Is the issue in lazy loading? or in fact that Entity1 and Entity2 can have same ID, therefore the references are mixed? (I expect that and this should be part of the answer below)
Anyhow, I would say that we can achieve what you need: map the Note with just one table EntityToNotes. And that is good.
But, in general, I would descourage you from using the many-to-many. It is just my own feeling, experience. Below are some links with more explanation:
Am I doing many to many incorrectly when using fluent nhibernate?
NHibernate how do you map a crossreference table to a bag?
Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships?
Draft of the SOLUTION:
So, firstly we have to extend the table "EntityToNotes" with two columns
EntityToNoteId column - we need a primary key for new pairing object
Discriminator column
The Discriminator column will be used for (almost like a standard inheritance)
inserting Discriminator value during creation
filtering te IList<Notes> per Entity
These could be the pairing Entity (with an abstract base gathering the common stuff)
public abstract class EntityToNote<TEntity>
{
public abstract string Discriminator { get; set; }
public virtual TEntity Entity {get;set;}
public virtual Note Note {get;set;}
}
// the pairing objects
public class EntityType1ToNote : EntityToNote<EntityType1>
{
string _discriminator = "EntityType1"; // here we set the discriminator
public virtual string Discriminator
{
get { return _discriminator; }
set { _discriminator = value; }
}
...
// Similar for other pairing objects
The Entities will now be referencing lists of pairing objects
public class EntityType1 : Entity
{
public virtual IList<EntityType1ToNote> Notes {get;set;}
...
public class EntityType2 : Entity
{
public virtual IList<EntityType2ToNote> Notes { get; set; }
...
Here is snippet of the mapping (all other Entities will have usual mapping, including ClassMaps for EntityType1ToNote, EntityType2ToNote...)
public class EntityType1Map : ClassMap<EntityType1>
{
public EntityType1Map()
{
Id(m => m.Id);
Map(m => m.EntityTypeName1);
HasMany(m => m.Notes)
// this "table" setting is redundant, it will come from EntityType1ToNote
//.Table("EntityToNotes")
.KeyColumn("EntityId")
// here is the trick, that only related rows will be selected
.Where("Discriminator = 'EntityType1'")
.Cascade.AllDeleteOrphan();
}
}
As I tried to explain in the links provided, we gained this way a lot. Mostly the ability to use more columns on the pairing table - e.g. Discriminator (later we can have more columns like SortBy...) and we are able to use powerful searching with subqueries - see Query on HasMany reference
Also, in fact, the pairing could be mapped via the real inheritance... But the main point here is: Instead of many-to-many we introduced the pairing object and gained a lot
I'm using fluent nhibernate to map a parent child relationship to the sql database.
Most of the times i let the parent entity save it's child entities,they are inserted in 1 transaction and if i'm not mistaken this wouldn't be possible if i used .Inverse() and sql identity columns.
The problem i have is that in 1 specific case i want to update the child entity and ONLY the child entity.
When i do this with my current setup the child record will lose the relationship to it's parent(if the parent object is null) or will replace my parent object completely(if i insert a dummy parent with just an id).
Does anyone know a way to achieve a single record update without affecting the foreign key?
I can think of a manual sql statement or a stored procedure but i'm hoping there is an nhibernate way.
I have the following setup (simplified for your convenience) :
public ProjectMap()
{
Table("Project");
Id(p=> p.Id);
HasMany(p => p.Risks).Not.LazyLoad();
}
public RiskMap()
{
Table("ProjectRisk");
Id(r=> r.Id);
References(r => r.Project).Column("ProjectId");
Map(r => r.Description);
}
public class Project
{
public virtual int Id { get; set; }
public virtual IList<Risk> Risks { get; set; }
}
public class Risk
{
public virtual int Id { get; set; }
public virtual string Description{ get; set; }
public virtual Project Project { get; set; }
}
As Miroslav suggested i'm using an HQL update statement now.
It's a little messy for my taste but it gets the job done.
Session.CreateQuery("update Risk set Closed = :completed where Id = :id")
.SetInt32("id", id)
.SetBoolean("completed", completed)
.ExecuteUpdate();