Create EF Core Include().ThenInclude() like function? - c#

I'm writing a few simple extension methods to create DTOs out of entities defined in the domain, however, those entities have properties that are also entities, and I'd like to be able to write something (that I personally find elegant) like they do in EF Core with Include().ThenInclude().
Ideally I'd like to be able to write something like
return myEntity.ToDto().Include(entity => entity.SubEntity).ThenInclude(subEntity => subEntity.AnotherSubEntity);
Is it possible?
The idea is that if I just call ToDto() I would simply receive a basic DTO object where all simple type properties are set but all complex type properties are null, unless I specify that I want to inclue one (or more) of the properties too.

AutoMapper Queryable Extensions provides a convenient way to query entities with relationships and map them to DTOs in one step.
It produces an optimized SQL query needed to copy that data.
Example:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<BlogDTO, Blog>().ReverseMap();
cfg.CreateMap<PostDTO, Post>().ReverseMap();
});
return _context.Blogs.Where(b => b.Id == Id)
.Include(x => x.Posts)
.ProjectTo<BlogDTO>(config)
.ToList();

I've actually managed to do it by sort of copying how EF Core does it with a lot of reflection and expression trees, and by creating my own visitor to then process the whole expression tree.
I am now able to write code like: myEntity.AsDtoable().Include( x => x.ComplexTypeProp ).ThenInclude( x => x.AnotherComplexTypeProp ).ToDto< EntityDto >();

Related

Entity Framework Core: Custom Linq methods for querying view models from DbContext

I am developing a .NET Core web API using .NET Core 2.0 and Entity Framework 2.0. The project and especially the amount of view models keep growing and the complexity becomes more intense (more nested models). The main point for my question is that the view models share a lot of sub models.
And I bet I don't use the correct keywords for googling because I can't find what I want to know.
Currently I query and map them (according to some defined logic) directly via the DbContext (each time aka for each view model). For example:
List<MyViewModel1> items = await MyDbContext.MyDbModel1
.Where(dbm1 => dbm1.SomeValue > 10)
.Select
(
dbm1 => new MyViewModel1
{
ValueName = dbm1.SomeValue,
NestedModel1 = new NestedViewModel1
{
SomeValue1 = dbm1.OtherTableModel1.Value1,
SomeValue2 = dbm1.OtherTableModel1.Value2
},
NestedModel2 = (!dbm1.OtherTableModel2Id.HasValue) ? null :
new NestedViewModel2
{
SomeCalculatedValue = dbm1.OtherTableModel2.Value1 + dbm1.OtherTableModel2.Value2,
SomeOtherValue = dbm1.OtherTableModel2.Value3
}
}
)
.ToListAsync();
Now the problem is: NestedViewModel2 (the one with the calculation) is attached to multiple view models and so far I write this calculation every time in the same way as above (for MyViewModel1, MyViewModel2, MyViewModel3,...). And as it is, requirements change regulary. And each time the requirements change I have to spot all places in my growing code and fix the calculation on multiple places.
That approach does not follow DRY (don't repeat yourself).
But I can't find information if it is possible (and how) to write some custom linq extension methods (which entity framework will understand for the linq to SQL conversion) like:
SelectViewModel1WithValueGreaterThan(this DbSet, int greaterThan)
SelectViewModel2List(this DbSet)
SelectViewModel3Details(this DbSet)
and especially have the Nested Models select in one place (that can get called from the above methods).
SelectNestedModel1(???)
SelectNestedModel2(???)
Any hints for me how to pack those queries into custom linq extension methods so the code follows the DRY principle and can be reused?
You should take a look at a mapping framework to map the DB Entities to the ViewModels. For example, we are using Automapper to achieve this.
This allows you to define the mappings centrally and reuse them. This should simplify most of the code, because the logic you want to refactor is in the select part of the query.
MappingConfig.CreateMap<MyDbModel1, MyViewModel1>()
.ForMember(vm => vm.ValueName, o => o.MapFrom(ent => ent.SomeValue))
.ForMember(vm => vm.NestedModel1, o => o.MapFrom(ent => ent.OtherTableModel1));
MappingConfig.CreateMap<OtherTableModel1, NestedViewModel1>()
.ForMember(vm => vm.SomeValue1 , o => o.MapFrom(ent => ent.Value1))
.ForMember(vm => vm.SomeValue2 , o => o.MapFrom(ent => ent.Value2));
var items = Mapper.Map<MyViewModel1[]>(MyDbContext.MyDbModel1.Where(dbm1 => dbm1.SomeValue > 10));

Left right DTO to object AutoMapper- C#

We recently implemented automapper and currently looking to optimise assigning values from the DTO to model. currently we are doing something like
model.Property1 = dto.Property1;
model.SomePropertyType = dto.PropertyType;
model.Property2 = dto.Property2;
Now this could go pretty long and repetitive task to all Mapper classes.
Is there a way to simplify this on AutoMapper?
If you are using Automapper then have you not defined the maps (profile)? I believe, you might have defined those so please use those to instruct Automapper how to map source object to Target.
Another point Automapper also works based on naming convention, so if you have same property name in both source and target then it will automap automatically. So you don't unnecessary define the mapping fort it. To override mapping for a property (or whose name/type does not match), you can use .ForMember method.
cfg.CreateMap<MyDTO, MyModel>()
.ForMember(destination => destination.PropertyType,
opts => opts.MapFrom(source => source.SomePropertyType ));
You can read about Automapper at here.
Now in the code to get the mapped object, use it like
Mapper.Map<MyModel>(object of MyDTO);

Is it possible to map IQueryable<CatDTO> to IQueryable<CatEf>?

This is a question for Automapper professionals. I have tried to do query mapping for several days already - and no luck. Seems like Automapper is not intended to be used the way I want to use it. But maybe I am wrong. So here is the question...
I have such classes:
CatDto (Name, Age, Toys (collection of ToyDto objects))
ToyDto (CatName, ToyName, Cat (parent CatDto object))
Cat (comes from Entity Framework, has properties similar to those in CatDto)
Toy (comes from Entity Framework, has properties similar to those in ToyDto)
I want to write a generic read function in my data access layer, something like this:
IEnumerable<CatDto> Read(IQueryable<CatDto> query) {
// here "query" is converted
// to Entity Framework query by means of AutoMapper,
// EF query gets executed,
// I convert EF entities (Cat) back to CatDto - this is not essential
// result is returned
}
I will call this function in different manners. Example:
var q = new ObjectModel.Collection(Of CatDto)).AsQueryable();
q = q.Where(c => c.Toys.Count() > 1);
var someResultVar = Read(q);
So far any attempts to implement such behavior have failed. I wonder if Automapper is a helper here or am I going completely wrong way?
I believe the functionality you want is in UseAsDataSource
You can't map IQueryable, but you shouldn't need to with UseAsDataSource
Example
IQueryable<CatDto> someResultVar = new ObjectModel.Collection(Of CatDto)).AsQueryable().UseAsDataSource().For(Of OrderLineDTO).Where(c => c.Toys.Count() > 1);
When you enumerate it will convert Lambda from CatDto to CatEf and call ProjectTo<CatDto> and return CatDto objects

