C# WPF MVVM 在多种数据库连接类型之间变化
C# WPF MVVM change between multiple database connection types
我正在准备使用 C# WPF 的期末考试,使用 MVVM 创建一个需要支持多个数据库引擎的查询编辑器,并且想知道在一个视图中切换多个数据库连接类型的最佳实践是什么。
这包括 oracle、mssql、mysql 连接等。
我想到了两种方案来执行此操作,即:
A) 创建数据库连接的新实例,它会创建一个新视图 window 进行显示,以便用户可以使用该特定连接。
B) 创建全局访问列表,通过书面命令在连接之间切换。等 'change database to xxxx',对于他们正在显示的当前视图。
我要搜索的是方案 B),因此它对用户来说更灵活。到目前为止,我被引导阅读有关依赖注入和继承的内容,其中委托抽象基类来解决这个问题。
第二件事是如何在命令字段中访问此列表,根据写入的数据库名称找到数据库的名称,并更改它们正在显示的(此)当前视图的连接类型。但是,这需要是唯一的,因为我们不能在任何 viewModels 中硬编码连接类型。
目前我被引导使用 DataServices 和 MVVMLight nuget,它为每种连接类型创建一个。这里我将连接存储在一个列表中:
public class MySqlService : IMySqlService
{
private List<MySqlConnection> Connections = new List<MySqlConnection>();
public MySqlConnection AddConnection(string hostName, string userName, string userPassword, string dataBase)
{
var connectionString = $"Server={hostName};database={dataBase};user id={userName};password={userPassword};";
var mySqlCon = new MySqlConnection(connectionString);
if(mySqlCon.State == ConnectionState.Closed)
{
mySqlCon.Open();
Connections.Add(mySqlCon);
return mySqlCon;
}
else
{
return null;
}
}
Result case
最佳做法是不要动态更改数据库类型。
在现实世界的应用程序中,您实际上并不会在 Oracle 和 sql 服务器或 mysql 之间动态切换。您的数据位于给定的数据库中,并且就在那里。更改为另一个是一件大事,这将需要移植数据,员工需要学习新的 rdbms,很可能需要重写大量的存储过程。
一些软件包旨在支持多个不同的 rdbms,但这是在安装之前做出的一次性决定。
一个客户有 sql 服务器,这就是他们想要使用的。另一个客户有 Oracle,所以这就是他们希望使用的。
当然也有例外。
客户可能希望您的小型系统在本地安装,并通过使用像 sql express 这样的免费 rdbms 来降低成本。
通常支持一次性安装选项。
在设计这样的系统时,通常会尽量减少需要切换的内容。
这并不总是可能的。
对于简单的系统,有时 "just" 需要更改连接字符串,这由配置文件处理。
其他人有更复杂的要求,然后倾向于尽可能封装在存储过程中。这样您的代码可以保持不变,但 Oracle 有一个存储过程可以执行特定于 Oracle 的操作,而 sql 服务器数据库有一个存储过程可以执行 sql 服务器特定的操作。这意味着为每个选项编写、测试和优化存储过程。这是昂贵的,而且远非理想。不过有 "up" 的一面。如果客户公司有 DBA,他们可能会调整您的存储过程以提高性能。
我在 Stack.Exchange site 上找到了一个描述的回复,其中包含以下回复,以防它被删除:
短:
您想要的是您的应用程序使用的接口的多个实现。
像这样:
public interface IDatabase
{
void SaveToDatabase();
void ReadFromDatabase();
}
public class MySQLDatabase : IDatabase
{
public MySQLDatabase ()
{
//init stuff
}
public void SaveToDatabase(){
//MySql implementation
}
public void ReadFromDatabase(){
//MySql implementation
}
}
public class SQLLiteDatabase : IDatabase
{
public SQLLiteDatabase ()
{
//init stuff
}
public void SaveToDatabase(){
//SQLLite implementation
}
public void ReadFromDatabase(){
//SQLLite implementation
}
}
//Application
public class Foo {
public IDatabase db = GetDatabase();
public void SaveData(){
db.SaveToDatabase();
}
private IDatabase GetDatabase()
{
if(/*some way to tell if should use MySql*/)
return new MySQLDatabase();
else if(/*some way to tell if should use MySql*/)
return new SQLLiteDatabase();
throw new Exception("You forgot to configure the database!");
}
}
至于在应用程序中 运行 时设置正确 IDatabase 实现的更好方法,您应该研究 "Factory Method" 和 "Dependancy Injection".
长:
这个问题,尤其是在数据库方面,已经问了太多次了。在这里,我将尝试彻底向您展示使用抽象(使用接口)使您的应用程序耦合度更低、功能更丰富的好处。
在进一步阅读之前,如果您还不了解依赖注入,我建议您先阅读并对其有一个基本的了解。您可能还想检查适配器设计模式,这基本上就是将实现细节隐藏在接口的 public 方法后面的意思。
依赖注入与工厂设计模式相结合,是编写策略设计模式的基石和简单方法,这是 IoC 原则的一部分。
不要打电话给我们,我们会打电话给你。 (又名好莱坞原则)。
使用抽象解耦应用程序
1.制作抽象层
如果您使用像 C++ 这样的语言进行编码,您可以创建一个接口或抽象 class,并向该接口添加通用方法。因为接口和抽象 classes 都有你不能直接使用它们的行为,但你必须实现(在接口的情况下)或扩展(在抽象 class 的情况下)它们,代码本身已经表明,您将需要有特定的实现来完成接口或抽象给出的合同 class.
您的(非常简单的示例)数据库接口可能如下所示(DatabaseResult 或 DbQuery classes 分别是您自己的表示数据库操作的实现):
public interface Database
{
DatabaseResult DoQuery(DbQuery query);
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
bool IsInTransaction();
}
因为这是一个接口,它本身并没有真正做任何事情。所以你需要一个class来实现这个接口。
public class MyMySQLDatabase : Database
{
private readonly CSharpMySQLDriver _mySQLDriver;
public MyMySQLDatabase(CSharpMySQLDriver mySQLDriver)
{
_mySQLDriver = mySQLDriver;
}
public DatabaseResult DoQuery(DbQuery query)
{
// This is a place where you will use _mySQLDriver to handle the DbQuery
}
public void BeginTransaction()
{
// This is a place where you will use _mySQLDriver to begin transaction
}
public void RollbackTransaction()
{
// This is a place where you will use _mySQLDriver to rollback transaction
}
public void CommitTransaction()
{
// This is a place where you will use _mySQLDriver to commit transaction
}
public bool IsInTransaction()
{
// This is a place where you will use _mySQLDriver to check, whether you are in a transaction
}
}
现在你有一个 class 实现了数据库,接口刚刚变得有用。
2。使用抽象层
在您的应用程序中的某处,您有一个方法,让我们调用方法 SecretMethod,只是为了好玩,在这个方法中您必须使用数据库,因为您想要获取一些数据。
现在你有一个接口,你不能直接创建它(呃,那我怎么用它),但是你有一个class我的MySQL数据库,它可以使用新关键字。
太棒了!我想使用数据库,所以我将使用 MyMySQLDatabase.
您的方法可能如下所示:
public void SecretMethod()
{
var database = new MyMySQLDatabase(new CSharpMySQLDriver());
// you will use the database here, which has the DoQuery,
// BeginTransaction, RollbackTransaction and CommitTransaction methods
}
这样不好。您直接在该方法中创建一个 class,如果您在 SecretMethod 中执行此操作,则可以安全地假设您将在其他 30 种方法中执行相同的操作。如果您想将 MyMySQLDatabase 更改为不同的 class,例如 MyPostgreSQLDatabase,则必须在所有 30 个方法中更改它。
另一个问题是,如果创建 MyMySQLDatabase 失败,该方法将永远不会完成,因此将无效。
我们首先重构 MyMySQLDatabase 的创建,将其作为参数传递给方法(这称为依赖注入)。
public void SecretMethod(MyMySQLDatabase database)
{
// use the database here
}
这解决了您永远无法创建 MyMySQLDatabase 对象的问题。因为 SecretMethod 需要一个有效的 MyMySQLDatabase 对象,所以如果发生某些事情并且该对象永远不会传递给它,则该方法永远不会 运行。这完全没问题。
在某些应用程序中,这可能就足够了。您可能会满意,但让我们重构它以使其更好。
又一次重构的目的
你可以看到,现在 SecretMethod 使用了一个 MyMySQLDatabase 对象。假设您从 MySQL 迁移到 MSSQL。您真的不想更改 SecretMethod 中的所有逻辑,SecretMethod 是一种对作为参数传递的数据库变量调用 BeginTransaction 和 CommitTransaction 方法的方法,因此您创建了一个新的 class MyMSSQLDatabase,它也将具有 BeginTransaction和 CommitTransaction 方法。
那你继续修改SecretMethod的声明如下。
public void SecretMethod(MyMSSQLDatabase database)
{
// use the database here
}
并且由于 classes MyMSSQLDatabase 和 MyMySQLDatabase 具有相同的方法,您无需更改任何其他内容,它仍然有效。
哦等等!
您有一个数据库接口,由 MyMySQLDatabase 实现,您还有 MyMSSQLDatabase class,它具有与 MyMySQLDatabase 完全相同的方法,也许是MSSQL 驱动程序也可以实现数据库接口,因此您将其添加到定义中。
public class MyMSSQLDatabase : Database { }
但是如果我以后不想再使用 MyMSSQLDatabase,因为我切换到 PostgreSQL 怎么办?我将不得不再次替换 SecretMethod 的定义?
是的,你会的。这听起来不对。现在我们知道,MyMSSQLDatabase 和 MyMySQLDatabase 具有相同的方法,并且都实现了 Database 接口。因此,您将 SecretMethod 重构为如下所示。
public void SecretMethod(Database database)
{
// use the database here
}
注意,SecretMethod 如何不再知道您使用的是 MySQL、MSSQL 还是 PotgreSQL。它知道它使用数据库,但不关心具体实现。
现在,如果您想为 PostgreSQL 创建新的数据库驱动程序,则根本不需要更改 SecretMethod。您将创建一个 MyPostgreSQLDatabase,使其实现数据库接口,一旦您完成 PostgreSQL 驱动程序的编码并且它可以工作,您将创建它的实例并将其注入 SecretMethod。
3。获取所需的数据库实施
在调用 SecretMethod 之前,您仍然需要决定您想要数据库接口的哪个实现(无论是 MySQL、MSSQL 还是 PostgreSQL)。为此,您可以使用工厂设计模式。
public class DatabaseFactory
{
private Config _config;
public DatabaseFactory(Config config)
{
_config = config;
}
public Database getDatabase()
{
var databaseType = _config.GetDatabaseType();
Database database = null;
switch (databaseType)
{
case DatabaseEnum.MySQL:
database = new MyMySQLDatabase(new CSharpMySQLDriver());
break;
case DatabaseEnum.MSSQL:
database = new MyMSSQLDatabase(new CSharpMSSQLDriver());
break;
case DatabaseEnum.PostgreSQL:
database = new MyPostgreSQLDatabase(new CSharpPostgreSQLDriver());
break;
default:
throw new DatabaseDriverNotImplementedException();
break;
}
return database;
}
}
如您所见,工厂从配置文件中知道要使用哪种数据库类型(同样,Config class 可能是您自己的实现)。
理想情况下,您的依赖注入容器中将包含 DatabaseFactory。你的过程可能看起来像这样。
public class ProcessWhichCallsTheSecretMethod
{
private DIContainer _di;
private ClassWithSecretMethod _secret;
public ProcessWhichCallsTheSecretMethod(DIContainer di, ClassWithSecretMethod secret)
{
_di = di;
_secret = secret;
}
public void TheProcessMethod()
{
Database database = _di.Factories.DatabaseFactory.getDatabase();
_secret.SecretMethod(database);
}
}
看,你在创建特定数据库类型的过程中怎么无处可去。不仅如此,你根本没有创造任何东西。您正在对存储在依赖注入容器(_di 变量)中的 DatabaseFactory 对象调用 GetDatabase 方法,该方法将 return 根据您的配置为您提供正确的数据库接口实例。
如果在使用 PostgreSQL 3 周后,您想回到 MySQL,您打开一个配置文件并将 DatabaseDriver 字段的值从 DatabaseEnum.PostgreSQL 更改为 DatabaseEnum.MySQL.你完成了。突然,您的应用程序的其余部分通过更改一行再次正确使用 MySQL。
我正在准备使用 C# WPF 的期末考试,使用 MVVM 创建一个需要支持多个数据库引擎的查询编辑器,并且想知道在一个视图中切换多个数据库连接类型的最佳实践是什么。
这包括 oracle、mssql、mysql 连接等。
我想到了两种方案来执行此操作,即:
A) 创建数据库连接的新实例,它会创建一个新视图 window 进行显示,以便用户可以使用该特定连接。
B) 创建全局访问列表,通过书面命令在连接之间切换。等 'change database to xxxx',对于他们正在显示的当前视图。
我要搜索的是方案 B),因此它对用户来说更灵活。到目前为止,我被引导阅读有关依赖注入和继承的内容,其中委托抽象基类来解决这个问题。
第二件事是如何在命令字段中访问此列表,根据写入的数据库名称找到数据库的名称,并更改它们正在显示的(此)当前视图的连接类型。但是,这需要是唯一的,因为我们不能在任何 viewModels 中硬编码连接类型。
目前我被引导使用 DataServices 和 MVVMLight nuget,它为每种连接类型创建一个。这里我将连接存储在一个列表中:
public class MySqlService : IMySqlService
{
private List<MySqlConnection> Connections = new List<MySqlConnection>();
public MySqlConnection AddConnection(string hostName, string userName, string userPassword, string dataBase)
{
var connectionString = $"Server={hostName};database={dataBase};user id={userName};password={userPassword};";
var mySqlCon = new MySqlConnection(connectionString);
if(mySqlCon.State == ConnectionState.Closed)
{
mySqlCon.Open();
Connections.Add(mySqlCon);
return mySqlCon;
}
else
{
return null;
}
}
Result case
最佳做法是不要动态更改数据库类型。
在现实世界的应用程序中,您实际上并不会在 Oracle 和 sql 服务器或 mysql 之间动态切换。您的数据位于给定的数据库中,并且就在那里。更改为另一个是一件大事,这将需要移植数据,员工需要学习新的 rdbms,很可能需要重写大量的存储过程。
一些软件包旨在支持多个不同的 rdbms,但这是在安装之前做出的一次性决定。
一个客户有 sql 服务器,这就是他们想要使用的。另一个客户有 Oracle,所以这就是他们希望使用的。
当然也有例外。
客户可能希望您的小型系统在本地安装,并通过使用像 sql express 这样的免费 rdbms 来降低成本。
通常支持一次性安装选项。
在设计这样的系统时,通常会尽量减少需要切换的内容。
这并不总是可能的。
对于简单的系统,有时 "just" 需要更改连接字符串,这由配置文件处理。
其他人有更复杂的要求,然后倾向于尽可能封装在存储过程中。这样您的代码可以保持不变,但 Oracle 有一个存储过程可以执行特定于 Oracle 的操作,而 sql 服务器数据库有一个存储过程可以执行 sql 服务器特定的操作。这意味着为每个选项编写、测试和优化存储过程。这是昂贵的,而且远非理想。不过有 "up" 的一面。如果客户公司有 DBA,他们可能会调整您的存储过程以提高性能。
我在 Stack.Exchange site 上找到了一个描述的回复,其中包含以下回复,以防它被删除:
短:
您想要的是您的应用程序使用的接口的多个实现。
像这样:
public interface IDatabase
{
void SaveToDatabase();
void ReadFromDatabase();
}
public class MySQLDatabase : IDatabase
{
public MySQLDatabase ()
{
//init stuff
}
public void SaveToDatabase(){
//MySql implementation
}
public void ReadFromDatabase(){
//MySql implementation
}
}
public class SQLLiteDatabase : IDatabase
{
public SQLLiteDatabase ()
{
//init stuff
}
public void SaveToDatabase(){
//SQLLite implementation
}
public void ReadFromDatabase(){
//SQLLite implementation
}
}
//Application
public class Foo {
public IDatabase db = GetDatabase();
public void SaveData(){
db.SaveToDatabase();
}
private IDatabase GetDatabase()
{
if(/*some way to tell if should use MySql*/)
return new MySQLDatabase();
else if(/*some way to tell if should use MySql*/)
return new SQLLiteDatabase();
throw new Exception("You forgot to configure the database!");
}
}
至于在应用程序中 运行 时设置正确 IDatabase 实现的更好方法,您应该研究 "Factory Method" 和 "Dependancy Injection".
长:
这个问题,尤其是在数据库方面,已经问了太多次了。在这里,我将尝试彻底向您展示使用抽象(使用接口)使您的应用程序耦合度更低、功能更丰富的好处。
在进一步阅读之前,如果您还不了解依赖注入,我建议您先阅读并对其有一个基本的了解。您可能还想检查适配器设计模式,这基本上就是将实现细节隐藏在接口的 public 方法后面的意思。
依赖注入与工厂设计模式相结合,是编写策略设计模式的基石和简单方法,这是 IoC 原则的一部分。
不要打电话给我们,我们会打电话给你。 (又名好莱坞原则)。
使用抽象解耦应用程序
1.制作抽象层
如果您使用像 C++ 这样的语言进行编码,您可以创建一个接口或抽象 class,并向该接口添加通用方法。因为接口和抽象 classes 都有你不能直接使用它们的行为,但你必须实现(在接口的情况下)或扩展(在抽象 class 的情况下)它们,代码本身已经表明,您将需要有特定的实现来完成接口或抽象给出的合同 class.
您的(非常简单的示例)数据库接口可能如下所示(DatabaseResult 或 DbQuery classes 分别是您自己的表示数据库操作的实现):
public interface Database
{
DatabaseResult DoQuery(DbQuery query);
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
bool IsInTransaction();
}
因为这是一个接口,它本身并没有真正做任何事情。所以你需要一个class来实现这个接口。
public class MyMySQLDatabase : Database
{
private readonly CSharpMySQLDriver _mySQLDriver;
public MyMySQLDatabase(CSharpMySQLDriver mySQLDriver)
{
_mySQLDriver = mySQLDriver;
}
public DatabaseResult DoQuery(DbQuery query)
{
// This is a place where you will use _mySQLDriver to handle the DbQuery
}
public void BeginTransaction()
{
// This is a place where you will use _mySQLDriver to begin transaction
}
public void RollbackTransaction()
{
// This is a place where you will use _mySQLDriver to rollback transaction
}
public void CommitTransaction()
{
// This is a place where you will use _mySQLDriver to commit transaction
}
public bool IsInTransaction()
{
// This is a place where you will use _mySQLDriver to check, whether you are in a transaction
}
}
现在你有一个 class 实现了数据库,接口刚刚变得有用。
2。使用抽象层
在您的应用程序中的某处,您有一个方法,让我们调用方法 SecretMethod,只是为了好玩,在这个方法中您必须使用数据库,因为您想要获取一些数据。
现在你有一个接口,你不能直接创建它(呃,那我怎么用它),但是你有一个class我的MySQL数据库,它可以使用新关键字。
太棒了!我想使用数据库,所以我将使用 MyMySQLDatabase.
您的方法可能如下所示:
public void SecretMethod()
{
var database = new MyMySQLDatabase(new CSharpMySQLDriver());
// you will use the database here, which has the DoQuery,
// BeginTransaction, RollbackTransaction and CommitTransaction methods
}
这样不好。您直接在该方法中创建一个 class,如果您在 SecretMethod 中执行此操作,则可以安全地假设您将在其他 30 种方法中执行相同的操作。如果您想将 MyMySQLDatabase 更改为不同的 class,例如 MyPostgreSQLDatabase,则必须在所有 30 个方法中更改它。
另一个问题是,如果创建 MyMySQLDatabase 失败,该方法将永远不会完成,因此将无效。
我们首先重构 MyMySQLDatabase 的创建,将其作为参数传递给方法(这称为依赖注入)。
public void SecretMethod(MyMySQLDatabase database)
{
// use the database here
}
这解决了您永远无法创建 MyMySQLDatabase 对象的问题。因为 SecretMethod 需要一个有效的 MyMySQLDatabase 对象,所以如果发生某些事情并且该对象永远不会传递给它,则该方法永远不会 运行。这完全没问题。
在某些应用程序中,这可能就足够了。您可能会满意,但让我们重构它以使其更好。
又一次重构的目的
你可以看到,现在 SecretMethod 使用了一个 MyMySQLDatabase 对象。假设您从 MySQL 迁移到 MSSQL。您真的不想更改 SecretMethod 中的所有逻辑,SecretMethod 是一种对作为参数传递的数据库变量调用 BeginTransaction 和 CommitTransaction 方法的方法,因此您创建了一个新的 class MyMSSQLDatabase,它也将具有 BeginTransaction和 CommitTransaction 方法。
那你继续修改SecretMethod的声明如下。
public void SecretMethod(MyMSSQLDatabase database)
{
// use the database here
}
并且由于 classes MyMSSQLDatabase 和 MyMySQLDatabase 具有相同的方法,您无需更改任何其他内容,它仍然有效。
哦等等!
您有一个数据库接口,由 MyMySQLDatabase 实现,您还有 MyMSSQLDatabase class,它具有与 MyMySQLDatabase 完全相同的方法,也许是MSSQL 驱动程序也可以实现数据库接口,因此您将其添加到定义中。
public class MyMSSQLDatabase : Database { }
但是如果我以后不想再使用 MyMSSQLDatabase,因为我切换到 PostgreSQL 怎么办?我将不得不再次替换 SecretMethod 的定义?
是的,你会的。这听起来不对。现在我们知道,MyMSSQLDatabase 和 MyMySQLDatabase 具有相同的方法,并且都实现了 Database 接口。因此,您将 SecretMethod 重构为如下所示。
public void SecretMethod(Database database)
{
// use the database here
}
注意,SecretMethod 如何不再知道您使用的是 MySQL、MSSQL 还是 PotgreSQL。它知道它使用数据库,但不关心具体实现。
现在,如果您想为 PostgreSQL 创建新的数据库驱动程序,则根本不需要更改 SecretMethod。您将创建一个 MyPostgreSQLDatabase,使其实现数据库接口,一旦您完成 PostgreSQL 驱动程序的编码并且它可以工作,您将创建它的实例并将其注入 SecretMethod。
3。获取所需的数据库实施
在调用 SecretMethod 之前,您仍然需要决定您想要数据库接口的哪个实现(无论是 MySQL、MSSQL 还是 PostgreSQL)。为此,您可以使用工厂设计模式。
public class DatabaseFactory
{
private Config _config;
public DatabaseFactory(Config config)
{
_config = config;
}
public Database getDatabase()
{
var databaseType = _config.GetDatabaseType();
Database database = null;
switch (databaseType)
{
case DatabaseEnum.MySQL:
database = new MyMySQLDatabase(new CSharpMySQLDriver());
break;
case DatabaseEnum.MSSQL:
database = new MyMSSQLDatabase(new CSharpMSSQLDriver());
break;
case DatabaseEnum.PostgreSQL:
database = new MyPostgreSQLDatabase(new CSharpPostgreSQLDriver());
break;
default:
throw new DatabaseDriverNotImplementedException();
break;
}
return database;
}
}
如您所见,工厂从配置文件中知道要使用哪种数据库类型(同样,Config class 可能是您自己的实现)。
理想情况下,您的依赖注入容器中将包含 DatabaseFactory。你的过程可能看起来像这样。
public class ProcessWhichCallsTheSecretMethod
{
private DIContainer _di;
private ClassWithSecretMethod _secret;
public ProcessWhichCallsTheSecretMethod(DIContainer di, ClassWithSecretMethod secret)
{
_di = di;
_secret = secret;
}
public void TheProcessMethod()
{
Database database = _di.Factories.DatabaseFactory.getDatabase();
_secret.SecretMethod(database);
}
}
看,你在创建特定数据库类型的过程中怎么无处可去。不仅如此,你根本没有创造任何东西。您正在对存储在依赖注入容器(_di 变量)中的 DatabaseFactory 对象调用 GetDatabase 方法,该方法将 return 根据您的配置为您提供正确的数据库接口实例。
如果在使用 PostgreSQL 3 周后,您想回到 MySQL,您打开一个配置文件并将 DatabaseDriver 字段的值从 DatabaseEnum.PostgreSQL 更改为 DatabaseEnum.MySQL.你完成了。突然,您的应用程序的其余部分通过更改一行再次正确使用 MySQL。