【问题标题】:How does C# lambda work?C# lambda 是如何工作的?
【发布时间】:2010-03-08 02:46:52
【问题描述】:

我正在尝试实现搜索数据库的方法 Find。

我忘了说我使用的是 Postgresql,所以我不能使用内置的 LINQ to SQL。

我希望它是这样的:

var user = User.Find(a => a.LastName == "Brown");

就像在 List 类中完成的一样。但是当我查看 List 的源代码(感谢 Reflector)时,我看到了:

public T Find(Predicate<T> match)
{
    if (match == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        if (match(this._items[i]))
        {
            return this._items[i];
        }
    }
    return default(T);
}

我该如何实现这个东西?我需要获取这些参数才能进行搜索。

解决方案

好的,我现在明白了,我需要使用 LINQ to SQL 来完成所有这些好的表达式,否则我将不得不花费大量时间重新实现轮子。

由于我不能使用 LINQ to SQL,所以我实现了这个简单的方法:

public static User Find(User match, string orderBy = "")
    {
        string query = "";
        if (!String.IsNullOrEmpty(match.FirstName)) query += "first_name='" + match.FirstName + "'";
        if (!String.IsNullOrEmpty(match.LastName)) query += "last_name='" + match.LastName+ "'";
        return Find(query + (!String.IsNullOrEmpty(orderBy) ? orderBy : ""));
    }

这是如何使用它:

var user = User.Find(new User { FirstName = "Bob", LastName = "Brown" });

【问题讨论】:

  • 在重新实现之前,您可能需要检查 Linq to Sql 或实体框架。
  • 请明确说明您希望访问哪些参数。
  • 我忘了说我使用的是Postgresql,所以我不能使用内置的LINQ to SQL。
  • 如果有人搜索包含 ' 的名称,更新后的代码会发生什么情况?您需要使用正确的查询参数。
  • 你说得对,我不在这里做任何检查。这只是原型。

标签: c# lambda find


【解决方案1】:

你的方法应该接受Expression&lt;Func&lt;User&gt;&gt;

这将为您提供表达式树而不是委托,您可以分析并序列化为 SQL 或转换为您的数据库具有的任何其他 API 调用。

如果您希望一切都是通用的,您可能希望继续实现 IQueryable 接口。有用的信息可以在这里找到:LINQ Tips: Implementing IQueryable Provider

虽然对于一个简单的场景,我建议不要将所有事情都复杂化并坚持使用表达式树并返回普通的 IEnumerable&lt;T&gt; 甚至 List&lt;T&gt;

对于您的情况,第一个版本的代码可能如下所示:

public IEnumerable<T> Get(Expression<Func<T, bool>> condition)
{
    if (condition.Body.NodeType == ExpressionType.Equal)
    {
        var equalityExpression = ((BinaryExpression)condition.Body);

        var column = ((MemberExpression)equalityExpression.Left).Member.Name;

        var value = ((ConstantExpression)equalityExpression.Right).Value;

        var table = typeof(T).Name;

        var sql = string.Format("select * from {0} where {1} = '{2}'", table, column, value);

        return ExecuteSelect(sql);
    }

    return Enumerable.Empty<T>();
}

当您想要处理新的和新的场景时,它的复杂性会快速增长,因此请确保您对每个场景都有可靠的单元测试。

C# Samples for Visual Studio 2008 包含ExpressionTreeVisualizer,这将帮助您更轻松地挖掘表达式树,以了解如何从中提取您需要的信息。

当然,如果您可以坚持使用现有的 LINQ 实现,我建议您这样做。 Linq to SQL 用于 SQL Server 数据库,Linq to Entities 用于许多不同的数据库,Linq to NHibernate 用于 NHbernate 项目。

可以在此处找到许多其他 LINQ 提供程序:Link to Everything: A List of LINQ Providers。实施 LINQ 提供程序的工作量并不小,因此重用经过测试和支持的解决方案是个好主意。

【讨论】:

  • +1 这确实是唯一能回答发帖者所寻找内容的答案。您不能真正在对象上使用 Enumerable.Where 之类的东西来搜索数据库中的单个项目。但是,请注意,这种方法不是很通用(它依赖于非常具体的约定)。
  • 这是一个很好的例子,唯一的问题是我需要几个参数来用于搜索。类似“last_name='{1}' AND first_name='{2}' AND age={3}”
  • @Alex,参数的数量无关紧要。这就是传递表达式树的美妙之处。您仍然只需要接受一个 Expression> ,LINQ 会负责将您传递给它的任何表达式转换为 SQL 查询。这当然假设您有一个 Postgresql LINQ 提供程序。自己实施将是一个巨大的痛苦。
