How to avoid circular references with AutoMapper? - c#

I have the following models (and corresponding DTOs):
public class Link
{
public int Id {get; set;}
public int FirstLinkId {get; set;}
public int SecondLinkId {get; set;}
public virtual Link FirstLink {get; set;}
public virtual Link SecondLInk {get; set;}
}
public class OtherObject
{
public int Id {get; set;}
public int LinkId {get; set;}
public string Name {get; set;}
public virtual Link Link {get; set;}
}
In my scenario, I can have a Link object where FirstLink and/or SecondLink can be null, references to other objects, or references to the same object.
Now I want to load an OtherObject entity from the db using EF. I load the entity itself and also the Link object associated with it. This is done perfectly by EF.
In this particular case, both FirstLink and SecondLink are the same as Link, therefore, when automapping from model to dto it just keeps on mapping into oblivion.
My mapping is:
Mapper.CreateMap<OtherObject, OtherObjectDto>().Bidirectional()
.ForMember(model => model.LinkId, option => option.Ignore());
where Bidirectional() is this extension:
public static IMappingExpression<TDestination, TSource> Bidirectional<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
return Mapper.CreateMap<TDestination, TSource>();
}
Is there way to tell Automapper not to map further down the tree in this case?

The way I would handle this is to create separate DTO objects for the children:
public class Employee
{
public int Id {get; set;}
public string Name { get; set; }
public Employee Supervisor {get; set; }
}
public class EmployeeDto {
public int Id {get; set;}
public string Name { get; set; }
public SupervisorDto Supervisor { get; set; }
public class SupervisorDto {
public int Id {get; set;}
public string Name { get; set; }
}
}
Mapper.CreateMap<Employee, EmployeeDto>();
Mapper.CreateMap<Employee, EmployeeDto.SupervisorDto>();
Don't let your DTOs be recursive/self-referential. Be explicit in your structure on how deep you want it to go.
EF can't do recursive joins, you're only doing one level, so don't make your DTOs go nuts with infinitely deep relationships. Be explicit.

Related

Automapper with Entity Framework Core recursive issue

