【问题标题】:Merging 2 C# IQueryable合并 2 C# IQueryable
【发布时间】:2015-03-06 18:11:05
【问题描述】:

我对 IQueryable 没有太多经验

我要做的是根据传入的约束列表搜索用户,这些约束可以是用户名,也可以是电话号码。根据我想返回有限信息的类型。然后,我想将 3 个 IQueryables 合并为一个,并将条目与匹配的 id 和用户名结合起来,以维护最多的信息。

这是我目前所拥有的:

public IQueryable<User> Search(String[] criteria)
{
        var query = Database.Get<User>();

        IQueryable<User> phoneQuery = null;
        IQueryable<User> emailQuery = null;
        IQueryable<User> nameQuery = null;

        foreach (String str in criteria)
        {
            // Check if it is a phone number
            if (Regex.IsMatch(str, @"([0-9]{3})?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$"))
            {
                phoneQuery = query.Where(
                       u => u.PhoneNumber.ToLower() == (str))
                       .Select(i => new User
                       {
                           Id = i.Id,
                           UserName = i.Name,
                           PhoneNumber = i.PhoneNumber
                       })
            }
            // check if it is an email 
            else if (criteria.Contains("@"))
            {
                emailQuery = query.Where(
                       u => u.Email.ToLower() == (str))
                       .Select(i => new User
                       {
                           Id = i.Id,
                           UserName = i.Name,
                           Email = i.Email
                       })
            }
            else
            {
                nameQuery = query.Where(
                       u => u.UserName.ToLower() == (str))
                       .Select(i => new User
                       {
                           Id = i.Id,
                           UserName = i.Name,
                       })
            }
        }
        // Merge the 3 queries combining entries if the username and id match and maintain the maximum amount of information

        return query;

    }

【问题讨论】:

  • 我相信与此类似的问题已经有了答案:[如何合并两个 IQueryable 列表][1] [1]:stackoverflow.com/questions/4003813/…
  • 为什么不只进行一次查询,检查输入字符串是否匹配电话号码或电子邮件或姓名?
  • 我不知道该怎么做的部分是,如果我要合并两个条目,则将列表中的信息保留更多。例如,如果一个用户只有用户名和 id 信息,而另一个用户有用户名 id 和电话号码,我希望返回的用户有用户名 id 和电话号码。
  • 你可以在这里简单地使用IEnumerable&lt;T&gt;,我也强烈建议你不要使用匿名类型。
  • @mbeckish 因为如果他们按用户名搜索,我不想返回电话号码或电子邮件。我只想返回他们实际搜索的信息以及 id 和用户名。

标签: c# asp.net linq


【解决方案1】:

您的代码存在一些问题:

ToList() 将执行查询。如果您稍后调用AsQueryable(),您只需在本地对象上创建一个对象查询。这基本上失去了IQueryable的概念,所以你最好删除所有ToList()AsQueryable()调用。

您可以将其设为单个查询,而不是合并三个查询,如下所示:

Expression predicateBody = Expression.Constant(false);
Expression userParameter = Expression.Parameter("user", typeof(User));
Expression userUserName = Expression.MakeMemberAccess(...);
Expression userPhoneNumber = Expression.Cal(...);
Expression userEmail = Expression.Call(...);

foreach (String str in criteria)
{
    // Check if it is a phone number
    if (Regex.IsMatch(str, @"([0-9]{3})?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$"))
    {
         predicateBody = Expression.Or(predicateBody, Expression.Equals(userPhoneNumber, Expression.Constant(str)));
    }
    // check if it is an email 
    else if (criteria.Contains("@"))
    {
         predicateBody = Expression.Or(predicateBody, Expression.Equals(userEmail, Expression.Constant(str)));
    }
    else
    {
         predicateBody = Expression.Or(predicateBody, Expression.Equals(userUserName, Expression.Constant(str)));
    }
}

return query.Where(Expression.Lambda<Func<User, bool>>(predicateBody, userParameter))
     .GroupBy(u => u.Id)
     .Select(users => new User()
          {
               Id = users.Key,
               UserName = users.Select(u => u.UserName).Intersect(criteria).FirstOrDefault(),
               Email = users.Select(u => u.Email).Intersect(criteria).FirstOrDefault(),
               PhoneNumber = users.Select(u => u.PhoneNumber).Intersect(criteria).FirstOrDefault()
          });

编辑抱歉,我误解了合并问题。

Edit2如果预先对条件进行排序,还有一种不需要手动创建表达式树的解决方案。

Edit3我明白了,我忘记了信息有限的部分。

