【问题标题】:Better way to write this linq query?编写此 linq 查询的更好方法?
【发布时间】:2017-07-12 04:23:29
【问题描述】:

目前我正在使用 switch 操作的组合来生成 linq 查询,我认为代码有点臃肿。

有什么办法可以优化这段代码,也许可以动态构建它?

public string[] GetPeopleAutoComplete(string filter, int maxResults, string searchType, string searchOption)
{
    var query = from people in _context.People select people;
    switch (searchOption)
    {
        case "StartsWith":
            switch (searchType)
            {
                case "IdentityCode":
                    query = query.Where(o => o.IdentityCode.StartsWith(filter));
                    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
                case "Firstname":
                    query = query.Where(o => o.Firstname.StartsWith(filter));
                    return query.Select(o => o.Firstname).Distinct().Take(maxResults).ToArray();
                case "Surname":
                    query = query.Where(o => o.Surname.StartsWith(filter));
                    return query.Select(o => o.Surname).Distinct().Take(maxResults).ToArray();
            }
            break;

        case "EndsWith":
            switch (searchType)
            {
                case "IdentityCode":
                    query = query.Where(o => o.IdentityCode.EndsWith(filter));
                    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
                case "Firstname":
                    query = query.Where(o => o.Firstname.EndsWith(filter));
                    return query.Select(o => o.Firstname).Distinct().Take(maxResults).ToArray();
                case "Surname":
                    query = query.Where(o => o.Surname.EndsWith(filter));
                    return query.Select(o => o.Surname).Distinct().Take(maxResults).ToArray();
            }
            break;

        case "Contains":
            switch (searchType)
            {
                case "IdentityCode":
                    query = query.Where(o => o.IdentityCode.Contains(filter));
                    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
                case "Firstname":
                    query = query.Where(o => o.Firstname.Contains(filter));
                    return query.Select(o => o.Firstname).Distinct().Take(maxResults).ToArray();
                case "Surname":
                    query = query.Where(o => o.Surname.Contains(filter));
                    return query.Select(o => o.Surname).Distinct().Take(maxResults).ToArray();
            }
            break;
    }

    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
}

