【问题标题】:Split logic from a lambda expression to mutliple LINQ Expressions [duplicate]将逻辑从 lambda 表达式拆分为多个 LINQ 表达式 [重复]
【发布时间】:2016-11-24 12:20:04
【问题描述】:

下面的代码示例说明了我的问题。 如何将 lambda 的一部分放在其他地方?

如何从我的“GetGurus()”方法中调用“Foo”? 我希望 LINQ 将其转换为 1 条语句。

public enum GuruLevel
 {
    NotSet,
    Goku,
    SuperSayan
}

 private IEnumerable<PersonInfo> GetGurus()
 {
    using (var context = new CRMContext())
    {
        var persons = context.Person
            .Where(p => p.Experience > 10)
            .OrderBy(p => p.DateOfBirth)
            .Select(p => new PersonInfo())
            {
                StackOverFlowName = p.StackOverFlowName,
                Experience = p.Experience,
                GuruStatus = Foo //(p.Experience > 9000) ? GuruLevel.SuperSayan : GuruLevel.Goku
            }
        return persons.ToList();
    } 
 }

public static System.Linq.Expressions.Expression<Func<Person, GuruLevel>> Foo
{
    get
    {
        return bar => (bar.Experience > 9000) ? GuruLevel.SuperSayan : GuruLevel.Goku;
    }
}

【问题讨论】:

  • 我正在阅读您的链接,看来这正是我所需要的。虽然实现起来似乎相当复杂:(
  • 很遗憾您没有发布答案,因为您给出了最佳答案!我安装了DelegateDecompiler。它有效!
  • 事实上,这个问题原来是我提到的问题的重复。理想情况下,它应该因此而关闭。
  • 我们的终端用户现在也很开心!我们项目查询的性能提升超出预期!我喜欢它!

标签: c# linq lambda linq-to-entities


【解决方案1】:

这不是您问题的直接答案,但它与您提出问题的原因有关。

我假设,您正在使用某种 DB,因此您不能使用常规方法,因为 DB 的上下文不会知道它们。

例如,您实际上可以执行对数据执行某些操作的私有方法,但您不能使用自定义类型或类。在您的示例中,“GuruLevel”将不会被 DB 识别。

我建议不要尝试在 DB 端进行转换。而是以 DB 格式检索您需要的所有数据,并在应用程序端进行转换。

using (var context = new CRMContext())
{
    var persons = context.Person
        .Where(p => p.Experience > 10)
        .OrderBy(p => p.DateOfBirth)
        .Select(p => new {p.StackOverFlowName, p.Experience }) //or whole object, or other fields if needed
        .ToList() //executes query, all following code will be in a context of application not DB
        .Select(p => new PersonInfo())
        {
            StackOverFlowName = p.StackOverFlowName,
            Experience = p.Experience,
            GuruStatus = GetGuruStatus(p.Experience)
        }
    return persons.ToList();
}

private GuruLevel GetGuruLevel(int exp)
{
    return exp > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku
}

【讨论】:

    【解决方案2】:

    这个逻辑是PersonInfo 类的职责。这段代码通常放在构造函数或工厂类中,完全符合单一职责原则。

    class PersonInfo
    {
        public string StackOverFlowName { get; set; }
        public int Experience { get; set; }
        public GuruLevel GuruStatus { get; set; }
    
        public void PersonInfo(Person p)
        {
          StackOverFlowName = p.StackOverFlowName;
          Experience = p.Experience;
          GuruStatus = p.Experience > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku;
        }
    }
    

    然后将您的服务方法更改为:

    private IEnumerable<PersonInfo> GetGurus()
    {
       using (var context = new CRMContext())
       {
           var persons = context.Person
               .Where(p => p.Experience > 10)
               .OrderBy(p => p.DateOfBirth)
               .ToList()
               .Select(p => new PersonInfo(p))
           return persons.ToList();
       } 
    }
    

    或将责任转移到工厂:

    public class PersonInfoFactory
    {
        public PersonInfo Create(Person person)
        {
            return new PersonInfo
            {
                StackOverFlowName = p.StackOverFlowName,
                Experience = p.Experience,
                GuruStatus = ExperienceBasedStatus(p.Experience)
            };
        }
    
        private GuruLevel ExperienceBasedStatus(int experience) => experience > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku;
    }
    

    【讨论】:

    • 如果 CrmContext 是 EF6,这将在查询翻译期间在运行时崩溃。不支持带参数的构造函数。
    • 我喜欢它,但问题是关于拆分 lambda。还是 +1
    • @Impworks 没错,他可能会在初始化之前执行这个查询。
    • @bixarrio 我试图避免产生更多代码异味样本的答案。如果作者知道Expression 类,我假设他能够将此属性转换为方法。
    • 在这个简化的代码示例中可以做到这一点。在现实世界中,“GuruLevel”是根据 PersonInfo 中不可用但在 Person 中可用的更多字段计算的。
    【解决方案3】:

    改变这个;

    public static Func<Person, GuruLevel> Foo
    {
        get
        {
            return bar => (bar.Experience > 9000) ? GuruLevel.SuperSayan : GuruLevel.Goku;
        }
    }
    

    那么;

    private IEnumerable<PersonInfo> GetGurus()
    {
        using (var context = new CRMContext())
        {
            var persons = context.Person
                .Where(p => p.Experience > 10)
                .OrderBy(p => p.DateOfBirth)
                .Select(p => new PersonInfo())
                {
                    StackOverFlowName = p.StackOverFlowName,
                    Experience = p.Experience,
                    GuruStatus = Foo(p)
                }
            return persons.ToList();
        } 
    }
    

    只是一个注释;这是一种奇怪的做法。你可以让 Foo 成为一个正常的函数,这样就可以了;

    public GuruLevel Foo(Person bar)
    {
        return bar.Experience > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku;
    }
    ...
    GuruStatus = Foo(p)
    

    【讨论】:

    • 感谢您的尝试,但这会导致:'LINQ to Entities 不支持 LINQ 表达式节点类型'Invoke'。'
    • @Tony_KiloPapaMikeGolf 答案适用于 linq-to-objects;你没有指定你在 EF 的上下文中工作
    • 没注意。我不知道它是EF。也许我的编辑会起作用,因为它是一个标准功能。不确定。我不使用 EF
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-10
    • 1970-01-01
    相关资源
    最近更新 更多