【问题标题】:Combining Linq Expressions for Dto Selector为 Dto 选择器组合 Linq 表达式
【发布时间】:2014-10-22 12:14:23
【问题描述】:

我们的项目中有很多 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 语句,以免失去性能改进。

我怎样才能做到这一点?

【问题讨论】:

  • 你的类和 dtos 有什么抽象吗?也许您可以实现一个通用方法来创建表达式。
  • 你需要什么抽象?对于此示例,可以安全地假设 dto 和 class 是相同的。我不确定我将如何实现通用方法。我不想失去 EF 解析表达式并基于它构建 sql 表达式的能力。

标签: linq lambda entity-framework-6


【解决方案1】:

您是否考虑过使用 Automapper ?您可以定义您的 Dto 并在原始实体和 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>()

当您使用实体框架(例如)查询数据库时,您可以使用类似这样的方法仅获取 PersonDto 列:

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

这将返回一个姓氏包含“John”的 PersonDtos 列表,例如,如果您运行 sql 分析器,您将看到仅选择了 PersonDto 列。

Automapper 还支持层次结构,例如,如果您的 Person 有一个与之链接的地址,您想为其返回 AddressDto。

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

【讨论】:

  • 我不知道 Automapper 可以做到这一点!我不认为我会将它引入这个项目,因为这意味着引入另一个依赖项,我们几乎完成了。出于兴趣,我还想知道如何手工完成。仍然是一个很好的提醒。
【解决方案2】:

我想了想,也没有想出什么“厉害”的解决方案。

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

  1. 使用占位符并完全重写表达式树。

类似的,

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 等。这绝对会给你更好的“可重用性”率。它不能 100% 解决您的问题,但它可以帮助您更好地编写查询。例如:from.MyCList.Select(dtoCSelector)

【讨论】:

  • 我会在版本 1 中使用 EF 的表达式功能吗?我不确定 EF 是否会正确解释“Magic.Swap”。你能用代码 sn-p 详细说明 2 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多