Simple but good example on how to use Dapper with

2020-06-17 04:00发布

问题:

I am trying to understand how to use Dependency Injection with Dapper (IDbConnection) and still being able to use built in dispose.

I have found a couple of articles on the web but non that I think is easy to understand.

What I am trying to figure out is how to make this simple class be testable:

public class UserProfileRepository : IUserProfileRepository
{
    private readonly IConfigRepository _configRepository;

    public UserProfileRepository(IConfigRepository configRepository)
    {
        _configRepository = configRepository;
    }

    public UserProfile GetUserProfile(string userId)
    {
        const string query = @"Select UserId, UserName
                                From Users
                                Where UserId = @UserId";

        using (var conn = new SqlConnection(_configRepository.GetConnectionString("MyConnectionString")))
        {
            conn.Open();
            return conn.Query<UserProfile>(query, new { UserId = userId }).SingleOrDefault();
        }
    }
}

I have a config repository that looks like this so I can mock the request to web.config away:

public class ConfigRepository : IConfigRepository
{
    public string GetConnectionString(string key)
    {
        var conString = ConfigurationManager.ConnectionStrings[key];
        if (conString != null)
        {
            return conString.ConnectionString;
        }

        return string.Empty;
    }
}

I have read that you could use ConnectionFactory but has not figur out how to implement it and still know I am disposing it correct.

Can anyone point me in the right direction?

回答1:

Best connection creation mechanism as per my experience is the combination of DependencyInjection and ConnectionFactory. I am getting rid of IConfigRepository, since here all the work is done using factory

Advantages are Multi fold:

  • Create a connection object at runtime in transaction or thread scope
  • At runtime change the data provider and thus database of the system ( using Connection Factory)

What you shall do (in Code):

Declare the IDBConnection object in the Data access Layer:

[Inject] // Property Injection
public IDBConnection Connection {get; set;}

Declare the binding using a DI framework like Ninject:

Bind<IDBConnection>().ToMethod(ctx => 
ConnectionFactory.CreateDbConnection("DefaultConnection"));

Create the DBConnection Factory as follows:

Connection factory fetches the Connection provider and connection string from the config file as follows:

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=<Value>;Initial Catalog=<Value>;User Id=<Value>;Password=<Value>" providerName="System.Data.SqlClient" />
</connectionStrings>

Identifier is DefaultConnection, which is using the SqlClient provider, but at run time can be changed to the different client like Oracle, MySql

 using System;
 using System.Data.Common;

 public static class ConnectionFactory
    {
        /// <summary>
        /// Create DBConnection type based on provider name and connection string
        /// </summary>
        /// <param name="connectionIdentifier"></param>
        /// <returns></returns>
        public static DbConnection CreateDbConnection(string connectionIdentifier)
        {
            // Provider name setting
            var providerNameValue = ConfigurationManager.ConnectionStrings[connectionIdentifier].ProviderName;

            // Connection string setting
            var connectionStringValue = ConfigurationManager.ConnectionStrings[connectionIdentifier].ConnectionString;

            // Assume failure.
            DbConnection connection;

            // Null connection string cannot be accepted
            if (connectionStringValue == null) return null;

            // Create the DbProviderFactory and DbConnection.
            try
            {
                // Fetch provider factory
                var factory = DbProviderFactories.GetFactory(providerNameValue);

                // Create Connection
                connection = factory.CreateConnection();

                // Assign connection string
                if (connection != null)
                    connection.ConnectionString = connectionStringValue;
            }
            catch (Exception ex)
            {
                connection = null;
            }
            // Return the connection.
            return connection;
        }
}

How to use it:

For a single call and dispose

using(Connection)
{
 ...
}

For a Transaction context, use as-is, no using required

Regarding Mocking:

Which ever Mock framework you use for the Unit testing you have to mock the result of the UserProfileRepository :: GetUserProfile(string userId), this would be easier instead of filling a MockConnection using dependency Injection, which will make it complex. DI is good for real use case, for filling connection object at runtime