结合Linq表达式的Dto选择器

时间:2014-10-22 12:14:23

标签: linq lambda entity-framework-6

我们的项目中有很多Dto类,并且在不同的场合使用来自实体框架上下文的表达式来选择它们。这样做的好处是,EF可以解析我们的请求,并从中构建一个很好的SQL语句。

不幸的是,这导致了非常大的表达,因为我们无法将它们组合起来。

因此,如果您有一个具有3个属性的类DtoA,并且其中一个属于具有5个属性的DtoB类,并且其中一个属于具有10个属性的DtoC类,则必须编写一个大选择器。

public static Expression<Func<ClassA, DtoA>> ToDto =
        from => new DtoA
        {
            Id = from.Id,
            Name = from.Name,
            Size = from.Size,
            MyB = new DtoB
            {
               Id = from.MyB.Id,
               ...
               MyCList = from.MyCList.Select(myC => new DtoC
                   {
                      Id = myC.Id,
                      ...
                   }
            }
        };

此外,它们无法重复使用。如果你有DtoD,它也具有DtoB类的属性,你将不得不再次粘贴所需的DtoB和DtoC代码。

public static Expression<Func<ClassD, DtoD>> ToDto =
        from => new DtoD
        {
            Id = from.Id,
            Length = from.Length,
            MyB = new DtoB
            {
               Id = from.MyB.Id,
               ...
               MyCList = from.MyCList.Select(myC => new DtoC
                   {
                      Id = myC.Id,
                      ...
                   }
            }
        };

所以这会很快升级。请注意,提到的代码只是一个例子,但你明白了。

我想为每个类定义一个表达式,然后根据需要组合它们,以及EF仍然能够解析它并生成SQL语句,以免失去性能提升。

我怎样才能实现这个目标?

2 个答案:

答案 0 :(得分:1)

我想了一下,我没有想出任何&#34;真棒&#34;溶液

基本上你在这里有两个一般的选择,

  1. 使用占位符并完全重写表达式树。
  2. 像这样,

    public static Expression<Func<ClassA, DtoA>> DtoExpression{
        get{
            Expression<Func<ClassA, DtoA>> dtoExpression =  classA => new DtoA(){
                BDto = Magic.Swap(ClassB.DtoExpression),
            };
    
            // todo; here you have access to dtoExpression,
            // you need to use expression transformers
            // in order to find & replace the Magic.Swap(..) call with the
            // actual Expression code(NewExpression),
            // Rewriting the expression tree is no easy task,
            // but EF will be able to understand it this way.
    
            // the code will be quite tricky, but can be solved
            // within ~50-100 lines of code, I expect.
            // For that, see ExpressionVisitor.
    
            // As ExpressionVisitor detects the usage of Magic.Swap,
            // it has to check the actual expression(ClassB.DtoExpression),
            // and rebuild it as MemberInitExpression & NewExpression,
            // and the bindings have to be mapped to correct places.
    
            return Magic.Rebuild(dtoExpression);
        }
    
    1. 另一种方法是仅开始使用Expression类(抛弃LINQ)。通过这种方式,您可以从零开始编写查询,并且可重用性会很好,但事情会变得更加困难。你失去了类型安全。 Microsoft对动态表达式有很好的参考。如果以这种方式构建所有内容,则可以重用许多功能。例如,您定义NewExpression,然后如果需要,您可以稍后重复使用它。

    2. 第三种方法是基本上使用lambda语法:.Where.Select等。这样可以提供更好的&#34;可重用性&#34;率。它没有100%解决您的问题,但它可以帮助您更好地撰写查询。例如:from.MyCList.Select(dtoCSelector)

答案 1 :(得分:0)

您是否考虑过使用Automapper?你可以定义你的Dtos并在原始实体和Dto之间创建一个映射和/或反之亦然,并且使用投影,你不需要任何select语句,因为Automapper会自动为你做这个并且它只会投影dto的属性进入SQL查询。

例如,如果您有一个具有以下结构的Person表:

public class Person
{
        public int Id { get; set; }
        public string Title { get; set; }
        public string FamilyName { get; set; }
        public string GivenName { get; set; }
        public string Initial { get; set; }
        public string PreferredName { get; set; }
        public string FormerTitle { get; set; }
        public string FormerFamilyName { get; set; }
        public string FormerGivenName { get; set; }
}

你的dto是这样的:

public class PersonDto
{
        public int Id { get; set; }
        public string Title { get; set; }
        public string FamilyName { get; set; }
        public string GivenName { get; set; }
}

你可以在这个

之间创建Person和PersonDto之间的映射
    Mapper.CreateMap<Person, PersonDto>()

当您使用Entity Framework查询数据库时(例如),您可以使用类似这样的内容来获取PersonDto列:

ctx.People.Where(p=> p.FamilyName.Contains("John")) 
                .Project()
                .To<PersonDto>()
                .ToList();  

将返回一个PersonDtos列表,其姓氏包含“John”,如果您运行sql profiler,您将看到只选择了PersonDto列。

Automapper也支持层次结构,如果你的Person有一个链接到它的地址,你想为它返回AddressDto。

我认为值得一看并检查它,它清除了手动映射所需的许多混乱。