0

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.

This includes having etc. oracle, mssql, mysql connections.

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.

B) Make a global access list to switch between connections by written command. etc. 'change database to xxxx', for the current view they are displaying.

What i'm searching for, is scenario B), so it's more flexible for the user. 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.

Currently i'm guided using DataServices, with MVVMLight nuget, where it's created one per connection type. 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

2 Answers 2

1

I've found an described response on Stack.Exchange site which has below response, in case it gets removed:

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".


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.

Don't call us, we will call you. (AKA the Hollywood principle).

Decoupling an application using abstraction

1. Making the abstraction layer

You create an interface - or abstract class, if you are coding in a language like C++ - and add generic methods to this interface. 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):

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

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.

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.

GREAT! I want to use a database, so I will use the 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. 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.

Another problem is, if the creation of MyMySQLDatabase failed, the method would never finish and therefore would be invalid.

We start by refactoring the creation of the MyMySQLDatabase by passing it as a parameter to the method (this is called dependency injection).

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

This solves you the problem, that the MyMySQLDatabase object could never be created. 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. 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. Let's assume you moved from MySQL to 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.

Then you go ahead and change the declaration of SecretMethod to the following.

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.

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.

public class MyMSSQLDatabase : Database { }

But what if I, in the future, don't want to use the MyMSSQLDatabase anymore, because I switched to PostgreSQL? I would have to, again, replace the definition of the 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. So you refactor the SecretMethod to look like this.

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

Notice, how the SecretMethod no longer knows, whether you are using MySQL, MSSQL or 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. 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.

3. Obtaining the desired implementation of Database

You still have to decide, before calling the SecretMethod, which implementation of the Database interface you want (whether it is MySQL, MSSQL or 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).

Ideally, you will have the DatabaseFactory inside your dependency injection container. 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.

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. And you are done. Suddenly the rest of your application correctly uses the MySQL again, by changing one single line.

Sign up to request clarification or add additional context in comments.

Comments

0

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

Some software packages are intended to support multiple different rdbms but this is a one off decision that is taken prior to install.

One client has sql server and that's what they want to use. Another client has Oracle so that's what they expect to use.

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.

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

1 Comment

Hi Andy I'm very glad for your informative response to my post. You are right, and i managed to connect to oracle aswel using local ODBC settings filled by the user, if the driver is there. So in those cases it requires the user to have some stuff pre-installed in other to make this work. I'm missing the oracle cloud though. BUT.. So far i only see an option to create a new instance of the connection for the specifik DB type. But this means re-writing same UI views and classes for each of them to make it work. This seems to be overkill, where i still hope i some how can make it work my way

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.