I have a many-to-many relationship with EF Core 2.x.
I have created 3 classes:
public class Place
{
public string Name {get; set;}
public int Id {get; set;}
public ICollection<PlaceUser> Users {get; set;}
}
public class User
{
public string Name {get; set;}
public int Id {get; set;}
public ICollection<PlaceUser> Places {get; set;}
}
public class PlaceUser
{
public int UserId{get; set;}
public User User{get; set;}
public int PlaceId{get; set;}
public Place Place{get; set;}
}
public class PlaceDto
{
public string Name {get; set;}
public int Id {get; set;}
public ICollection<PlaceUserDto> Users {get; set;}
}
In my dbcontext, I set up the relationship. Everything works well.
But when I want to Map my Dto Place to my Place Object in my object place I have a recursivity and overflow exception:
I have:
Place
|-> Users
|-> User
|-> Place
|-> Users
|-> ...
I tried in my config of mapper to use depth but it's not working.
The only workaround I have found is:
if(place!=null && place.Users!=null)
{ // I set all the place.Users[i].Place = null; and place.Users[i].User=null;}
But it's an ugly solution and not convenient at all.
So which solution can I use?
Thanks,
I added the automapper config:
configuration.CreateMap<Place, PlaceDto>().MaxDepth(1);
https://stackoverflow.com/a/48824922/8101300
https://stackoverflow.com/a/57134333/8101300
CreateMap<Foo, Bar>().PreserveReferences();

How to return this result using linq in entity framework( 2 foreign key mapping table)

I have a table that contains 2 foreign key that reference separately to 2 different table.
I would like to return the result of all person that has course of "Science".
How to retrieve the record back using LINQ?
This is what i gotten so far:
return
_ctx.Person
.Include(u => u.Course
.Where(ug=>ug.CourseName== "Science"));
This is not working as it shows the error.
The Include path expression must refer to a navigation property
defined on the type
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<Person> Persons { get; set; }
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<Course> Courses { get; set; }
}
This is the mapping table. Only contains 2 foreign key from 2 different table.
I could not use this table inside the solution.As the code first won't generate this table as it doesn't contain it's own PK.
//This is not shown in the EntityFramework when generating Code First.
public class PersonCouseMap
{
public int PersonID {get; set;}
public int CourseID {get; set;}
}
Update : this works after I switched the entity.
return _ctx.Course
.Include(u=>u.Person)
.Where(ug=>ug.CourseName == "Sciene");
Anyone can explain why it won't work the another way round.
I need to display a List of Person who have course of "Science",
not Course Science that has a list of user.
The original query does not work because you've pushed the Where predicate inside the Include expression, which is not supported as indicated by the exception message.
The Include method is EF specific extension method used to eager load related data. It has nothing to do with the query filtering.
To apply the desired filter person that has course of "Science" you need Any based predicate since the Person.Courses is a collection:
return _ctx.Person
.Where(p => p.Courses.Any(c => c.CourseName == "Science"));
To include the related data in the result, combine it with Include call(s):
return _ctx.Person
.Include(p => p.Courses)
.Where(p => p.Courses.Any(c => c.CourseName == "Science"));
It looks like there is no relations between these two entites, you can establish a relationship by making the following changes to your code:
Here I am assuming that you want to establish Many-to-Many relationship between these two tables by having a third entity PersonCourseMap
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<CoursePersons> Courses { get; set; }
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<PersonCourse> Courses { get; set; }
}
public class PersonCourseMap
{
public int PersonID {get; set;}
public int CourseID {get; set;}
public virtual ICollection<Person> Persons { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
After making above changes you can simply navigate through properties.
Include Foreign Key Mapping
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<Person> Person {get; set}
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<Course> Course {get; set;}
}
using System.ComponentModel.DataAnnotation.Schema;
public class PersonCouseMap
{
[ForeignKey("Person")]
public int PersonID {get; set;}
[ForeignKey("Course")]
public int CourseID {get; set;}
public virtual ICollection<Person> Person {get; set;}
public virtual ICollection<Course> Course {get; set;}
}

Entity Framework one-to-zero with different tables

I am wondering if it is possible to make one table related to many.
This is what I got now (working one-to-zero-or-one relation between Report and ReportHeader):
public class Report
{
public int Id {get; set;}
public ReportHeader ReportHeader {get; set;}
[ForeignKey("ReportHeader")]
public int ReportHeaderId {get; set;}
}
public class ReportHeader
{
[Key, ForeignKey("Report")]
public int Id {get; set;}
public Report Report {get; set;}
}
At this point I want to add table named Style to Report BUT also to table ReportHeader. Thus, the relations would look like this:
Report
|--ReportHeader
| |-- Style
|
|-- Style
After that the classes should look like:
public class Report
{
public int Id {get; set;}
public ReportHeader ReportHeader {get; set;}
public Style Style {get; set;}
[ForeignKey("ReportHeader")]
public int ReportHeaderId {get; set;}
[ForeignKey("Style")]
public int StyleId {get; set;}
}
public class ReportHeader
{
[Key, ForeignKey("Report")]
public int Id {get; set;}
[ForeignKey("Style")]
public int StyleId {get; set;}
public Report Report {get; set;}
public Style Style {get; set;}
}
This is so much fun... until it comes to think about the Style class. At this point I have no idea how to design it. Is that even possible to make that class be in two relations with different tables?
public class Style
{
// ???
//[Key, ForeignKey("Report"), ForeignKey("ReportHeader")]
public int Id {get; set;}
public ReportHeader ReportHeader {get; set;}
public Report Report {get; set;}
}
At this case you should to mask your desired relation: one-to-one as many-to-one:
public BaseClass
{
//indeed, collection always will have zero or one items
public virtual ICollection<Style> styles {get; set;}
[NotMapped]
public Style style {
get { return styles.FirstOrDefault(); }
set { styles.Add(value); };
}
}
public class Report : BaseClass
{
//other stuff...
}
public class ReportHeader : BaseClass
{
//other stuff...
}
public class Style
{
public int Id {get; set;}
public virtual Report report {get; set;}
[Index(IsUnique = true)]//to ensure that relation is exactly one-to-one
public int? reportId {get; set;}
public virtual ReportHeader reportHeader {get; set;}
[Index(IsUnique = true)]//to ensure that relation is exactly one-to-one
public int? reportHeaderId {get; set;}
}

Code first : Sequence contains more than one matching element - multiple classes with multiple references

I'm getting the "Sequence contains more than one matching element" error message while attempting to run the "Add-Migration" command. I believe that the issue is stemming from the fact that I have multiple classes that have multiple references to a shared class.
class User {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UserId {get; set;}
public string Name {get; set;}
// other stuff removed for display purposes
[InverseProperty("CreateUser")]
public virtual ICollection<Foo> FooCreateUser { get; set; }
[InverseProperty("ModifyUser")]
public virtual ICollection<Foo> FooModifyUser { get; set; }
[InverseProperty("CreateUser")]
public virtual ICollection<Bar> BarCreateUser { get; set; }
[InverseProperty("ModifyUser")]
public virtual ICollection<Bar> BarModifyUser { get; set; }
}
class Foo {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid FooId {get; set;}
[InverseProperty("FooCreateUser")]
public User CreateUser {get; set;}
[InverseProperty("FooModifyUser")]
public User ModifyUser {get; set;}
}
class Bar {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid BarId {get; set;}
[InverseProperty("BarCreateUser")]
public User CreateUser {get; set;}
[InverseProperty("BarModifyUser")]
public User ModifyUser {get; set;}
}
What is wrong with the above code?
Do I have to change the field names in the [Bar] and [Foo] database tables to be unique?
i.e. change [Foo][CreateUser] to [Foo][Foo_CreateUser]?
I need to have a "CreateUser" and "ModifyUser" field on multiple database tables and it doesn't make any sense (from a database design perspective) to have all of those fields uniquely named.
How do I tell CodeFirst to do that?

Should foreign Id properties be mapped from Model to Dto?

If I have the following model:
public class Customer
{
public int Id {get; set;}
public int CustomerTypeId {get; set;}
public virtual CustomerType {get; set;}
}
Should the Dto exclude foreign Id's to look like this:
public class CustomerDto
{
public int Id {get; set;}
public virtual CustomerType {get; set;}
}
And when using Graphdiff to update the object graph, will EF know that CustomerType maps to CustomerTypeId?
Yes, you need to use it but you can avoid virtual member declaration. If you use AutoMapper, then the mapping will be done automatically. So, your Dto will look like this:
public class CustomerDto
{
public int Id {get; set;}
public int CustomerTypeId {get; set;}
}
And the mapping:
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CustomerDto, Customer>();

Categories