I'm trying to map nested ICollection of one of my models to existing Dto, but I'm struggling to Map it properly with AutoMapper
Models:
public class Ingredient : BaseEntity<long>
{
[MaxLength(100)]
public string Name { get; set; }
[ForeignKey("Id")]
public int CustomerId { get; set; }
public bool IsPackaging { get; set; }
public virtual ICollection<ProductIngredient> ProductIngredient { get; set; }
public virtual ICollection<IngredientComposition> IngredientComposition { get; set; }
}
Collection Model:
public class IngredientComposition : BaseEntity<int>
{
[MaxLength(20)]
public string Type { get; set; }
[MaxLength(200)]
public string Key { get; set; }
[MaxLength(200)]
public string Value { get; set; }
}
Dto:
public class IngredientDto
{
public long Id { get; set; }
public DateTime CretedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public string Name { get; set; }
public int CustomerId { get; set; }
public int UsedCount { get; set; }
public bool IsPackaging { get; set; }
public IList<Composition> Ingredients { get; set; }
}
public class Composition
{
public string Type { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
My maps looks as follows as I'm struggling to properly set "ForMemeber" method(s):
CreateMap<Ingredient, IngredientDto>();
CreateMap<IngredientDto, Ingredient>();
Any help much appropriated!
Thanks
EDIT:
This is how I'm getting data:
return await _context.Ingredients
.Where(i => i.CustomerId ==_userResolverService.GetCustomerId())
.Include(i => i.IngredientComposition)
.Select(i => _mapper.Map<Ingredient, IngredientDto>(i))
.OrderBy(i => i.Name)
.ToListAsync();
First, you must do add CreateMap<IngredientComposition, Composition>(); and after doing this you must do change your Linq Query. You can use AutoMapper.EF6
return _context.Ingredients
.Where(i => i.CustomerId ==_userResolverService.GetCustomerId())
.Include(i => i.IngredientComposition)
.ProjectToList<IngredientDto>();
after use this you donot need use Select.
note: donot forget add _mapper.ConfigurationProvider in ProjectToList
ProjectToList<IngredientDto>(_mapper.ConfigurationProvider);
if you don't set it to get this Exception:
Mapper not initialized. Call Initialize with Appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
more detail.
Update: your properties must have the same name.if you change Dto property Ingredients to IngredientComposition don't need use ForMember.
Actually this worked for me:
Query:
return await _context.Ingredients.Where(i => i.CustomerId == _userResolverService.GetCustomerId())
.Include(sx=>sx.IngredientComposition)
.ProjectTo<IngredientDto>()
.ToListAsync();
Maps:
First of All as you suggested, internal collection mapping then main objects mapping + ForMember which worked once internal objects were mapped
CreateMap<IngredientComposition, Composition>().ReverseMap();
CreateMap<Ingredient, IngredientDto>().ForMember(d => d.Ingredients, opt=>opt.MapFrom(c=>c.IngredientComposition)).ReverseMap();
Thanks for all help!
If you make a map for the child DTO, Automapper is smart enough to figure it out without the ForMember:
CreateMap<IngredientComposition , Composition>()
.ReverseMap(); //Reverse map tells AM to go both ways
CreateMap<Ingredient, IngredientDto>()
.ReverseMap();
// CreateMap<IngredientDto, Ingredient>(); ** Not needed with ReverseMap()
Related
I am having an issue with EF returning NULL values within a child list. Here is my model that I am trying to get:
public class CompoundIngredient : Ingredient
{
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public string UserId { get; set; }
public CompoundIngredient()
{
MeasuredIngredients = new List<MeasuredIngredient>();
IsPublic = true;
}
}
However, when I do this:
return await _dataContext.CompoundIngredients
.Include(a => a.MeasuredIngredients)
.ThenInclude(a => a.MeasurementType)
.Include(a => a.MeasuredIngredients)
.ThenInclude(a => a.Ingredient)
.ThenInclude(a => a.IngredientType)
.FirstOrDefaultAsync(c => c.DisplayValue == name);
I get back a collection of 4 items. 2 items are populated and 2 are NULL.
Here is the data in the DB
As you can see from the picture there are 4 entries in the table, 2 of which belong to CompoundIngredientId 6 which is the ID of the ingredient who matches the Name value.
Why am I getting 4 results back, 2 of which are null?
EDIT:
So here are the models
public class CompoundIngredient : Ingredient
{
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public string UserId { get; set; }
public CompoundIngredient()
{
MeasuredIngredients = new List<MeasuredIngredient>();
IsPublic = true;
}
}
public class Ingredient
{
public int Id { get; set; }
public string DisplayValue { get; set; }
public string Description { get; set; }
public bool IsPublic { get; set; }
public IngredientType IngredientType { get; set; }
public int IngredientTypeId { get; set; }
public int CompanyId { get; set; }
public string UserName { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedDate { get; set; }
}
public class MeasuredIngredient
{
public int Id { get; set; }
public decimal Amount { get; set; }
public int MeasurementTypeId { get; set; }
public MeasurementType MeasurementType { get; set; }
public int IngredientId { get; set; }
public Ingredient Ingredient { get; set; }
public int? UseId { get; set; }
public Use Use { get; set; }
}
public class Recipe
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsPublic { get; set; }
public int RecipeCategoryId { get; set; }
public RecipeCategory RecipeCategory { get; set; }
public int SocialMediaId { get; set; }
public SocialMedia SocialMedia { get; set; }
public virtual List<TimeTemp> TimeTemps { get; set; }
public virtual List<RecipeFuel> RecipeFuels{ get;set; }
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public List<RecipeStep> RecipeSteps { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedDate { get; set; }
public Recipe()
{
MeasuredIngredients = new List<MeasuredIngredient>();
RecipeSteps = new List<RecipeStep>();
SocialMedia = new SocialMedia();
RecipeFuels = new List<RecipeFuel>();
TimeTemps = new List<TimeTemp>();
IsPublic = true;
}
}
As you can see Measured Ingredient isnt exclusive to CompoundIngredient. Recipe also has a List on it as well.
as far as configurations i dont have much
public class MeasuredIngredientConfiguration : IEntityTypeConfiguration<MeasuredIngredient>
{
public void Configure(EntityTypeBuilder<MeasuredIngredient> builder)
{
builder.Property(p => p.UseId).IsRequired(false);
}
}
public class IngredientConfiguration : IEntityTypeConfiguration<Ingredient>
{
public void Configure(EntityTypeBuilder<Ingredient> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
}
}
public class RecipeConfiguration : IEntityTypeConfiguration<Recipe>
{
public void Configure(EntityTypeBuilder<Recipe> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
}
}
here are the tables in the DB with FKs
I highly suspect the issue will stem from CompoundIngredient inheriting from Ingredient using TPH inheritance (One table with a Discriminator) then being referenced by MeasuredIngredient, and this relationship not being set up quite right. Which version of EF Core is this?
Overall the relationship between these entities/tables feels "odd". You have an ingredient, then a "compound" ingredient that is made up of one or more MeasuredIngredient. (which does not extend ingredient) A Measured ingredient contains one Ingredient, and optionally one CompoundIngredient.
Given a compound ingredient represents just a collection of measured ingredients, this just feels a bit off. It sounds like you want a recipe to contain a list of ingredients where each is associated with a measurement (The MeasuredIngredient) but that "ingredient" may be a combination of other ingredients (with associated measurements) where you may want to possibly avoid duplicating data.
I built a simple test with the core relationships in EF Core 5 and I was able to get the expected results. The important detail here was ensuring the relationship between the 3 classes (and possibly other related classes) is configured correctly. For example, cutting down the object model down to the core I came up with:
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CompoundIngredient : Ingredient
{
public virtual ICollection<MeasuredIngredient> MeasuredIngredients { get; set; } = new List<MeasuredIngredient>();
}
public class MeasuredIngredient
{
public int Id { get; set; }
publi int IngredientId { get; set; }
public virtual Ingredient Ingredient { get; set; }
}
Creating test records with the relationships you showed and running EF Core 5 I wasn't able to reproduce the issue, but I honestly did not feel comfortable with leaving EF to sort out the discriminator and relationships.
The bits I didn't like were:
CompoundIngredient extends Ingredient while containing a Many relationship to MeasuredIngredient where there is no corresponding "One" relationship on MeasuredIngredient, but it does have a "One" relationship with Ingredient.
The discriminator here is implied, not configured.
What I am more comfortable with was:
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CompoundIngredient : Ingredient
{
public virtual ICollection<MeasuredIngredient> MeasuredIngredients { get; set; } = new List<MeasuredIngredient>();
}
public class MeasuredIngredient
{
public int Id { get; set; }
public virtual Ingredient Ingredient { get; set; }
public virtual CompoundIngredient CompoundIngredient { get; set; }
}
Then explicitly mapping the relationships to ensure there is no confusion on FKs:
public class MeasuredIngredientConfiguration : IEntityTypeConfiguration<MeasuredIngredient>
{
public void Configure(EntityTypeBuilder<MeasuredIngredient> builder)
{
builder.Property(p => p.UseId).IsRequired(false);
builder.HasOne(p => p.Ingredient)
.WithMany()
.IsRequired()
.HasForeignKey("IngredientId");
builder.HasOne(p => p.CompoundIngredient)
.WithMany(p => p.MeasuredIngredients)
.IsRequired(false)
.HasForeignKey("CompoundIngredientId");
}
}
public class IngredientConfiguration : IEntityTypeConfiguration<Ingredient>
{
public void Configure(EntityTypeBuilder<Ingredient> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
builder.HasDiscriminator<string>("Discriminator")
.HasValue<Ingredient>("I")
.HasValue<CompoundIngredient>("C"); // Whichever discriminator values you want to use.
}
}
I generally do not have FKs exposed in entities for navigation properties, opting for shadow properties. This should work just as well with the FK fields mapped.
Now I had excluded this configuration and this example did work with EF Core 5. I was also trying to force a misconfiguration around possibly the CompoundIngredientId and IngredientId in the measured ingredient, but outside of generating specific configuration errors around missing assumed FKs I wasn't able to reproduce your issue. It could also be behaviour specific to the version of EF Core you are using.
You could try adding the explicit mapping to see if that solves or otherwise changes your results. Getting null entries in your collection smells like EF is trying to parse the CompoundIngredient -> MeasuredIngredient, but it is getting other measured Ingredients with the same Ingredient reference (1-2) but not the matching compound ingredient ID. It's definitely a weird one.
Otherwise I would look to temporarily eliminate all other references such as Recipe, measurement type, etc. down to the simplest possible example and data set that reproduces the problem. This becomes easier to investigate options to identify where/what is getting mixed up.
Hopefully this gives you some ideas on how to get to the bottom of the issue.
Turns out the issue is not with EF… after looking further into it EF is returning the proper counts and relations. The issue is higher up during the deserialization of the json being returned from the API. I created a new question for this
JsonSerializer.DeserializeAsync<> Creating Null Items in Collections
I'm trying to map a navigation property on a one to one relationship but I'm getting null values, here are my entities
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
public int BranchId { get; set; }
public Branch Branch{ get; set; }
}
public class Branch
{
public int BranchId { get; set; }
public string Name { get; set; }
}
And this is my Dto
public class ClientDto
{
public int ClientId { get; set; }
public string Name { get; set; }
public string BranchName { get; set; }
}
And these are the mapping configurations I have tried
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Client, ClientDto>().
ForMember(
dest => dest.BranchName,
opt => opt.MapFrom (src => src.Branch.Name)
);
}
}
and
CreateMap<Client, ClientDto>().IncludeMembers(src => src.Branch);
CreateMap<Branch, ClientDto>().ForMember(
dest => dest.BranchName,
opt => opt.MapFrom(b => b.Name)
);
in both cases I got nulls on BranchName, thank you.
You need to Include the navigation property on your repository (if using) implementation:
var client = await _context.Clients.Include(c => c.Branch).FirstOrDefaultAsync(c => c.Id == id);
Please make sure your actual object (usually from database using ef) includes the desired navigation property by using the Include – S. M. JAHANGIR
This is the correct answer.
I am using the following ComplexTypes:
CityStateRegion.cs
public class CityStateRegion
{
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string Airport { get; set; }
}
Address.cs
public class Address
{
public string AddressOne { get; set; }
public string AddressTwo { get; set; }
public CityStateRegion Area { get; set; }
}
The reason why I broke these into two separate complextypes is b/c I want to use the CityStateRegion in some Entities while needing the full address in others...
public class Person
{
public Guid PersonId { get; private set; }
public CityStateRegion Location { get; set; }
}
versus
public class Company
{
public Guid CompanyId { get; private set; }
public Address Address { get; set; }
}
I found that I had to do the following in FluentApi to make sure all fields relating to Area came across as required:
//Address ComplexType Configurations
modelBuilder.ComplexType<Address>()
.Property(a => a.AddressOne).HasMaxLength(100).IsOptional();
modelBuilder.ComplexType<Address>()
.Property(a => a.AddressTwo).HasMaxLength(100).IsOptional();
modelBuilder.ComplexType<Address>()
.Property(a => a.Area.State).IsRequired(); //CityStateRegion
modelBuilder.ComplexType<Address>()
.Property(a => a.Area.City).IsRequired(); //CityStateRegion
//... more props omitted
Then I wonder... what is the difference between the following?
modelBuilder.Entity<Company>()
.Property(c => c.Address.Area.City).HasMaxLength(50).Required();
vs
modelBuilder.ComplexType<Address>()
.Property(a => a.Area.City).HasMaxLength(50).Required();
vs
modelBuilder.ComplexType<CityStateRegion>()
.Property(a => a.City).HasMaxLength(50).IsOptional();
The problem is - sometimes it should be required and other times it shouldn't.
To recap:
Is a nested ComplexType typical?
What's the standard way of setting ComplexType properties if they change depending on which Entity is using them? I don't believe I have much of a choice in options but wanted to see how others would set this up. Does one property setting trump the next? I had troubles finding the answer on MSDN and don't feel like trying out three different migrations.
I have 2 tables that saved family members and the below LINQ is 100% work and got result return. I tried to map using Automapper but it's does not work , the customerViewItem does not have data and no error , could some one please advise, thanks in advance.
POCO
public class Cust_ProfileTbl
{
[Key]
public long bintAccountNo { get; set; }
public string nvarCardName { get; set; }
public string varEmail { get; set; }
public virtual ICollection<Cust_ProfileFamilyTbl> profileFamilyParents { get; set; }
public virtual ICollection<Cust_ProfileFamilyTbl> profileFamilyChildren { get; set; }
}
public class Cust_ProfileFamilyTbl
{
[Key]
public int intProfileFamily { get; set; }
public long bintAccountNo { get; set; }
public long bintAccountNoMember { get; set; }
public virtual Cust_ProfileTbl custProfileParent { get; set; }
public virtual Cust_ProfileTbl custProfileChild { get; set; }
}
In onModelCreating
modelBuilder.Entity<Cust_ProfileFamilyTbl>()
.HasRequired(m => m.custProfileParent)
.WithMany(t => t.profileFamilyParents)
.HasForeignKey(m => m.bintAccountNo)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Cust_ProfileFamilyTbl>()
.HasRequired(m => m.custProfileChild)
.WithMany(t => t.profileFamilyChildren)
.HasForeignKey(m => m.bintAccountNoMember)
.WillCascadeOnDelete(false);
ViewModels
public class Profile
{
public long bintAccountNo { get; set; }
public string varCardNo { get; set; }
public string nvarCardName { get; set; }
public string varEmail { get; set; }
public virtual ICollection<ProfileFamily> profileFamilyParents { get; set; }
public virtual ICollection<ProfileFamily> profileFamilyChildren { get; set; }
public Profile()
{
profileFamilyParents = new Collection<ProfileFamily>();
profileFamilyChildren = new Collection<ProfileFamily>();
}
}
public class ProfileFamily
{
public int intProfileFamily { get; set; }
public long bintAccountNo { get; set; }
public long bintAccountNoMember { get; set; }
public Profile custProfileParent { get; set; }
}
LINQ and AutoMapper
System.Linq.Expressions.Expression<Func<Cust_ProfileTbl, bool>> wherep = (x) => x.bintAccountNo.Equals(1);
Cust_ProfileTbl rs = (from family in context.member.Include("profileFamilyParents.custProfileChild")
.Where(wherep)
select family).Single();
Mapper.CreateMap<Cust_ProfileTbl, EFWeb.ViewModels.Profile>();
EFWeb.ViewModels.Profile customerViewItem = Mapper.Map<Cust_ProfileTbl, EFWeb.ViewModels.Profile>(rs);
First thing is you should not create a map for mapping a List<A> to a List<B>. You should just create a map from A to B--automapper knows how to map a List<A> to a List<B> if you give it a map from A to B. This means your map should be:
Mapper.CreateMap<Cust_ProfileTbl, EFWeb.ViewModels.Profile>();
Second, automapper will not know how to map your ICollection<Cust_ProfileFamilyTbl> properties in your entity to the ICollection<ProfileFamily> properties in your viewmodel. You need to provide a map for those types:
Mapper.CreateMap<Cust_ProfileFamilyTbl, EFWeb.ViewModels.ProfileFamily>();
It is always bet to initialize your maps inside of the startup entry point of the app. In an MVC project this would be in the global.asax and in a WPF application it would be the app.xaml.cs file.
If we were initializing in the global.asax, it would look something like this:
protected void Application_Startup()
{
Mapper.CreateMap<Cust_ProfileTbl, EFWeb.ViewModels.Profile>();
}
Notice we're not mapping from type to type, not from list of type to list of type, meaning we need to iteratively map.
List<EFWeb.ViewModels.Profile> customerViewItem = rs.Select(x => Mapper.Map<Cust_ProfileTbl>(x)).ToList();
Now, there's a good chance that your map will still fail, since your properties do not match on each side. If this is the case, you should use .IgnoreMember() extension methods on your CreateMap to ignore the members that cannot be mapped because they have no corresponding receiving type.
I have the following model:
A workflowconfiguration has a Brand and also has a type.
A Workflowconfiguration has a collection of Approvers.
The Approvers entity has property Status and username.
In another part of the application, we have an entity called RequestBase
This entity has a string property called CurrentStatus.
I need to make a query with linq or EF Lambda expressions that returns me ALL requests which status matches the username on the approvers entity.
I know its a little bit complicated, or really much complicated, lol.
These are my entities(simplified)
public class RequestBase
{
public int RequestBaseId { get; set; }
public string CurrentStatus { get; set; }
}
public class WorkflowConfiguration
{
public int WorkflowConfigurationId { get; set; }
public WorkflowType WorkflowType { get; set; }
public Brand Brand { get; set; }
public virtual ICollection<Approver> Approvers { get; set; }
}
public class Approver
{
public Approver()
{
}
public Approver(string approverUserName)
{
Name = approverUserName;
}
public int Id { get; set; }
public string Name { get; set; }
public string StepName { get; set; } -----||||>>>>>> Must match status?
public int Order { get; set; }
}
and my query?
obviously it does not even compile
return _context.RequestBases.
.Where(a => a.CurrentStatus.Any(workflowconfiguration.Select(b=>b.Approvers.Select(c => c.StepName))));
_context.RequestBases
.Where(rb => _context.Workflowconfiguration
.Any(wc => wc.Approvers
.Any(a => a.StepName == rb.CurrentStatus)));