如何减少代码中的 SQL 查询量?
How can I reduce the amount of SQL-querying in my code?
我正在编写一个与 MS-SQL 服务器数据库通信的大型 C# 应用程序。
随着应用程序变得越来越大,我发现自己编写了越来越多的 "boilerplate" 代码,其中包含各种 SQL 各种 类 查询和如下形式的查询:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
public class SomeForm : Form
{
public void LoadData(int ticketId)
{
// some multi-table SQL select and join query
string sqlQuery = @"
SELECT TOP(1) [Foo].Id AS FooId, [Foo].Name AS FooName, [Foo].Address AS FooAddress
[Bar].Name AS BarName, [Bar].UnitPrice AS BarPrice,
[Bif].Plop
FROM [dbo].[Foo]
INNER JOIN [dbo].[Bar]
ON [Bar].Id = [Foo].BarId
INNER JOIN [dbo].[Bif]
ON [Bar].BifId = [Bif].Id
WHERE [Foo].TicketId = @ticketId";
SqlCommand sqlCmd = new SqlCommand();
sqlCmd.CommandText = sqlQuery;
sqlCmd.Parameters.AddWithValue("@ticketId", ticketId);
// connection string params etc and connection open/close handled by this call below
DataTable resultsDataTable = SqlQueryHelper.ExecuteSqlReadCommand(sqlCmd);
if (resultsDataTable.Rows.Count > 0)
{
var row = resultsDataTable.Rows[0];
// read-out the fields
int fooId = 0;
if (!row.IsNull("FooId"))
fooId = row.Field<int>("FooId");
string fooName = "";
if (!row.IsNull("FooName"))
fooName = row.Field<string>("FooName");
// read out further fields...
// display in form
this.fooNameTextBox.Text = fooName;
// etc.
}
}
}
这个项目中有几十个表单在概念上都在做同样的事情,只是使用不同的 SQL 查询(选择不同的列等)
并且每次打开表格时,都在不断地查询数据库。
对于本地数据库服务器,速度还可以,但通过慢速 VPN 使用该应用程序会很痛苦。
有没有更好的方法来减少查询数据库的量?某种将数据库缓存在内存中并对内存中数据执行查询的方法?
我已将一些数据表添加到项目中的数据源,但无法理解如何执行上述复杂查询。
有更好的方法吗?
感谢大家的所有建议!
我可以建议几件事:
- 而不是使用 ADO.NET switch to Entity Framework,您可以使用 LINQ 来
SQL/LINQ to EF(较新版本)并用简单的 C# 语言写下查询,而不用担心查询 SQL.
- 使用存储过程,SQL函数,视图 - 写在SQL服务器数据库中,其中调用由 SQL 服务器 缓存,这提供了更高效的执行、安全性和更好的可维护性。
- 为了使您的 查询 对数据库表更有效,请考虑对您在过滤操作(如搜索)中更频繁使用的数据的表使用 Full-Text Index。
- 在您的 C#(包括与 Entity Framework 的集成)代码中使用 Repository and Unit of Work 模式,它实际上将完全按照您的意愿执行操作,即收集数额SQL 查询并将它们发送给SQL服务器执行一次,而不是一个一个地发送 Queries。这不仅会大大提高性能,还会使您的编码尽可能简单。
Note: One of the problems with your queries is related not only to their executions but also on Opening and closing SQL database connections each time you need to execute the particular Query. This problem is solved with the Repository and Unit of Work design patterns approach.
- 根据您的业务需求,对数据使用 In Memory or Database 缓存,这对很多用户来说都是重复的。
除了Entity Framework还有一个解决方案。
例如您可以使用 The Sharp Factory.
它是一个商业产品,但它不仅像 Entity Framework 一样映射数据库对象,而且还创建了一个基于层的完整存储库。
如果你愿意花钱,我觉得比EF好
Entity Framework 有一些缺点。例如,您的存储库将 泄漏 遍及您的层。
因为您需要在实体的所有消费者上引用 Entity Framework...所以即使您的架构看起来正确,在运行时您仍然可以在不知不觉中从上层执行 sql 查询.
我认为使用框架访问数据集等建议都不能解决您的问题。问题不在于编写 SQL 或 LINQ 查询。您总是必须在某个时候向数据库或数据集发送查询。
问题出在你查询数据库的地方。
你关于写作 "code containing various SQL queries in various classes and forms" 的陈述让我不寒而栗。我最近在一家公司工作,他们做的完全一样。结果,他们无法再维护他们的数据库。维护仍然是可能的,但只是基本的、非常 expensive/time 消耗和非常令人沮丧的 - 所以没有人喜欢这样做,因此没有人这样做,结果它变得越来越糟。查询变得越来越慢,唯一可能的快速解决方法是为数据库服务器购买更多带宽。
实际查询分散在整个项目中,因此无法 improve/refactor 查询(并识别它们)或改进 table 设计。但最糟糕的是,他们无法切换到更快或更便宜的数据库模型,例如图结构数据库,尽管有强烈的愿望和迫切需要这样做。这会让你在顾客面前显得邋遢。在这样的环境下工作绝对没有乐趣(所以我离开了)。听起来很糟糕?
您确实应该将数据库和 SQL 从您的业务代码中分离出来。所有这些都应该隐藏在界面后面:
IRepository repository = Factory.GetRepository();
// IRepository exposes query methods, which hide
// the actual query and the database related code details from the repository client or business logic
var customers = repository.GetCustomers();
在您的项目中传播此代码没有坏处。它将提高可维护性和可读性。它将数据持久性与实际业务逻辑分开,因为您是 hiding/encapsulating 所有细节,如实际数据库、查询和查询语言。如果你想切换到另一个数据库,你只需要实现一个新的 IRepository
来修改现有的查询。更改数据库不会破坏应用程序。
并且所有查询都在一个 location/layer 中实现。在谈论查询时,这也包括 LINQ 查询(这就是为什么使用像 Entity Framework 这样的框架不能解决您的问题的原因)。您可以使用依赖注入或工厂模式来分发 IRepository
的实现。这甚至允许在运行时在不同数据库之间切换而无需重新编译应用程序。
使用此模式(存储库模式)还可以将实体框架等框架与您的业务逻辑分离。
看看存储库模式。如果为时不晚,您应该开始重构现有代码。如果保持原样,价格太高了。
关于缓存,我知道数据库或 DBMS 已经非常非常有效地处理数据缓存。你能做的就是
- 在本地缓存数据,例如如果数据不会远程更改(因为
其他一些客户会修改它)例如本地客户端设置。这个
可以显着减少网络流量的方法。
- 在本地缓存数据,例如如果您经常访问此数据集并且远程更改不太可能发生或不会产生影响。您可以定期更新本地数据存储或使其无效,例如一段时间后强制客户端派发新查询以更新缓存。
- 您还可以使用 LINQ 在本地过滤数据。这也可能减少
网络流量,尽管您最终可能会读取更多数据
必要的。此外,过滤通常由
数据库管理系统。
- 可以考虑升级数据库服务器或者VPN增加
带宽。
- 索引也将显着缩短查找时间。
- 您应该考虑重构所有 SQL 查询。有许多
有关如何提高 SQL 查询性能的文章。你的方式
建立查询将对性能产生重大影响或
执行时间,尤其是大数据。
- 您可以使用数据虚拟化。当您只能向用户显示其中的 20 条记录时,从数据库中提取数千条记录是没有意义的。当用户滚动视图时提取更多数据。或者更好的是,显示一个预先选择的列表,例如最近的项目,并允许用户搜索感兴趣的数据。这样,您 只读取 用户明确要求的数据。这将大大提高整体性能,因为通常用户只对很少的记录感兴趣。
在引入接口(依赖倒置)之前
以下示例旨在说明,这是架构或设计的问题,而不是框架或库的问题。
库或框架可以在不同层面提供帮助,但无法解决问题,该问题是通过在整个业务代码中传播 环境特定 查询引入的。查询应该始终是中立的。查看业务代码时,您不应该能够判断数据是从文件还是数据库中获取的。此详细信息必须隐藏或封装。
当您将实际的数据库访问代码(无论是直接使用纯 SQL 还是在框架的帮助下)分散到整个业务代码中时,如果没有附加数据库,您将无法编写单元测试。这是不希望的。它使测试变得过于复杂,并且测试将执行得不必要地慢。您想要测试您的业务逻辑而不是数据库。这是单独的测试。您通常希望模拟数据库。
问题:
您需要跨应用程序模型或业务逻辑的多个位置的数据库中的数据。
最直观的方法是随时随地在您需要数据的地方调度数据库查询。这意味着,当数据库是 Postgre 数据库时,所有代码当然会使用 PostgreSQL 或某些框架,如 Entity Framework 或一般的 ORM。如果您决定更改数据库或 DBMS,例如对于某些 Oracle 或想使用不同的框架来管理您的实体,您将被迫接触并重写 每个 使用 PostgreSQL 或 Entity Framework 的代码。
在大型业务应用程序中,这将是迫使您的公司保留现有资源并让您的团队梦想更美好世界的原因。挫败感会上升。维护与数据库相关的代码几乎是不可能的,容易出错且耗时。由于实际的数据库访问不是集中的,重写数据库相关代码意味着爬取整个应用程序。最坏的情况是无意义的 SQL 查询字符串的传播,没有人理解或记住。在不牺牲宝贵且昂贵的时间和团队资源的情况下,不可能迁移到新数据库或重构查询以提高性能。
想象以下简化的符号方法在应用程序的业务逻辑中以某种形式重复,可能访问不同的实体并使用不同的过滤器,但使用相同的查询语言、框架或库。假设我们找到类似代码一千次:
private IEnumerable GetCustomers()
{
// Use Entity Framework to manage the database directly
return DbContext.Customers;
}
我们引入了与框架的紧密耦合,因为它已深入到我们的业务代码中。代码 "knows" 如何管理数据库。它知道 Entity Framework 因为它必须在任何地方使用它的 类 或 API。
证明是,如果您想用其他框架替换 Entity Framework 或只是想放弃它,您将不得不在数千处重构代码 - 在您的应用程序中使用此框架的任何地方。
在引入接口(依赖倒置)并封装所有数据库访问后
依赖倒置将通过引入接口帮助消除对具体 类 的依赖。由于我们更喜欢组件和 类 之间的松散耦合,以在使用辅助框架或普通 SQL 方言时增强灵活性、可测试性和可维护性,因此我们必须包装此特定代码并将其隐藏在接口后面(存储库模式).
我们现在引入了接口方法,例如 GetHighPriorityCustomers
和 [=19],而不是一千个显式使用数据库框架或 SQL 或 LINQ 查询来读取、写入或过滤数据的地方=].如何提供数据或从哪种数据库中获取数据是细节,只有此接口的实现才知道。
现在应用程序不再直接使用任何框架或数据库特定语言:
interface IRepository
{
IEnumerable<Customer> GetHighPriorityCustomers();
IEnumerable<Customer> GetAllCustomers();
}
之前的一千个地方现在看起来像:
private IRepository Repository { get; } // Initialized e.g. from constructor
private IEnumerable GetCustomers()
{
// Use a repository hidden behind an interface.
// We don't know in this place (business logic) how the interface is implemented
// and what classes it uses. When the implementation changes from Entity Framework to something else,, no changes have to be made here (loose coupling).
return this.Repository.GetAllCustomers();
}
执行IRepository
:
class EntityFrameworkRepository : IRepository
{
IEnumerable<Customer> GetAllCustomers()
{
// If we want to drop Entity Framework, we just have to provide a new implementation of IRepository
return DbContext.Customers;
}
...
}
现在,您决定使用普通 SQL。唯一要做的更改是实现一个新的 IRepository
,而不是更改千位以删除专用代码:
class MySqlRepository : IRepository
{
// The caller still accesses this method via the interface IRepository.GetAllCustomers()
IEnumerable<Customer> GetAllCustomers()
{
return this.Connection.ExecuteQuery("SELECT * FROM ...");
}
...
}
现在您决定将 MySQL 替换为 Microsoft SQL。您所要做的就是实现一个新的 IRepository
.
您可以换入和换出任何数据库并更改查询语言或引入辅助框架,而不会影响您的原始业务逻辑。一旦写入,再也不会触及(至少对于有关数据库的更改)。
如果将实现移至单独的程序集,甚至可以在运行时交换它们。
我正在编写一个与 MS-SQL 服务器数据库通信的大型 C# 应用程序。
随着应用程序变得越来越大,我发现自己编写了越来越多的 "boilerplate" 代码,其中包含各种 SQL 各种 类 查询和如下形式的查询:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
public class SomeForm : Form
{
public void LoadData(int ticketId)
{
// some multi-table SQL select and join query
string sqlQuery = @"
SELECT TOP(1) [Foo].Id AS FooId, [Foo].Name AS FooName, [Foo].Address AS FooAddress
[Bar].Name AS BarName, [Bar].UnitPrice AS BarPrice,
[Bif].Plop
FROM [dbo].[Foo]
INNER JOIN [dbo].[Bar]
ON [Bar].Id = [Foo].BarId
INNER JOIN [dbo].[Bif]
ON [Bar].BifId = [Bif].Id
WHERE [Foo].TicketId = @ticketId";
SqlCommand sqlCmd = new SqlCommand();
sqlCmd.CommandText = sqlQuery;
sqlCmd.Parameters.AddWithValue("@ticketId", ticketId);
// connection string params etc and connection open/close handled by this call below
DataTable resultsDataTable = SqlQueryHelper.ExecuteSqlReadCommand(sqlCmd);
if (resultsDataTable.Rows.Count > 0)
{
var row = resultsDataTable.Rows[0];
// read-out the fields
int fooId = 0;
if (!row.IsNull("FooId"))
fooId = row.Field<int>("FooId");
string fooName = "";
if (!row.IsNull("FooName"))
fooName = row.Field<string>("FooName");
// read out further fields...
// display in form
this.fooNameTextBox.Text = fooName;
// etc.
}
}
}
这个项目中有几十个表单在概念上都在做同样的事情,只是使用不同的 SQL 查询(选择不同的列等) 并且每次打开表格时,都在不断地查询数据库。
对于本地数据库服务器,速度还可以,但通过慢速 VPN 使用该应用程序会很痛苦。
有没有更好的方法来减少查询数据库的量?某种将数据库缓存在内存中并对内存中数据执行查询的方法?
我已将一些数据表添加到项目中的数据源,但无法理解如何执行上述复杂查询。
有更好的方法吗?
感谢大家的所有建议!
我可以建议几件事:
- 而不是使用 ADO.NET switch to Entity Framework,您可以使用 LINQ 来 SQL/LINQ to EF(较新版本)并用简单的 C# 语言写下查询,而不用担心查询 SQL.
- 使用存储过程,SQL函数,视图 - 写在SQL服务器数据库中,其中调用由 SQL 服务器 缓存,这提供了更高效的执行、安全性和更好的可维护性。
- 为了使您的 查询 对数据库表更有效,请考虑对您在过滤操作(如搜索)中更频繁使用的数据的表使用 Full-Text Index。
- 在您的 C#(包括与 Entity Framework 的集成)代码中使用 Repository and Unit of Work 模式,它实际上将完全按照您的意愿执行操作,即收集数额SQL 查询并将它们发送给SQL服务器执行一次,而不是一个一个地发送 Queries。这不仅会大大提高性能,还会使您的编码尽可能简单。
Note: One of the problems with your queries is related not only to their executions but also on Opening and closing SQL database connections each time you need to execute the particular Query. This problem is solved with the Repository and Unit of Work design patterns approach.
- 根据您的业务需求,对数据使用 In Memory or Database 缓存,这对很多用户来说都是重复的。
除了Entity Framework还有一个解决方案。
例如您可以使用 The Sharp Factory.
它是一个商业产品,但它不仅像 Entity Framework 一样映射数据库对象,而且还创建了一个基于层的完整存储库。
如果你愿意花钱,我觉得比EF好
Entity Framework 有一些缺点。例如,您的存储库将 泄漏 遍及您的层。
因为您需要在实体的所有消费者上引用 Entity Framework...所以即使您的架构看起来正确,在运行时您仍然可以在不知不觉中从上层执行 sql 查询.
我认为使用框架访问数据集等建议都不能解决您的问题。问题不在于编写 SQL 或 LINQ 查询。您总是必须在某个时候向数据库或数据集发送查询。
问题出在你查询数据库的地方。
你关于写作 "code containing various SQL queries in various classes and forms" 的陈述让我不寒而栗。我最近在一家公司工作,他们做的完全一样。结果,他们无法再维护他们的数据库。维护仍然是可能的,但只是基本的、非常 expensive/time 消耗和非常令人沮丧的 - 所以没有人喜欢这样做,因此没有人这样做,结果它变得越来越糟。查询变得越来越慢,唯一可能的快速解决方法是为数据库服务器购买更多带宽。
实际查询分散在整个项目中,因此无法 improve/refactor 查询(并识别它们)或改进 table 设计。但最糟糕的是,他们无法切换到更快或更便宜的数据库模型,例如图结构数据库,尽管有强烈的愿望和迫切需要这样做。这会让你在顾客面前显得邋遢。在这样的环境下工作绝对没有乐趣(所以我离开了)。听起来很糟糕?
您确实应该将数据库和 SQL 从您的业务代码中分离出来。所有这些都应该隐藏在界面后面:
IRepository repository = Factory.GetRepository();
// IRepository exposes query methods, which hide
// the actual query and the database related code details from the repository client or business logic
var customers = repository.GetCustomers();
在您的项目中传播此代码没有坏处。它将提高可维护性和可读性。它将数据持久性与实际业务逻辑分开,因为您是 hiding/encapsulating 所有细节,如实际数据库、查询和查询语言。如果你想切换到另一个数据库,你只需要实现一个新的 IRepository
来修改现有的查询。更改数据库不会破坏应用程序。
并且所有查询都在一个 location/layer 中实现。在谈论查询时,这也包括 LINQ 查询(这就是为什么使用像 Entity Framework 这样的框架不能解决您的问题的原因)。您可以使用依赖注入或工厂模式来分发 IRepository
的实现。这甚至允许在运行时在不同数据库之间切换而无需重新编译应用程序。
使用此模式(存储库模式)还可以将实体框架等框架与您的业务逻辑分离。
看看存储库模式。如果为时不晚,您应该开始重构现有代码。如果保持原样,价格太高了。
关于缓存,我知道数据库或 DBMS 已经非常非常有效地处理数据缓存。你能做的就是
- 在本地缓存数据,例如如果数据不会远程更改(因为 其他一些客户会修改它)例如本地客户端设置。这个 可以显着减少网络流量的方法。
- 在本地缓存数据,例如如果您经常访问此数据集并且远程更改不太可能发生或不会产生影响。您可以定期更新本地数据存储或使其无效,例如一段时间后强制客户端派发新查询以更新缓存。
- 您还可以使用 LINQ 在本地过滤数据。这也可能减少 网络流量,尽管您最终可能会读取更多数据 必要的。此外,过滤通常由 数据库管理系统。
- 可以考虑升级数据库服务器或者VPN增加 带宽。
- 索引也将显着缩短查找时间。
- 您应该考虑重构所有 SQL 查询。有许多 有关如何提高 SQL 查询性能的文章。你的方式 建立查询将对性能产生重大影响或 执行时间,尤其是大数据。
- 您可以使用数据虚拟化。当您只能向用户显示其中的 20 条记录时,从数据库中提取数千条记录是没有意义的。当用户滚动视图时提取更多数据。或者更好的是,显示一个预先选择的列表,例如最近的项目,并允许用户搜索感兴趣的数据。这样,您 只读取 用户明确要求的数据。这将大大提高整体性能,因为通常用户只对很少的记录感兴趣。
在引入接口(依赖倒置)之前
以下示例旨在说明,这是架构或设计的问题,而不是框架或库的问题。 库或框架可以在不同层面提供帮助,但无法解决问题,该问题是通过在整个业务代码中传播 环境特定 查询引入的。查询应该始终是中立的。查看业务代码时,您不应该能够判断数据是从文件还是数据库中获取的。此详细信息必须隐藏或封装。
当您将实际的数据库访问代码(无论是直接使用纯 SQL 还是在框架的帮助下)分散到整个业务代码中时,如果没有附加数据库,您将无法编写单元测试。这是不希望的。它使测试变得过于复杂,并且测试将执行得不必要地慢。您想要测试您的业务逻辑而不是数据库。这是单独的测试。您通常希望模拟数据库。
问题:
您需要跨应用程序模型或业务逻辑的多个位置的数据库中的数据。
最直观的方法是随时随地在您需要数据的地方调度数据库查询。这意味着,当数据库是 Postgre 数据库时,所有代码当然会使用 PostgreSQL 或某些框架,如 Entity Framework 或一般的 ORM。如果您决定更改数据库或 DBMS,例如对于某些 Oracle 或想使用不同的框架来管理您的实体,您将被迫接触并重写 每个 使用 PostgreSQL 或 Entity Framework 的代码。
在大型业务应用程序中,这将是迫使您的公司保留现有资源并让您的团队梦想更美好世界的原因。挫败感会上升。维护与数据库相关的代码几乎是不可能的,容易出错且耗时。由于实际的数据库访问不是集中的,重写数据库相关代码意味着爬取整个应用程序。最坏的情况是无意义的 SQL 查询字符串的传播,没有人理解或记住。在不牺牲宝贵且昂贵的时间和团队资源的情况下,不可能迁移到新数据库或重构查询以提高性能。
想象以下简化的符号方法在应用程序的业务逻辑中以某种形式重复,可能访问不同的实体并使用不同的过滤器,但使用相同的查询语言、框架或库。假设我们找到类似代码一千次:
private IEnumerable GetCustomers()
{
// Use Entity Framework to manage the database directly
return DbContext.Customers;
}
我们引入了与框架的紧密耦合,因为它已深入到我们的业务代码中。代码 "knows" 如何管理数据库。它知道 Entity Framework 因为它必须在任何地方使用它的 类 或 API。
证明是,如果您想用其他框架替换 Entity Framework 或只是想放弃它,您将不得不在数千处重构代码 - 在您的应用程序中使用此框架的任何地方。
在引入接口(依赖倒置)并封装所有数据库访问后
依赖倒置将通过引入接口帮助消除对具体 类 的依赖。由于我们更喜欢组件和 类 之间的松散耦合,以在使用辅助框架或普通 SQL 方言时增强灵活性、可测试性和可维护性,因此我们必须包装此特定代码并将其隐藏在接口后面(存储库模式).
我们现在引入了接口方法,例如 GetHighPriorityCustomers
和 [=19],而不是一千个显式使用数据库框架或 SQL 或 LINQ 查询来读取、写入或过滤数据的地方=].如何提供数据或从哪种数据库中获取数据是细节,只有此接口的实现才知道。
现在应用程序不再直接使用任何框架或数据库特定语言:
interface IRepository
{
IEnumerable<Customer> GetHighPriorityCustomers();
IEnumerable<Customer> GetAllCustomers();
}
之前的一千个地方现在看起来像:
private IRepository Repository { get; } // Initialized e.g. from constructor
private IEnumerable GetCustomers()
{
// Use a repository hidden behind an interface.
// We don't know in this place (business logic) how the interface is implemented
// and what classes it uses. When the implementation changes from Entity Framework to something else,, no changes have to be made here (loose coupling).
return this.Repository.GetAllCustomers();
}
执行IRepository
:
class EntityFrameworkRepository : IRepository
{
IEnumerable<Customer> GetAllCustomers()
{
// If we want to drop Entity Framework, we just have to provide a new implementation of IRepository
return DbContext.Customers;
}
...
}
现在,您决定使用普通 SQL。唯一要做的更改是实现一个新的 IRepository
,而不是更改千位以删除专用代码:
class MySqlRepository : IRepository
{
// The caller still accesses this method via the interface IRepository.GetAllCustomers()
IEnumerable<Customer> GetAllCustomers()
{
return this.Connection.ExecuteQuery("SELECT * FROM ...");
}
...
}
现在您决定将 MySQL 替换为 Microsoft SQL。您所要做的就是实现一个新的 IRepository
.
您可以换入和换出任何数据库并更改查询语言或引入辅助框架,而不会影响您的原始业务逻辑。一旦写入,再也不会触及(至少对于有关数据库的更改)。
如果将实现移至单独的程序集,甚至可以在运行时交换它们。