Is there a way to pass the properties I want to retrieve from the DB in a Select dynamically, I don't know the properties I need beforehand and I don't want to write the conditions in my repository.
I don't want to retrieve all the fields at once, just the ones I need based on some conditions.
For example:
public class Student
{
public string Property1 {get; set;}
public string Property2 {get; set;}
//other properties here
[NotMapped]
public string Selected
{
if(condition)
return Property1;
else
return Property2;
}
}
and in the service layer I have
query.Select(s => new StudentViewModel
{
Value = s.Selected; //this one will get the property we want based on a condition
//other stuff here
//OtherValue = s.OtherProperty
}
).FirstOrDefault();
Selector
An easy but ugly way is to use a Selector:
query.Select(Selector()).FirstOrDefault();
And the Selector can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if (Condition())
return x => new StudentViewModel
{
Name = x.Property1,
OtherName = x.OtherName
};
return x => new StudentViewModel
{
Name = x.Property2,
OtherName = x.OtherName
};
}
As you can see the obviously downside here is that you need to copy/paste all other selected properties. That is why its ugly.
AutoMapper
Configs
You can use AutoMapper with different configurations. First you need to define a standard mapping for all properties that don't need to be dynamic.
public static void AddStandardStudentMap(this IMappingExpression<Student, StudentViewModel> map) { map.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherProperty)) .ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherProperty2)); // you can concat .ForMember() for each property you need. }
Next, you need to define the different configs and add the AddStandardStudentMap
method to each invidual mapping.
var config1 = new MapperConfiguration(cfg => cfg.CreateMap<Student, StudentViewModel>() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property1)) .AddStandardStudentMap() ); var config2 = new MapperConfiguration(cfg => cfg.CreateMap<Student, StudentViewModel>() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property2)) .AddStandardStudentMap() );
After this, just use your conditions to decide which config do you need
IConfigurationProvider provider; if(Condition()) provider = config1; else provider = config2;
And then instead of .Select()
use:
query.ProjectTo<StudentViewModel>(provider).FirstOrDefault();
As we can see this solution is still ugly and has a lot of overhead but its needed in some cases, thats why i stated it here.
Expression
This is a bit similar to the Configs but brings you more flexibility and less writing effort.
First create a config but this time with a Selector
var config = new MapperConfiguration(cfg => cfg.CreateMap<Student, StudentViewModel>() .ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherName)) .ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherName2)) // and so on. Map all your properties that are not dynamically. // and then the Selector .ForMember(dest => dest.Name, opt => opt.MapFrom(src => Selector())) );
The Selector method can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector() { if(Condition()) return src => src.Property1; else return src => src.Property2; }
And then just use it like the configs solution but without selecting a particular config:
query.ProjectTo<StudentViewModel>(config).FirstOrDefault();
Conclusion
I know this is a lot input and there are even more possibilities to achieve the behaviour that you want, with or without AutoMapper. But i would suggest you (grounded on the information you gave us) to use AutoMapper with Expressions. It should give the flexibility you need and is extensible for further requirements.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.