自定义 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")));
我正在开发 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")));