Note: There is a dependency injection bug when upgrading netcore 3.0. You can temporarily use the autofac container, waiting to be fixed.
A dapper extension library.
1.Support MySQL,SQL Server,PostgreSQL,SQLite,Oracle and ODBC.
2.Support cache.
3.Support sql separation.
4.Support reading and writing separation.
5.Support performance monitoring.
The default connection name is 'DefaultConnection'
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=localhost;port=3306;Pooling=true;Initial Catalog=ShopDB;User Id=root;Password=123456;SslMode=none;",
"MySqlConnection": "Data Source=localhost;port=3306;Pooling=true;Initial Catalog=ShopDB;User Id=root;Password=123456;SslMode=none;",
"SQLite1Connection": "data source=db//test1.db",
"SQLite2Connection": "data source=db//test2.db",
"master_slave": {
"Master": "data source=db//test.master.db",
"Slaves": [
{
"ConnectionString": "data source=db//test1.db",
"Weight": 4
},
{
"ConnectionString": "data source=db//test2.db",
"Weight": 6
}
]
}
}
}
If you need to customize the source of the database connection string, you can implement the IConnectionStringProvider interface.
public class CustomConnectionStringProvider : IConnectionStringProvider
{
public string GetConnectionString(string connectionName, bool enableMasterSlave = false, bool readOnly = false)
{
throw new NotImplementedException();
}
}
Register custom provider
services.AddDapperConnectionStringProvider<CustomConnectionStringProvider>();
Note:Dependency injection only supports a single database and the default connection name is 'DefaultConnection'. If you need to use multiple databases, use autofac.
public void ConfigureServices(IServiceCollection services)
{
//For Sqlite
services.AddDapperForSQLite();
//For sql server
services.AddDapperForMSSQL();
//For MySql
services.AddDapperForMySQL();
//For ODBC
services.AddDapperForODBC();
//For Postgre
services.AddDapperForPostgreSQL();
//For Oracle
services.AddDapperForOracle();
}
public class ValuesController : ControllerBase
{
private IDapper Dapper { get;}
public ValuesController(IDapper dapper)
{
Dapper = dapper;
}
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
var result = await Dapper.QueryAsync("select * from tab;");
return Ok(result);
}
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.Populate(services);
builder.AddDapperForMSSQL("MySqlConnection", "msql-conn");
builder.AddDapperForSQLite("SQLite1Connection", "sqlite1-conn").AddDapperForSQLite("SQLite2Connection", "sqlite2-conn");
builder.RegisterAssemblyTypes(Assembly.GetEntryAssembly())
.Where(t => t.Name.EndsWith("Controller"))
.PropertiesAutowired().InstancePerLifetimeScope();
ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
public class ValuesController : ControllerBase
{
private IDapper SQLiteRepo1 { get; }
private IDapper SQLiteRepo2 { get; }
public ValuesController(IResolveContext context)
{
SQLiteRepo1 = context.ResolveDapper("sqlite1-conn");
SQLiteRepo2 = context.ResolveDapper("sqlite2-conn");
}
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
var r1 = await SQLiteRepo1.QueryAsync("select * from COMPANY LIMIT 1 OFFSET 0");
var r2 = await SQLiteRepo2.QueryAsync("select * from COMPANY LIMIT 1 OFFSET 0");
return Ok(new { r1, r2 });
}
}
Note:If you’re using metadata filters (DependencyDapperAttribute or WithAttributeFiltering in your constructors), you need to register those components using the WithAttributeFiltering extension. Note that if you’re only using filters but not attributed metadata, you don’t actually need the AttributedMetadataModule. Metadata filters stand on their own.
public class ValuesController : ControllerBase
{
private IDapper Repo1 { get; }
private IDapper Repo2 { get; }
public ValuesController([DependencyDapper("sqlite1-conn")]IDapper rep1, [DependencyDapper("sqlite2-conn")]IDapper rep2)
{
Repo1 = rep1;
Repo2 = rep2;
}
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
var r1 = await Repo1.QueryAsync("select * from COMPANY;");
var r2 = await Repo3.QueryAsync("select * from COMPANY;");
return Ok(new { r1, r2 });
}
}
The paging method has four SQL variables built in: @Skip, @Take, @TakeStart, @TakeEnd.
select * from tab order by id desc limit @Skip, @Take;
SQL Server 2005
select * from (select ROW_NUMBER() over(order by id desc) as row_num,id,title from tab) tab1 where row_num between @TakeStart and @TakeEnd;
SQL Server 2012
select * from tab offset @Skip rows fetch next @Take rows only;
static void Main(string[] args)
{
//registration
DapperFactory.CreateInstance().ConfigureServices(service =>
{
service.AddDapperForSQLite();
}).ConfigureContainer(container =>
{
container.AddDapperForSQLite("Sqlite2", "sqlite2");
}).ConfigureConfiguration(builder =>
{
builder.SetBasePath(Directory.GetCurrentDirectory());
builder.AddJsonFile("appsettings.json");
}).Build();
//query database
DapperFactory.Step(dapper =>
{
var query = dapper.Query("select * from Contact;");
Console.WriteLine(JsonConvert.SerializeObject(query));
});
}
Like mybatis, but does not support Dynamic SQL. Modify the xml file to take effect immediately, no need to restart the application.
public void ConfigureServices(IServiceCollection services)
{
services.AddSQLSeparationForDapper(Path.Combine(Directory.GetCurrentDirectory(), "sql"));
}
<?xml version="1.0" encoding="utf-8" ?>
<sql-set>
<sql name="COMPANY.list.query"><![CDATA[select * from COMPANY where id=@id;]]></sql>
<paging-sql name="COMPANY.paging">
<count>select count(*) from COMPANY;</count>
<query>select * from COMPANY limit @Skip,@Take;</query>
</paging-sql>
</sql-set>
The name must be globally unique.
var list = await Repo1.QueryAsync<Company>(name: "COMPANY.list.query",new{ id=1 });
var page = await Repo1.QueryPageAsync<Company>(name: "COMPANY.paging", 1,20 );
public void ConfigureServices(IServiceCollection services)
{
services.AddDapperCachingInRedis(new RedisConfiguration
{
AllMethodsEnableCache = false,
ConnectionString = "localhost:6379,password=nihao123#@!"
});
//Redis partition mode
//services.AddDapperCachingInPartitionRedis(new PartitionRedisConfiguration
//{
//AllMethodsEnableCache = false,
//Connections = new[] { "localhost:6379,password=nihao123#@!,defaultDatabase=1", "localhost:6379,password=nihao123#@!,defaultDatabase=2" }
//});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDapperCachingInMemory(new RedisConfiguration
{
AllMethodsEnableCache = false
});
}
It is recommended to use a custom cache key, because the built-in key generator is based on all parameters MD5 hash, which affects performance.
public async Task<IActionResult> Get()
{
int pageindex = 1;
var page = await Repo1.QueryPageAsync<object>("select count(*) from COMPANY;", "select * from COMPANY limit @Take OFFSET @Skip;", pageindex, 20, enableCache: true, cacheKey: $"page:{pageindex}", cacheExpire: TimeSpan.FromSeconds(100));
return Ok(page);
}
If the cache does not exist, it adds a lock when reading data from the database to prevent cache breakdown.If the lock cannot be acquired, a DapperCacheException will be thrown, which needs to be caught in your business code. At this time, you need to consider the issues of Cache Penetration and Cache Breakdown and Cache Avalanche. Dapper.Extensions is just a low-level data access library. Preventing malicious attacks is not its responsibility. You can add Bloom filters before this.
Dapper.Extensions.MiniProfiler just adds support for MiniProfiler. To enable MiniProfiler, you need to configure it yourself. Please check the documentation.
public void ConfigureServices(IServiceCollection services)
{
services.AddMiniProfilerForDapper();
}
services.AddDapperForSQLite(monitorBuilder =>
{
monitorBuilder.Threshold = 200;
monitorBuilder.EnableLog = true;
monitorBuilder.AddMonitorHandler<MyMonitorHandler>();
});
Option | Description |
---|---|
Threshold | Slow SQL command execution time critical value, greater than this value will trigger the monitoring event, default 200, unit millisecond |
EnableLog | Write the log when there is a slow query |
AddMonitorHandler | Add a custom processor |
To use read and write separation, you must use autofac injection.
{
"ConnectionStrings": {
"master_slave": {
"Master": "data source=db//test.master.db",
"Slaves": [
{
"ConnectionString": "data source=db//test1.db",
"Weight": 4
},
{
"ConnectionString": "data source=db//test2.db",
"Weight": 6
}
]
}
}
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.AddDapperForSQLite("master_slave", "master_slave", enableMasterSlave:true);
}
public class ValuesController : ControllerBase
{
private IDapper Writer { get; }
private IDapper Reader { get; }
public ValuesController([DependencyDapper("master_slave")]IDapper writer, [DependencyDapper("master_slave",readOnly:true)]IDapper reader)
{
Writer = writer;
Reader = reader;
}
[HttpGet]
public async Task<IActionResult> Get()
{
await writer.ExecuteAsync("delete * from COMPANY;");
var result = await reader.QueryAsync("select * from COMPANY;");
return Ok(result);
}
}
Similar to the usage of string.format(), the content in {} is determined by the parameters; it supports ‘else’, {sql1: sql2}, if the parameter is true, use sql1, otherwise use sql2.
Example:
var id=1;
var sql="select * from tab where 1=1 {and id=@id} {and status=0} and {r1=1:r2=2} and {t1=1:t2=2};".Splice(id>0,false, true, false);
Output:
select * from tab where 1=1 and id=@id and r1=1 and t2=2;
Extended market installation extension plug-in(SQLSpliceHighlight) highlights
// Initialization
public void ConfigureServices(IServiceCollection services)
{
SnowflakeUtils.Initialization(1, 1);
}
// generate
public IActionResult GenerateId()
{
return Ok(SnowflakeUtils.GenerateId());
}