AutoMapper - flatten domain model to view model. One is to many relationship - c#

I have a complex domain model which has many relationships with other entities in the system.
For the purpose of editing this model I want to set up a view model that simplifies things. I want to break up the model into smaller logical chunks with edit screens for each part instead of trying to represent the whole model on one screen.
In the domain model where I have a one is to many relationship it is represented like this:
public partial class CbItemsContent
{
public CbItemsContent()
{
this.cbItemsContentRegulators = new HashSet<cbItemsContentRegulator>();
}
public int ItemContentId { get; set; }
public int ItemID { get; set; }
......
public virtual CbItem CbItem { get; set; }
public virtual ICollection<cbItemsContentRegulator> cbItemsContentRegulators { get; set; }
}
cbItemsContentRegulator is another set of entities that are owned by CbItemsContent (the model shown above)
I would like to replace the Collection of cbItemsContentRegulators in my model with a simplified viewModel called ItemContentRegulatorsViewModel like this:
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public int[] RegulatorIds { get; set; }
}
which reduces the relationship to the ItemContent parent ID and an int array if Regulator IDs
is what I am trying to do possible?
How do I Map My collection of CbItemsContentRegulators to an int[] of Ids

Assuming that you're using Entity Framework, you should change the view model to
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public IList<int> RegulatorIds { get; set; } // IList
}
No you can define the mapping:
var profile = Mapper.CreateProfile("SomeName");
profile.CreateMap<CbItemsContent,ItemContentRegulatorsViewModel>()
.ForMember(dest => dest.RegulatorIds,
m => m.MapFrom(src => src.cbItemsContentRegulators.Select(c => c.RegulatorId)));
Now you can use it in a query like:
context.CbItemsContents.ProjectTo<ItemContentRegulatorsViewModel>()
With RegulatorIds as an array this would throw an exception that ToArray is not recognized.

Related

AutoMapper How to select Collection's property, project it into DTO & return projected collection?

Source models:
public class ExampleModel
{
public int Id { get; set; }
public float Something { get; set; }
public string Info { get; set; } = "";
}
public class ExampleModelContainer
{
public int Id { get; set; }
public ExampleModel Model { get; set; } = null!;
}
public class LargeEntity
{
public int Id { get; set; }
public List<ExampleModelContainer> ModelEntries { get; set; } = null!;
}
Target models:
public class ExampleModelDto
{
public float Something { get; set; }
public string Info { get; set; } = "";
}
public class LargeEntityDto
{
public int Id { get; set; }
public List<ExampleModelDto> Models { get; set; } = null!;
}
AutoMapper profile:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<ExampleModel, ExampleModelDto>();
// One of my attempt, I thought converting container into model, and automatically example model into DTO. Failed
CreateMap<ExampleModelContainer, ExampleModel>()
.ConvertUsing(x => x.Model);
CreateMap<LargeEntity, LargeEntityDto>()
.ForMember(x => x.Models,
opt => opt.MapFrom(y => y.ModelEntries));
}
}
Expected result:
LargeEntities' List<ExampleModelContainer> collection is mapped into List<ExampleModelDto>.
Actual result:
Unhandled exception. AutoMapper.AutoMapperConfigurationException: The following member on AutoMapperExps.Models.LargeEntityDto cannot be mapped:
Models
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type AutoMapperExps.Models.LargeEntityDto.
Context:
Mapping to member Models from AutoMapperExps.Models.LargeEntity to AutoMapperExps.Models.LargeEntityDto
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
at AutoMapper.Configuration.ConfigurationValidator.AssertConfigurationIsValid(IEnumerable`1 typeMaps)
at AutoMapper.Configuration.ConfigurationValidator.AssertConfigurationExpressionIsValid(IEnumerable`1 typeMaps)
at AutoMapper.MapperConfiguration.AssertConfigurationIsValid()
at Program.Main(String[] args) in D:\Experiments\AutoMapperExps\Program.cs:line 1
How I see it possible to be made:
Mapper takes source List<ExampleModelContainer> collection
Mapper Selects Model property from each element of collection
Mapper projects ExampleModel (returned from property above) into ExampleModelDto
Mapper returns List<ExampleModelDto> as a final result.
Note: This is made for Entity Framework queries. I want to do most projection operations on server-side.
TLDR: AutoMapper throws the exception because it cannot find a mapping ExampleModelContainer -> ExampleDto. If you define a mapping the error is fixed, e.g.:
CreateMap<ExampleModelContainer, ExampleModelDto>()
.IncludeMembers(x => x.Model);
The mapping tries to take several steps at once. If I understand your sample right, the configured mappings are like this:
ExampleModel -> ExampleDto
ExampleModelContainer -> ExampleModel
LargeEntity -> LargeEntityDto
When mapping the Models property in for LargeEntityDto, you are trying to map a List<ExampleModelContainer> without further configuration to a List<ExampleModelDto>. It may seem clear for AutoMapper to take the route
ExampleModelContainer -> ExampleModel -> ExampleDto
in this case, because there is only one path. But what if there were several paths to get from a ExampleModelContainer to a ExampleDto? The library would not be able to make a decision that solves all possible combinations.
This means that you as a developer have to make the decision by configuring a mapping from ExampleModelContainer to ExampleModelDto, e.g.:
CreateMap<ExampleModelContainer, ExampleModelDto>()
.IncludeMembers(x => x.Model);
This way, AutoMapper has a direct mapping between the types and is able to map the properties. See this fiddle to test.

