简体   繁体   English

C#WPF MVVM在多个数据库连接类型之间进行更改

[英]C# WPF MVVM change between multiple database connection types

I'm prepering for my final exam with C# WPF using MVVM creating an query editor which needs to support multiple database motors, and wondering what's the best practice of having multiple database connection types to switch between in a view. 我使用MVVM创建一个需要支持多个数据库电机的查询编辑器,并且想知道在视图中切换多个数据库连接类型的最佳做法,我正在使用C#WPF进行期末考试。

This includes having etc. oracle, mssql, mysql connections. 这包括有oracle,mssql,mysql连接等。

I thought of two scenarios to do this which is: 我想到了两个这样做的场景:

A) Create a new instance of a database connection, where it creates a new view window to display so the user can work for that specifik connection. A)创建数据库连接的新实例,在该实例中创建一个新的视图窗口以便显示,以便用户可以为该特定连接工作。

B) Make a global access list to switch between connections by written command. B)创建一个全局访问列表,通过书面命令在连接之间切换。 etc. 'change database to xxxx', for the current view they are displaying. 等'将数据库更改为xxxx',用于显示当前视图。

What i'm searching for, is scenario B), so it's more flexible for the user. 我正在寻找的是场景B),因此它对用户来说更灵活。 I'm so far being guided to read about dependency injection and inheritance, where it delegating from abstract baseclass to resolve this. 到目前为止,我被引导阅读有关依赖注入和继承的内容,它从抽象基类委托来解决这个问题。

The second thing is how to access this list afterwards in the command field, find the name of a database based on the database name written, and change the connection type for the (this) current view they are displaying. 第二件事是如何在命令字段中访问此列表,根据写入的数据库名称查找数据库的名称,并更改它们正在显示的(此)当前视图的连接类型。 But, this needs to be unique due we cannot hard-code the connection type in any viewModels. 但是,这需要是唯一的,因为我们无法在任何viewModels中对连接类型进行硬编码。

Currently i'm guided using DataServices, with MVVMLight nuget, where it's created one per connection type. 目前我使用DataServices和MVVMLight nuget进行引导,每个连接类型创建一个。 Here i store the connection in one list: 在这里,我将连接存储在一个列表中:

 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 结果案例

Best practice is that you simply don't dynamically change database type. 最佳做法是您不能动态更改数据库类型。

In real world apps you don't really dynamically change between oracle and sql server or mysql. 在真实世界的应用程序中,您并不真正在oracle和sql server或mysql之间动态更改。 Your data is in a given database and that's where it's staying. 您的数据位于给定的数据库中,而这就是它所处的位置。 It's a big deal changing to another one and that will necessitate porting data, staff learning the new rdbms, quite possibly rewriting swathes of stored procedures. 这是一个很大的变化到另一个,这将需要移植数据,工作人员学习新的rdbms,很可能重写大量的存储过程。

Some software packages are intended to support multiple different rdbms but this is a one off decision that is taken prior to install. 某些软件包旨在支持多个不同的rdbms,但这是在安装之前采取的一次性决策。

One client has sql server and that's what they want to use. 一个客户端有sql server,这就是他们想要使用的东西。 Another client has Oracle so that's what they expect to use. 另一个客户端拥有Oracle,因此他们期望使用它。

There are exceptions, of course. 当然也有例外。

A client might want your small system to be installed locally and keep costs down by using a free rdbms like sql express. 客户可能希望您的小型系统在本地安装,并通过使用免费的rdbms(如sql express)来降低成本。

One off install choices are often supported. 通常支持一种安装选择。

When designing such a system it is usual to try and minimise what needs to be switched out. 在设计这样的系统时,通常会尝试最小化需要切换的系统。

This is not always possible. 这并不总是可行的。

For simple systems it can sometimes be "just" a matter of a connection string to change and this is handled by config file. 对于简单的系统,它有时可能只是“改变”连接字符串的问题,这由配置文件处理。

Others have more complicated requirements and the tendency then is to encapsulate within stored procedures if possible. 其他人有更复杂的要求,如果可能的话,倾向于封装在存储过程中。 That way your code can remain the same but Oracle has a stored procedure which does oracle specific stuff and the sql server database has a stored procedure does sql server specific stuff. 这样你的代码可以保持不变,但Oracle有一个存储过程,它执行oracle特定的东西和sql server数据库有一个存储过程做sql server特定的东西。 This means writing, testing and optimising stored procedures for each option. 这意味着为每个选项编写,测试和优化存储过程。 Which is costly and far from ideal. 这是昂贵的,远非理想。 There is an "up" side though. 虽然有一个“向上”的一面。 If the client company has a DBA, they can potentially tweak your stored procedures for performance. 如果客户公司有DBA,他们可能会调整存储过程的性能。

I've found an described response on Stack.Exchange site which has below response, in case it gets removed: 我在Stack.Exchange网站上找到了一个描述的响应,响应低于响应,以防它被删除:

Short: 短:

What you want is multiple implementations for the interface that your application uses. 您想要的是您的应用程序使用的接口的多个实现。

like so: 像这样:

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!");
    }
}

As far as a better way of setting up the correct IDatabase implementation at run time in your application, you should look into things like "Factory Method", and "Dependancy Injection". 至于在应用程序中运行时设置正确的IDatabase实现的更好方法,您应该查看诸如“Factory Method”和“Dependancy Injection”之类的内容。


Long: 长:

This question, especially in the database context, has been asked too many times. 这个问题,特别是在数据库环境中,已被问过太多次了。 Here I will try to thoroughly show you the benefit of using abstraction (using interfaces) to make your application less coupled and more versatile. 在这里,我将尝试彻底向您展示使用抽象(使用接口)使您的应用程序更少耦合和更多功能的好处。

Before reading further, I recommend you to read and get a basic understanding of Dependency injection, if you do not know it yet. 在进一步阅读之前,如果您还不知道,我建议您阅读并基本了解依赖注入。 You might also want to check the Adapter design pattern, which is basically what hiding implementation details behind interface's public methods means. 您可能还想检查适配器设计模式,这基本上是隐藏接口的公共方法背后的实现细节。

Dependency injection, coupled with Factory design pattern, is the foundation stone and an easy way to code the Strategy design pattern, which is a part of IoC principle. 依赖注入与工厂设计模式相结合,是编写策略设计模式的基石,也是IoC原则的一部分。

Don't call us, we will call you. 不要打电话给我们,我们会打电话给你。 (AKA the Hollywood principle). (AKA好莱坞原则)。

Decoupling an application using abstraction 使用抽象解耦应用程序

1. Making the abstraction layer 1.制作抽象层

You create an interface - or abstract class, if you are coding in a language like C++ - and add generic methods to this interface. 如果使用C ++等语言进行编码,则可以创建接口(或抽象类),并将泛型方法添加到此接口。 Because both interfaces and abstract classes have the behaviour of you not being able to use them directly, but you have to either implement (in case of interface) or extend (in case of abstract class) them, the code itself already suggests, you will need to have specific implementations to fullfil the contract given by either the interface or the abstract class. 因为接口和抽象类都有你无法直接使用它们的行为,但你必须实现(在接口的情况下)或扩展(在抽象类的情况下)它们,代码本身已经建议,你将需要具有特定的实现来完成由接口或抽象类给出的契约。

Your (very simple example) database interface might look like this (the DatabaseResult or DbQuery classes respectively would be your own implementations representing database operations): 您的(非常简单的示例)数据库接口可能看起来像这样(DatabaseResult或DbQuery类分别是您自己的表示数据库操作的实现):

public interface Database
{
    DatabaseResult DoQuery(DbQuery query);
    void BeginTransaction();
    void RollbackTransaction();
    void CommitTransaction();
    bool IsInTransaction();
}

Because this is an interface, it itself does not really do anything. 因为这是一个界面,它本身并没有真正做任何事情。 So you need a class to implement this interface. 所以你需要一个类来实现这个接口。

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
    }
}

Now you have a class which implements the Database, the interface just became useful. 现在你有一个实现数据库的类,界面变得有用了。

2. Using the abstraction layer 2.使用抽象层

