Files
Nick Craver ae7a29a9f1 VS 2017 .csproj Migration
Due to the way VS test works (by injecting an executable entry point), the performance tests needed to be factored out. I also organized all of our existing tests better along the way.
Actual code changes to Dapper itself are very minor, only formatting and documentation fixes (which we need many more of).

The build.ps1 script is upated to work, but note that <frameworkAssemblies> is not working in .nuspec inside the packages in the new .csproj system. I consider this to be a blocker. Issue is here: https://github.com/NuGet/Home/issues/4853
2017-03-25 19:34:10 -04:00

338 lines
13 KiB
C#

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);
}
private 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)
{
}
}
private DbConnection _connection;
private int _commandTimeout;
private 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>));
}
private 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;
}
}
}
}