/* License: http://www.apache.org/licenses/LICENSE-2.0 Home page: http://code.google.com/p/dapper-dot-net/ */ using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data; using Dapper; using System.Collections.Concurrent; using System.Reflection; using System.Text; using System.Data.Common; using System.Diagnostics; using System.Reflection.Emit; namespace Dapper { /// /// A container for a database, assumes all the tables have an Id column named Id /// /// public abstract class Database : IDisposable where TDatabase : Database, new() { public 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 cols_params = string.Join(",", paramNames.Select(p => "@" + p)); var sql = "set nocount on insert " + TableName + " (" + cols + ") values (" + cols_params + ") 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) { if (o is DynamicParameters) { return (o as DynamicParameters).ParameterNames.ToList(); } List paramNames; if (!paramNameCache.TryGetValue(o.GetType(), out paramNames)) { paramNames = new List(); foreach (var prop in o.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public)) { var attribs = prop.GetCustomAttributes(typeof(IgnorePropertyAttribute), true); var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute; if (attr==null || (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) { this.connection = connection; this.commandTimeout = commandTimeout; if (tableConstructor == null) { tableConstructor = CreateTableConstructorForTable(); } tableConstructor(this as TDatabase); } internal virtual Action CreateTableConstructorForTable() { return CreateTableConstructor(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) { var dm = new DynamicMethod("ConstructInstances", null, new Type[] { typeof(TDatabase) }, true); var il = dm.GetILGenerator(); var setters = GetType().GetProperties() .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == tableType) .Select(p => Tuple.Create( p.GetSetMethod(true), p.PropertyType.GetConstructor(new Type[] { 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.Count() == 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: transaction).Count() == 1; } public int Execute(string sql, dynamic param = null) { return SqlMapper.Execute(connection, sql, param as object, transaction, commandTimeout: this.commandTimeout); } public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) { return SqlMapper.Query(connection, 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 SqlMapper.Query(connection, 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 SqlMapper.Query(connection, 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 SqlMapper.Query(connection, 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 SqlMapper.Query(connection, sql, map, param as object, transaction, buffered, splitOn); } public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) { return SqlMapper.Query(connection, sql, param as object, transaction, buffered); } public Dapper.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) { if (transaction != null) { transaction.Rollback(); } connection.Close(); connection = null; } } } }