自定义 C# Dapper ORM 包装器

Custom C# Dapper ORM wrapper

我正在开发 asp.net 网络 API REST 服务。我的数据存储在 MySQL 关系数据库中。在数据访问层,我想使用 Dapper 微型 ORM,所以我想创建某种我自己的 ORM 包装器方法。如果我将来决定更改为其他一些 ORM,我将不需要重写我的整个 DAL 层代码。

你觉得我的方法怎么样?这是代码:

public abstract class BaseORMCommandSettings //SQL command base class
{
    public string CommandText { get; private set; }
    public object Parameters { get; private set; }
    public IDbTransaction Transaction { get; private set; }
    public int? CommandTimeout { get; private set; }
    public CommandType? CommandType { get; private set; }
    public CancellationToken CancellationToken { get; private set; }

    public BaseORMCommandSettings(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null,
                             CommandType? commandType = null, CancellationToken cancellationToken = default(CancellationToken))
    {
        this.CommandText = commandText;
        this.Parameters = parameters;
        this.Transaction = transaction;
        this.CommandTimeout = commandTimeout;
        this.CommandType = commandType;
        this.CancellationToken = cancellationToken;
    }
}

public class DapperCommandSettings : BaseORMCommandSettings//dapper cmd impl
{
    public CommandFlags Flags { get; private set; }

    public DapperCommandSettings(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null,
                             CommandType? commandType = null, CancellationToken cancellationToken = default(CancellationToken), CommandFlags flags = CommandFlags.Buffered)
         :base(commandText, parameters, transaction, commandTimeout, commandType, cancellationToken)
    {
        this.Flags = flags;
    }
}

public interface ICustomORM //base interface, for now have only generic Read 
                            list method
{
    IEnumerable<T> Read<T>(BaseORMCommandSettings cmd);
}

public class DapperORM : ICustomORM //my own dapper ORM wrapper implentation
{
    private readonly IDbConnection con;

    public DapperORM(IDbConnection con)
    {
        this.con = con;
    }

    public IEnumerable<T> Read<T>(BaseORMCommandSettings cmd)
    {
        var cmdDapper = cmd as DapperCommandSettings;
        var dapperCmd = new CommandDefinition(cmdDapper.CommandText, cmdDapper.Parameters, cmdDapper.Transaction,
                                              cmdDapper.CommandTimeout, cmdDapper.CommandType, cmdDapper.Flags, 
                                              cmdDapper.CancellationToken);

        return con.Query<T>(dapperCmd);
    }
}

在此先感谢您的任何帮助。

是的。请不要这样做。 Dapper 存在,并享有它所取得的成功,因为它提供了一种简洁、富有表现力的执行 ADO 的方式。它不是 ORM。如果你包装小巧玲珑,你就失去了简洁的表达界面,你就失去了重点。 ORM(dapper 不是)的存在部分是为了提供数据库的可移植性。开始谈论 ORM 可移植性会让人们绝望地用头撞墙!就用Dapper,佩服一下

我建议您进行 OO 设计并创建一个只有一个方法的对象,该方法 returns 一些对象类型的序列。通过这种方式,您可以创建一个 PAge 对象并通过构造函数传递参数,这样您就可以创建不同类型的页面:SqlPages、DapperPages、TestablePages 等。这样你就有了一种灵活的工作方式,你可以设计你的代码并在最后留下 infraestructure/database 细节。我会将数据库的细节封装在对象中,不要让细节在你的代码中传播:

/// <summary>
/// DTO
/// </summary>
public class MyDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}

/// <summary>
/// Define a contract that get a sequence of something
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IFetch<T>
{
    IEnumerable<T> Fetch();
}

/// <summary>
/// Define a pageTemplate
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class PageTemplate<T> : IFetch<T>
{
    protected readonly int pageSize;
    protected readonly int page;

    public PageTemplate(int page, int pageSize)
    {
        this.page = page;
        this.pageSize = pageSize;
    }
    public abstract IEnumerable<T> Fetch();
}

/// <summary>
/// Design a MyDto Page object, Here you are using the Template method
/// </summary>
public abstract class MyDtoPageTemplate : PageTemplate<MyDto>
{
    public MyDtoPageTemplate(int page, int pageSize) : base(page, pageSize) { }
}

/// <summary>
/// You can use ado.net for full performance or create a derivated class of MyDtoPageTemplate to use Dapper
/// </summary>
public sealed class SqlPage : MyDtoPageTemplate
{
    private readonly string _connectionString;
    public SqlPage(int page, int pageSize, string connectionString) : base(page, pageSize)
    {
        _connectionString = connectionString;
    }