ExpressMapper / EntityFramework - No parameterless constructor defined for this object

I am trying to use ExpressMapper to map data entities to models.
If I map entity to a model directly (both of them having same properties) then it is working fine.
But if I map linked entities to model then I am getting an error
There was an error: System.MissingMethodException: No
parameterless constructor defined for this object.
Database structure:
ExpressMapper Registration:
Mapper.Register<DiscountDaysOfWeek, DiscountDaysOfWeekModel>()
.Member(dest => dest.DiscountDayId, src => src.DiscountDayId)
.Member(dest => dest.DiscountDaysOfWeekId, src => src.DiscountDaysOfWeekId)
.Member(dest => dest.DiscountId, src => src.DiscountId)
.Member(dest => dest.Discountday, src => src.DiscountDay.Day);
Invoked like this:
var disDays = discs.SelectMany(x => x.DiscountDaysOfWeeks)
.Map<IQueryable<DiscountDaysOfWeek>, IQueryable<DiscountDaysOfWeekModel>>();
Getting the error message at the invoke.
DiscountDaysOfWeekModel:
public class DiscountDaysOfWeekModel
{
public int DiscountDaysOfWeekId { get; set; }
public int DiscountId { get; set; }
public int DiscountDayId { get; set; }
public string Discountday { get; set; }
}
DiscountDayOfWeek (Generated by EF)
public partial class DiscountDaysOfWeek
{
public int DiscountDaysOfWeekId { get; set; }
public int DiscountId { get; set; }
public int DiscountDayId { get; set; }
public virtual DiscountDay DiscountDay { get; set; }
public virtual Discount Discount { get; set; }
}
DiscountDay(Generated by EF):
public partial class DiscountDay
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public DiscountDay()
{
this.DiscountDaysOfWeeks = new HashSet<DiscountDaysOfWeek>();
}
public int DiscountDayId { get; set; }
public string Day { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<DiscountDaysOfWeek> DiscountDaysOfWeeks { get; set; }
}
Sample working one: In the below working sample the model and entities are having same properties
Mapper.Register<DiscountPreventedPriceEnding, DiscountPreventedPriceEndingModel>();
var execPriceEndings = discs.SelectMany(x => x.DiscountPreventedPriceEndings)
.Map<IQueryable<DiscountPreventedPriceEnding>, IQueryable<DiscountPreventedPriceEndingModel>>();
Any help would be greatly appreciated.
I appreciate this is an extremely old question, but given that I just spent 4 hours debugging a similar There was an error: System.MissingMethodException: No parameterless constructor defined for this object error on ExpressMapper, I thought I'd chime in with my findings.
So we had a situation similar to yours, in that we had domain models like so (all the following is simplified examples):
public class Owner
{
public int? ID { get; set; }
public string Name { get; set; }
}
public class Animal
{
public int? ID { get; set; }
public string Name { get; set; }
public int? OwnerID { get; set; }
[ForeignKey("OwnerID")]
public Owner Owner { get; set; }
}
With the following view model (i.e. what our APIs send out and receive):
public class AnimalViewModel
{
public int? ID { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
}
With mappings like so:
Mapper.Register<Animal, AnimalViewModel>();
Mapper.Register<AnimalViewModel, Animal>();
On mapping to or from the domain model and the view model we'd get the MissingMethodException, despite the fact that both the view model and the domain model had public, default constructors. The solution was to manually map the related entities in the domain model and exclude them from ExpressMapper's mappings like so:
Mapper.Register<Animal, AnimalViewModel>()
.Ignore(a => a.Owner);
Mapper.Register<AnimalViewModel, Animal>()
.Ignore(a => a.Owner);
From reading EntityMapper's source code, it seems the MissingMethodException is a total red herring which has nothing to do with the actual issue. The actual issue seems to be that it can't figure out how to convert one type to another. In our case -- where complex objects were mapped to/from primitives as above -- it was sufficient to exclude the related objects from the mapper and do it manually.
EDIT:
Upon further investigation, we traced the root problem in our case back to the fact that EF proxy creation creates generated types (e.g. 'MyModel_14289012') which don't match the types registered in the mapper. To prevent this, apply the following to your context:
Context.Configuration.LazyLoadingEnabled = false;
Context.Configuration.ProxyCreationEnabled = false;
and manually include any nested/related objects required in your model like so:
Context.Animals
.Include(a => a.Owner);
This fetches the related entities, but as their actual type rather than the EF-generated type.
Entity Framework uses a parameterless constructor to instantiate classes and reflection to populate class properties. If you have constructors with parameters, then the default parameterless constructor is hidden and you have to add it to your Entity classes for Entity Framework to use.
But if I map linked entities to model then I am getting an error
If your child entities are missing the parameterless constructor and lazy loaded, then EF is failing when it attempts to instantiate the child entity which doesn't have a parameterless constructor.
Note: the parameterless constructor doesn't have to be public.

Creating a Blog Comments and Reply section using ASP.NET MVC 4 (nested collections)

I'm building a Blog Comment and Reply section and I have these three classes mapped to my DB. The first class holds a collection of related comments to an article, the second class holds a collection of related remarks to the comments:
public class Article
{
public int ArticleID { get; set; }
public byte[] Image { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime DatePublished { get; set; }
public string Author { get; set; }
public CategoryTyp Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int ArticleID { get; set; }
public int CategoryID { get; set; }
public int UserID { get; set; }
public string Description { get; set; }
public DateTime CommentDate { get; set; }
public virtual ICollection<Remark> Remarks { get; set; }
}
public class Remark
{
public int RemarkID { get; set; }
public int CommentID { get; set; }
public int ArticleID { get; set; }
public string RemarkDetail { get; set; }
public DateTime RemarkTime { get; set; }
}
And inside my Controller:
public ActionResult GetArticle(int id)
{
var article = db.Articles.Include("Comments").Where(a => a.ArticleID == id).SingleOrDefault();
return View(article);
}
I understand the basis of eager loading but my questions are:
How do you implement it when you're pulling data from multiple related tables?
What is the best practice of populating it to the View? Once I create a View Model how do I stuff the related collections?
1) With multiple related tables you can have two scenarios:
a) Multiple top level relations: you simply add multiple Include statements (I would suggest using lambda expressions instead of strings for this, to avoid typos).
db.Articles
.Include(a=>a.Comments)
.Include(a=>a.SomethingElse)
.FirstOrDefault(a=>ArticleID==id); // Side note: I would suggest this instead of your Where plus SingleOrDefault
For these scenarios I always use a helper method like this one.
b) Multiple nested related entities:
db.Articles
.Include(a=>a.Comments.Select(c=>c.Remarks)
.FirstOrDefault(a=>ArticleID==id);
2) It's a bit up to you how you pass the data to the views. One best practice I can tell you is that you shouldn't let views lazy load any dependant entities or collections. So your use of Include is correct, but I would even suggest to remove the virtual (deactivate lazy loading) to avoid missing an Include by accident.
Regarding the ViewModels you mention, you are actually not using view models, but your data models. This is OK in most cases, unless you need to format the data somehow or add extra information. Then you would need to create a View Model and map it from the data coming from EF.
Another scenario would be if you used WebAPI or an Ajax Action. In that case, I would suggest to use a DTO (equivalent to a ViewModel) to be able to better control the data returned and its serialization.
One last comment about ViewModels is that if you have heavy entities but you only need a few properties, a good choice is to use Projections, to instruct EF to only load the required properties, instead of the full object.
db.Articles
.Include(a=>a.Comments)
.Select(a=>new ArticleDto { Id = a.ArticleID, Title = a.Title })
.ToListAsync();
This will translate to a "SELECT ArticleID, Title FROM Articles", avoiding returning the article bodies and other stuff that you might not need.
You can chain the relationships with Include. For example:
var article = db.Articles.Include("Comments.Remarks").Where(a => a.ArticleID == id).SingleOrDefault();
I'm not sure what you mean by your second question, though. By issuing this query you already have all the comments and all the remarks for those comments. Therefore, you can access them off of the article instance out of the box:
foreach (var comment in article.Comments)
{
...
foreach (var remark in comment.Remarks)
{
...
}
}
How you handle that with your view model is entirely up to you. You could map the comments/remarks to view models of their own, set them directly on the view model, etc. That's all down to what the needs of your application are, and no one but you can speak to that.

It is possible to query my case with single query using EF? (where condition for entity and another one for nested entity)

I have two entities nested to each other, they have one-to-many relation themselves.
For me it requires two queries that to get the enabled modules (Module.IsEnabled == 1) and their ModuleScreen property populated by the also enabled (ModuleScreen.IsEnabled == 1) ModuleScreen objects.
Is there a way to query this by one query?
I already met this question where the problem is the same and the accepted answer shows that it requires two queries.
public class Module
{
public virtual int Id
{
get;
set;
}
public virtual int IsEnabled
{
get;
set;
}
public virtual IEnumerable<ModuleScreen> ModuleScreen
{
get;
set;
}
public class ModuleScreen
{
public virtual int Id
{
get;
set;
}
public virtual int IsEnabled
{
get;
set;
}
public virtual Module Module
{
get;
set;
}
}
You can achieve that projecting what you need onto an anonymous type:
var query=context.Modules
.Where(m=>m.IsEnabled == 1)
.Select(m=> new { ModuleId= m.Id,
ModuleScreen=m.ModuleScreen.Where(m=>m.IsEnabled == 1)
}
);
In case you don't want to work with an anonymous type, you can also create another class to save the info you want to project:
public class ModuleDTO
{
public int Id{get; set;}
public IEnumerable<ModuleScreen> ModuleScreens{get; set;}
}
And then you can do this:
var query=context.Modules
.Where(m=>m.IsEnabled == 1)
.Select(m=> new ModuleDTO{ Id= m.Id,
ModuleScreens=m.ModuleScreen.Where(m=>m.IsEnabled == 1)
}
);
DTO is nothing more than a container class that exposes properties to save the info you want to project. DTOs help to further decouple presentation from the service layer and the domain model.

Many to many relation - have I done it right?

The idea is pretty simple. I have a list of tags. When I create a question I want to add some tags to it.
Models:
public class QuestionModel
{
public int Id { get; set; }
public String Content { get; set; }
public ICollection<TagModeltoQuestionModel> Tags { get; set; }
[NotMapped]
public ICollection<TagModel> AssignedTags { get { return Tags.Select(x => x.Tag).ToList(); } }
public int UserId { get; set; }
}
public class QuestionViewModel // helper - not in database
{
public QuestionModel Model { get; set; }
public ICollection<TagModel> Tags { get; set; }
}
public class TagModel
{
public int Id { get; set; }
public String Name { get; set; }
public ICollection<TagModeltoQuestionModel> Questions { get; set; }
[NotMapped]
public bool Assigned { get; set; }
[NotMapped]
public ICollection<QuestionModel> AssignedQuestions { get { return Questions.Select(x => x.Question).ToList(); } }
}
public class TagModeltoQuestionModel // many to many
{
[Key, Column(Order = 0)]
public int TagId { get; set; }
[Key, Column(Order = 1)]
public int QuestionId { get; set; }
public virtual QuestionModel Question { get; set; }
public virtual TagModel Tag { get; set; }
}
Controller:
[HttpPost]
public ActionResult Edit(QuestionViewModel questionViewModel)
{
if (ModelState.IsValid)
{
_repo.Update(questionViewModel.Model, questionViewModel.Tags); // see repo code below
return RedirectToAction("Index");
}
return View(questionViewModel.Model);
}
Repo:
public void Update(QuestionModel entity, ICollection<TagModel> tags)
{
AssignTags(entity, tags);
Db.Attach(entity);
Db.SaveChanges();
}
private void AssignTags(QuestionModel entity, ICollection<TagModel> tags)
{
tags = tags.Where(x => x.Assigned).ToArray(); // remove unassigned comming form View --> Controller
var linkedTags =
Db.TagsToQuestions.Where(x => x.QuestionId == entity.Id);
var linkedTagsIds = linkedTags.Select(x => x.TagId);
var selectedTagsIds = tags.Select(x => x.Id);
var oldTags = linkedTags.Where(x => !selectedTagsIds.Contains(x.TagId));
var newTags = tags.Where(x => !linkedTagsIds.Contains(x.Id)).Select(x=> new TagModeltoQuestionModel{QuestionId=entity.Id,TagId=x.Id});
foreach (var t in oldTags)
Db.Delete(t);
foreach (var t in newTags)
Db.Add(t);
Db.SaveChanges();
}
This works fine, though I'm not sure if this is the right way to go (in fact I implemented the whole many-to-many logic myself). Is there a smarter way to let EF do the job for me? I dug through a bunch of tutorials, but none of them worked for me.
Additionally I feel that AssignTags method could be written in a better way, so any comments concerning that also appreciated.
EDIT
According to haim770's answer I simplified the model the way he suggested.
My controller now looks like that:
public void Update(QuestionModel entity, ICollection<TagModel> tags)
{
Db.Attach(entity);
//these lines give the same result
//var ids = tags.Select(y => y.Id).ToArray();
//entity.Tags = Db.Tags.Where(x => ids.Contains(x.Id)).ToArray();
tags.ForEach(x => Db.Attach(x));
entity.Tags = tags;
Db.SaveChanges();
}
SaveChanges results in an error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
inner:
{"A duplicate value cannot be inserted into a unique index. [ Table name = TagModelQuestionModels,Constraint name = PK_TagModelQuestionModels ]
So how to implement it correctly?
You do not need the TagModeltoQuestionModel class. You could model many-to-many relations like this:
public class QuestionModel
{
//....
public ICollection<TagModel> Tags { get; set; }
}
public class TagModel
{
//....
public ICollection<QuestionModel> Questions { get; set; }
}
Question holds a reference to many Tags and each Tag holds a reference to many Questions.
The whole point of Entity Framework (like any other ORM) is to spare you from having to model your objects and their relations in a database-like way but rather let you model it in a pure Object Oriented way then letting the ORM do the 'dirty work' of intermediate-tables, foreign keys etc...

Categories

Resources