Creating maps for members using Automapper

I'm beginning AutoMapper and had a question. I came across sample code like this:
Automapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));
So this would let me convert a book to a book model, and then map the src to the src.author.name property because there was not a 1-1 mapping.
To confirm, it does not work in reverse on its own, meaning I now need to explicitly do this:
Mapper.Mapper.CreateMap<BookViewModel, Book>()
.ForMember(dest => dest.Author.Name,
opts => opts.MapFrom(src => src.Author));
Is that correct? So to further confirm, if I had 50 view models and views, I'd literally need 100 (one for the way in, one for the way out, plus any additional lines of code in the .ForMember expression)
Is this true? Is this a common practice (i.e. you could potentially see hundreds of lines of code to handle the field mapping back and forth for multiple DTOs with properties that do not match up 1-1)?
CreateMap creates a mapping for AutoMapper to use later when required. This is required only once in the lifetime of your Application. In order to make use of this mapping you have defined, you just need to call Automapper.Map method. Thus you don't have to create the mapping again and again.
Example:
var myBookViewModel = Automapper.Map<Book, BookViewModel>(myBook);
You are correct saying that defining a mapper for Book to BookViewModel will not make Automapper to create a mapper for BookViewModel to Book automatically for you. You have to create the mapper.
In essence yes, you'd need basically to specify both back and forth configurations to handle each DTO mapping. Without a deterministic pattern it is impossible to guess how the mapping would work between complex object when members' names are different, or when the properties you want are further within other objects, like in your example.
However, Automapper is fairly smart if you follow some basic patterns. In your case, if you change the DTO property name to AuthorName instead of Author, the CreateMap will properly map without having to specify the mapping manually (they call this "Flattening"). Automapper will even map methods to properties if they follow a GetProperty() => Property { get; set; } pattern. Renaming your DTO properties to follow this pattern could probably save you a bunch of lines. Take a look at their starters guide for other ways to simplify your mappings.

Automapper, MapFrom and EF dynamic proxies

I have been trying to map my domain objects to a report view model. Things all worked well in testing where I faked the entity framework code out and used a builder to return a fully populated pocco object. Now that I am actually hitting the database and returning data I am seeing some wierd dynamic proxy type errors.
Here is a sample of my code:
public class ContactMapping : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Contact, ReportRowModel>()
.ForMember(dest => dest.Gender, opt => opt.MapFrom(src => src.Gender.Name));
}
}
And the mapping code is like this:
var contact = GetContactFor(clientPolicy);
Mapper.DynamicMap(contact, rowModel);
return rowModel;
The contact fields all populate correctly except for the rowModel.Gender field which is returning System.Data.Entity.DynamicProxies.Gender_3419AAE86B58120AA2983DA212CFFEC4E42296DA14DE0836B3E25D7C6252EF18
I have seen solutions where people have had problems using Map instead of DynamicMap, but I haven't found anything where a .ForMember mapping is failing like this.
Any suggestions.
Your EF query is not returning the Gender, it is returning a Proxy that can get Gender for you when evaluated, which is not of the type that AutoMapper built a mapping to handle.
You either need to eagerly fetch Gender in your query, or use AutoMapper's IQueryable Extention's Project method to have AutoMapper emit an anonymous projection (again, in your query), rather than try to apply the AutoMapping after the result has been returned from your EF context.
This is good practice in general to avoid Select N+1 issues.
I've got the same issue right now with version 4.x, reverting to 3.3.1 fixed the issue.

Categories