I have two entities with a many-to-many relationship, and I have a third entity to represent this relationship, these are classroom user and userclassroom respectively. I want to retrieve a specific classroom, users registered in this classroom, and messages from this classroom, I wrote the following query for this:
await _genericRepository.GetAsync(x => x.Id.ToString() == request.classroomId,
x => x.Messages, x => x.Tags, x => x.Users);
But the related entities in the returned data are constantly repeating themselves, you can check it from the picture below.
Is this normal or is it an error, if it is an error, what is the solution?
Entities:
public class AppUser: IdentityUser
{
public ICollection<UserClassroom> Classrooms { get; set; }
public List<Message> Messages { get; set; }
}
public class Classroom
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Tag> Tags { get; set; }
public List<Message> Messages { get; set; }
public virtual ICollection<UserClassroom> Users { get; set; }
}
public class UserClassroom
{
public string UserId { get; set; }
public Guid ClassroomId { get; set; }
public AppUser AppUser { get; set; }
public Classroom Classroom { get; set; }
public DateTime JoinDate { get; set; } = DateTime.Now;
}
It looks like you have a circular dependency between Classroom and UserClassroom.
Classroom has a collection of UserClassroom, and each UserClassroom has a Classroom which will point back to the Classroom which will point back to the UserClassroom which will... - you get the point.
I would suggest you remove the Classroom property from UserClassroom as you already have the ClassroomId that you can use to retrieve the Classroom if you need to.
This isn't an error. Even for 1:n relations you can have this behaviour. It simply says you have Navigation Properties in both ways.
Let's say you have the classes: Pet, PetOwner. When the navigation properties are set correctly you can access the Pet of an PetOwner. The Pet then holds the PetOwner reference, which again holds the Pet reference. And that way you can navigate indefinitly.
Related
Consider the following scenario. I have 3 classes, representing a many-to-many (N-to-N) relationship between Student and Subject:
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public long RegistrationNumber { get; set; }
public virtual ICollection<Grade> Grades { get; set; }
}
public class Grade
{
public long Id { get; set; }
public int Value { get; set; }
public virtual Student Student { get; set; }
public virtual Subject Subject { get; set; }
}
public class Subject
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Grade> Grades { get; set; }
}
I want to retrieve the list of all students, with their grades, for each subject. To do so, I use:
// context being DbContext
var res = context.Student.Include(s => s.Grades).ThenInclude(g => g.Subject);
As properties are lazy-loaded, I expected each subject to only contain their "Name" property. However, upon inspection, I found that the "Grades" list is also set, with a list of all the grades assigned to the subject. This, of course, causes an object cycle.
I want to avoid that circular referencing, i.e. obtain a list where each subject's only set property is "Name". How can I do it?
If you use asp.net core 3.0 MVC/Web API, just follow below steps to overcome circular reference using NewtonsoftJson.
1.Install Microsoft.AspNetCore.Mvc.NewtonsoftJson package(version depends on your project)
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.0.0
2.Add below code in startup
services.AddControllersWithViews().AddNewtonsoftJson(x =>
{
x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
You can always manually select the name only, like
context.Student.Select(x => x.Name);
But this does not work with the Navigation properties and the automaticly generated Joins between the tables. There it's 'all or nothing'.
Or you have to do the join completly manually, without navigation properties.
But your structure isn't that complicated and not vulnerable to circularities.
Just start of with the Grade, with the anchor element in the middle.
context.Grade.Include(x => Subject).Include(x =>Student)
This is at least the easier way to load your entire structure and may be an approach for a starting point for manual joins.
Maybe you add a
.GroupBy(x => x.Student)
To get closer to your list of students.
You cannot skip the "loading" of the collection, cause it's the grades, that is loaded first. So first there are the elements of the collection, than there is the subject entity. It makes no sense not to put the data in the collection.
Following Jawad's advice, I ended up using LINQ Select statements.
First, I wrote some DTO's:
public class StudentDTO
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
public long RegistrationNumber { get; set; }
public IEnumerable<GradeDTO> Grades { get; set; }
}
public class GradeDTO
{
public int Value { get; set; }
public virtual StudentDTO Student { get; set; }
public virtual SubjectDTO Subject { get; set; }
}
public class SubjectDTO
{
public string Name { get; set; }
public virtual IEnumerable<GradeDTO> Grades { get; set; }
}
And then:
var res = from student in context.Student
select new StudentDTO
{
Name = student.Name,
Birthday = student.Birthday,
RegistrationNumber = student.RegistrationNumber,
Grades = from grade in student.Grades
select new GradeDTO
{
Value = grade.Value,
Subject = new SubjectDTO
{
Name = grade.Subject.Name
}
}
};
If I have a Course class, that has a collection of students (ICollection<Person>) as follows:
public class Person
{
public Person()
{
this.Courses = new HashSet<Course>();
}
public int PersonId { get; set; }
[Required]
public string PersonName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Person>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Person> Students { get; set; }
}
I end up with this structure in the database (as expected):
(note the PersonCourses table)
However, in my example, I also want to add an instructor to the course.
This instructor is also a Person, who can attend courses just like everyone else, so I adjust the above classes as shown below:
public class Person
{
public Person()
{
this.Courses = new HashSet<Course>();
}
public int PersonId { get; set; }
[Required]
public string PersonName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<Course> InstructedCourses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Person>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Person> Students { get; set; }
public virtual Person Instructor { get; set; }
}
What I was expecting to see is the same database structure as above, but with an additional table created that linked a person to many courses.
However, what I got was this:
(Note that the PersonCourses table has gone)
What I was Expecting/Hoping to see was similar to this:
It's probably worth stating that the reason I've not got a separate Instructor/Person class is that I'm expecting that any Person can create a course, and thus become an instructor for that course.
Firstly - Is this possible to achieve via code-first in EF? I'm assuming so..
Secondly - What is it I'm doing wrong?
Thirdly - Is it the weekend yet?
All help appreciated :)
This is one reason I don't like / recommend code-first. It looks like EF got confused with the second InstructedCourses collection and instead just set up the instructor reference back from the course, though it seems to have just made the students collection a 1-to-many as well.
I would seriously consider either:
A) changing you domain to define an Instructor entity vs. Student entity
or
B) Do schema first with the proper EF mappings to the tables you want.
I don't think any DBA is going to want to see things like course_personId / Person_personId throughout the schema that they are one day going to need to support and optimize.
Instructors and Students can extend a base "Person" class with either table per entity or an identifier. Course to instructor and course to student relationships can then be defined more clearly. The limitation would be if you wanted the same "person" to be able to be referenced as both an instructor and a student.
I'm using entity framework code first approach
I have a class
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
and a class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
When the database is created I get one table Movies with Id, Title, Director_Id and a table Person with Id and Name.
I expect to have a table Movies_Persons with columns Movie_Id and Actor_Id
How can I achieve this?
Your Problem is, that you don`t tell the Person Class, that there can be multiple Movies per person.
So by adding the following line in your person class:
public virtual ICollection<Movie> Movies { get; set; }
Your entity knows that both your classes can have multiple references to the other class.
To fulfill this requirement Entity Framework will create a third table with Movie_ID and Person_ID.
If you want more informations just look for:
Entity Framework - Many to many relationship
or follow this link:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
You can check out the other articels on that page too, if you are new to entity framework.
UPDATE:
Sorry i missed, that you are already have another reference to your person table.
Here you have to tell your entity framework, which way you want to reference the two tables by fluent api.
Check out this stackoverflow answer. That should do the trick.
You have to insert this code into your OnModelCreating Function of your DbContext Class.
So your final code should look like this:
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public virtual Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Movie> Movies_Actors { get; set; }
public virtual ICollection<Movie> Movies_Directors { get; set; }
}
And in your OnModelCreating add following code:
modelBuilder.Entity<Movie>()
.HasMany(a => a.Actors)
.WithMany(a => a.Movies_Actors)
.Map(x =>
{
x.MapLeftKey("Movie_ID");
x.MapRightKey("Person_ID");
x.ToTable("Movie_Actor");
});
modelBuilder.Entity<Movie>()
.HasRequired<Person>(s => s.Director)
.WithMany(s => s.Movies_Directors);
I don't have the possibility to test the code, but that should do the trick.
If you have to do some adjustments to make it work, plz add them in the comments, so other ppl can benefit from it.
I have a simple User Class
public class User
{
public int ID { get; set; }
[Required]
public virtual ApplicationUser LoginID { get; set; }
[Required]
public string Name { get; set; }
public string JobTitle { get; set; }
[DefaultValue(UserRole.Standard)]
public UserRole Role { get; set; }
public virtual Company Company { get; set; }
public string Email { get { return LoginID.Email; } }
public bool HasAccess(UserRole TargetRole)
{
//Non-relevant logic
}
}
And I also have a Company class defined as
public class Company
{
public int ID { get; set; }
[Required]
[MaxLength(length: 70)]
public string Name { get; set; }
public virtual ICollection<User> Employees { get; set; }
public virtual ICollection<CompanyEmailDomain> Domains { get; set; }
public ICollection<User> Managers { get { return Employees.Where(x => x.Role == UserRole.Manager).ToList(); } }
}
However, when I run the add-migration command, it tries to add 3 Foreign keys on the User table to the Company table. Can anyone tell me why this would be the case?
AddColumn("dbo.Users", "Company_ID", c => c.Int());
AddColumn("dbo.Users", "Company_ID1", c => c.Int());
AddColumn("dbo.Users", "Company_ID2", c => c.Int());
Entity Framework simply counts the associations between User and Company. It detects three of them:
Company in User.
Employees in Company
Managers in Company
They're all 1-n (Company - User), so, EF concludes, User needs three foreign keys.
You know that Managers is a computed property. In fact, the property shouldn't even be be mapped. You should add the [NotMapped] attribute to it or map it as ignored by the fluent mapping API.
Also, you know that User.Company and Company.Employees are two ends of one association. But because of the two ICollection<User> properties, EF doesn't know which one to choose for the other end (the inverse end) of User.Company.
Now if you unmap Company.Managers, EF will see two properties --User.Company and Company.Employees-- and assume they belong together. So by unmapping one property, only one foreign key will be created.
I have created these entities Product, Order, OrderedItem in EF using Code First.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public int IssuedQuantity { get; set; }
[NotMapped]
public int InhandQuantity { get; set; }
public virtual ICollection<OrderedItem> OrderedItems { get; set; }
...
}
public class Order
{
public int Id { get; set; }
public string ReferenceNumber { get; set; }
public virtual ICollection<OrderedItem> OrderedItems { get; set; }
...
}
public class OrderedItem
{
public int OrderId { get; set; }
public string ProductId { get; set; }
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
...
}
Now I want to get all products by passing current user id to a stored procedure. It will then return all products along with total product quantity currently in user's hand.
The problem is that EF is not mapping SP results back to Product entity for NotMapped properties. i.e. all properties in product entity have values but NotMapped properties are set to NULL even when I return their values from SP.
What I want to ask is that does EF support this kind of functionality? If yes then how?
NOTE I know about Computed Properties but that will create unneccessary columns in tables and I don't want that, since these properties are calculated at run-time.
NOTE I know that I don't need to create OrderedItem entity. But I am storing some other properties in it, which are removed here for brevity.
I'm quite sure that EF does not support dynamic mapping (you could try to change the mapping metadata but is not a clean way or delete the mapping cache but then EF will be very slow). In this case the razionale is that the entity are 2 different entities because they have different data. In your case probably the best thing is to do 2 entities the ProductWithQuantities that inherits from Product.
BTW Thinking about ERPs, the model of orders/wms usually is different. Products does not contain informations about QtyOnHand or sales/buy information. Usually is another object (Inventory?) that contains this informations.
I would create a View Model of the product with all the required properties and pass that to the view instead of the Product model. Then you are not constrained by the mappings of the Product model and you do not have to use the [NotMapped] Attribute on the fields.
[NotMapped]
public class ProductVM
{
public int Id { get; set; }
public string Name { get; set; }
public int IssuedQuantity { get; set; }
public int InhandQuantity { get; set; }
public virtual ICollection<OrderedItem> OrderedItems { get; set; }
...
}
I hope that helps.