var phoneNumbers = new List<string>();
var emails = new List<string>();
var userNames = new List<string>();

foreach (var str in criteria)
{
    // Check if it is a phone number
    if (Regex.IsMatch(str, @"([0-9]{3})?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$"))
    {
         phoneNumbers.Add(criteria);
    }
    // check if it is an email 
    else if (criteria.Contains("@"))
    {
         emails.Add(crietria);
    }
    else
    {
         userNames.Add(criteria);
    }
}

return query.Where(u => phoneNumbers.Contains(u.PhoneNumber)
       || emails.Contains(u.Email)
       || userNames.Contains(u.UserName))
       .GroupBy(u => u.Id)
       .Select(users => new User()
              {
                   Id = users.Key,
                   UserName = users.Select(u => u.UserName).Intersect(userNames).FirstOrDefault(),
                   Email = users.Select(u => u.Email).Intersect(emails).FirstOrDefault(),
                   PhoneNumber = users.Select(u => u.PhoneNumber).Intersect(phoneNumbers).FirstOrDefault()
              });

【讨论】:

  • 阅读有关问题的 cmets; OP特别说他不能这样做。此外,没有任何理由手动构建表达式。只需使用 lambda,您就可以节省大量精力,并使代码更具可读性/可维护性;也就是说,如果这实际上是一种可行的方法(当然不是)。
  • @Servy 您不能在此处使用 lamda,因为 where 子句实际上是您在编译时不知道的多个子句的结合。至少,如果您不想多次连接查询,这可能会导致严重的性能问题。如果您真的有更好的选择,请继续发布。
  • 但它们在编译时是已知的。你可以用 lambdas 写出来。
  • @Servy 不,因为您不知道运行时的标准。您可以为每个条件创建一个 lambda,但无法将多个 lambda 与 or 连接起来。我的意思是,它是一个直接实现的库函数,但它不包含在 .NET 框架中。
  • OP 在问题中展示了如何正确地做到这一点。他有所有文件管理器的 lambda,你执行所有这些,然后合并所有结果。这样可以正常工作,只是他希望以特殊方式合并值(您的答案也无法处理)。
【解决方案2】:

这就是我最终的结果。

public IQueryable<User> Search(String[] criteria)
        {
            var query = Database.Get<User>();
            List<User> res = new List<User>(); 
            foreach (String str in criteria)
            {
                // Check if it is a phone number

                if (Regex.IsMatch(str, @"([0-9]{3})?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$"))
                {
                    var users = query.Where(
                           u => u.PhoneNumber.ToLower() == (str))
                           .Select(i => new User
                           {
                               Id = i.Id,
                               UserName = i.Name,
                               Email = null,
                               PhoneNumber = i.PhoneNumber
                           });

                    // Multiple users can have the same phone so add all results
                    foreach (User u in users)
                    {
                        if (u != null) { res.Add(u); }
                    }

                }
                // Check if it is an email match 
                else if (str.Contains("@"))
                {
                    var user = query.Where(
                           u => u.Email.ToLower() == (str))
                           .Select(i => new User
                           {
                           Id = i.Id,
                           UserName = i.Name,
                           Email = i.Email,
                           PhoneNumber = null
                       }).SingleOrDefault<User>(); // Only one user can use an email

                if (user != null) { res.Add(user); }
            }
            // Otherwise it is a username
            // NOTE: If a username is all digits and dashes it won't be 
            // searched for because it is interpreted as a phone number!
            else
            {
                var user = query.Where(
                       u => u.UserName.ToLower() == (str))
                       .Select(i => new User
                       {
                           Id = i.Id,
                           UserName = i.Name,
                           Email = null,
                           PhoneNumber = null
                       }).SingleOrDefault<User>(); // Only one user can use an email

                if (user != null) { res.Add(user); }
            }
        }

        query = res.AsQueryable(); 

        // Group the results by username and id and return all information that was found
        query = from u in query
                group u by new
                {
                    u.Id,
                    u.UserName
                } into g
                select new User()
                {
                    Id = g.Key.Id,
                    UserName = g.Key.UserName,
                    Email = g.Select(m => m.Email).SkipWhile(string.IsNullOrEmpty).FirstOrDefault(), 
                    PhoneNumber = g.Select(m => m.PhoneNumber).SkipWhile(string.IsNullOrEmpty).FirstOrDefault()
                };

        return query;
    }

【讨论】:

    猜你喜欢
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    • 2021-10-26
    • 1970-01-01
    • 2011-11-11
    • 1970-01-01
    相关资源
    最近更新 更多