为实体框架中的投影动态传递Select中的属性



有没有一种方法可以在Select中动态传递我想从DB中检索的属性,我事先不知道我需要的属性,也不想在我的存储库中写入条件。

我不想一次检索所有字段,只想根据某些条件检索我需要的字段。

例如:

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;
}
}

在服务层我有

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:

query.Select(Selector()).FirstOrDefault();

选择器可以是这样的:

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
};
}

正如您所看到的,这里明显的缺点是您需要复制/粘贴所有其他选定的属性。这就是它丑陋的原因。

自动映射器

  1. 配置

    您可以将AutoMapper与不同的配置一起使用。首先,您需要为所有不需要动态的属性定义一个标准映射。

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

    接下来,您需要定义不同的配置,并将AddStandardStudentMap方法添加到每个invidual映射中。

    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()
    );
    

    在这之后,只需使用您的条件来决定您需要的配置

    IConfigurationProvider provider;
    if(Condition())
    provider = config1;
    else
    provider = config2;
    

    然后使用代替.Select()

    query.ProjectTo<StudentViewModel>(provider).FirstOrDefault();
    

    正如我们所看到的,这个解决方案仍然很丑陋,有很多开销,但在某些情况下是需要的,这就是我在这里说的原因。

  2. 表达式

    这与Configs有点相似,但为您带来了更大的灵活性和更少的编写工作量。

    首先创建一个配置,但这次使用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()))
    );
    

    Selector方法可以如下所示:

    private static Expression<Func<Student, StudentViewModel>> Selector()
    {
    if(Condition())
    return src => src.Property1;
    else
    return src => src.Property2;
    }
    

    然后像使用configs解决方案一样使用它,但不选择特定的配置:

    query.ProjectTo<StudentViewModel>(config).FirstOrDefault();
    

结论

我知道这是一个很大的投入,无论有没有AutoMapper,都有更多的可能性来实现你想要的行为。但我建议您(根据您提供的信息)使用带有表达式的AutoMapper。它应该提供您需要的灵活性,并且可以扩展以满足进一步的需求。

最新更新