LINQ to SQL在翻译我的一个查询时做了很糟糕的工作,所以我手工重写了它。问题是重写必然涉及IN
子句,我不能为我的生活弄清楚如何为此目的将集合传递给ExecuteQuery
。我在这里看到的唯一可以提出的是在整个查询字符串上使用string.Format
来克服它 - 但这会阻止查询在查询缓存中结束
这样做的正确方法是什么?
注意:请注意我正在使用传递给 ExecuteQuery
的原始SQL。我在第一句话中说过。告诉我使用Contains
没有帮助,除非您知道将Contains
与原始SQL混合的方法。
答案 0 :(得分:7)
在Cheezburger.com上,我们经常需要将AssetID或UserID列表传递给存储过程或数据库查询。
传递此列表的一种方法是使用动态SQL。
IEnumerable<long> assetIDs = GetAssetIDs(); var myQuery = "SELECT Name FROM Asset WHERE AssetID IN (" + assetIDs.Join(",") + ")"; return Config.GetDatabase().ExecEnumerableSql(dr=>dr.GetString("Name"), myQuery);
这是一件非常糟糕的事情:
';DROP TABLE Asset;SELECT '
我们的网站已经死了。但是,它确实具有以下优点:在DB端不需要额外的解码,因为资产ID是由查询解析器找到的。
SQL Server 2008增加了一项新功能:用户可以定义表值数据库类型。 大多数其他类型是标量(它们只返回一个值),但表值类型可以包含多个值,只要值是表格式。
我们定义了三种类型:varchar_array
,int_array
和bigint_array
。
CREATE TYPE bigint_array AS TABLE (Id bigint NOT NULL PRIMARY KEY)
存储过程和以编程方式定义的SQL查询都可以使用这些表值类型。
IEnumerable<long> assetIDs = GetAssetIDs(); return Config.GetDatabase().ExecEnumerableSql(dr=>dr.GetString("Name"), "SELECT Name FROM Asset WHERE AssetID IN (SELECT Id FROM @AssetIDs)", new Parameter("@AssetIDs", assetIDs));
<强>优点强>
<强>缺点强>
This article是了解有关TVP的更多信息的绝佳资源。
答案 1 :(得分:3)
如果你不能使用表值参数,这个选项比xml选项快一点,同时仍然允许你远离动态sql:将连接的值列表作为字符串参数传递,并解析分隔的字符串返回查询中的值。有关如何有效解析的说明,请参阅this article。
答案 2 :(得分:2)
我怀疑你是在使用SQL Server 2005.表值参数直到2008年才添加,但您仍然可以使用XML data type在客户端和服务器之间传递集合。 / p>
答案 3 :(得分:0)
这适用于SQL Server 2005(及更高版本):
create procedure IGetAListOfValues
@Ids xml -- This will recevie a List of values
as
begin
-- You can load then in a temp table or use it as a subquery:
create table #Ids (Id int);
INSERT INTO #Ids
SELECT DISTINCT params.p.value('.','int')
FROM @Ids.nodes('/params/p') as params(p);
...
end
您必须使用如下参数调用此过程:
exec IGetAListOfValues
@Ids = '<params> <p>1</p> <p>2</p> </params>' -- xml parameter
nodes函数使用xPath表达式。在这种情况下,它是/params/p
,这就是XML使用<params>
作为root,<p>
作为元素的方式。
value函数将每个p
元素内的文本强制转换为int,但您可以轻松地将其与其他数据类型一起使用。在此示例中,有一个DISTINCT以避免重复值,但是,当然,您可以根据您想要实现的目标将其删除。
我有一个辅助(扩展)方法,用于转换字符串中的IEnumerable<T>
,该字符串类似于执行示例中显示的字符串。创建一个很容易,并在需要时让它为您完成工作。 (您必须测试T的数据类型并转换为可在SQL Server端解析的适当字符串)。这样你的C#代码更干净,你的SP遵循相同的模式来接收参数(你可以根据需要传入尽可能多的列表)。
一个优点是您无需在数据库中创建任何特殊内容即可使其正常工作。
当然,您不需要像我的示例中那样创建临时表,但您可以将查询直接用作IN
谓词中的子查询
WHERE MyTableId IN (SELECT DISTINCT params.p.value('.','int')
FROM @Ids.nodes('/params/p') as params(p) )
答案 4 :(得分:0)
我不是100%确定我正确理解了这个问题,但是LinqToSql的ExecuteQuery有一个参数重载,并且查询应该使用类似于string.Format的格式。
使用此重载对于SQL注入是安全的,并且在幕后LinqToSql将其转换为使用带参数的sp_executesql。
以下是一个例子:
string sql = "SELECT * FROM city WHERE city LIKE {0}";
db.ExecuteQuery(sql, "Lon%"); //Note that we don't need the single quotes
这样即使使用动态sql,也可以使用参数化查询的好处。
然而,当使用带有动态数量参数的IN时,有两个选项:
动态构造字符串,然后将值作为数组传递,如:
string sql = "SELECT * FROM city WHERE zip IN (";
List<string> placeholders = new List<string>();
for(int i = 0; i < zips.Length;i++)
{
placeholders.Add("{"+i.ToString()+"}");
}
sql += string.Join(",",placeholders.ToArray());
sql += ")";
db.ExecuteQuery(sql, zips.ToArray());
我们可以使用Linq扩展方法使用更紧凑的方法,如
string sql = "SELECT * FROM city WHERE zip IN ("+
string.Join("," , zips.Select(z => "{" + zips.IndexOf(f).ToString() + "}"))
+")";
db.ExecuteQuery(sql, zips.ToArray());