using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Collections.Concurrent;
using System.Reflection.Emit;
using Dapper;
#if NETSTANDARD1_3
using DataException = System.InvalidOperationException;
#else
using System.Threading;
#endif
namespace Dapper.Contrib.Extensions
{
///
/// The Dapper.Contrib extensions for Dapper
///
public static partial class SqlMapperExtensions
{
///
/// Defined a proxy object with a possibly dirty state.
///
public interface IProxy //must be kept public
{
///
/// Whether the object has been changed.
///
bool IsDirty { get; set; }
}
///
/// Defines a table name mapper for getting table names from types.
///
public interface ITableNameMapper
{
///
/// Gets a table name from a given .
///
/// The to get a name from.
/// The table name for the given .
string GetTableName(Type type);
}
///
/// The function to get a database type from the given .
///
/// The connection to get a database type name from.
public delegate string GetDatabaseTypeDelegate(IDbConnection connection);
///
/// The function to get a a table name from a given
///
/// The to get a table name for.
public delegate string TableNameMapperDelegate(Type type);
private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary();
private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary();
private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter();
private static readonly Dictionary AdapterDictionary
= new Dictionary
{
["sqlconnection"] = new SqlServerAdapter(),
["sqlceconnection"] = new SqlCeServerAdapter(),
["npgsqlconnection"] = new PostgresAdapter(),
["sqliteconnection"] = new SQLiteAdapter(),
["mysqlconnection"] = new MySqlAdapter(),
["fbconnection"] = new FbAdapter()
};
private static List ComputedPropertiesCache(Type type)
{
if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable pi))
{
return pi.ToList();
}
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
ComputedProperties[type.TypeHandle] = computedProperties;
return computedProperties;
}
private static List ExplicitKeyPropertiesCache(Type type)
{
if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi))
{
return pi.ToList();
}
var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList();
ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties;
return explicitKeyProperties;
}
private static List KeyPropertiesCache(Type type)
{
if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi))
{
return pi.ToList();
}
var allProperties = TypePropertiesCache(type);
var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
if (keyProperties.Count == 0)
{
var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase));
if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute))
{
keyProperties.Add(idProp);
}
}
KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties;
}
private static List TypePropertiesCache(Type type)
{
if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable pis))
{
return pis.ToList();
}
var properties = type.GetProperties().Where(IsWriteable).ToArray();
TypeProperties[type.TypeHandle] = properties;
return properties.ToList();
}
private static bool IsWriteable(PropertyInfo pi)
{
var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
if (attributes.Count != 1) return true;
var writeAttribute = (WriteAttribute)attributes[0];
return writeAttribute.Write;
}
private static PropertyInfo GetSingleKey(string method)
{
var type = typeof(T);
var keys = KeyPropertiesCache(type);
var explicitKeys = ExplicitKeyPropertiesCache(type);
var keyCount = keys.Count + explicitKeys.Count;
if (keyCount > 1)
throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property");
if (keyCount == 0)
throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property");
return keys.Count > 0 ? keys[0] : explicitKeys[0];
}
///
/// Returns a single entity by a single id from table "Ts".
/// Id must be marked with [Key] attribute.
/// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
///
/// Interface or type to create and populate
/// Open SqlConnection
/// Id of the entity to get, must be marked with [Key] attribute
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Entity of T
public static T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
if (!GetQueries.TryGetValue(type.TypeHandle, out string sql))
{
var key = GetSingleKey(nameof(Get));
var name = GetTableName(type);
sql = $"select * from {name} where {key.Name} = @id";
GetQueries[type.TypeHandle] = sql;
}
var dynParms = new DynamicParameters();
dynParms.Add("@id", id);
T obj;
if (type.IsInterface())
{
var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary;
if (res == null)
return null;
obj = ProxyGenerator.GetInterfaceProxy();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
}
else
{
obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault();
}
return obj;
}
///
/// Returns a list of entites from table "Ts".
/// Id of T must be marked with [Key] attribute.
/// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
///
/// Interface or type to create and populate
/// Open SqlConnection
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Entity of T
public static IEnumerable GetAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof(List);
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
{
GetSingleKey(nameof(GetAll));
var name = GetTableName(type);
sql = "select * from " + name;
GetQueries[cacheType.TypeHandle] = sql;
}
if (!type.IsInterface()) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout);
var result = connection.Query(sql);
var list = new List();
foreach (IDictionary res in result)
{
var obj = ProxyGenerator.GetInterfaceProxy();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
list.Add(obj);
}
return list;
}
///
/// Specify a custom table name mapper based on the POCO type name
///
public static TableNameMapperDelegate TableNameMapper;
private static string GetTableName(Type type)
{
if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name;
if (TableNameMapper != null)
{
name = TableNameMapper(type);
}
else
{
//NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework
var tableAttr = type
#if NETSTANDARD1_3
.GetTypeInfo()
#endif
.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic;
if (tableAttr != null)
{
name = tableAttr.Name;
}
else
{
name = type.Name + "s";
if (type.IsInterface() && name.StartsWith("I"))
name = name.Substring(1);
}
}
TypeTableName[type.TypeHandle] = name;
return name;
}
///
/// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list.
///
/// The type to insert.
/// Open SqlConnection
/// Entity to insert, can be list of entities
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Identity of inserted entity, or number of inserted rows if inserting a list
public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var isList = false;
var type = typeof(T);
if (type.IsArray)
{
isList = true;
type = type.GetElementType();
}
else if (type.IsGenericType() && type.GetTypeInfo().ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
isList = true;
type = type.GetGenericArguments()[0];
}
var name = GetTableName(type);
var sbColumnList = new StringBuilder(null);
var allProperties = TypePropertiesCache(type);
var keyProperties = KeyPropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbColumnList.Append(", ");
}
var sbParameterList = new StringBuilder(null);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
sbParameterList.AppendFormat("@{0}", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbParameterList.Append(", ");
}
int returnVal;
var wasClosed = connection.State == ConnectionState.Closed;
if (wasClosed) connection.Open();
if (!isList) //single entity
{
returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
else
{
//insert list of entities
var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})";
returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
}
if (wasClosed) connection.Close();
return returnVal;
}
///
/// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
///
/// Type to be updated
/// Open SqlConnection
/// Entity to be updated
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if updated, false if not found or not modified (tracked entities)
public static bool Update(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToUpdate is IProxy proxy && !proxy.IsDirty)
{
return false;
}
var type = typeof(T);
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType())
{
type = type.GetGenericArguments()[0];
}
var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
keyProperties.AddRange(explicitKeyProperties);
var computedProperties = ComputedPropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < nonIdProps.Count; i++)
{
var property = nonIdProps[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < nonIdProps.Count - 1)
sb.AppendFormat(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count; i++)
{
var property = keyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < keyProperties.Count - 1)
sb.AppendFormat(" and ");
}
var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
return updated > 0;
}
///
/// Delete entity in table "Ts".
///
/// Type of entity
/// Open SqlConnection
/// Entity to delete
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if deleted, false if not found
public static bool Delete(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToDelete == null)
throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete));
var type = typeof(T);
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType())
{
type = type.GetGenericArguments()[0];
}
var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
keyProperties.AddRange(explicitKeyProperties);
var sb = new StringBuilder();
sb.AppendFormat("delete from {0} where ", name);
var adapter = GetFormatter(connection);
for (var i = 0; i < keyProperties.Count; i++)
{
var property = keyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < keyProperties.Count - 1)
sb.AppendFormat(" and ");
}
var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout);
return deleted > 0;
}
///
/// Delete all entities in the table related to the type T.
///
/// Type of entity
/// Open SqlConnection
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if deleted, false if none found
public static bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var name = GetTableName(type);
var statement = $"delete from {name}";
var deleted = connection.Execute(statement, null, transaction, commandTimeout);
return deleted > 0;
}
///
/// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object).
/// Please note that this callback is global and will be used by all the calls that require a database specific adapter.
///
public static GetDatabaseTypeDelegate GetDatabaseType;
private static ISqlAdapter GetFormatter(IDbConnection connection)
{
var name = GetDatabaseType?.Invoke(connection).ToLower()
?? connection.GetType().Name.ToLower();
return !AdapterDictionary.ContainsKey(name)
? DefaultAdapter
: AdapterDictionary[name];
}
private static class ProxyGenerator
{
private static readonly Dictionary TypeCache = new Dictionary();
private static AssemblyBuilder GetAsmBuilder(string name)
{
#if NETSTANDARD1_3 || NETSTANDARD2_0
return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run);
#else
return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run);
#endif
}
public static T GetInterfaceProxy()
{
Type typeOfT = typeof(T);
if (TypeCache.TryGetValue(typeOfT, out Type k))
{
return (T)Activator.CreateInstance(k);
}
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(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
}
#if NETSTANDARD1_3 || NETSTANDARD2_0
var generatedType = typeBuilder.CreateTypeInfo().AsType();
#else
var generatedType = typeBuilder.CreateType();
#endif
TypeCache.Add(typeOfT, generatedType);
return (T)Activator.CreateInstance(generatedType);
}
private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
{
var propType = typeof(bool);
var field = typeBuilder.DefineField("_" + nameof(IProxy.IsDirty), propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(nameof(IProxy.IsDirty),
System.Reflection.PropertyAttributes.None,
propType,
new[] { 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_" + nameof(IProxy.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_" + nameof(IProxy.IsDirty),
getSetAttr,
null,
new[] { 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_" + nameof(IProxy.IsDirty));
var setMethod = typeof(IProxy).GetMethod("set_" + nameof(IProxy.IsDirty));
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
return currSetPropMthdBldr;
}
private static void CreateProperty(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[] { 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[] { 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);
}
}
}
///
/// Defines the name of a table to use in Dapper.Contrib commands.
///
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
///
/// Creates a table mapping to a specific name for Dapper.Contrib commands
///
/// The name of this table in the database.
public TableAttribute(string tableName)
{
Name = tableName;
}
///
/// The name of the table in the database
///
public string Name { get; set; }
}
///
/// Specifies that this field is a primary key in the database
///
[AttributeUsage(AttributeTargets.Property)]
public class KeyAttribute : Attribute
{
}
///
/// Specifies that this field is a explicitly set primary key in the database
///
[AttributeUsage(AttributeTargets.Property)]
public class ExplicitKeyAttribute : Attribute
{
}
///
/// Specifies whether a field is writable in the database.
///
[AttributeUsage(AttributeTargets.Property)]
public class WriteAttribute : Attribute
{
///
/// Specifies whether a field is writable in the database.
///
/// Whether a field is writable in the database.
public WriteAttribute(bool write)
{
Write = write;
}
///
/// Whether a field is writable in the database.
///
public bool Write { get; }
}
///
/// Specifies that this is a computed column.
///
[AttributeUsage(AttributeTargets.Property)]
public class ComputedAttribute : Attribute
{
}
}
///
/// The interface for all Dapper.Contrib database operations
/// Implementing this is each provider's model.
///
public partial interface ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert);
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
void AppendColumnName(StringBuilder sb, string columnName);
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
}
///
/// The SQL Server database adapter.
///
public partial class SqlServerAdapter : ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id";
var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
var first = multi.Read().FirstOrDefault();
if (first == null || first.id == null) return 0;
var id = (int)first.id;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length == 0) return id;
var idProperty = propertyInfos[0];
idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
return id;
}
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}]", columnName);
}
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
}
}
///
/// The SQL Server Compact Edition database adapter.
///
public partial class SqlCeServerAdapter : ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ToList();
if (r[0].id == null) return 0;
var id = (int)r[0].id;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length == 0) return id;
var idProperty = propertyInfos[0];
idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
return id;
}
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}]", columnName);
}
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
}
}
///
/// The MySQL database adapter.
///
public partial class MySqlAdapter : ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout);
var id = r.First().id;
if (id == null) return 0;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length == 0) return Convert.ToInt32(id);
var idp = propertyInfos[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return Convert.ToInt32(id);
}
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("`{0}`", columnName);
}
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("`{0}` = @{1}", columnName, columnName);
}
}
///
/// The Postgres database adapter.
///
public partial class PostgresAdapter : ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var sb = new StringBuilder();
sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
// If no primary key then safe to assume a join table with not too much data to return
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length == 0)
{
sb.Append(" RETURNING *");
}
else
{
sb.Append(" RETURNING ");
var first = true;
foreach (var property in propertyInfos)
{
if (!first)
sb.Append(", ");
first = false;
sb.Append(property.Name);
}
}
var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList();
// Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys
var id = 0;
foreach (var p in propertyInfos)
{
var value = ((IDictionary)results[0])[p.Name.ToLower()];
p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\"", columnName);
}
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
}
}
///
/// The SQLite database adapter.
///
public partial class SQLiteAdapter : ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id";
var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
var id = (int)multi.Read().First().id;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length == 0) return id;
var idProperty = propertyInfos[0];
idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
return id;
}
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\"", columnName);
}
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
}
}
///
/// The Firebase SQL adapeter.
///
public partial class FbAdapter : ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
var keyName = propertyInfos[0].Name;
var r = connection.Query($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout);
var id = r.First().ID;
if (id == null) return 0;
if (propertyInfos.Length == 0) return Convert.ToInt32(id);
var idp = propertyInfos[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return Convert.ToInt32(id);
}
///
/// Adds the name of a column.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}", columnName);
}
///
/// Adds a column equality to a parameter.
///
/// The string builder to append to.
/// The column name.
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0} = @{1}", columnName, columnName);
}
}