我正在尝试实现搜索数据库的方法。
我忘了提到我正在使用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" });
你的方法应该接受Expression<Func<User>>
。
这将为您提供表达式树而不是委托,您可以分析和序列化为SQL或转换为您的数据库具有的任何其他API调用。
如果您希望所有内容都是通用的,您可能希望继续实现IQueryable接口。有用的信息可以在这里找到:LINQ Tips: Implementing IQueryable Provider
虽然对于一个简单的场景,我建议不要复杂化所有内容并坚持使用表达式树并返回普通的IEnumerable<T>
甚至List<T>
。
对于您的情况,第一版代码可能如下所示:
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实现,我建议您这样做。 SQL Server数据库有Linq to SQL
,许多不同数据库有Linq to Entities
,NHbernate项目有Linq to NHibernate
。
许多其他LINQ提供商可以在这里找到:Link to Everything: A List of LINQ Providers。实现LINQ提供程序的工作量并不重要,因此重用经过测试和支持的解决方案是个好主意。
完全一样的方式。只需将this._items
替换为您的用户集合。
还要使用User类型替换类型参数T.
源代码中的lambda表达式可以在编译时转换为已编译的可执行委托或表达式树。通常我们将lambda与委托关联,但在你的情况下,因为你说你想要访问参数(在这种情况下,我假设你的意思是LastName
和"Brown"
然后你想要一个表达式树。
一旦你有了一个表达式树,你就可以解析它以确切地看到它是什么,将它翻译成你实际需要做的事情。
这里有一些关于表达树的问题。
Bit Curious to understand Expression Tree in .NET
听起来你肯定会重新发明一个非常复杂的车轮。我确信这将是一次有用的学习体验,但您应该查看LINQ to Entities或LINQ to SQL以进行实际编程。
也许我只是没有理解这个问题,但已经有了一种方法可以做你想做的事:Enumerable.Where。
如果您需要找到单个元素,请改用SingleOrDefault
或FirstOrDefault
。
你可以这样做:
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()函数,并且它总是希望迭代整个users表。但它为您提供了您想要的确切语义。
好吧,我现在明白我需要做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" });
一种方法是创建一个匿名委托,如下所示:
Predicate<User> Finder = delegate(User user)
{
return user.LastName == "Brown";
}
var User = User.Find(Finder);