Somewhere in your application, you have a method, let's call the method SecretMethod, just for fun, and inside this method you have to use the database, because you want to fetch some data. 在你的应用程序的某个地方,你有一个方法,让我们调用方法SecretMethod,只是为了好玩,在这个方法中你必须使用数据库,因为你想获取一些数据。

Now you have an interface, which you cannot create directly (uh, how do I use it then), but you have a class MyMySQLDatabase, which may be constructed using the new keyword. 现在你有了一个你无法直接创建的接口(呃,我该如何使用它),但你有一个MyMySQLDatabase类,它可以使用new关键字构建。

GREAT! 大! I want to use a database, so I will use the MyMySQLDatabase. 我想使用数据库,所以我将使用MyMySQLDatabase。

Your method might look like this: 您的方法可能如下所示:

public void SecretMethod()
{
    var database = new MyMySQLDatabase(new CSharpMySQLDriver());

    // you will use the database here, which has the DoQuery,
    // BeginTransaction, RollbackTransaction and CommitTransaction methods
}

This is not good. 不是很好。 You are directly creating a class inside this method, and if you are doing it inside the SecretMethod, it is safe to assume you would be doing the same in 30 other methods. 您正在此方法中直接创建一个类,如果您在SecretMethod中执行此操作,则可以安全地假设您将在其他30个方法中执行相同的操作。 If you wanted to change the MyMySQLDatabase to a different class, such as MyPostgreSQLDatabase, you would have to change it in all your 30 methods. 如果要将MyMySQLDatabase更改为其他类(例如MyPostgreSQLDatabase),则必须在所有30种方法中更改它。

Another problem is, if the creation of MyMySQLDatabase failed, the method would never finish and therefore would be invalid. 另一个问题是,如果MyMySQLDatabase的创建失败,该方法永远不会完成,因此无效。

We start by refactoring the creation of the MyMySQLDatabase by passing it as a parameter to the method (this is called dependency injection). 我们首先通过将MyMySQLDatabase作为参数传递给方法来重构MyMySQLDatabase的创建(这称为依赖注入)。

public void SecretMethod(MyMySQLDatabase database)
{
    // use the database here
}

This solves you the problem, that the MyMySQLDatabase object could never be created. 这解决了您无法创建MyMySQLDatabase对象的问题。 Because the SecretMethod expects a valid MyMySQLDatabase object, if something happened and the object would never be passed to it, the method would never run. 因为SecretMethod需要一个有效的MyMySQLDatabase对象,如果发生了某些事情且该对象永远不会传递给它,该方法将永远不会运行。 And that is totally fine. 这完全没问题。

In some applications this might be enough. 在某些应用中,这可能就足够了。 You may be satisfied, but let's refactor it to be even better. 你可能会感到满意,但让我们重构它会更好。

The purpose of another refactoring 另一个重构的目的

You can see, right now the SecretMethod uses a MyMySQLDatabase object. 您可以看到,SecretMethod现在使用MyMySQLDatabase对象。 Let's assume you moved from MySQL to MSSQL. 假设您从MySQL迁移到MSSQL。 You do not really feel like changing all the logic inside your SecretMethod, a method which calls a BeginTransaction and CommitTransaction methods on the database variable passed as a parameter, so you create a new class MyMSSQLDatabase, which will also have the BeginTransaction and CommitTransaction methods. 你真的不想改变SecretMethod中的所有逻辑,这是一个在作为参数传递的数据库变量上调用BeginTransaction和CommitTransaction方法的方法,因此你创建了一个新的类MyMSSQLDatabase,它也有BeginTransaction和CommitTransaction方法。

Then you go ahead and change the declaration of SecretMethod to the following. 然后继续将SecretMethod的声明更改为以下内容。

public void SecretMethod(MyMSSQLDatabase database)
{
    // use the database here
}

And because the classes MyMSSQLDatabase and MyMySQLDatabase have the same methods, you do not need to change anything else and it will still work. 并且因为MyMSSQLDatabase和MyMySQLDatabase类具有相同的方法,所以您不需要更改任何其他内容,它仍然可以工作。

Oh wait! 等一下!

