提问者:小点点

不使用实体框架(或similiar)时如何组织MySQL数据库连接


基于这两个样本

  • https://github.com/jasontaylordev/cleanarchitecture
  • https://github.com/jasontaylordev/northwindtraders

我在我的API项目中添加了一个应用程序和基础设施层。 重要的是,我将只使用mysql.data包来处理数据库(没有实体框架或其他帮助库)。

我认为在应用程序层为存储库定义接口是一个很好的实践

public interface IUsersRepository
{
    Task<IList<User>> GetUsers();
    Task<User> GetUserByUsername(string username);
    // ...
}

并在基础结构层实现它们。 因此,当涉及到通过IServiceCollection设置DI容器时,我可以使用services.addtransient(typeof(IUsersRepository),typeof(UsersRepository));设置那些存储库。 由于我没有使用ORM工具,我必须自己设置连接。 这就是为什么我在应用层定义了一个接口

public interface IDatabaseContext
{
    DbConnection DatabaseConnection { get; }
}

并在基础结构层中创建到MySQL数据库的连接

public class DatabaseContext : IDatabaseContext
{
    public DbConnection DatabaseConnection { get; }

    public DatabaseContext()
    {
        DatabaseConnection = new MySqlConnection("server=127.0.0.1;uid=root;pwd=12345;database=test");
    }
}

要使其可注入,我使用services.addSingleton(typeof(IDatabaseContext),typeof(DatabaseContext));将其添加到服务集合中

我认为实现存储库应该只关心它们自己的查询,因为它们可能会被事务链接起来。 目前他们不关心连接

public class UsersRepository : IUsersRepository
{
    private readonly IDatabaseContext databaseContext;

    public UsersRepository(IDatabaseContext databaseContext)
    {
        this.databaseContext = databaseContext;
    }

    public async Task<IList<User>> GetUsers()
    {
        using (DbCommand getUsersCommand = databaseContext.DatabaseConnection.CreateCommand())
        {
            // setup command string, parameters and execute command afterwards 
        }
    }
}

问题是,现在每个存储库调用在应用程序层执行之前都需要一个连接处理。 我的意思是我必须这样结束电话

await databaseContext.DatabaseConnection.OpenAsync();
IList<User> users = await usersRepository.GetUsers();
// ...
await databaseContext.DatabaseConnection.CloseAsync();

因此调用类需要注入存储库和IDatabaseContext。 我也不确定为每个查询/事务打开/关闭连接是否是个好主意。

也许有一些更好的方法来增强现有的方法。 我想创建一个自我管理的数据库连接。 应用程序层不应该打开/关闭连接。 它应该只调用存储库方法。 存储库方法也不应该这样做,因为它们可能在事务中运行,并且只有第一个查询应该打开事务,最后一个查询应该关闭事务。

如果只使用SQL逻辑定义新的存储库方法,并且所有的连接都只处理一次,那将是非常棒的。 有什么想法吗?


共1个答案

匿名用户

首先,如果您在MySql连接器上启用连接池,那么您可以跳过closeSync调用,并在每次使用连接时dispose连接,这将允许连接器的池机制根据需要重用连接。 要启用它,请在连接字符串中添加pooling=true

其次,为了避免为存储库创建基类并在其上实现所有连接处理所需的所有额外代码,我将创建一个函数,该函数包含函数和某种类型的静态工厂,以减少代码重写:

//static DB factory
public static class DBFactory
{
    public async Task<DBConnection> GetConnection()
    {
        //Create here your connection
        var newCon = //..
        await newCon.OpenAsync();
        return newCon;
    }
}

//Base class for repositories
public abstract class BaseRepository
{
    protected async Task<T> ExecuteResultWithConnection<T>(Func<DBConnection, Task<T>> RepositoryMethod)
    {
        using(var dbCon = await DBFactory.GetConnection())
        {
            return await RepositoryMethod(dbCon);
        }
    }

    protected async Task ExecuteWithConnection(Func<DBConnection, Task> RepositoryMethod)
    {
        using(var dbCon = await DBFactory.GetConnection())
        {
            await RepositoryMethod(dbCon);
        }
    }


}

//Example of repository
public class TestRepository : BaseRepository
{
    public async Task<IList<TestObject>> GetTestObjects()
    {
        return await ExecuteResultWithConnection(async (dbCon) => {

            //Here you have your connection ready to be used as dbCon

            return yourResult;

        });
    }

    public async Task AddTestObject(TestObject NewObject)
    {
        await ExecuteWithConnection(async (dbCon) => {

            //Here you have your connection ready to be used as dbCon

        });
    }
}

现在,调用存储库是完全干净的:

var repo = new TestRepository();
var objs = await repo.GetTestObjects();
await repo.AddTestObject(new TestObject{ /* whatever */ });