I have the two following Entitites with the corresponding EntityTypeConfigurations
public class Master
{
public int Id { get; set; }
public int RequestId { get; set; }
public string ClientNo { get; set; }
public string SomeValue { get; set; }
public ICollection<Child> Childs { get; set; }
}
public class MasterConfig : EntityTypeConfiguration<Master>
{
public MasterConfig()
{
ToTable("Master", "MySchema");
HasKey(k => k.Id);
HasMany(m => m.Childs)...
// Connect Master.RequestId to Child.RequestId
// and Master.ClientNo to Child.ClientNo
}
}
public class Child
{
public int Id { get; set; }
public int RequestId { get; set; }
public string ClientNo { get; set; }
public string SomeOtherValue { get; set; }
}
public class ChildConfig : EntityTypeConfiguration<Child>
{
public ChildConfig()
{
ToTable("Child", "MySchema");
HasKey(k => k.Id);
}
}
I would like to configure it that way that when I do
myAppContext.Masters.Include(m => m.Childs).First(m => m.Id == 4);
It would load all the Master with ID 4 and the Corresponding maching Childs.
Somehow I can't make it work.
Relationships are based on FK to PK. In your scenario RequestId is not primary key either of Master or Child. You should have Request model, which has RequsetId as PK and should have navigation properties to Master, and Child model should not directly bound to Request, it should bound to Master. Your models should look like this:
public class Request
{
public int Id { get; set; }
public string ClientNo { get; set; }
public virtual ICollection<Master> MasterCollection { get; set; }
}
public class RequestMap : EntityTypeConfiguration<Request>
{
HasKey(m => m.Id);
}
public class Master
{
// Id is same as RequestId
public int Id { get; set; }
public int RequestId { get; set; }
public string SomeValue { get; set; }
public virtual Request Request { get; set; }
public virtual ICollection<Child> Childs { get; set; }
}
public class MasterConfig : EntityTypeConfiguration<Master>
{
public MasterConfig()
{
ToTable("Master", "MySchema");
HasKey(k => k.Id);
// Map Request and Master
HasRequired(m => m.Request)
.WithMany(m => m.MasterCollection)
.HasForeignKey(m => m.RequestId);
}
}
public class Child
{
public int Id { get; set; }
public string SomeOtherValue { get; set; }
public int MasterId { get; set; }
public virtual Master Master { get; set; }
}
public class ChildConfig : EntityTypeConfiguration<Child>
{
public ChildConfig()
{
ToTable("Child", "MySchema");
HasKey(k => k.Id);
HasRequired(m => m.Master)
.WithMany(m => m.Childs)
.HasForeignKey(m => m.MasterId);
}
}
By changing your models to suit this, now you can load your Master and related Childs as you want to:
Master master = myAppContext.Masters
.Include(m => m.Childs)
.First(m => m.Id == 4);
If you want to load Request data according to Master then:
Master master = myAppContext.Masters
.Include(m => m.Request)
.Include(m => m.Childs)
.First(m => m.Id == 4);
You can also load Master and Request details for any child:
Child child = myAppContext.Childs
.Include(m => m.Master.Request)
.First(m => m.Id == 2);
Related
I have an entity as Plan with multiple sub-plans (children), each of which could be null.
For the PlanDto, I am trying to load up a list of all children rather than having a separate property for each child like the entity.
I have already achieved it manually through a foreach loop but now I am trying to do it via AutoMapper, which is failing for some reason.
Entities:
public class Plan
{
public virtual int Id { get; set; }
public DateTime Date { get; set; }
public virtual PlanDetail PlanChild1 { get; set; }
public virtual ObservationCare PlanChild2 { get; set; }
}
public class PlanDetail
{
public virtual int Id { get; set; }
public virtual Plan Plan { get; set; }
public virtual string Description { get; set; }
}
public class ObservationCare
{
public virtual int Id { get; set; }
public virtual Plan Plan { get; set; }
public virtual string Description { get; set; }
}
DTOs:
public class PlanDto: EntityDto
{
public DateTime Date { get; set; }
public IEnumerable<ChildPlan> ChildPlan { get; set; }
}
public class ChildPlan : EntityDto
{
public ChildPlanType Type { get; set; }
}
public enum ChildPlanType
{
PlanDetail,
ObservationCare
}
AutoMapper config:
configuration.CreateMap<Plan, PlanDto>();
configuration.CreateMap<PlanDetail, ChildPlan>()
.ForMember(dto => dto.Type, options => options.MapFrom(p => ChildPlanType.PlanDetail));
configuration.CreateMap<ObservationCare, ChildPlan>()
.ForMember(dto => dto.Type, options => options.MapFrom(p => ChildPlanType.ObservationCare));
Mapping attempt:
var output = new List<PlanDto>();
var plans = await _planRepository.GetAll().ToList();
foreach (var plan in plans)
{
output.Add(ObjectMapper.Map<PlanDto>(plan));
}
I do not know why ChildPlan DTOs in the output list are always null!
You have to specify the mapping for PlanDto.ChildPlan:
configuration.CreateMap<Plan, PlanDto>()
.ForMember(dto => dto.ChildPlan,
options => options.MapFrom(
p => new object[] { p.PlanChild1, p.PlanChild2 }.Where(c => c != null)));
If you are using Entity Framework Core, you have to use eager-loading:
var plans = await _planRepository.GetAll()
.Include(p => p.PlanChild1)
.Include(p => p.PlanChild2)
.ToList();
There's also a simpler and more efficient way to map a list:
var output = ObjectMapper.Map<List<PlanDto>>(plans);
I have 3 Models, 3 tables in DB (created with EF migrations):
public class Announce {
public int Id { get; set; }
public Location Location { get; set;}
public int LocationId { get; set; }
}
public class Location {
public int Id { get; set; }
public string Name { get; set; }
public District District { get; set; }
public string DistrictId { get; set; }
}
public class District {
public int Id { get; set; }
public string Name { get; set; }
}
and a Dto class :
public class AnnounceForListDto {
public int Id { get; set; }
public string LocationName { get; set; }
public string DistrictName{ get; set; }
}
And in AutoMapperProfile :
CreateMap<District, AnnounceForListDto>()
.ForMember(dest => dest.DistrictName, opt =>
{
opt.MapFrom(dis => dis.Name);
});
And I want to getAnnounces as :
public async Task<IEnumerable<Announce>> GetAnnounces()
{
var announces = await _context.Announce
.Include(prop => prop.Photos)
.Include(prop => prop.Location)
.ToListAsync();
return announces;
}
I need to include the District Name (or District Id, because I use AutoMapper to equalise DistrictName from AnnounceForListDto with District.Name) in my list.
I tried something as .Include(prop => prop.Location.District.Name), but get an error, "Include can use only one "dot").
Maybe .ThenInclude(Location => Location.District) this help and I wrong in my Dto declaration?
My Dto is used in Controller :
[HttpGet]
public async Task<IActionResult> GetAnnounces()
{
var announces = await _repo.GetAnnounces();
var announcesToReturn = _mapper.Map<IEnumerable<AnnounceForListDto>>(announces);
return Ok(announcesToReturn);
}
Solved :
Create relation between Location and District
then, my Repo method :
var announces = await _context.Announce
.Include(prop => prop.Photos)
.Include(prop => prop.Location)
.ThenInclude(prop => prop.District)
.ToListAsync();
return announces;
public class Module
{
public int id { get; set; }
public string moduleName { get; set; }
//navigation property
public virtual HashSet<Policy> policies { get; set; }
}
public class Policy
{
public int id { get; set; }
//foreign keys
public int subscriberId { get; set; }
//navigation properties
public virtual Subscriber subscriber { get; set; }
}
public class Subscriber
{
public int id { get; set; }
public string name { get; set; }
public int subscriptionId { get; set; }
// Navigation property
public virtual HashSet<Policy> policies { get; set; }
}
I have 3 related objects.
Module - Policy - Subscriber
A module has multiple policies
A policy has one subscriber
I need to list all the policies and subscribers under a certain module in JSON format. Due to the posts that I found on web I created this query:
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new {
m.id,
m.moduleName,
m.policies
}) ;
This only gives the result below. As you can see the details of Subscriber entity under policies are not present (NULL) :( What is wrong?
[{"id":1,"moduleName":"module1",
"policies":[{"id":1,"subscriberId":1,"subscriber":null}]}]
Since you are using dynamics in your Select method, you have to build it all out like this:
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new {
m.id,
m.moduleName,
policies = m.policies.Select(p => new
{
p.id,
p.subscriberId,
subscriber = new
{
p.subscriber.id,
p.subscriber.name,
p.subscriber.subscriptionId,
}
}
});
I typically use real Dto classes so if the Dto ever needs to be updated, refactoring will work properly. I would also consider using a DtoFactory to handle the construction, but you could do it with linq like this:
public class ModuleDto
{
public int id { get; set; }
public string moduleName { get; set; }
public IEnumerable<PolicyDto> policies { get; set; }
}
public class PolicyDto
{
public int id { get; set; }
public int subscriberId { get; set; }
public SubscriberDto subscriber { get; set; }
}
public class SubscriberDto
{
public int id { get; set; }
public string name { get; set; }
public int subscriptionId { get; set; }
}
...other code here...
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new ModuleDto {
m.id,
m.moduleName,
policies = m.policies.Select(p => new PolicyDto
{
p.id,
p.subscriberId,
subscriber = new SubsciberDto
{
p.subscriber.id,
p.subscriber.name,
p.subscriber.subscriptionId,
}
}
});
It gets a little messy to read the linq statement. This is why I typically use a DtoFactory to generate the Dtos from the models.
First of all I have these two models to store a post in two tables one for shared data and the other contains cultured data for English and Arabic
public class Post
{
public int Id { set; get; }
public bool Active { get; set; }
public bool Featured { get; set; }
public virtual ICollection<PostContent> Contents { get; set; }
}
public class PostContent
{
public int Id { set; get; }
public string Title { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
[StringLength(2)]
public string Culture { get; set; }
public int PostId { get; set; }
[InverseProperty("PostId")]
public virtual Post Post{ set; get; }
}
Mapping
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("Posts");
}
}
public class PostContentMap : EntityTypeConfiguration<PostContent>
{
public PostContentMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(p => p.Post).WithMany(p => p.Contents).HasForeignKey(p=>p.PostId);
ToTable("PostContents");
}
}
I have two questions
1- Is these models are connected properly. Is there something else I need to do ?
2- I need to select all Posts with their contents where the culture of the content 'en' for example. I used this:
var res = context.Posts.Include(p => p.Contents.Single(c => c.Culture.Equals("en")));
and have this error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.Parameter name: path
If you know you are not going to support more than two cultures then I would just add to your Post class.
public class Post
{
public Post()
{
Contents = new List<PostContent>();
}
public int Id { set; get; }
public bool Active { get; set; }
public bool Featured { get; set; }
public int? EnglishContentId { get;set;}
public int? ArabicContentId { get;set;}
PostContent EnglishContent {get;set;}
PostContent ArabicContent {get;set;}
}
public class PostContent
{
public int Id { set; get; }
public string Title { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
[StringLength(2)]
public string Culture { get; set; }/*This property is not required*/
}
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("Posts");
HasOptional(p => p.EnglishContent).WithMany().HasForeignKey(p=>p.EnglishContentId);
HasOptional(p => p.ArabicContent).WithMany().HasForeignKey(p=>p.ArabicContentId);
}
}
public class PostContentMap : EntityTypeConfiguration<PostContent>
{
public PostContentMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("PostContents");
}
}
The Above design will simplify your design and queries, will improve the performance alot.
But if you might have to support more cultures then you got the design and mapping right.
As far as EF 5, include does not allow filters, but I am not sure about EF 6.0
atleast you can get all posts that have english contents as follows
Add using System.Data.Entity;
var res = context.Posts.Include(p => p.Contents).Where(c => c.Contents.Any(cp=>cp.Culture.Equals("en")));
Here's sample model classes, which being use with Entity Framework Code First:
public class Master
{
public int Id { get; set; }
public Collection<Detail> Details { get; set; }
}
public class Detail
{
public int Id { get; set; }
public Master Master { get; set; }
public SubDetail SubDetail1 { get; set; }
public SubDetail SubDetail2 { get; set; }
}
public class SubDetail
{
public int Id { get; set; }
}
I want to load Master with all of its details and sub-details explicitly. To load details I'm using Include:
context.Masters.Include("Details").Where(master => master.Id == 1);
What should I use to load sub-details?
Try
context.Masters.Include(m => m.Details.Select(d => d.SubDetail1))
.Include(m => m.Details.Select(d => d.SubDetail2))
.Where(master => master.Id == 1);
Non generic version
context.Masters.Include("Details.SubDetail1")
.Include("Details.SubDetail2")
.Where(master => master.Id == 1);