简体   繁体   中英

Dynamically passing properties in Select for projection in Entity Framework

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

  1. 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.

  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM