How to map many-to-many relationship with Automapper and EntityFramework - entity-framework

I am using Automapper in order to be able to map my Entities to DTOs. Some of my entities are in many-to-many relationship and when I perform the mapping I get stack overflow error:
This is my entity and corresponding entity dto:
public partial class Group
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Group()
{
Tags = new HashSet<Tag>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
[StringLength(2147483647)]
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Tag> Tags { get; set; }
}
public partial class Tag
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Tag()
{
Groups = new HashSet<Group>();
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
[Required]
[StringLength(2147483647)]
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Group> Groups { get; set; }
}
As you can see, this is many to many relationship between Groups and Tags.
My DTOs look like this:
public class GroupDto
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<TagDto> Tags { get; set; }
public ICollection<long> TagIds { get; set; }
}
public class TagDto
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<GroupDto> Groups { get; set; }
}
Mapping configuration looks like this:
CreateMap<Group, GroupDto>();
CreateMap<GroupDto, Group>();
CreateMap<Tag, TagDto>();
CreateMap<TagDto, Tag>();
The exception gets fired from here:
var groupEntity = this._unitOfWork.GroupRepository.GetGroups().FirstOrDefault(g => g.Id == id);
if (groupEntity == null)
return null;
var groupAsDto = this._mapper.Map<Group, GroupDto>(groupEntity);
How can I avoid this and get a proper DTO from the Entity? I observed the Entity returned by the repository and it is as expected.

Related

.NET Core: How to merge nested one-to-many relations in dto