You have a Database interface, which the MyMySQLDatabase implements, you also have the MyMSSQLDatabase class, which has exactly the same methods as MyMySQLDatabase, perhaps the MSSQL driver could also implement the Database interface, so you add it to the definition. 您有一个MyMySQLDatabase实现的Database接口,您还有MyMSSQLDatabase类,它与MyMySQLDatabase具有完全相同的方法,也许MSSQL驱动程序也可以实现Database接口,因此您将它添加到定义中。

public class MyMSSQLDatabase : Database { }

But what if I, in the future, don't want to use the MyMSSQLDatabase anymore, because I switched to PostgreSQL? 但是,如果我将来不再使用MyMSSQLDatabase,因为我切换到PostgreSQL会怎样? I would have to, again, replace the definition of the SecretMethod? 我将不得不再次取代SecretMethod的定义?

Yes, you would. 是的,你愿意。 And that does not sound right. 这听起来不对。 Right now we know, that MyMSSQLDatabase and MyMySQLDatabase have the same methods and both implement the Database interface. 现在我们知道,MyMSSQLDatabase和MyMySQLDatabase具有相同的方法,并且都实现了Database接口。 So you refactor the SecretMethod to look like this. 所以你重构SecretMethod看起来像这样。

public void SecretMethod(Database database)
{
    // use the database here
}

Notice, how the SecretMethod no longer knows, whether you are using MySQL, MSSQL or PotgreSQL. 请注意,SecretMethod如何不再知道,您使用的是MySQL,MSSQL还是PotgreSQL。 It knows it uses a database, but does not care about the specific implementation. 它知道它使用数据库,但不关心具体的实现。

Now if you wanted to create your new database driver, for PostgreSQL for example, you won't need to change the SecretMethod at all. 现在,如果您想创建新的数据库驱动程序,例如,对于PostgreSQL,您根本不需要更改SecretMethod。 You will make a MyPostgreSQLDatabase, make it implement the Database interface and once you are done coding the PostgreSQL driver and it works, you will create its instance and inject it into the SecretMethod. 您将创建一个MyPostgreSQLDatabase,使其实现Database接口,一旦您完成PostgreSQL驱动程序的编码并且它可以工作,您将创建其实例并将其注入SecretMethod。

3. Obtaining the desired implementation of Database 3.获得所需的数据库实现

You still have to decide, before calling the SecretMethod, which implementation of the Database interface you want (whether it is MySQL, MSSQL or PostgreSQL). 在调用SecretMethod之前,您仍然必须决定所需的数据库接口的实现(无论是MySQL,MSSQL还是PostgreSQL)。 For this, you can use the factory design pattern. 为此,您可以使用工厂设计模式。

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;
    }
}

The factory, as you can see, knows which database type to use from a config file (again, the Config class may be your own implementation). 正如您所见,工厂知道从配置文件中使用哪种数据库类型(同样,Config类可能是您自己的实现)。

Ideally, you will have the DatabaseFactory inside your dependency injection container. 理想情况下,您将在依赖注入容器中拥有DatabaseFactory。 Your process then may look like this. 那么您的流程可能如下所示。

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);
    }
}

Look, how nowhere in the process you are creating a specific database type. 看,在创建特定数据库类型的过程中无处可去。 Not only that, you aren't creating anything at all. 不仅如此,你根本就没有创造任何东西。 You are calling a GetDatabase method on the DatabaseFactory object stored inside your dependency injection container (the _di variable), a method, which will return you the correct instance of Database interface, based on your configuration. 您正在依赖注入容器(_di变量)中存储的DatabaseFactory对象上调用GetDatabase方法,该方法将根据您的配置返回正确的Database接口实例。

If, after 3 weeks of using PostgreSQL, you want to go back to MySQL, you open a single configuration file and change the value of DatabaseDriver field from DatabaseEnum.PostgreSQL to DatabaseEnum.MySQL. 如果在使用PostgreSQL 3周后,您想要返回MySQL,则打开一个配置文件并将DatabaseDriver字段的值从DatabaseEnum.PostgreSQL更改为DatabaseEnum.MySQL。 And you are done. 你完成了。 Suddenly the rest of your application correctly uses the MySQL again, by changing one single line. 突然,您的应用程序的其余部分通过更改一行来再次正确使用MySQL。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM