You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
12 KiB
337 lines
12 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Data;
|
|
using System.Collections.Concurrent;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Data.Common;
|
|
using System.Reflection.Emit;
|
|
|
|
namespace Dapper
|
|
{
|
|
/// <summary>
|
|
/// A container for a database, assumes all the tables have an Id column named Id
|
|
/// </summary>
|
|
/// <typeparam name="TDatabase"></typeparam>
|
|
public abstract partial class Database<TDatabase> : IDisposable where TDatabase : Database<TDatabase>, new()
|
|
{
|
|
public partial class Table<T, TId>
|
|
{
|
|
internal Database<TDatabase> database;
|
|
internal string tableName;
|
|
internal string likelyTableName;
|
|
|
|
public Table(Database<TDatabase> database, string likelyTableName)
|
|
{
|
|
this.database = database;
|
|
this.likelyTableName = likelyTableName;
|
|
}
|
|
|
|
public string TableName
|
|
{
|
|
get
|
|
{
|
|
tableName = tableName ?? database.DetermineTableName<T>(likelyTableName);
|
|
return tableName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Insert a row into the db
|
|
/// </summary>
|
|
/// <param name="data">Either DynamicParameters or an anonymous type or concrete type</param>
|
|
/// <returns></returns>
|
|
public virtual int? Insert(dynamic data)
|
|
{
|
|
var o = (object)data;
|
|
List<string> paramNames = GetParamNames(o);
|
|
paramNames.Remove("Id");
|
|
|
|
string cols = string.Join(",", paramNames);
|
|
string colsParams = string.Join(",", paramNames.Select(p => "@" + p));
|
|
var sql = "set nocount on insert " + TableName + " (" + cols + ") values (" + colsParams + ") select cast(scope_identity() as int)";
|
|
|
|
return database.Query<int?>(sql, o).Single();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update a record in the DB
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="data"></param>
|
|
/// <returns></returns>
|
|
public int Update(TId id, dynamic data)
|
|
{
|
|
List<string> paramNames = GetParamNames((object)data);
|
|
|
|
var builder = new StringBuilder();
|
|
builder.Append("update ").Append(TableName).Append(" set ");
|
|
builder.AppendLine(string.Join(",", paramNames.Where(n => n != "Id").Select(p => p + "= @" + p)));
|
|
builder.Append("where Id = @Id");
|
|
|
|
DynamicParameters parameters = new DynamicParameters(data);
|
|
parameters.Add("Id", id);
|
|
|
|
return database.Execute(builder.ToString(), parameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete a record for the DB
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <returns></returns>
|
|
public bool Delete(TId id)
|
|
{
|
|
return database.Execute("delete from " + TableName + " where Id = @id", new { id }) > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grab a record with a particular Id from the DB
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <returns></returns>
|
|
public T Get(TId id)
|
|
{
|
|
return database.Query<T>("select * from " + TableName + " where Id = @id", new { id }).FirstOrDefault();
|
|
}
|
|
|
|
public virtual T First()
|
|
{
|
|
return database.Query<T>("select top 1 * from " + TableName).FirstOrDefault();
|
|
}
|
|
|
|
public IEnumerable<T> All()
|
|
{
|
|
return database.Query<T>("select * from " + TableName);
|
|
}
|
|
|
|
static ConcurrentDictionary<Type, List<string>> paramNameCache = new ConcurrentDictionary<Type, List<string>>();
|
|
|
|
internal static List<string> GetParamNames(object o)
|
|
{
|
|
var parameters = o as DynamicParameters;
|
|
if (parameters != null)
|
|
{
|
|
return parameters.ParameterNames.ToList();
|
|
}
|
|
|
|
List<string> paramNames;
|
|
if (!paramNameCache.TryGetValue(o.GetType(), out paramNames))
|
|
{
|
|
paramNames = new List<string>();
|
|
foreach (var prop in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetGetMethod(false) != null))
|
|
{
|
|
var attribs = prop.GetCustomAttributes(typeof(IgnorePropertyAttribute), true);
|
|
var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute;
|
|
if (attr==null || (!attr.Value))
|
|
{
|
|
paramNames.Add(prop.Name);
|
|
}
|
|
}
|
|
paramNameCache[o.GetType()] = paramNames;
|
|
}
|
|
return paramNames;
|
|
}
|
|
}
|
|
|
|
public class Table<T> : Table<T, int> {
|
|
public Table(Database<TDatabase> database, string likelyTableName)
|
|
: base(database, likelyTableName)
|
|
{
|
|
}
|
|
}
|
|
|
|
DbConnection _connection;
|
|
int _commandTimeout;
|
|
DbTransaction _transaction;
|
|
|
|
public static TDatabase Init(DbConnection connection, int commandTimeout)
|
|
{
|
|
TDatabase db = new TDatabase();
|
|
db.InitDatabase(connection, commandTimeout);
|
|
return db;
|
|
}
|
|
|
|
internal static Action<TDatabase> tableConstructor;
|
|
|
|
internal void InitDatabase(DbConnection connection, int commandTimeout)
|
|
{
|
|
_connection = connection;
|
|
_commandTimeout = commandTimeout;
|
|
if (tableConstructor == null)
|
|
{
|
|
tableConstructor = CreateTableConstructorForTable();
|
|
}
|
|
|
|
tableConstructor(this as TDatabase);
|
|
}
|
|
|
|
internal virtual Action<TDatabase> CreateTableConstructorForTable()
|
|
{
|
|
return CreateTableConstructor(typeof(Table<>), typeof(Table<,>));
|
|
}
|
|
|
|
public void BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted)
|
|
{
|
|
_transaction = _connection.BeginTransaction(isolation);
|
|
}
|
|
|
|
public void CommitTransaction()
|
|
{
|
|
_transaction.Commit();
|
|
_transaction = null;
|
|
}
|
|
|
|
public void RollbackTransaction()
|
|
{
|
|
_transaction.Rollback();
|
|
_transaction = null;
|
|
}
|
|
|
|
protected Action<TDatabase> CreateTableConstructor(Type tableType)
|
|
{
|
|
return CreateTableConstructor(new[] {tableType});
|
|
}
|
|
|
|
protected Action<TDatabase> CreateTableConstructor(params Type[] tableTypes)
|
|
{
|
|
var dm = new DynamicMethod("ConstructInstances", null, new[] { typeof(TDatabase) }, true);
|
|
var il = dm.GetILGenerator();
|
|
|
|
var setters = GetType().GetProperties()
|
|
.Where(p => p.PropertyType.IsGenericType() && tableTypes.Contains(p.PropertyType.GetGenericTypeDefinition()))
|
|
.Select(p => Tuple.Create(
|
|
p.GetSetMethod(true),
|
|
p.PropertyType.GetConstructor(new[] { typeof(TDatabase), typeof(string) }),
|
|
p.Name,
|
|
p.DeclaringType
|
|
));
|
|
|
|
foreach (var setter in setters)
|
|
{
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
// [db]
|
|
|
|
il.Emit(OpCodes.Ldstr, setter.Item3);
|
|
// [db, likelyname]
|
|
|
|
il.Emit(OpCodes.Newobj, setter.Item2);
|
|
// [table]
|
|
|
|
var table = il.DeclareLocal(setter.Item2.DeclaringType);
|
|
il.Emit(OpCodes.Stloc, table);
|
|
// []
|
|
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
// [db]
|
|
|
|
il.Emit(OpCodes.Castclass, setter.Item4);
|
|
// [db cast to container]
|
|
|
|
il.Emit(OpCodes.Ldloc, table);
|
|
// [db cast to container, table]
|
|
|
|
il.Emit(OpCodes.Callvirt, setter.Item1);
|
|
// []
|
|
}
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
return (Action<TDatabase>)dm.CreateDelegate(typeof(Action<TDatabase>));
|
|
}
|
|
|
|
static ConcurrentDictionary<Type, string> tableNameMap = new ConcurrentDictionary<Type, string>();
|
|
private string DetermineTableName<T>(string likelyTableName)
|
|
{
|
|
string name;
|
|
|
|
if (!tableNameMap.TryGetValue(typeof(T), out name))
|
|
{
|
|
name = likelyTableName;
|
|
if (!TableExists(name))
|
|
{
|
|
name = "[" + typeof(T).Name + "]";
|
|
}
|
|
|
|
tableNameMap[typeof(T)] = name;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
private bool TableExists(string name)
|
|
{
|
|
string schemaName = null;
|
|
|
|
name = name.Replace("[", "");
|
|
name = name.Replace("]", "");
|
|
|
|
if(name.Contains("."))
|
|
{
|
|
var parts = name.Split('.');
|
|
if (parts.Length == 2)
|
|
{
|
|
schemaName = parts[0];
|
|
name = parts[1];
|
|
}
|
|
}
|
|
|
|
var builder = new StringBuilder("select 1 from INFORMATION_SCHEMA.TABLES where ");
|
|
if (!string.IsNullOrEmpty(schemaName)) builder.Append("TABLE_SCHEMA = @schemaName AND ");
|
|
builder.Append("TABLE_NAME = @name");
|
|
|
|
return _connection.Query(builder.ToString(), new { schemaName, name }, _transaction).Count() == 1;
|
|
}
|
|
|
|
public int Execute(string sql, dynamic param = null)
|
|
{
|
|
return _connection.Execute(sql, param as object, _transaction, _commandTimeout);
|
|
}
|
|
|
|
public IEnumerable<T> Query<T>(string sql, dynamic param = null, bool buffered = true)
|
|
{
|
|
return _connection.Query<T>(sql, param as object, _transaction, buffered, _commandTimeout);
|
|
}
|
|
|
|
public IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
|
|
{
|
|
return _connection.Query(sql, map, param as object, transaction, buffered, splitOn);
|
|
}
|
|
|
|
public IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
|
|
{
|
|
return _connection.Query(sql, map, param as object, transaction, buffered, splitOn);
|
|
}
|
|
|
|
public IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
|
|
{
|
|
return _connection.Query(sql, map, param as object, transaction, buffered, splitOn);
|
|
}
|
|
|
|
public IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null)
|
|
{
|
|
return _connection.Query(sql, map, param as object, transaction, buffered, splitOn);
|
|
}
|
|
|
|
public IEnumerable<dynamic> Query(string sql, dynamic param = null, bool buffered = true)
|
|
{
|
|
return _connection.Query(sql, param as object, _transaction, buffered);
|
|
}
|
|
|
|
public SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
|
|
{
|
|
return SqlMapper.QueryMultiple(_connection, sql, param, transaction, commandTimeout, commandType);
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_connection.State != ConnectionState.Closed)
|
|
{
|
|
_transaction?.Rollback();
|
|
|
|
_connection.Close();
|
|
_connection = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|