    public override IEnumerable<MyDto> Fetch()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            //This can be injected from contructor or encapsulated here, use a Stored procedure, is fine
            string commandText = "Select Something";
            using (var command = new SqlCommand(commandText, connection))
            {
                connection.Open();
                using (var reader = command.ExecuteReader())
                {
                    if (reader.HasRows) yield break;
                    while (reader.Read())
                    {
                        yield return new MyDto()
                        {
                            Id = reader.GetInt32(0),
                            Name = reader.GetString(1),
                            Value = reader.GetString(2)
                        };
                    }
                }
            }
        }
    }
}

/// <summary>
/// You can test and mock the fetcher
/// </summary>
public sealed class TestPage : IFetch<MyDto>
{
    public IEnumerable<MyDto> Fetch()
    {
        yield return new MyDto() { Id = 0, Name = string.Empty, Value = string.Empty };
        yield return new MyDto() { Id = 1, Name = string.Empty, Value = string.Empty };
    }
}

public class AppCode
{
    private readonly IFetch<MyDto> fetcher;
    /// <summary>
    /// From IoC, inject a fetcher object
    /// </summary>
    /// <param name="fetcher"></param>
    public AppCode(IFetch<MyDto> fetcher)
    {
        this.fetcher = fetcher;
    }
    public IEnumerable<MyDto> FetchDtos()
    {
        return fetcher.Fetch();
    }
}

public class CustomController
{
    private readonly string connectionString;

    public void RunSql()
    {
        var fetcher = new SqlPage(1, 10, connectionString);
        var appCode = new AppCode(fetcher);
        var dtos = appCode.FetchDtos();
    }

    public void RunTest()
    {
        var fetcher = new TestPage();
        var appCode = new AppCode(fetcher);
        var dtos = appCode.FetchDtos();
    }
}

包装 Dapper 有一些好处:

  • 您不必在每个数据库访问中为每个 SqlConnection 编写“using”语句
  • 当您想使用不同的 ORM 时,只需替换此包装器中的方法就足够了

枚举 IDataAccess

using System.Collections.Generic;
using System.Threading.Tasks;

namespace Server.Database
{
    public interface IDataAccess
    {
        Task<List<T>> Query<T>(string sql, object parameters = null);
        public Task Execute<T>(string sql, T parameters);
    }
}

围绕 Dapper 的 DataAccess 包装体

public class DataAccess : IDataAccess
{
    private readonly string _connectionString;

    public DataAccess(string connectionString)
    {
        _connectionString = connectionString;
    }

    //if needed extend parameters to those that Dapper allows. Like: transaction, commandTimeout, commandType...
    public async Task<List<T>> Query<T>(string sql, object parameters = null)
    {
        using (SqlConnection connection = new SqlConnection(_connectionString))
        {
            var rows = await connection.QueryAsync<T>(sql, parameters);
            return rows.ToList();
        }
    }

    public Task Execute<T>(string sql, T parameters)
    {
        using (SqlConnection connection = new SqlConnection(_connectionString))
        {
            return connection.ExecuteAsync(sql, parameters);
        }
    }
}

我在我的函数中这样调用这些包装器:

namespace Server.Database.App
{
    public class Changelog
    {
        private readonly IDataAccess _dataAccess;

        public Changelog(IDataAccess dataAccess)
        {
            _dataAccess = dataAccess;
        }

        public List<ChangelogRecord> GetAllRecords()
        {
            var sql = "SELECT * FROM Changelog ORDER BY id DESC";
            return _dataAccess.Query<ChangelogRecord>(sql).Result;
        }
    }
}

在 Controller 中我调用了这些函数:

namespace Server.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ChangelogController : ControllerBase
    {
        private readonly Changelog _changelog;

        public ChangelogController(IDataAccess dataAccess)
        {
            _changelog = new Changelog(dataAccess);
        }

        [HttpGet]
        [Route("RecordCount")]
        public int GetNumberOfRecords()
        {
            return _changelog.GetNumberOfRecords();
        }
    }
}

为了link数据访问你必须添加到

server startup->ConfigureServices:

services.AddSingleton<IDataAccess>(new DataAccess(Configuration.GetConnectionString("yourConnectionStringName")));

参考:https://www.youtube.com/watch?v=_JxC6EUxbDo