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 { /// /// A container for a database, assumes all the tables have an Id column named Id /// /// public abstract partial class Database : IDisposable where TDatabase : Database, new() { public partial class Table { internal Database database; internal string tableName; internal string likelyTableName; public Table(Database database, string likelyTableName) { this.database = database; this.likelyTableName = likelyTableName; } public string TableName { get { tableName = tableName ?? database.DetermineTableName(likelyTableName); return tableName; } } /// /// Insert a row into the db /// /// Either DynamicParameters or an anonymous type or concrete type /// public virtual int? Insert(dynamic data) { var o = (object)data; List 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(sql, o).Single(); } /// /// Update a record in the DB /// /// /// /// public int Update(TId id, dynamic data) { List 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); } /// /// Delete a record for the DB /// /// /// public bool Delete(TId id) { return database.Execute("delete from " + TableName + " where Id = @id", new { id }) > 0; } /// /// Grab a record with a particular Id from the DB /// /// /// public T Get(TId id) { return database.Query("select * from " + TableName + " where Id = @id", new { id }).FirstOrDefault(); } public virtual T First() { return database.Query("select top 1 * from " + TableName).FirstOrDefault(); } public IEnumerable All() { return database.Query("select * from " + TableName); } static ConcurrentDictionary> paramNameCache = new ConcurrentDictionary>(); internal static List GetParamNames(object o) { var parameters = o as DynamicParameters; if (parameters != null) { return parameters.ParameterNames.ToList(); } List paramNames; if (!paramNameCache.TryGetValue(o.GetType(), out paramNames)) { paramNames = new List(); 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 : Table { public Table(Database 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 tableConstructor; internal void InitDatabase(DbConnection connection, int commandTimeout) { _connection = connection; _commandTimeout = commandTimeout; if (tableConstructor == null) { tableConstructor = CreateTableConstructorForTable(); } tableConstructor(this as TDatabase); } internal virtual Action 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 CreateTableConstructor(Type tableType) { return CreateTableConstructor(new[] {tableType}); } protected Action 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)dm.CreateDelegate(typeof(Action)); } static ConcurrentDictionary tableNameMap = new ConcurrentDictionary(); private string DetermineTableName(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 Query(string sql, dynamic param = null, bool buffered = true) { return _connection.Query(sql, param as object, _transaction, buffered, _commandTimeout); } public IEnumerable Query(string sql, Func 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 Query(string sql, Func 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 Query(string sql, Func 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 Query(string sql, Func 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 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; } } } }