How could merge nested child entity in parent?
I have these three entities:
public class Faculty
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Group> Groups { get; set; }
}
public class Group
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Expected results in ResultDto is:
public class ResultDto
{
public Guid FacultyId { get; set; }
public string FacultyName { get; set; }
public ICollection<User> Users { get; set; }
}
You're looking for SelectMany:
var results = context.Faculties.Select(f => new ResultDto
{
FacultyId = f.Id,
FacultyName = f.Name,
Users = f.Groups.SelectMany(g => g.Users).ToList()
}
This will run in EF-core versions like 5 and 6, also in 3, but slightly less efficiently.

DbSet property of type class returns null

I'm creating an API for an app. The DbContext I have trouble with looks like this:
public class SchoolPlannerDbContext : DbContext
{
public SchoolPlannerDbContext(DbContextOptions<SchoolPlannerDbContext> options) : base(options) { }
public DbSet<Activity> Activities { get; set; }
public DbSet<Room> Rooms { get; set; }
public DbSet<Subject> Subjects { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Group> Groups { get; set; }
}
The Activity class is as follows:
public class Activity
{
public int ID { get; set; }
[Required]
public Teacher Teacher { get; set; }
[Required]
public Room Room { get; set; }
[Required]
public Subject Subject { get; set; }
[Required]
public Group Group { get; set; }
[Required]
public int Slot { get; set; }
[Required]
public int Day { get; set; }
}
All the other properties contain an int ID and a string Name.
My controller looks like this:
public class SqlPlannerData : ISchoolPlannerData
{
private readonly SchoolPlannerDbContext db;
public SqlPlannerData(SchoolPlannerDbContext db)
{
this.db = db;
}
public IEnumerable<Activity> GetActivities()
{
return db.Activities;
}
public IEnumerable<Group> GetGroups()
{
return db.Groups;
}
}
GetGroups() works as intended and returns an IEnumerable with properties set correctly.
My problem is that when I'm trying to access db.Activities, the properties of type, say, Teacher (non-basic types like int) are set to null:
Debugger screenshot.
However, there is a row in the database that looks like this. I.e. the columns exist in the database.
What do I do to make GetActivities() return an IEnumerable with correctly set properties?
Some properties are null because of lazy loading you need to include them
return db.Activities
.Include(i => i.Teacher)
.Include(i => i.Room)
.Include(i => i.Subject)
.Include(i => i.Group)
.ToList()
Each propety Id can be configured by EF5+ as shadows. But I usually prefer to add all Ids explicitely. This way I have much less problem when I am using db context in the project. But is is optional and you can leave it as is
public class Activity
{
public int ID { get; set; }
[Required]
public int? TeacherId { get; set; }
[Required]
public int? RoomId { get; set; }
[Required]
public int? SubjectId { get; set; }
[Required]
public int? GroupId { get; set; }
public virtual Teacher Teacher { get; set; }
public virtual Room Room { get; set; }
public virtual Subject Subject { get; set; }
public virtual Group Group { get; set; }
[Required]
public int Slot { get; set; }
[Required]
public int Day { get; set; }
}
and in order to get list activities you have to add ToList() or ToArray() at least
public IEnumerable<Activity> GetActivities()
{
return db.Activities.ToArray();
}
and by the way, you can' t using not nullabe Id as required becaue it is relevant
[Required]
public int TeacherId { get; set; }
since int by default is 0 and it is a valid value and required will not be working

LINQ query throw exception on FirstOrDefault method

I'm using EF core, and I have a many-to-many relationship between two entity
IotaProject <--> User
Here's entities & dto related to the question
public class IotaProject
{
[Key]
public int Id { get; set; }
[Required]
public string ProjectName { get; set; }
[Required]
public DateTime Create { get; set; }
public ICollection<ProjectOwnerJoint> Owners { get; set; } = new List<ProjectOwnerJoint>();
}
public class ProjectOwnerJoint
{
public int IotaProjectId { get; set; }
public IotaProject IotaProject { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User
{
[Key]
public int Id { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string ShortName { get; set; }
[Required]
public string Email { get; set; }
public ICollection<ProjectOwnerJoint> OwnedProjects { get; set; } = new List<ProjectOwnerJoint>();
}
public class ApplicationDbContext : DbContext
{
public DbSet<IotaProject> IotaProjects { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<ProjectOwnerJoint> ProjectOwnerJoint { get; set; }
}
public class IotaProjectDisplayDto
{
public int Id { get; set; }
public string ProjectName { get; set; }
public DateTime Create { get; set; }
public UserMinDto Owner { get; set; }
public int Count { get; set; }
public IEnumerable<UserMinDto> Reviewers { get; set; }
}
public class UserMinDto
{
public int Id { get; set; }
public string FullName { get; set; }
public string ShortName { get; set; }
}
Following LINQ is the problem, the LINQ purpose is to convert IotaProject to IotaProjectDisplayDto, and key part is that Owners property of IotaProject is ICollection and Owner property in IotaProjectDisplayDto is just one single element UserMinDto, so I only need to get the first element of IotaProject's Owners and that's FirstOrDefault() comes.
IEnumerable<IotaProjectDisplayDto> results = _db.IotaProjects.Select(x => new IotaProjectDisplayDto
{
Id = x.Id,
ProjectName = x.ProjectName,
Create = x.Create,
Owner = x.Owners.Select(y => y.User).Select(z => new UserMinDto { Id = z.Id, FullName = z.FullName, ShortName = z.ShortName }).FirstOrDefault()
});
return results;
it throws run-time exception
Expression of type 'System.Collections.Generic.List`1[ToolHub.Shared.iota.UserMinDto]' cannot be used for parameter
of type 'System.Linq.IQueryable`1[ToolHub.Shared.iota.UserMinDto]'
of method 'ToolHub.Shared.iota.UserMinDto FirstOrDefault[UserMinDto](System.Linq.IQueryable`1[ToolHub.Shared.iota.UserMinDto])' (Parameter 'arg0')
I'm guessing it's probably related to deferred execution, but after read some posts, I still can't resolve it.
Any tips would be appreciated.
Right now, the only way I can get this work is I change type of Owner property in IotaProjectDisplayDto into IEnumrable, which will no longer need FirstOrDefault() to immediate execution. And later on, I manually get the first element in the client to display.
This issue happened in Microsoft.EntityFrameworkCore.SqlServer 3.0.0-preview7.19362.6
I end up downgrade to EF core stable 2.2.6 as Ivan suggested in comment, and everything works fine.

EF Code First 6 and many-to-many with entity mapping

Following the example in this question: How to create a many-to-many mapping in Entity Framework? I would like to have a table mapping where I can add or remove many-to-many relationships without having to go through the Media or Contract entities.
Essentially, I would like to have:
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Contract> Contracts { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<Media> Medias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
public Media Media { get; set; }
public int MediaId { get; set; }
public Contract Contract { get; set; }
public int ContractId { get; set; }
}
Is it possible to configure this scenario using the FluentAPI?
afaik not with the ContractMedia entity, but you can:
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<ContractMedia> Contracts { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> Medias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
public Media Media { get; set; }
public int MediaId { get; set; }
public Contract Contract { get; set; }
public int ContractId { get; set; }
}
or
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Contract> Contracts { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<Media> Medias { get; set; }
}
that will lead to the creation of a non mapped association table in the database.

EF6 generates all the entity classes (tt .cs) in single file (eg. model.cs) and not as separate .cs file

I am using EF6. I have 2 tables Events and Users for which I used database first approach to generate the EDMX models and entity classes. Everything works fine but the entity classes are coming under a single file in this case EventSample.cs(shown below). I am not getting separate files for each entity as Event.cs and User.cs. Please let me know if this is correct and if not how to rectify it?
public partial class Event
{
public Event()
{
this.Users = new HashSet<User>();
}
public int Id { get; set; }
public string Title { get; set; }
public System.DateTime Time { get; set; }
public string Location { get; set; }
public string Description { get; set; }
public int Creator_Id { get; set; }
public virtual User User { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public partial class User
{
public User()
{
this.Events = new HashSet<Event>();
this.Events1 = new HashSet<Event>();
}
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Event> Events { get; set; }
public virtual ICollection<Event> Events1 { get; set; }
}