在LINQ-to-SQL的ExecuteQuery中使用IN子句

Ben*_*ack 4 linq sql-server linq-to-sql

LINQ to SQL在翻译我的一个查询时做了很糟糕的工作,所以我手工重写了它.问题是重写必然涉及一个IN条款,我不能为我的生活弄清楚如何ExecuteQuery为此目的传递集合.我在这里看到的唯一可以提出的就是使用string.Format整个查询字符串来克服它 - 但这会阻止查询在查询缓存中结束.

这样做的正确方法是什么?

注意:请注意我使用的原始SQL传递给 ExecuteQuery.我在第一句话中说过.告诉我使用Contains是没有用的,除非你知道一种混合Contains原始SQL的方法.

Ste*_*sek 7

表值参数

在Cheezburger.com上,我们经常需要将AssetID或UserID列表传递给存储过程或数据库查询.

糟糕的方式:动态SQL

传递此列表的一种方法是使用动态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);
Run Code Online (Sandbox Code Playgroud)

这是一件非常糟糕的事情:

  1. 动态SQL通过简化SQL注入攻击为攻击者提供了一个弱点.
    由于我们通常只是将数字连接在一起,这是不太可能的,但是如果你开始将字符串连接在一起,那么所需要的只是一个用户输入';DROP TABLE Asset;SELECT ' 并且我们的网站已经死了.
  2. 存储过程不能具有动态SQL,因此查询必须存储在代码中而不是存储在数据库模式中.
  3. 每次运行此查询时,都必须重新计算查询计划.对于复杂的查询,这可能非常昂贵.

但是,它确实具有以下优点:在DB端不需要额外的解码,因为资产ID是由查询解析器找到的.

好方法:表值参数

SQL Server 2008增加了一项新功能:用户可以定义表值数据库类型.大多数其他类型是标量(它们只返回一个值),但表值类型可以包含多个值,只要值是表格.

我们定义了三种类型:varchar_array,int_array,和bigint_array.

CREATE TYPE bigint_array AS TABLE (Id bigint NOT NULL PRIMARY KEY)
Run Code Online (Sandbox Code Playgroud)

存储过程和以编程方式定义的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));
Run Code Online (Sandbox Code Playgroud)

好处

  1. 可以毫不费力地在存储过程和程序化SQL中使用
  2. 不易受SQL注入攻击
  3. 可缓存,稳定的查询
  4. 不锁定架构表
  5. 不限于8k的数据
  6. 由于没有CSV字符串的连接或解码,因此DB服务器和Mine应用程序完成的工作量减少了.
  7. 查询分析器可以导出"典型使用"统计信息,这可以带来更好的性能.

缺点

  1. 仅适用于SQL Server 2008及更高版本.
  2. 传言TVP在执行查询之前完全预先缓冲,这意味着服务器可能会拒绝非常大的TVP.对此谣言的进一步调查正在进行中.

进一步阅读

本文是了解TVP的更多信息.

  • 这种很酷的技术适用于lolcats.谁知道?:P (2认同)