如何在 ADO.NET 项目的代码中存储大型查询
How to store large queries inside code for ADO.NET project
我正在开发一个 Web 项目,其数据访问层基于 ADO.NET(执行速度最快)。项目中有一些非常大的 SQL 查询,它们是用 C# 代码内联编写的。我想知道我是否可以将这些查询更优雅地移动到某个地方以减少一些混乱,但我不确定可以使用什么方法。我知道资源文件,但这些不能在这里使用,因为某些查询已参数化。
语言:C#
我建议将查询放入 SQL 存储过程,并使用 ADODB.Command 对象 运行 它们。您可以将相同的原则应用于代码 运行ning System.Data.SqlClient 查询。
根据您构建查询的方式的复杂性,创建多个查询来替换您的内联调用可能是有意义的。特别是,如果您有一段按产品或按商店或按链(例如)过滤的代码,并且所有这些都由一个动态 C# 代码块处理以构建命令,则最好有 3 个单独的SQL 中的存储过程来处理每种情况,而不是尝试在一个过程中复制该动态行为。
这种方法的另一个好处是,由于 SQL 构建查询计划的方式,您可能会发现存储过程的整体性能和索引调整机会更好。
关于 SQL 如何管理查询计划的一些一般性评论:
如果您在数据库中构建存储过程,SQL 将在每个过程第一次 运行 时生成一个查询计划,使用调用所需的任何参数和流程来优化查询。如果您动态加载查询 - 无论是使用生成的动态 SQL 还是通过加载保存为文件的 SQL 脚本 - 然后 SQL 运行 每次调用时都会进行此分析 运行.
每隔 运行 生成查询计划会影响性能。根据您的数据库和查询,此命中可能非常小 - 对于每天一次 运行s 的查询几毫秒 - 或者非常重要 - 对于 运行s 的查询一两秒每天数千或数百万次。
将您的调用分成 3 个单独的过程是个好主意,因为 SQL 服务器根据第一个 运行 示例构建计划。如果您有一个采用可选 ID 值的过程,如果您传递该值,则 returns 一行,如果您不传递该值,则所有行……然后取决于首先调用哪个,SQL 将尝试在您每次调用它时进行索引查找或 table 扫描,这两者都不是另一个操作的最佳选择。将其拆分为两个单独的调用允许 SQL 为每个操作生成最佳查询计划。
另一方面,更多的是用于日志记录和分析。许多 SQL 性能工具(包括 SQL 中内置的工具)能够查看同一存储过程的许多相关调用,并确定长期性能趋势。有些工具甚至可以很好地精确定位过程中 运行 表现不佳的确切部分。但是,如果您使用的是动态生成的 SQL,那么这些调用都会变成一片独立事件的海洋。因此,如果您的 运行ning 存储过程每天冒泡一次或两次,那么您每天进行数百万次的 3 秒调用将会丢失,但如果 3 秒调用是一个存储过程,那么您可以看到它共同成为您服务器工作负载的 90%,并且是重构和查询调优的理想选择。
因此,虽然将多个类似查询生成为单独的存储过程感觉有点违反 DRY 原则,但您希望在使用 SQL 时调整心态以获得最佳性能.
如果您不能或不愿意使用存储过程并且更愿意将 Sql 保持在 C# 代码附近,您可以提取 Sql 并将它们放入外部文件,包含在根据图像进行项目。
选项 1:在 sub-folder
中与可执行文件一起复制的文本文件
并像这样访问它的内容:
private String LoadFileContent()
{
String fileName = "Sql\LoadAllData.sql";
if (!File.Exists(fileName))
{
String errorMessage = String.Format("File '{0}' does not exist or access to it is denied", fileName);
throw new FileNotFoundException(errorMessage, fileName);
}
String fileContent = String.Empty;
using (StreamReader sr = File.OpenText(fileName))
{
fileContent = sr.ReadToEnd();
}
return fileContent;
}
或
选项 2:作为资源嵌入程序集中的文本文件
并使用此方法访问文件:
private String LoadAssemblyResource()
{
Assembly assembly = Assembly.GetExecutingAssembly();
String fileName = "WhosebugWinForm.SQL.LoadAllData.sql";
// Handy bit of debug code to list all the resource names in case there
// is an issue trying to find/load a resource
String[] resourceNames = assembly.GetManifestResourceNames();
String fileContent = String.Empty;
using (Stream stream = assembly.GetManifestResourceStream(fileName))
{
if (stream == null)
{
String errorMessage = String.Format("Resource File '{0}' does not exist", fileName);
throw new MissingManifestResourceException(errorMessage);
}
using (StreamReader reader = new StreamReader(stream))
{
fileContent = reader.ReadToEnd();
}
}
return fileContent;
}
如果您正在开发 Web 应用程序或 Web 服务,我会推荐嵌入式资源的后一种方法,这样您就不必太担心 Web 服务器上的映射路径、安全性和文本文件 hacked/altered.
这两种方法都替换代码中的字符串文字并将它们移动到外部文件。一旦加载了外部文件,仍然可以像以前一样操作字符串。
我经常根据具体情况和相关 Sql 的大小使用这两种方法。
我正在开发一个 Web 项目,其数据访问层基于 ADO.NET(执行速度最快)。项目中有一些非常大的 SQL 查询,它们是用 C# 代码内联编写的。我想知道我是否可以将这些查询更优雅地移动到某个地方以减少一些混乱,但我不确定可以使用什么方法。我知道资源文件,但这些不能在这里使用,因为某些查询已参数化。
语言:C#
我建议将查询放入 SQL 存储过程,并使用 ADODB.Command 对象 运行 它们。您可以将相同的原则应用于代码 运行ning System.Data.SqlClient 查询。
根据您构建查询的方式的复杂性,创建多个查询来替换您的内联调用可能是有意义的。特别是,如果您有一段按产品或按商店或按链(例如)过滤的代码,并且所有这些都由一个动态 C# 代码块处理以构建命令,则最好有 3 个单独的SQL 中的存储过程来处理每种情况,而不是尝试在一个过程中复制该动态行为。
这种方法的另一个好处是,由于 SQL 构建查询计划的方式,您可能会发现存储过程的整体性能和索引调整机会更好。
关于 SQL 如何管理查询计划的一些一般性评论:
如果您在数据库中构建存储过程,SQL 将在每个过程第一次 运行 时生成一个查询计划,使用调用所需的任何参数和流程来优化查询。如果您动态加载查询 - 无论是使用生成的动态 SQL 还是通过加载保存为文件的 SQL 脚本 - 然后 SQL 运行 每次调用时都会进行此分析 运行.
每隔 运行 生成查询计划会影响性能。根据您的数据库和查询,此命中可能非常小 - 对于每天一次 运行s 的查询几毫秒 - 或者非常重要 - 对于 运行s 的查询一两秒每天数千或数百万次。
将您的调用分成 3 个单独的过程是个好主意,因为 SQL 服务器根据第一个 运行 示例构建计划。如果您有一个采用可选 ID 值的过程,如果您传递该值,则 returns 一行,如果您不传递该值,则所有行……然后取决于首先调用哪个,SQL 将尝试在您每次调用它时进行索引查找或 table 扫描,这两者都不是另一个操作的最佳选择。将其拆分为两个单独的调用允许 SQL 为每个操作生成最佳查询计划。
另一方面,更多的是用于日志记录和分析。许多 SQL 性能工具(包括 SQL 中内置的工具)能够查看同一存储过程的许多相关调用,并确定长期性能趋势。有些工具甚至可以很好地精确定位过程中 运行 表现不佳的确切部分。但是,如果您使用的是动态生成的 SQL,那么这些调用都会变成一片独立事件的海洋。因此,如果您的 运行ning 存储过程每天冒泡一次或两次,那么您每天进行数百万次的 3 秒调用将会丢失,但如果 3 秒调用是一个存储过程,那么您可以看到它共同成为您服务器工作负载的 90%,并且是重构和查询调优的理想选择。
因此,虽然将多个类似查询生成为单独的存储过程感觉有点违反 DRY 原则,但您希望在使用 SQL 时调整心态以获得最佳性能.
如果您不能或不愿意使用存储过程并且更愿意将 Sql 保持在 C# 代码附近,您可以提取 Sql 并将它们放入外部文件,包含在根据图像进行项目。
选项 1:在 sub-folder
中与可执行文件一起复制的文本文件并像这样访问它的内容:
private String LoadFileContent()
{
String fileName = "Sql\LoadAllData.sql";
if (!File.Exists(fileName))
{
String errorMessage = String.Format("File '{0}' does not exist or access to it is denied", fileName);
throw new FileNotFoundException(errorMessage, fileName);
}
String fileContent = String.Empty;
using (StreamReader sr = File.OpenText(fileName))
{
fileContent = sr.ReadToEnd();
}
return fileContent;
}
或
选项 2:作为资源嵌入程序集中的文本文件
并使用此方法访问文件:
private String LoadAssemblyResource()
{
Assembly assembly = Assembly.GetExecutingAssembly();
String fileName = "WhosebugWinForm.SQL.LoadAllData.sql";
// Handy bit of debug code to list all the resource names in case there
// is an issue trying to find/load a resource
String[] resourceNames = assembly.GetManifestResourceNames();
String fileContent = String.Empty;
using (Stream stream = assembly.GetManifestResourceStream(fileName))
{
if (stream == null)
{
String errorMessage = String.Format("Resource File '{0}' does not exist", fileName);
throw new MissingManifestResourceException(errorMessage);
}
using (StreamReader reader = new StreamReader(stream))
{
fileContent = reader.ReadToEnd();
}
}
return fileContent;
}
如果您正在开发 Web 应用程序或 Web 服务,我会推荐嵌入式资源的后一种方法,这样您就不必太担心 Web 服务器上的映射路径、安全性和文本文件 hacked/altered.
这两种方法都替换代码中的字符串文字并将它们移动到外部文件。一旦加载了外部文件,仍然可以像以前一样操作字符串。
我经常根据具体情况和相关 Sql 的大小使用这两种方法。