【问题讨论】:

    标签: c# linq entity-framework


    【解决方案1】:

    这正是动态构建表达式有用的地方:

    public string[] GetPeopleAutoComplete(
        string filter, int maxResults, string searchType, string searchOption)
    {
        IQueryable<Person> query = _context.People;
    
        var property = typeof(Person).GetProperty(searchType);
        var method = typeof(string).GetMethod(searchOption, new[] { typeof(string) });
    
        query = query.Where(WhereExpression(property, method, filter));
    
        var resultQuery = query.Select(SelectExpression(property));
    
        if (searchType == "Firstname" || searchType == "Lastname")
            resultQuery = resultQuery.Distinct();
    
        return resultQuery.Take(maxResults).ToArray();
    }
    
    Expression<Func<Person, bool>> WhereExpression(
        PropertyInfo property, MethodInfo method, string filter)
    {
        var param = Expression.Parameter(typeof(Person), "o");
        var propExpr = Expression.Property(param, property);
        var methodExpr = Expression.Call(propExpr, method, Expression.Constant(filter));
        return Expression.Lambda<Func<Person, bool>>(methodExpr, param);
    }
    
    Expression<Func<Person, string>> SelectExpression(PropertyInfo property)
    {
        var param = Expression.Parameter(typeof(Person), "o");
        var propExpr = Expression.Property(param, property);
        return Expression.Lambda<Func<Person, string>>(propExpr, param);
    }
    

    这并不能解决您的默认情况,但应该相对容易添加。此外,像这样使用反射可能会很慢,因此您可能需要缓存 GetProperty()GetMethod() 的结果。

    另外需要注意的是,选择是否使用Distinct() 的部分仍然取决于属性名称,但也许你有更好的条件(或者你可以在属性上使用属性)。

    而且这两个辅助方法不需要知道任何关于Person 的信息,因此将它们设为通用是微不足道的。

    【讨论】:

      【解决方案2】:

      使用 Dynamic Linq to SQL 库可以轻松解决您的问题。

      博文:Dynamic query with Linq

      谓词生成器

      谓词构建器与动态 linq 库的工作方式相同,但主要区别在于它允许轻松编写更多类型安全的查询。

      使用动态 LINQ 库

      动态 LINQ 库允许构建具有不同 where 子句或 orderby 的查询。要使用动态 LINQ 库,您需要在项目中下载并安装文件。

      查看 Scott GU 的这篇文章http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

      【讨论】:

      • 当一个查询包含多个谓词(使用 andor 连接)时,Predicate Builder 很有用。这里不是这样。
      【解决方案3】:

      您可以使用上述动态 linq 选项,或者如果您想要更简单的东西,您可以将一些切换逻辑重构为更小的部分,然后进行简单的查询

      public string[] GetPeopleAutoComplete(string filter, int maxResults, string searchType, string searchOption)
          {
               var query = (from person in _context.People
                      where MatchesSearchCriteria(searchType, searchOption, filter)
                      select SelectAttribute(person,searchType,searchOption));
      
               if (RequiresDistinct(filter,searchType, searchOption))
                    query = query.Distinct();
      
               return query.Take(maxResults).ToArray();
          }
      
          private bool MatchesSearchCriteria(string searchType, string searchOption, string filter)
          { 
               //Implement some switching here...
          }
      
          private string SelectAttribute(Person person, string searchType, string searchOption)
          {
              //Implement some switching here to select the correct value from the person
          }
      
          private bool RequiresDistinct(string searchType, string searchOption)
          { 
              //Return true if you need to select distinct values for this type of search
          }
      

      【讨论】:

        【解决方案4】:

        我新建了2个Class,一个用来测试,一个用来比较...

        你想通过名字来区分..

        public class PeopleCollection
        {
            public people[] People;
        
            public class people
            {
                public string IdentityCode;
                public string Firstname;
                public string Surname;
            }
        }
        
        public class ForCompare : IEqualityComparer<PeopleCollection.people>
        {
            string _fieldName = "";
        
            public ForCompare(string fieldName)
            {
                _fieldName = fieldName;
            }
        
            public bool Equals(PeopleCollection.people a, PeopleCollection.people b)
            {
                return "IdentityCode".Equals(_fieldName) ? true : a.GetType().GetProperty(_fieldName).GetValue(a, null).Equals(b.GetType().GetProperty(_fieldName).GetValue(b, null));
            }
        
        
            public int GetHashCode(PeopleCollection.people a)
            {
                return a.GetHashCode();
            }
        }
        

        然后,方法如下↓

        public static string[] GetPeopleAutoComplete(string filter, int maxResults, string searchType, string searchOption)
            {
        
                var property = typeof(PeopleCollection.people).GetProperty(searchType);
                var method = typeof(string).GetMethod(searchOption, new[] { typeof(string) });
        
        
        
                var query = from people in _context.People select people;
        
                return query.Distinct(new ForCompare(searchType))
                    .Select(o => (string)property.GetValue(o, null))
                    .Where(value => (bool)method.Invoke(value, new object[] { filter }))
                    .Take(maxResults).ToArray();
            }
        

        希望对你有用...

        【讨论】:

          【解决方案5】:

          一般来说你想要这个:

          query.Where(o => o.PropertyName.MethodName(keyword));
               .Select(o => o.PropertyName).Take(maxResults).ToArray();
          

          这是一个例子:

          public class Person
          {
              public string FirstName { get; set; }
          }    
          
          static void Main(string[] args)
          {
              string propertyName = "FirstName";
              string methodName = "StartsWith";
              string keyword = "123";
          
              Type t = typeof(Person);
          
              ParameterExpression paramExp = Expression.Parameter(t, "p"); 
                 // the parameter: p
          
              MemberExpression memberExp = Expression.MakeMemberAccess(paramExp, t.GetMember(propertyName).FirstOrDefault());
                 // part of the body: p.FirstName
          
              MethodCallExpression callExp = Expression.Call(memberExp, typeof(string).GetMethod(methodName, new Type[] { typeof(string) }), Expression.Constant(keyword));
                 // the body: p.FirstName.StartsWith("123")
          
              Expression<Func<Person, bool>> whereExp = Expression.Lambda<Func<Person, bool>>(callExp, paramExp);
              Expression<Func<Person, string>> selectExp = Expression.Lambda<Func<Person, string>>(memberExp, paramExp);
          
              Console.WriteLine(whereExp);   // p => p.FirstName.StartsWith("123")
              Console.WriteLine(selectExp);  // p => p.FirstName
          
              List<Person> people = new List<Person>();
              List<string> firstNames = people.Where(whereExp.Compile()).Select(selectExp.Compile()).ToList();
              Console.Read();
          } 
          

          【讨论】:

          • 大家好,非常感谢您的回复,他们都非常有帮助。我只有一个问题。使用 Danny Chens 解决方案时,“过滤器”标准必须区分大小写。例如,在数据库中,IdentityCode 可以以大写“A”开头,但“a”不会产生任何结果?使用普通查询不会发生这种情况。我该如何解决?
          【解决方案6】:

          我想这就是你想要的......

          Dynamic LINQ

          【讨论】:

          • 使用“Dynamic Linq”或“Predicate Builder”,我是否能够通过将它们作为参数传递给方法来动态生成“Contains、EndsWith 或 StartsWith”关键字? “身份代码、名字和姓氏”字段也是如此。这就是我的全部目标,将代码压缩到几行?
          【解决方案7】:

          我现在已经把这一切都连接好了,正在研究生成的 sql,这就是我得到的:

          SELECT 
          [Project1].[Id] AS [Id], 
          [Project1].[Firstname] AS [Firstname], 
          [Project1].[LevelGroup] AS [LevelGroup], 
          [Project1].[IdentityCode] AS [IdentityCode], 
          [Project1].[C1] AS [C1], 
          [Project1].[Surname] AS [Surname]
          FROM ( SELECT 
              [Extent1].[Id] AS [Id], 
              [Extent1].[IdentityCode] AS [IdentityCode], 
              [Extent1].[Firstname] AS [Firstname], 
              [Extent1].[Surname] AS [Surname], 
              [Extent1].[LevelGroup] AS [LevelGroup], 
              (SELECT 
                  COUNT(1) AS [A1]
                  FROM [dbo].[Loans] AS [Extent2]
                  WHERE [Extent1].[Id] = [Extent2].[PersonId]) AS [C1]
              FROM [dbo].[People] AS [Extent1]
              WHERE [Extent1].[IdentityCode] LIKE N'a%'
          )  AS [Project1]
          ORDER BY [Project1].[Surname] ASC
          

          这个查询现在没有参数化!我该如何解决这个问题,我觉得使用这段代码不安全:?

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-06-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多