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

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.

Related

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.

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

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.

The Entity Framework created own columns for foreign keys

The Entity Framework ignores my foreign keys and creates own. How I can solve this problem?
I have few classes:
[Table("Entity")]
public class Entity
{
public Entity()
{
HeavyEntities = new List<HeavyEntity>();
}
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int EntityId { get; set; }
public string Name { get; set; }
public ICollection<HeavyEntity> HeavyEntities { get; set; }
}
[Table("HeavyEntity")]
public class HeavyEntity
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int EntityHeavyId { get; set; }
public int EntityId { get; set; }
public virtual Entity Entity { get; set; }
public int? TargetEntityId { get; set; }
public virtual Entity TargetEntity { get; set; }
public Relation Relation { get; set; }
}
[Table("Tech")]
public class Tech : Entity
{
public string Status { get; set; }
}
[Table("Company")]
public class Company : Entity
{
public string Country { get; set; }
}
Create and save entities:
var (context = new MyDbContex())
{
var tech = new Tech
{
Name = "Tech1",
Status = "New",
};
var company = new Company
{
Name = "Company1",
Country = "Ukraine"
};
tech.HeavyEntities.Add(new HeavyEntity
{
Relation = Relation.AsParent,
TargetEntity = company,
});
context.Techs.Add(tech);
context.Companies.Add(company);
context.SaveChanges();
}
For this classes Entity Framework creates the following table (HeavyEntity):
How I can map foreign keys to my fields?
SOLVED:
[Table("Entity")]
public class Entity
{
public Entity()
{
HeavyEntities = new List<HeavyEntity>();
}
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int EntityId { get; set; }
public string Name { get; set; }
[InverseProperty("Entity")]
public ICollection<HeavyEntity> HeavyEntities { get; set; }
}
[Table("HeavyEntity")]
public class HeavyEntity
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int EntityHeavyId { get; set; }
[ForeignKey("EntityId")]
public int EntityId { get; set; }
public virtual Entity Entity { get; set; }
[ForeignKey("TargetEntityId")]
public int? TargetEntityId { get; set; }
public virtual Entity TargetEntity { get; set; }
public Relation Relation { get; set; }
}
Add the ForeignKey attribute.
see https://msdn.microsoft.com/en-us/data/jj591583#Relationships
[Table("HeavyEntity")]
public class HeavyEntity
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int EntityHeavyId { get; set; }
public int EntityId { get; set; }
public virtual Entity Entity { get; set; }
public int? TargetEntityId { get; set; }
[ForeignKey("TargetEntityId")] // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
public virtual Entity TargetEntity { get; set; }
public Relation Relation { get; set; }
}

EF code first model not in sync with database

My EF Code First model for some reason is not in sync with the db. I'm getting this error:
{"Invalid column name 'Type_Id1'."}
The field is actually called 'Type_Id' so I'm not sure from where that 1 comes up. I have the table column called as Type_Id and also I've added a Type_Id in my type entity model.
Why might I be getting that error message, plus why I'm getting 1 at the end of the name?
Update
My Task class:
public class Task
{
public Task()
{
Language = 1;
Grades = new HashSet<Grade>();
Categories = new HashSet<Category>();
Subjects = new HashSet<Subject>();
Rooms = new Collection<Room>();
Tools = new Collection<Tool>();
}
[Key]
public int Id { get; set; }
public string Description { get; set; }
public virtual TaskType Type { get; set; }
public string Rules { get; set; }
[Required]
[StringLength(200), MinLength(1)]
public string Name { get; set; }
public int PreperationTime { get; set; }
public int InstructionTime { get; set; }
public int TaskTime { get; set; }
public int Type_Id { get; set; }
public string VideoLink { get; set; }
[Required]
public int Language { get; set; }
public int? MinimumParticipants { get; set; }
public int? MaximumParticipants { get; set; }
public int? Rating { get; set; }
[Required]
public string CreatedBy { get; set; }
public virtual ICollection<Grade> Grades { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<Subject> Subjects { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
public virtual ICollection<Tool> Tools { get; set; }
}
DBContext class:
public ApplicationDbContext() : base("DefaultConnection", false)
{
}
public DbSet<Task> Tasks { get; set; }
public DbSet<TaskType> TaskTypes { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
You need to add the FK attribute on your navigation property. EF is creating Type_Id1 because Type_Id already exists (although it can't tell by convention it is the FK).
[ForeignKey("Type_Id")]
public virtual TaskType Type { get; set; }
https://msdn.microsoft.com/en-us/data/jj591583.aspx#Relationships

Entity Framework 4.3 CF many - to - many relationship saving object?

When creating many to many relationship using EF 4.3 code first approach, I cannot save data to connecting table, also cannot any examples on how to fill this table using saving object to Icollection... Here is my example:
MODELS
public class Hospital
{
//PK
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string County { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public Guid User_Id { get; set; }
//FK
public virtual ICollection<Operator> Operators { get; set; }
}
public class Operator
{
//PK
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Dob { get; set; }
public string Email { get; set; }
//FK
public virtual ICollection<Hospital> Hospitals { get; set; }
}
public class Project: DbContext
{
public DbSet<Hospital> Hospitals { get; set; }
public DbSet<Operator> Operators { get; set; }
}
CONTROLLER
public void AddOperater()
{
Hospital h = new Hospital();
h = db.Hospitals.Single(a=>a.Id ==1);
var o = new Operator();
o.FirstName = "John";
o.LastName = "Doe";
o.Dob = new DateTime(1988,2,12);
o.Email = "johndoe#gmail.com";
o.Hospitals.Add(h);
db.SaveChanges();
}
With this approach I keep getting error here: o.Hospitals.Add(h); even when my Hospital instance is filled with data. How exactly to save data to both tables, the dbo.Operators and dbo.OperatorHospital which is relationship table?
o.Hospitals.Add(h) will fail because the list is a null list. You cannot call Add() on a null list. Typically most people get around this by instantiating the list in the constructor of the entity... like so... the current line is blowing up due to a CSharp issue.
public class Hospital
{
//PK
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string County { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public Guid User_Id { get; set; }
//FK
public virtual ICollection<Operator> Operators { get; set; }
public Hospital()
{
Operators = new List<Operator>();
}
}
public class Operator
{
//PK
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Dob { get; set; }
public string Email { get; set; }
//FK
public virtual ICollection<Hospital> Hospitals { get; set; }
public Operator()
{
Hospitals = new List<Hospital>();
}
}