【解决方案2】:

完全一样。只需将 this._items 替换为您的用户集合即可。

还将类型参数 T 替换为 User 类型。

【讨论】:

  • 是的,但是我需要执行“SELECT * FROM TABLE where last_name='”+ LastName”,我没有任何用户集合可以在其中找到它。
  • 如果您在该方法中查询 LINQ,您将需要使用 Konstantin 的建议来使用 Expression> 而不是 Predicate 然后您可以直接将其传递给 Where 扩展方法的 IQueryable。
【解决方案3】:

源代码中的 lambda 表达式可以在编译时转换为已编译的可执行委托或表达式树。通常我们将 lambda 与委托相关联,但在您的情况下,因为您说您想要访问参数(在这种情况下,我假设您的意思是 LastName"Brown",那么您需要一个表达式树。

一旦你有了一个表达式树,你就可以解析它,看看它到底是什么,然后将它翻译成你实际需要做的任何事情。

这里有几个关于表达式树的问题。

Expression trees for dummies?

Bit Curious to understand Expression Tree in .NET

听起来你肯定是在重新发明一个非常复杂的轮子。我相信这将是一次有用的学习体验,但您应该研究 LINQ to Entities 或 LINQ to SQL 以进行实际编程。

【讨论】:

  • +1 表示函数和表达式之间的区别,我认为这是问题的关键。
【解决方案4】:

也许我只是没有理解这个问题,但已经有一种方法可以做你想做的事:Enumerable.Where

如果您需要查找单个元素,请改用SingleOrDefaultFirstOrDefault

【讨论】:

  • 我忘了说我使用的是Postgresql,所以我不能使用内置的LINQ to SQL。
  • 在这种情况下,您肯定想使用@Konstantin 的答案;不要尝试像 List&lt;T&gt; 那样遍历 C# 中的元素,您需要使用表达式树来生成 SQL WHERE 子句。
【解决方案5】:

你可以这样做:

public static IEnumerable<User> Find(Predicate<User> match)
{
    //I'm not sure of the name
    using (var cn = new NpgsqlConnection("..your connection string..") )
    using (var cmd = new NpgsqlCommand("SELECT * FROM Users", cn))
    using (var rdr = cmd.ExecuteReader())
    {
        while (rdr.Read())
        {
           var user = BuildUserObjectFromIDataRecord(rdr);
           if (match(user)) yield return user;
        }
    }
}

然后你可以这样称呼它

var users = User.Find(a => a.LastName == "Brown");

请注意,这会返回任意数量的用户,您仍然必须实现 BuildUserObjectFromIDataRecord() 函数,并且它总是希望遍历整个用户表。但它为您提供了您想要的确切语义。

【讨论】:

  • 是的,我尝试过这样做。我确实实现了 BuildUserObjectFromIDataRecord。但唯一的问题是,脚本必须获取所有记录,然后在所有记录中查找必要的记录,而不是寻找 1 个特定用户。
【解决方案6】:

好的,我现在明白了,我需要使用 LINQ to SQL 来完成所有这些好的表达式,否则我将不得不花费大量时间重新实现轮子。

由于我不能使用 LINQ to SQL,所以我实现了这个简单的方法:

public static User Find(User match, string orderBy = "")
    {
        string query = "";
        if (!String.IsNullOrEmpty(match.FirstName)) query += "first_name='" + match.FirstName + "'";
        if (!String.IsNullOrEmpty(match.LastName)) query += "last_name='" + match.LastName+ "'";
        return Find(query + (!String.IsNullOrEmpty(orderBy) ? orderBy : ""));
    }

这是如何使用它:

var user = User.Find(new User { FirstName = "Bob", LastName = "Brown" });

【讨论】:

    【解决方案7】:

    一种方法是创建一个匿名委托,如下所示:

    Predicate<User> Finder = delegate(User user)
    {
        return user.LastName == "Brown";
    }
    
    var User = User.Find(Finder);
    

    【讨论】:

    • 这种冗长正是 lambdas 旨在避免的。
    猜你喜欢
    • 2018-06-30
    • 1970-01-01
    • 1970-01-01
    • 2013-06-18
    • 2012-02-01
    • 2010-10-28
    • 1970-01-01
    相关资源
    最近更新 更多