Commit aa511707 authored by Sam Saffron's avatar Sam Saffron

dapper contrib refactor ... pull it all into one file for nuget goodness

parent fcd385fd
...@@ -49,10 +49,8 @@ ...@@ -49,10 +49,8 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Extensions\ProxyGenerator.cs" />
<Compile Include="Extensions\SqlMapperExtensions.cs" /> <Compile Include="Extensions\SqlMapperExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Extensions\TypeExtension.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Dapper\Dapper.csproj"> <ProjectReference Include="..\Dapper\Dapper.csproj">
......
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
namespace Dapper.Contrib.Extensions
{
//TODO: Try hiding this interface
public interface IProxy
{
bool IsDirty { get; set; }
}
class ProxyGenerator
{
private static readonly Dictionary<Type, object> TypeCache = new Dictionary<Type, object>();
private static AssemblyBuilder GetAsmBuilder(string name)
{
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name },
AssemblyBuilderAccess.Run); //NOTE: to save, use RunAndSave
return assemblyBuilder;
}
public static T GetClassProxy<T>()
{
// A class proxy could be implemented if all properties are virtual
// otherwise there is a pretty dangerous case where internal actions will not update dirty tracking
throw new NotImplementedException();
}
public static T GetInterfaceProxy<T>()
{
Type typeOfT = typeof(T);
if (TypeCache.ContainsKey(typeOfT))
{
return (T)TypeCache[typeOfT];
}
var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
var interfaceType = typeof(IProxy);
var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.AddInterfaceImplementation(typeOfT);
typeBuilder.AddInterfaceImplementation(interfaceType);
//create our _isDirty field, which implements IProxy
var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder);
// Generate a field for each property, which implements the T
foreach (var property in typeof(T).GetProperties())
{
var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute);
CreateProperty<T>(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
}
var generatedType = typeBuilder.CreateType();
//assemblyBuilder.Save(name + ".dll"); //NOTE: to save, uncomment
var generatedObject = Activator.CreateInstance(generatedType);
TypeCache.Add(typeOfT, generatedObject);
return (T)generatedObject;
}
private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
{
var propType = typeof(bool);
var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty("IsDirty",
PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName |
MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty",
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty",
getSetAttr,
null,
new Type[] { propType });
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ret);
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(IProxy).GetMethod("get_" + "IsDirty");
var setMethod = typeof(IProxy).GetMethod("set_" + "IsDirty");
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
return currSetPropMthdBldr;
}
private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
{
//Define the field and the property
var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(propertyName,
PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual |
MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
getSetAttr,
null,
new Type[] { propType });
//store value in private field and set the isdirty flag
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldc_I4_1);
currSetIL.Emit(OpCodes.Call, setIsDirtyMethod);
currSetIL.Emit(OpCodes.Ret);
//TODO: Should copy all attributes defined by the interface?
if (isIdentity)
{
var keyAttribute = typeof(KeyAttribute);
var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { });
var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { });
property.SetCustomAttribute(attributeBuilder);
}
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(T).GetMethod("get_" + propertyName);
var setMethod = typeof(T).GetMethod("set_" + propertyName);
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
}
}
}
...@@ -7,12 +7,20 @@ ...@@ -7,12 +7,20 @@
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection.Emit;
using System.Threading;
using System.Runtime.CompilerServices;
namespace Dapper.Contrib.Extensions namespace Dapper.Contrib.Extensions
{ {
public static class SqlMapperExtensions public static class SqlMapperExtensions
{ {
public interface IProxy
{
bool IsDirty { get; set; }
}
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
...@@ -40,7 +48,6 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type) ...@@ -40,7 +48,6 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
KeyProperties[type.TypeHandle] = keyProperties; KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties; return keyProperties;
} }
private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type) private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
{ {
if (TypeProperties.ContainsKey(type.TypeHandle)) if (TypeProperties.ContainsKey(type.TypeHandle))
...@@ -62,7 +69,7 @@ private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type) ...@@ -62,7 +69,7 @@ private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
/// <param name="connection">Open SqlConnection</param> /// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param> /// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <returns>Entity of T</returns> /// <returns>Entity of T</returns>
public static T Get<T>(this IDbConnection connection, object id) where T : class public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
var type = typeof(T); var type = typeof(T);
string sql; string sql;
...@@ -108,7 +115,7 @@ private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type) ...@@ -108,7 +115,7 @@ private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
} }
else else
{ {
obj = connection.Query<T>(sql, dynParms).FirstOrDefault(); obj = connection.Query<T>(sql, dynParms, transaction: transaction, commandTimeout: commandTimeout).FirstOrDefault();
} }
return obj; return obj;
} }
...@@ -138,7 +145,7 @@ private static string GetTableName(Type type) ...@@ -138,7 +145,7 @@ private static string GetTableName(Type type)
/// <param name="connection">Open SqlConnection</param> /// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert</param> /// <param name="entityToInsert">Entity to insert</param>
/// <returns>Identity of inserted entity</returns> /// <returns>Identity of inserted entity</returns>
public static long Insert<T>(this IDbConnection connection, T entityToInsert) where T : class public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
using (var tx = connection.BeginTransaction()) using (var tx = connection.BeginTransaction())
{ {
...@@ -172,7 +179,7 @@ private static string GetTableName(Type type) ...@@ -172,7 +179,7 @@ private static string GetTableName(Type type)
sb.Append(", "); sb.Append(", ");
} }
sb.Append(") "); sb.Append(") ");
connection.Execute(sb.ToString(), entityToInsert); connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout);
//NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE
var r = connection.Query("select @@IDENTITY id"); var r = connection.Query("select @@IDENTITY id");
tx.Commit(); tx.Commit();
...@@ -187,7 +194,7 @@ private static string GetTableName(Type type) ...@@ -187,7 +194,7 @@ private static string GetTableName(Type type)
/// <param name="connection">Open SqlConnection</param> /// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param> /// <param name="entityToUpdate">Entity to be updated</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns> /// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static bool Update<T>(this IDbConnection connection, T entityToUpdate) where T : class public static bool Update<T>(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
var proxy = entityToUpdate as IProxy; var proxy = entityToUpdate as IProxy;
if (proxy != null) if (proxy != null)
...@@ -224,7 +231,7 @@ private static string GetTableName(Type type) ...@@ -224,7 +231,7 @@ private static string GetTableName(Type type)
if (i < keyProperties.Count() - 1) if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and "); sb.AppendFormat(" and ");
} }
var updated = connection.Execute(sb.ToString(), entityToUpdate); var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
return updated > 0; return updated > 0;
} }
...@@ -235,7 +242,7 @@ private static string GetTableName(Type type) ...@@ -235,7 +242,7 @@ private static string GetTableName(Type type)
/// <param name="connection">Open SqlConnection</param> /// <param name="connection">Open SqlConnection</param>
/// <param name="entityToDelete">Entity to delete</param> /// <param name="entityToDelete">Entity to delete</param>
/// <returns>true if deleted, false if not found</returns> /// <returns>true if deleted, false if not found</returns>
public static bool Delete<T>(this IDbConnection connection, T entityToDelete) where T : class public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
var type = typeof(T); var type = typeof(T);
...@@ -255,9 +262,167 @@ private static string GetTableName(Type type) ...@@ -255,9 +262,167 @@ private static string GetTableName(Type type)
if (i < keyProperties.Count() - 1) if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and "); sb.AppendFormat(" and ");
} }
var deleted = connection.Execute(sb.ToString(), entityToDelete); var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction: transaction, commandTimeout: commandTimeout);
return deleted > 0; return deleted > 0;
} }
class ProxyGenerator
{
private static readonly Dictionary<Type, object> TypeCache = new Dictionary<Type, object>();
private static AssemblyBuilder GetAsmBuilder(string name)
{
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name },
AssemblyBuilderAccess.Run); //NOTE: to save, use RunAndSave
return assemblyBuilder;
}
public static T GetClassProxy<T>()
{
// A class proxy could be implemented if all properties are virtual
// otherwise there is a pretty dangerous case where internal actions will not update dirty tracking
throw new NotImplementedException();
}
public static T GetInterfaceProxy<T>()
{
Type typeOfT = typeof(T);
if (TypeCache.ContainsKey(typeOfT))
{
return (T)TypeCache[typeOfT];
}
var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
var interfaceType = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy);
var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.AddInterfaceImplementation(typeOfT);
typeBuilder.AddInterfaceImplementation(interfaceType);
//create our _isDirty field, which implements IProxy
var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder);
// Generate a field for each property, which implements the T
foreach (var property in typeof(T).GetProperties())
{
var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute);
CreateProperty<T>(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
}
var generatedType = typeBuilder.CreateType();
//assemblyBuilder.Save(name + ".dll"); //NOTE: to save, uncomment
var generatedObject = Activator.CreateInstance(generatedType);
TypeCache.Add(typeOfT, generatedObject);
return (T)generatedObject;
}
private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
{
var propType = typeof(bool);
var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty("IsDirty",
System.Reflection.PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName |
MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty",
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty",
getSetAttr,
null,
new Type[] { propType });
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ret);
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("get_" + "IsDirty");
var setMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("set_" + "IsDirty");
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
return currSetPropMthdBldr;
}
private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
{
//Define the field and the property
var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(propertyName,
System.Reflection.PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual |
MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
getSetAttr,
null,
new Type[] { propType });
//store value in private field and set the isdirty flag
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldc_I4_1);
currSetIL.Emit(OpCodes.Call, setIsDirtyMethod);
currSetIL.Emit(OpCodes.Ret);
//TODO: Should copy all attributes defined by the interface?
if (isIdentity)
{
var keyAttribute = typeof(KeyAttribute);
var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { });
var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { });
property.SetCustomAttribute(attributeBuilder);
}
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(T).GetMethod("get_" + propertyName);
var setMethod = typeof(T).GetMethod("set_" + propertyName);
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
}
}
} }
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
......
using System;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Dapper.Contrib.Extensions
{
public static class TypeExtension
{
public static Boolean IsAnonymousType(this Type type)
{
if (type == null) return false;
var hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
var nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
var isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
return isAnonymousType;
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment