diff --git a/global.json b/global.json index 4df03fcd..24864722 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ -{ - "projects": [ "src" ], - "sdk": { - "version": "1.0.0-beta6" - } -} +{ + "projects": [ "src" ], + "sdk": { + "version": "1.0.0-rc1-update1" + } +} diff --git a/src/Dommel-net40/DommelMapper.cs b/src/Dommel/DommelMapper.cs similarity index 97% rename from src/Dommel-net40/DommelMapper.cs rename to src/Dommel/DommelMapper.cs index 4b759161..6a7eb80e 100644 --- a/src/Dommel-net40/DommelMapper.cs +++ b/src/Dommel/DommelMapper.cs @@ -1,1516 +1,1516 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Data; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using Dapper; - -#if DNXCORE50 -using IDbTransaction = System.Data.Common.DbTransaction; -using IDbConnection = System.Data.Common.DbConnection; -#endif - -namespace Dommel -{ - /// - /// Simple CRUD operations for Dapper. - /// - public static class DommelMapper - { - private static readonly IDictionary _sqlBuilders = new Dictionary - { - { "sqlconnection", new SqlServerSqlBuilder() }, - { "sqlceconnection", new SqlServerCeSqlBuilder() }, - { "sqliteconnection", new SqliteSqlBuilder() }, - { "npgsqlconnection", new PostgresSqlBuilder() }, - { "mysqlconnection", new MySqlSqlBuilder() } - }; - - private static readonly IDictionary _getQueryCache = new Dictionary(); - private static readonly IDictionary _getAllQueryCache = new Dictionary(); - private static readonly IDictionary _insertQueryCache = new Dictionary(); - private static readonly IDictionary _updateQueryCache = new Dictionary(); - private static readonly IDictionary _deleteQueryCache = new Dictionary(); - - /// - /// Retrieves the entity of type with the specified id. - /// - /// The type of the entity. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The entity with the corresponding id. - public static TEntity Get(this IDbConnection connection, object id) where TEntity : class - { - var type = typeof (TEntity); - - string sql; - if (!_getQueryCache.TryGetValue(type, out sql)) - { - var tableName = Resolvers.Table(type); - var keyProperty = Resolvers.KeyProperty(type); - var keyColumnName = Resolvers.Column(keyProperty); - - sql = string.Format("select * from {0} where {1} = @Id", tableName, keyColumnName); - _getQueryCache[type] = sql; - } - - var parameters = new DynamicParameters(); - parameters.Add("Id", id); - - return connection.Query(sql: sql, param: parameters).FirstOrDefault(); - } - - /// - /// Retrieves the entity of type with the specified id - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The mapping to perform on the entities in the result set. - /// The entity with the corresponding id joined with the specified types. - public static TReturn Get(this IDbConnection connection, object id, Func map) where TReturn : class - { - return MultiMap(connection, map, id).FirstOrDefault(); - } - - /// - /// Retrieves the entity of type with the specified id - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The mapping to perform on the entities in the result set. - /// The entity with the corresponding id joined with the specified types. - public static TReturn Get(this IDbConnection connection, - object id, - Func map) where TReturn : class - { - return MultiMap(connection, map, id).FirstOrDefault(); - } - - /// - /// Retrieves the entity of type with the specified id - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The mapping to perform on the entities in the result set. - /// The entity with the corresponding id joined with the specified types. - public static TReturn Get(this IDbConnection connection, - object id, - Func map) where TReturn : class - { - return MultiMap(connection, map, id).FirstOrDefault(); - } - - /// - /// Retrieves the entity of type with the specified id - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The fifth type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The mapping to perform on the entities in the result set. - /// The entity with the corresponding id joined with the specified types. - public static TReturn Get(this IDbConnection connection, - object id, - Func map) where TReturn : class - { - return MultiMap(connection, map, id).FirstOrDefault(); - } - - /// - /// Retrieves the entity of type with the specified id - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The fifth type parameter. - /// The sixth type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The mapping to perform on the entities in the result set. - /// The entity with the corresponding id joined with the specified types. - public static TReturn Get(this IDbConnection connection, - object id, - Func map) where TReturn : class - { - return MultiMap(connection, map, id).FirstOrDefault(); - } - - /// - /// Retrieves the entity of type with the specified id - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The fifth type parameter. - /// The sixth type parameter. - /// The seventh type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The id of the entity in the database. - /// The mapping to perform on the entities in the result set. - /// The entity with the corresponding id joined with the specified types. - public static TReturn Get(this IDbConnection connection, - object id, - Func map) where TReturn : class - { - return MultiMap(connection, map, id).FirstOrDefault(); - } - - /// - /// Retrieves all the entities of type . - /// - /// The type of the entity. - /// The connection to the database. This can either be open or closed. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// A collection of entities of type . - public static IEnumerable GetAll(this IDbConnection connection, bool buffered = true) where TEntity : class - { - var type = typeof (TEntity); - - string sql; - if (!_getAllQueryCache.TryGetValue(type, out sql)) - { - var tableName = Resolvers.Table(type); - sql = string.Format("select * from {0}", tableName); - _getAllQueryCache[type] = sql; - } - - return connection.Query(sql: sql, buffered: buffered); - } - - /// - /// Retrieves all the entities of type - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The mapping to perform on the entities in the result set. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// - /// A collection of entities of type - /// joined with the specified type types. - /// - public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) - { - return MultiMap(connection, map, buffered: buffered); - } - - /// - /// Retrieves all the entities of type - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The mapping to perform on the entities in the result set. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// - /// A collection of entities of type - /// joined with the specified type types. - /// - public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) - { - return MultiMap(connection, map, buffered: buffered); - } - - /// - /// Retrieves all the entities of type - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The mapping to perform on the entities in the result set. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// - /// A collection of entities of type - /// joined with the specified type types. - /// - public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) - { - return MultiMap(connection, map, buffered: buffered); - } - - /// - /// Retrieves all the entities of type - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The fifth type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The mapping to perform on the entities in the result set. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// - /// A collection of entities of type - /// joined with the specified type types. - /// - public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) - { - return MultiMap(connection, map, buffered: buffered); - } - - /// - /// Retrieves all the entities of type - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The fifth type parameter. - /// The sixth type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The mapping to perform on the entities in the result set. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// - /// A collection of entities of type - /// joined with the specified type types. - /// - public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) - { - return MultiMap(connection, map, buffered: buffered); - } - - /// - /// Retrieves all the entities of type - /// joined with the types specified as type parameters. - /// - /// The first type parameter. This is the source entity. - /// The second type parameter. - /// The third type parameter. - /// The fourth type parameter. - /// The fifth type parameter. - /// The sixth type parameter. - /// The seventh type parameter. - /// The return type parameter. - /// The connection to the database. This can either be open or closed. - /// The mapping to perform on the entities in the result set. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// - /// A collection of entities of type - /// joined with the specified type types. - /// - public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) - { - return MultiMap(connection, map, buffered: buffered); - } - - private static IEnumerable MultiMap(IDbConnection connection, Delegate map, object id = null, bool buffered = true) - { - var resultType = typeof(TReturn); - var resultTableName = Resolvers.Table(resultType); - var resultTableKeyColumnName = Resolvers.Column(Resolvers.KeyProperty(resultType)); - - var sql = string.Format("select * from {0}", resultTableName); - - var includeTypes = new[] - { - typeof (T1), - typeof (T2), - typeof (T3), - typeof (T4), - typeof (T5), - typeof (T6), - typeof (T7) - } - .Where(t => t != typeof (DontMap)) - .ToArray(); - - for (var i = 1; i < includeTypes.Length; i++) - { - // Determine the table to join with. - var sourceType = includeTypes[i - 1]; - var sourceTableName = Resolvers.Table(sourceType); - - // Determine the table name of the joined table. - var includeType = includeTypes[i]; - var foreignKeyTableName = Resolvers.Table(includeType); - - // Determine the foreign key and the relationship type. - ForeignKeyRelation relation; - var foreignKeyProperty = Resolvers.ForeignKeyProperty(sourceType, includeType, out relation); - var foreignKeyPropertyName = Resolvers.Column(foreignKeyProperty); - - // If the foreign key property is nullable, use a left-join. - var joinType = Nullable.GetUnderlyingType(foreignKeyProperty.PropertyType) != null - ? "left" - : "inner"; - - switch (relation) - { - case ForeignKeyRelation.OneToOne: - // Determine the primary key of the foreign key table. - var foreignKeyTableKeyColumName = Resolvers.Column(Resolvers.KeyProperty(includeType)); - - sql += string.Format(" {0} join {1} on {2}.{3} = {1}.{4}", - joinType, - foreignKeyTableName, - sourceTableName, - foreignKeyPropertyName, - foreignKeyTableKeyColumName); - break; - - case ForeignKeyRelation.OneToMany: - // Determine the primary key of the source table. - var sourceKeyColumnName = Resolvers.Column(Resolvers.KeyProperty(sourceType)); - - sql += string.Format(" {0} join {1} on {2}.{3} = {1}.{4}", - joinType, - foreignKeyTableName, - sourceTableName, - sourceKeyColumnName, - foreignKeyPropertyName); - break; - - case ForeignKeyRelation.ManyToMany: - throw new NotImplementedException("Many-to-many relationships are not supported yet."); - - default: - throw new NotImplementedException(string.Format("Foreign key relation type '{0}' is not implemented.", relation)); - } - } - - DynamicParameters parameters = null; - if (id != null) - { - sql += string.Format(" where {0}.{1} = @{1}", resultTableName, resultTableKeyColumnName); - - parameters = new DynamicParameters(); - parameters.Add("Id", id); - } - - switch (includeTypes.Length) - { - case 2: - return connection.Query(sql, (Func)map, parameters, buffered: buffered); - case 3: - return connection.Query(sql, (Func)map, parameters, buffered: buffered); - case 4: - return connection.Query(sql, (Func)map, parameters, buffered: buffered); - case 5: - return connection.Query(sql, (Func)map, parameters, buffered: buffered); - case 6: - return connection.Query(sql, (Func)map, parameters, buffered: buffered); - case 7: - return connection.Query(sql, (Func)map, parameters, buffered: buffered); - } - - throw new InvalidOperationException(); - } - - private class DontMap - { - } - - /// - /// Selects all the entities matching the specified predicate. - /// - /// The type of the entity. - /// The connection to the database. This can either be open or closed. - /// A predicate to filter the results. - /// - /// A value indicating whether the result of the query should be executed directly, - /// or when the query is materialized (using ToList() for example). - /// - /// A collection of entities of type matching the specified . - public static IEnumerable Select(this IDbConnection connection, Expression> predicate, bool buffered = true) - { - var type = typeof (TEntity); - - string sql; - if (!_getAllQueryCache.TryGetValue(type, out sql)) - { - var tableName = Resolvers.Table(type); - sql = string.Format("select * from {0}", tableName); - _getAllQueryCache[type] = sql; - } - - DynamicParameters parameters; - sql += new SqlExpression() - .Where(predicate) - .ToSql(out parameters); - - return connection.Query(sql: sql, param: parameters, buffered: buffered); - } - - /// - /// Represents a typed SQL expression. - /// - /// The type of the entity. - public class SqlExpression - { - private readonly StringBuilder _whereBuilder = new StringBuilder(); - private readonly DynamicParameters _parameters = new DynamicParameters(); - private int _parameterIndex; - - /// - /// Builds a SQL expression for the specified filter expression. - /// - /// The filter expression on the entity. - /// The current instance. - public virtual SqlExpression Where(Expression> expression) - { - AppendToWhere("and", expression); - return this; - } - - private void AppendToWhere(string conditionOperator, Expression expression) - { - var sqlExpression = VisitExpression(expression).ToString(); - AppendToWhere(conditionOperator, sqlExpression); - } - - private void AppendToWhere(string conditionOperator, string sqlExpression) - { - if (_whereBuilder.Length == 0) - { - _whereBuilder.Append(" where "); - } - else - { - _whereBuilder.AppendFormat(" {0} ", conditionOperator); - } - - _whereBuilder.Append(sqlExpression); - } - - /// - /// Visits the expression. - /// - /// The expression to visit. - /// The result of the visit. - protected virtual object VisitExpression(Expression expression) - { - switch (expression.NodeType) - { - case ExpressionType.Lambda: - return VisitLambda(expression as LambdaExpression); - - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - return VisitBinary((BinaryExpression)expression); - - case ExpressionType.Convert: - case ExpressionType.Not: - return VisitUnary((UnaryExpression)expression); - - case ExpressionType.New: - return VisitNew((NewExpression)expression); - - case ExpressionType.MemberAccess: - return VisitMemberAccess((MemberExpression)expression); - - case ExpressionType.Constant: - return VisitConstantExpression((ConstantExpression)expression); - } - - return expression; - } - - /// - /// Processes a lambda expression. - /// - /// The lambda expression. - /// The result of the processing. - protected virtual object VisitLambda(LambdaExpression epxression) - { - if (epxression.Body.NodeType == ExpressionType.MemberAccess) - { - var member = epxression.Body as MemberExpression; - if (member.Expression != null) - { - return string.Format("{0} = '1'", VisitMemberAccess(member)); - } - } - - return VisitExpression(epxression.Body); - } - - /// - /// Processes a binary expression. - /// - /// The binary expression. - /// The result of the processing. - protected virtual object VisitBinary(BinaryExpression expression) - { - object left, right; - var operand = BindOperant(expression.NodeType); - if (operand == "and" || operand == "or") - { - // Left side. - var member = expression.Left as MemberExpression; - if (member != null && - member.Expression != null && - member.Expression.NodeType == ExpressionType.Parameter) - { - left = string.Format("{0} = '1'", VisitMemberAccess(member)); - } - else - { - left = VisitExpression(expression.Left); - } - - // Right side. - member = expression.Right as MemberExpression; - if (member != null && - member.Expression != null && - member.Expression.NodeType == ExpressionType.Parameter) - { - right = string.Format("{0} = '1'", VisitMemberAccess(member)); - } - else - { - right = VisitExpression(expression.Right); - } - } - else - { - // It's a single expression. - left = VisitExpression(expression.Left); - right = VisitExpression(expression.Right); - - var paramName = "p" + _parameterIndex++; - _parameters.Add(paramName, value: right); - return string.Format("{0} {1} @{2}", left, operand, paramName); - } - - return string.Format("{0} {1} {2}", left, operand, right); - } - - /// - /// Processes a unary expression. - /// - /// The unary expression. - /// The result of the processing. - protected virtual object VisitUnary(UnaryExpression expression) - { - switch (expression.NodeType) - { - case ExpressionType.Not: - var o = VisitExpression(expression.Operand); - if (!(o is string)) - { - return !(bool)o; - } - - var memberExpression = expression.Operand as MemberExpression; - if (memberExpression != null && - Resolvers.Properties(memberExpression.Expression.Type).Any(p => p.Name == (string)o)) - { - o = string.Format("{0} = '1'", o); - } - - return string.Format("not ({0})", o); - case ExpressionType.Convert: - if (expression.Method != null) - { - return Expression.Lambda(expression).Compile().DynamicInvoke(); - } - break; - } - - return VisitExpression(expression.Operand); - } - - /// - /// Processes a new expression. - /// - /// The new expression. - /// The result of the processing. - protected virtual object VisitNew(NewExpression expression) - { - var member = Expression.Convert(expression, typeof (object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - return getter(); - } - - /// - /// Processes a member access expression. - /// - /// The member access expression. - /// The result of the processing. - protected virtual object VisitMemberAccess(MemberExpression expression) - { - if (expression.Expression != null && expression.Expression.NodeType == ExpressionType.Parameter) - { - return MemberToColumn(expression); - } - - var member = Expression.Convert(expression, typeof (object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - return getter(); - } - - /// - /// Processes a constant expression. - /// - /// The constant expression. - /// The result of the processing. - protected virtual object VisitConstantExpression(ConstantExpression expression) - { - return expression.Value ?? "null"; - } - - /// - /// Proccesses a member expression. - /// - /// The member expression. - /// The result of the processing. - protected virtual string MemberToColumn(MemberExpression expression) - { - return Resolvers.Column((PropertyInfo)expression.Member); - } - - /// - /// Returns the expression operand for the specified expression type. - /// - /// The expression type for node of an expression tree. - /// The expression operand equivalent of the . - protected virtual string BindOperant(ExpressionType expressionType) - { - switch (expressionType) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "and"; - case ExpressionType.OrElse: - return "or"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return expressionType.ToString(); - } - } - - /// - /// Returns the current SQL query. - /// - /// The current SQL query. - public string ToSql() - { - return _whereBuilder.ToString(); - } - - /// - /// Returns the current SQL query. - /// - /// When this method returns, contains the parameters for the query. - /// The current SQL query. - public string ToSql(out DynamicParameters parameters) - { - parameters = _parameters; - return _whereBuilder.ToString(); - } - - /// - /// Returns the current SQL query. - /// - /// The current SQL query. - public override string ToString() - { - return _whereBuilder.ToString(); - } - } - - /// - /// Inserts the specified entity into the database and returns the id. - /// - /// The type of the entity. - /// The connection to the database. This can either be open or closed. - /// The entity to be inserted. - /// Optional transaction for the command. - /// The id of the inserted entity. - public static int Insert(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null) where TEntity : class - { - var type = typeof (TEntity); - - string sql; - if (!_insertQueryCache.TryGetValue(type, out sql)) - { - var tableName = Resolvers.Table(type); - var keyProperty = Resolvers.KeyProperty(type); - var typeProperties = Resolvers.Properties(type).Where(p => p != keyProperty).ToList(); - - var columnNames = typeProperties.Select(Resolvers.Column).ToArray(); - var paramNames = typeProperties.Select(p => "@" + p.Name).ToArray(); - - var builder = GetBuilder(connection); - - sql = builder.BuildInsert(tableName, columnNames, paramNames, keyProperty); - - _insertQueryCache[type] = sql; - } - - var result = connection.Query(sql, entity, transaction); - return result.Single(); - } - - /// - /// Updates the values of the specified entity in the database. - /// The return value indicates whether the operation succeeded. - /// - /// The type of the entity. - /// The connection to the database. This can either be open or closed. - /// The entity in the database. - /// Optional transaction for the command. - /// A value indicating whether the update operation succeeded. - public static bool Update(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null) - { - var type = typeof (TEntity); - - string sql; - if (!_updateQueryCache.TryGetValue(type, out sql)) - { - var tableName = Resolvers.Table(type); - var keyProperty = Resolvers.KeyProperty(type); - var typeProperties = Resolvers.Properties(type).Where(p => p != keyProperty).ToList(); - - var columnNames = typeProperties.Select(p => string.Format("{0} = @{1}", Resolvers.Column(p), p.Name)).ToArray(); - - sql = string.Format("update {0} set {1} where {2} = @{3}", - tableName, - string.Join(", ", columnNames), - Resolvers.Column(keyProperty), - keyProperty.Name); - - _updateQueryCache[type] = sql; - } - - return connection.Execute(sql, entity, transaction) > 0; - } - - /// - /// Deletes the specified entity from the database. - /// Returns a value indicating whether the operation succeeded. - /// - /// The type of the entity. - /// The connection to the database. This can either be open or closed. - /// The entity to be deleted. - /// Optional transaction for the command. - /// A value indicating whether the delete operation succeeded. - public static bool Delete(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null) - { - var type = typeof (TEntity); - - string sql; - if (!_deleteQueryCache.TryGetValue(type, out sql)) - { - var tableName = Resolvers.Table(type); - var keyProperty = Resolvers.KeyProperty(type); - var keyColumnName = Resolvers.Column(keyProperty); - - sql = string.Format("delete from {0} where {1} = @{2}", tableName, keyColumnName, keyProperty.Name); - - _deleteQueryCache[type] = sql; - } - - return connection.Execute(sql, entity, transaction) > 0; - } - - /// - /// Helper class for retrieving type metadata to build sql queries using configured resolvers. - /// - public static class Resolvers - { - private static readonly IDictionary _typeTableNameCache = new Dictionary(); - private static readonly IDictionary _columnNameCache = new Dictionary(); - private static readonly IDictionary _typeKeyPropertyCache = new Dictionary(); - private static readonly IDictionary _typePropertiesCache = new Dictionary(); - private static readonly IDictionary _typeForeignKeyPropertyCache = new Dictionary(); - - /// - /// Gets the key property for the specified type, using the configured . - /// - /// The to get the key property for. - /// The key property for . - public static PropertyInfo KeyProperty(Type type) - { - PropertyInfo keyProperty; - if (!_typeKeyPropertyCache.TryGetValue(type, out keyProperty)) - { - keyProperty = _keyPropertyResolver.ResolveKeyProperty(type); - _typeKeyPropertyCache[type] = keyProperty; - } - - return keyProperty; - } - - /// - /// Gets the foreign key property for the specified source type and including type - /// using the configure d. - /// - /// The source type which should contain the foreign key property. - /// The type of the foreign key relation. - /// The foreign key relationship type. - /// The foreign key property for and . - public static PropertyInfo ForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation) - { - var key = string.Format("{0};{1}", sourceType.FullName, includingType.FullName); - - ForeignKeyInfo foreignKeyInfo; - if (!_typeForeignKeyPropertyCache.TryGetValue(key, out foreignKeyInfo)) - { - // Resole the property and relation. - var foreignKeyProperty = _foreignKeyPropertyResolver.ResolveForeignKeyProperty(sourceType, includingType, out foreignKeyRelation); - - // Cache the info. - foreignKeyInfo = new ForeignKeyInfo(foreignKeyProperty, foreignKeyRelation); - _typeForeignKeyPropertyCache[key] = foreignKeyInfo; - } - - foreignKeyRelation = foreignKeyInfo.Relation; - return foreignKeyInfo.PropertyInfo; - } - - private class ForeignKeyInfo - { - public ForeignKeyInfo(PropertyInfo propertyInfo, ForeignKeyRelation relation) - { - PropertyInfo = propertyInfo; - Relation = relation; - } - - public PropertyInfo PropertyInfo { get; private set; } - - public ForeignKeyRelation Relation { get; private set; } - } - - /// - /// Gets the properties to be mapped for the specified type, using the configured . - /// - /// The to get the properties from. - /// >The collection of to be mapped properties of . - public static IEnumerable Properties(Type type) - { - PropertyInfo[] properties; - if (!_typePropertiesCache.TryGetValue(type, out properties)) - { - properties = _propertyResolver.ResolveProperties(type).ToArray(); - _typePropertiesCache[type] = properties; - } - - return properties; - } - - /// - /// Gets the name of the table in the database for the specified type, - /// using the configured . - /// - /// The to get the table name for. - /// The table name in the database for . - public static string Table(Type type) - { - string name; - if (!_typeTableNameCache.TryGetValue(type, out name)) - { - name = _tableNameResolver.ResolveTableName(type); - _typeTableNameCache[type] = name; - } - return name; - } - - /// - /// Gets the name of the column in the database for the specified type, - /// using the configured . - /// - /// The to get the column name for. - /// The column name in the database for . - public static string Column(PropertyInfo propertyInfo) - { - var key = string.Format("{0}.{1}", propertyInfo.DeclaringType, propertyInfo.Name); - - string columnName; - if (!_columnNameCache.TryGetValue(key, out columnName)) - { - columnName = _columnNameResolver.ResolveColumnName(propertyInfo); - _columnNameCache[key] = columnName; - } - - return columnName; - } - - /// - /// Provides access to default resolver implementations. - /// - public static class Default - { - /// - /// The default column name resolver. - /// - public static readonly IColumnNameResolver ColumnNameResolver = new DefaultColumnNameResolver(); - - /// - /// The default property resolver. - /// - public static readonly IPropertyResolver PropertyResolver = new DefaultPropertyResolver(); - - /// - /// The default key property resolver. - /// - public static readonly IKeyPropertyResolver KeyPropertyResolver = new DefaultKeyPropertyResolver(); - - /// - /// The default table name resolver. - /// - public static readonly ITableNameResolver TableNameResolver = new DefaultTableNameResolver(); - } - } - - #region Property resolving - private static IPropertyResolver _propertyResolver = new DefaultPropertyResolver(); - - /// - /// Defines methods for resolving the properties of entities. - /// Custom implementations can be registerd with . - /// - public interface IPropertyResolver - { - /// - /// Resolves the properties to be mapped for the specified type. - /// - /// The type to resolve the properties to be mapped for. - /// A collection of 's of the . - IEnumerable ResolveProperties(Type type); - } - - /// - /// Sets the implementation for resolving key of entities. - /// - /// An instance of . - public static void SetPropertyResolver(IPropertyResolver propertyResolver) - { - _propertyResolver = propertyResolver; - } - - /// - /// Represents the base class for property resolvers. - /// - public abstract class PropertyResolverBase : IPropertyResolver - { - private static readonly HashSet _primitiveTypes = new HashSet - { - typeof (object), - typeof (string), - typeof(Guid), - typeof (decimal), - typeof (double), - typeof (float), - typeof (DateTime), - typeof (TimeSpan) - }; - - /// - /// Resolves the properties to be mapped for the specified type. - /// - /// The type to resolve the properties to be mapped for. - /// A collection of 's of the . - public abstract IEnumerable ResolveProperties(Type type); - - /// - /// Gets a collection of types that are considered 'primitive' for Dommel but are not for the CLR. - /// Override this if you need your own implementation of this. - /// - protected virtual HashSet PrimitiveTypes - { - get - { - return _primitiveTypes; - } - } - - /// - /// Filters the complex types from the specified collection of properties. - /// - /// A collection of properties. - /// The properties that are considered 'primitive' of . - protected virtual IEnumerable FilterComplexTypes(IEnumerable properties) - { - foreach (var property in properties) - { - var type = property.PropertyType; - type = Nullable.GetUnderlyingType(type) ?? type; - -#if DNXCORE50 - if (type.GetTypeInfo().IsPrimitive || type.GetTypeInfo().IsEnum || PrimitiveTypes.Contains(type)) -#else - if (type.IsPrimitive || type.IsEnum || PrimitiveTypes.Contains(type)) -#endif - { - yield return property; - } - } - } - } - - /// - /// Default implemenation of the interface. - /// - public class DefaultPropertyResolver : PropertyResolverBase - { - public override IEnumerable ResolveProperties(Type type) - { - return FilterComplexTypes(type.GetProperties()); - } - } - #endregion - - #region Key property resolving - private static IKeyPropertyResolver _keyPropertyResolver = new DefaultKeyPropertyResolver(); - - /// - /// Sets the implementation for resolving key properties of entities. - /// - /// An instance of . - public static void SetKeyPropertyResolver(IKeyPropertyResolver resolver) - { - _keyPropertyResolver = resolver; - } - - /// - /// Defines methods for resolving the key property of entities. - /// Custom implementations can be registerd with . - /// - public interface IKeyPropertyResolver - { - /// - /// Resolves the key property for the specified type. - /// - /// The type to resolve the key property for. - /// A instance of the key property of . - PropertyInfo ResolveKeyProperty(Type type); - } - - /// - /// Implements the interface by resolving key properties - /// with the [Key] attribute or with the name 'Id'. - /// - public class DefaultKeyPropertyResolver : IKeyPropertyResolver - { - /// - /// Finds the key property by looking for a property with the [Key] attribute or with the name 'Id'. - /// - public virtual PropertyInfo ResolveKeyProperty(Type type) - { - var allProps = Resolvers.Properties(type).ToList(); - - // Look for properties with the [Key] attribute. - var keyProps = allProps.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList(); - - if (keyProps.Count == 0) - { - // Search for properties named as 'Id' as fallback. - keyProps = allProps.Where(p => p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)).ToList(); - } - - if (keyProps.Count == 0) - { - throw new Exception(string.Format("Could not find the key property for type '{0}'.", type.FullName)); - } - - if (keyProps.Count > 1) - { - throw new Exception(string.Format("Multiple key properties were found for type '{0}'.", type.FullName)); - } - - return keyProps[0]; - } - } - #endregion - - #region Foreign key property resolving - /// - /// Describes a foreign key relationship. - /// - public enum ForeignKeyRelation - { - /// - /// Specifies a one-to-one relationship. - /// - OneToOne, - - /// - /// Specifies a one-to-many relationship. - /// - OneToMany, - - /// - /// Specifies a many-to-many relationship. - /// - ManyToMany - } - - private static IForeignKeyPropertyResolver _foreignKeyPropertyResolver = new DefaultForeignKeyPropertyResolver(); - - /// - /// Sets the implementation for resolving foreign key properties. - /// - /// An instance of . - public static void SetForeignKeyPropertyResolver(IForeignKeyPropertyResolver resolver) - { - _foreignKeyPropertyResolver = resolver; - } - - /// - /// Defines methods for resolving foreign key properties. - /// - public interface IForeignKeyPropertyResolver - { - /// - /// Resolves the foreign key property for the specified source type and including type. - /// - /// The source type which should contain the foreign key property. - /// The type of the foreign key relation. - /// The foreign key relationship type. - /// The foreign key property for and . - PropertyInfo ResolveForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation); - } - - /// - /// Implements the interface. - /// - public class DefaultForeignKeyPropertyResolver : IForeignKeyPropertyResolver - { - /// - /// Resolves the foreign key property for the specified source type and including type - /// by using + Id as property name. - /// - /// The source type which should contain the foreign key property. - /// The type of the foreign key relation. - /// The foreign key relationship type. - /// The foreign key property for and . - public virtual PropertyInfo ResolveForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation) - { - var oneToOneFk = ResolveOneToOne(sourceType, includingType); - if (oneToOneFk != null) - { - foreignKeyRelation = ForeignKeyRelation.OneToOne; - return oneToOneFk; - } - - var oneToManyFk = ResolveOneToMany(sourceType, includingType); - if (oneToManyFk != null) - { - foreignKeyRelation = ForeignKeyRelation.OneToMany; - return oneToManyFk; - } - - var msg = string.Format("Could not resolve foreign key property. Source type '{0}'; including type: '{1}'.", sourceType.FullName, includingType.FullName); - throw new Exception(msg); - } - - private static PropertyInfo ResolveOneToOne(Type sourceType, Type includingType) - { - // Look for the foreign key on the source type. - var foreignKeyName = includingType.Name + "Id"; - var foreignKeyProperty = sourceType.GetProperties().FirstOrDefault(p => p.Name == foreignKeyName); - - return foreignKeyProperty; - } - - private static PropertyInfo ResolveOneToMany(Type sourceType, Type includingType) - { - // Look for the foreign key on the including type. - var foreignKeyName = sourceType.Name + "Id"; - var foreignKeyProperty = includingType.GetProperties().FirstOrDefault(p => p.Name == foreignKeyName); - return foreignKeyProperty; - } - } - #endregion - - #region Table name resolving - private static ITableNameResolver _tableNameResolver = new DefaultTableNameResolver(); - - /// - /// Sets the implementation for resolving table names for entities. - /// - /// An instance of . - public static void SetTableNameResolver(ITableNameResolver resolver) - { - _tableNameResolver = resolver; - } - - /// - /// Defines methods for resolving table names of entities. - /// Custom implementations can be registerd with . - /// - public interface ITableNameResolver - { - /// - /// Resolves the table name for the specified type. - /// - /// The type to resolve the table name for. - /// A string containing the resolved table name for for . - string ResolveTableName(Type type); - } - - /// - /// Implements the interface by resolving table names - /// by making the type name plural and removing the 'I' prefix for interfaces. - /// - public class DefaultTableNameResolver : ITableNameResolver - { - /// - /// Resolves the table name by making the type plural (+ 's', Product -> Products) - /// and removing the 'I' prefix for interfaces. - /// - public virtual string ResolveTableName(Type type) - { - var name = type.Name + "s"; -#if DNXCORE50 - if (type.GetTypeInfo().IsInterface && name.StartsWith("I")) -#else - if (type.IsInterface && name.StartsWith("I")) -#endif - { - name = name.Substring(1); - } - - // todo: add [Table] attribute support. - return name; - } - } - #endregion - - #region Column name resolving - private static IColumnNameResolver _columnNameResolver = new DefaultColumnNameResolver(); - - /// - /// Sets the implementation for resolving column names. - /// - /// An instance of . - public static void SetColumnNameResolver(IColumnNameResolver resolver) - { - _columnNameResolver = resolver; - } - - /// - /// Defines methods for resolving column names for entities. - /// Custom implementations can be registerd with . - /// - public interface IColumnNameResolver - { - /// - /// Resolves the column name for the specified property. - /// - /// The property of the entity. - /// The column name for the property. - string ResolveColumnName(PropertyInfo propertyInfo); - } - - /// - /// Implements the . - /// - public class DefaultColumnNameResolver : IColumnNameResolver - { - /// - /// Resolves the column name for the property. This is just the name of the property. - /// - public virtual string ResolveColumnName(PropertyInfo propertyInfo) - { - return propertyInfo.Name; - } - } - #endregion - - #region Sql builders - /// - /// Adds a custom implementation of - /// for the specified ADO.NET connection type. - /// - /// - /// The ADO.NET conncetion type to use the with. - /// Example: typeof(SqlConnection). - /// - /// An implementation of the . - public static void AddSqlBuilder(Type connectionType, ISqlBuilder builder) - { - _sqlBuilders[connectionType.Name.ToLower()] = builder; - } - - private static ISqlBuilder GetBuilder(IDbConnection connection) - { - var connectionName = connection.GetType().Name.ToLower(); - ISqlBuilder builder; - return _sqlBuilders.TryGetValue(connectionName, out builder) ? builder : new SqlServerSqlBuilder(); - } - - /// - /// Defines methods for building specialized SQL queries. - /// - public interface ISqlBuilder - { - /// - /// Builds an insert query using the specified table name, column names and parameter names. - /// A query to fetch the new id will be included as well. - /// - /// The name of the table to query. - /// The names of the columns in the table. - /// The names of the parameters in the database command. - /// The key property. This can be used to query a specific column for the new id. This is optional. - /// An insert query including a query to fetch the new id. - string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty); - } - - private sealed class SqlServerSqlBuilder : ISqlBuilder - { - public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) - { - return string.Format("set nocount on insert into {0} ({1}) values ({2}) select cast(scope_identity() as int)", - tableName, - string.Join(", ", columnNames), - string.Join(", ", paramNames)); - } - } - - private sealed class SqlServerCeSqlBuilder : ISqlBuilder - { - public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) - { - return string.Format("insert into {0} ({1}) values ({2}) select cast(@@IDENTITY as int)", - tableName, - string.Join(", ", columnNames), - string.Join(", ", paramNames)); - } - } - - private sealed class SqliteSqlBuilder : ISqlBuilder - { - public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) - { - return string.Format("insert into {0} ({1}) values ({2}); select last_insert_rowid() id", - tableName, - string.Join(", ", columnNames), - string.Join(", ", paramNames)); - } - } - - private sealed class MySqlSqlBuilder : ISqlBuilder - { - public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) - { - return string.Format("insert into {0} ({1}) values ({2}) select LAST_INSERT_ID() id", - tableName, - string.Join(", ", columnNames), - string.Join(", ", paramNames)); - } - } - - private sealed class PostgresSqlBuilder : ISqlBuilder - { - public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) - { - var sql = string.Format("insert into {0} ({1}) values ({2}) select last_insert_rowid() id", - tableName, - string.Join(", ", columnNames), - string.Join(", ", paramNames)); - - if (keyProperty != null) - { - var keyColumnName = Resolvers.Column(keyProperty); - - sql += " RETURNING " + keyColumnName; - } - else - { - // todo: what behavior is desired here? - throw new Exception("A key property is required for the PostgresSqlBuilder."); - } - - return sql; - } - } - #endregion - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Dapper; + +#if DOTNET5_4 +using IDbTransaction = System.Data.Common.DbTransaction; +using IDbConnection = System.Data.Common.DbConnection; +#endif + +namespace Dommel +{ + /// + /// Simple CRUD operations for Dapper. + /// + public static class DommelMapper + { + private static readonly IDictionary _sqlBuilders = new Dictionary + { + { "sqlconnection", new SqlServerSqlBuilder() }, + { "sqlceconnection", new SqlServerCeSqlBuilder() }, + { "sqliteconnection", new SqliteSqlBuilder() }, + { "npgsqlconnection", new PostgresSqlBuilder() }, + { "mysqlconnection", new MySqlSqlBuilder() } + }; + + private static readonly IDictionary _getQueryCache = new Dictionary(); + private static readonly IDictionary _getAllQueryCache = new Dictionary(); + private static readonly IDictionary _insertQueryCache = new Dictionary(); + private static readonly IDictionary _updateQueryCache = new Dictionary(); + private static readonly IDictionary _deleteQueryCache = new Dictionary(); + + /// + /// Retrieves the entity of type with the specified id. + /// + /// The type of the entity. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The entity with the corresponding id. + public static TEntity Get(this IDbConnection connection, object id) where TEntity : class + { + var type = typeof (TEntity); + + string sql; + if (!_getQueryCache.TryGetValue(type, out sql)) + { + var tableName = Resolvers.Table(type); + var keyProperty = Resolvers.KeyProperty(type); + var keyColumnName = Resolvers.Column(keyProperty); + + sql = string.Format("select * from {0} where {1} = @Id", tableName, keyColumnName); + _getQueryCache[type] = sql; + } + + var parameters = new DynamicParameters(); + parameters.Add("Id", id); + + return connection.Query(sql: sql, param: parameters).FirstOrDefault(); + } + + /// + /// Retrieves the entity of type with the specified id + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The mapping to perform on the entities in the result set. + /// The entity with the corresponding id joined with the specified types. + public static TReturn Get(this IDbConnection connection, object id, Func map) where TReturn : class + { + return MultiMap(connection, map, id).FirstOrDefault(); + } + + /// + /// Retrieves the entity of type with the specified id + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The mapping to perform on the entities in the result set. + /// The entity with the corresponding id joined with the specified types. + public static TReturn Get(this IDbConnection connection, + object id, + Func map) where TReturn : class + { + return MultiMap(connection, map, id).FirstOrDefault(); + } + + /// + /// Retrieves the entity of type with the specified id + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The mapping to perform on the entities in the result set. + /// The entity with the corresponding id joined with the specified types. + public static TReturn Get(this IDbConnection connection, + object id, + Func map) where TReturn : class + { + return MultiMap(connection, map, id).FirstOrDefault(); + } + + /// + /// Retrieves the entity of type with the specified id + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The fifth type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The mapping to perform on the entities in the result set. + /// The entity with the corresponding id joined with the specified types. + public static TReturn Get(this IDbConnection connection, + object id, + Func map) where TReturn : class + { + return MultiMap(connection, map, id).FirstOrDefault(); + } + + /// + /// Retrieves the entity of type with the specified id + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The fifth type parameter. + /// The sixth type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The mapping to perform on the entities in the result set. + /// The entity with the corresponding id joined with the specified types. + public static TReturn Get(this IDbConnection connection, + object id, + Func map) where TReturn : class + { + return MultiMap(connection, map, id).FirstOrDefault(); + } + + /// + /// Retrieves the entity of type with the specified id + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The fifth type parameter. + /// The sixth type parameter. + /// The seventh type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The id of the entity in the database. + /// The mapping to perform on the entities in the result set. + /// The entity with the corresponding id joined with the specified types. + public static TReturn Get(this IDbConnection connection, + object id, + Func map) where TReturn : class + { + return MultiMap(connection, map, id).FirstOrDefault(); + } + + /// + /// Retrieves all the entities of type . + /// + /// The type of the entity. + /// The connection to the database. This can either be open or closed. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// A collection of entities of type . + public static IEnumerable GetAll(this IDbConnection connection, bool buffered = true) where TEntity : class + { + var type = typeof (TEntity); + + string sql; + if (!_getAllQueryCache.TryGetValue(type, out sql)) + { + var tableName = Resolvers.Table(type); + sql = string.Format("select * from {0}", tableName); + _getAllQueryCache[type] = sql; + } + + return connection.Query(sql: sql, buffered: buffered); + } + + /// + /// Retrieves all the entities of type + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The mapping to perform on the entities in the result set. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// + /// A collection of entities of type + /// joined with the specified type types. + /// + public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) + { + return MultiMap(connection, map, buffered: buffered); + } + + /// + /// Retrieves all the entities of type + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The mapping to perform on the entities in the result set. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// + /// A collection of entities of type + /// joined with the specified type types. + /// + public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) + { + return MultiMap(connection, map, buffered: buffered); + } + + /// + /// Retrieves all the entities of type + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The mapping to perform on the entities in the result set. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// + /// A collection of entities of type + /// joined with the specified type types. + /// + public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) + { + return MultiMap(connection, map, buffered: buffered); + } + + /// + /// Retrieves all the entities of type + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The fifth type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The mapping to perform on the entities in the result set. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// + /// A collection of entities of type + /// joined with the specified type types. + /// + public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) + { + return MultiMap(connection, map, buffered: buffered); + } + + /// + /// Retrieves all the entities of type + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The fifth type parameter. + /// The sixth type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The mapping to perform on the entities in the result set. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// + /// A collection of entities of type + /// joined with the specified type types. + /// + public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) + { + return MultiMap(connection, map, buffered: buffered); + } + + /// + /// Retrieves all the entities of type + /// joined with the types specified as type parameters. + /// + /// The first type parameter. This is the source entity. + /// The second type parameter. + /// The third type parameter. + /// The fourth type parameter. + /// The fifth type parameter. + /// The sixth type parameter. + /// The seventh type parameter. + /// The return type parameter. + /// The connection to the database. This can either be open or closed. + /// The mapping to perform on the entities in the result set. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// + /// A collection of entities of type + /// joined with the specified type types. + /// + public static IEnumerable GetAll(this IDbConnection connection, Func map, bool buffered = true) + { + return MultiMap(connection, map, buffered: buffered); + } + + private static IEnumerable MultiMap(IDbConnection connection, Delegate map, object id = null, bool buffered = true) + { + var resultType = typeof(TReturn); + var resultTableName = Resolvers.Table(resultType); + var resultTableKeyColumnName = Resolvers.Column(Resolvers.KeyProperty(resultType)); + + var sql = string.Format("select * from {0}", resultTableName); + + var includeTypes = new[] + { + typeof (T1), + typeof (T2), + typeof (T3), + typeof (T4), + typeof (T5), + typeof (T6), + typeof (T7) + } + .Where(t => t != typeof (DontMap)) + .ToArray(); + + for (var i = 1; i < includeTypes.Length; i++) + { + // Determine the table to join with. + var sourceType = includeTypes[i - 1]; + var sourceTableName = Resolvers.Table(sourceType); + + // Determine the table name of the joined table. + var includeType = includeTypes[i]; + var foreignKeyTableName = Resolvers.Table(includeType); + + // Determine the foreign key and the relationship type. + ForeignKeyRelation relation; + var foreignKeyProperty = Resolvers.ForeignKeyProperty(sourceType, includeType, out relation); + var foreignKeyPropertyName = Resolvers.Column(foreignKeyProperty); + + // If the foreign key property is nullable, use a left-join. + var joinType = Nullable.GetUnderlyingType(foreignKeyProperty.PropertyType) != null + ? "left" + : "inner"; + + switch (relation) + { + case ForeignKeyRelation.OneToOne: + // Determine the primary key of the foreign key table. + var foreignKeyTableKeyColumName = Resolvers.Column(Resolvers.KeyProperty(includeType)); + + sql += string.Format(" {0} join {1} on {2}.{3} = {1}.{4}", + joinType, + foreignKeyTableName, + sourceTableName, + foreignKeyPropertyName, + foreignKeyTableKeyColumName); + break; + + case ForeignKeyRelation.OneToMany: + // Determine the primary key of the source table. + var sourceKeyColumnName = Resolvers.Column(Resolvers.KeyProperty(sourceType)); + + sql += string.Format(" {0} join {1} on {2}.{3} = {1}.{4}", + joinType, + foreignKeyTableName, + sourceTableName, + sourceKeyColumnName, + foreignKeyPropertyName); + break; + + case ForeignKeyRelation.ManyToMany: + throw new NotImplementedException("Many-to-many relationships are not supported yet."); + + default: + throw new NotImplementedException(string.Format("Foreign key relation type '{0}' is not implemented.", relation)); + } + } + + DynamicParameters parameters = null; + if (id != null) + { + sql += string.Format(" where {0}.{1} = @{1}", resultTableName, resultTableKeyColumnName); + + parameters = new DynamicParameters(); + parameters.Add("Id", id); + } + + switch (includeTypes.Length) + { + case 2: + return connection.Query(sql, (Func)map, parameters, buffered: buffered); + case 3: + return connection.Query(sql, (Func)map, parameters, buffered: buffered); + case 4: + return connection.Query(sql, (Func)map, parameters, buffered: buffered); + case 5: + return connection.Query(sql, (Func)map, parameters, buffered: buffered); + case 6: + return connection.Query(sql, (Func)map, parameters, buffered: buffered); + case 7: + return connection.Query(sql, (Func)map, parameters, buffered: buffered); + } + + throw new InvalidOperationException(); + } + + private class DontMap + { + } + + /// + /// Selects all the entities matching the specified predicate. + /// + /// The type of the entity. + /// The connection to the database. This can either be open or closed. + /// A predicate to filter the results. + /// + /// A value indicating whether the result of the query should be executed directly, + /// or when the query is materialized (using ToList() for example). + /// + /// A collection of entities of type matching the specified . + public static IEnumerable Select(this IDbConnection connection, Expression> predicate, bool buffered = true) + { + var type = typeof (TEntity); + + string sql; + if (!_getAllQueryCache.TryGetValue(type, out sql)) + { + var tableName = Resolvers.Table(type); + sql = string.Format("select * from {0}", tableName); + _getAllQueryCache[type] = sql; + } + + DynamicParameters parameters; + sql += new SqlExpression() + .Where(predicate) + .ToSql(out parameters); + + return connection.Query(sql: sql, param: parameters, buffered: buffered); + } + + /// + /// Represents a typed SQL expression. + /// + /// The type of the entity. + public class SqlExpression + { + private readonly StringBuilder _whereBuilder = new StringBuilder(); + private readonly DynamicParameters _parameters = new DynamicParameters(); + private int _parameterIndex; + + /// + /// Builds a SQL expression for the specified filter expression. + /// + /// The filter expression on the entity. + /// The current instance. + public virtual SqlExpression Where(Expression> expression) + { + AppendToWhere("and", expression); + return this; + } + + private void AppendToWhere(string conditionOperator, Expression expression) + { + var sqlExpression = VisitExpression(expression).ToString(); + AppendToWhere(conditionOperator, sqlExpression); + } + + private void AppendToWhere(string conditionOperator, string sqlExpression) + { + if (_whereBuilder.Length == 0) + { + _whereBuilder.Append(" where "); + } + else + { + _whereBuilder.AppendFormat(" {0} ", conditionOperator); + } + + _whereBuilder.Append(sqlExpression); + } + + /// + /// Visits the expression. + /// + /// The expression to visit. + /// The result of the visit. + protected virtual object VisitExpression(Expression expression) + { + switch (expression.NodeType) + { + case ExpressionType.Lambda: + return VisitLambda(expression as LambdaExpression); + + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + return VisitBinary((BinaryExpression)expression); + + case ExpressionType.Convert: + case ExpressionType.Not: + return VisitUnary((UnaryExpression)expression); + + case ExpressionType.New: + return VisitNew((NewExpression)expression); + + case ExpressionType.MemberAccess: + return VisitMemberAccess((MemberExpression)expression); + + case ExpressionType.Constant: + return VisitConstantExpression((ConstantExpression)expression); + } + + return expression; + } + + /// + /// Processes a lambda expression. + /// + /// The lambda expression. + /// The result of the processing. + protected virtual object VisitLambda(LambdaExpression epxression) + { + if (epxression.Body.NodeType == ExpressionType.MemberAccess) + { + var member = epxression.Body as MemberExpression; + if (member.Expression != null) + { + return string.Format("{0} = '1'", VisitMemberAccess(member)); + } + } + + return VisitExpression(epxression.Body); + } + + /// + /// Processes a binary expression. + /// + /// The binary expression. + /// The result of the processing. + protected virtual object VisitBinary(BinaryExpression expression) + { + object left, right; + var operand = BindOperant(expression.NodeType); + if (operand == "and" || operand == "or") + { + // Left side. + var member = expression.Left as MemberExpression; + if (member != null && + member.Expression != null && + member.Expression.NodeType == ExpressionType.Parameter) + { + left = string.Format("{0} = '1'", VisitMemberAccess(member)); + } + else + { + left = VisitExpression(expression.Left); + } + + // Right side. + member = expression.Right as MemberExpression; + if (member != null && + member.Expression != null && + member.Expression.NodeType == ExpressionType.Parameter) + { + right = string.Format("{0} = '1'", VisitMemberAccess(member)); + } + else + { + right = VisitExpression(expression.Right); + } + } + else + { + // It's a single expression. + left = VisitExpression(expression.Left); + right = VisitExpression(expression.Right); + + var paramName = "p" + _parameterIndex++; + _parameters.Add(paramName, value: right); + return string.Format("{0} {1} @{2}", left, operand, paramName); + } + + return string.Format("{0} {1} {2}", left, operand, right); + } + + /// + /// Processes a unary expression. + /// + /// The unary expression. + /// The result of the processing. + protected virtual object VisitUnary(UnaryExpression expression) + { + switch (expression.NodeType) + { + case ExpressionType.Not: + var o = VisitExpression(expression.Operand); + if (!(o is string)) + { + return !(bool)o; + } + + var memberExpression = expression.Operand as MemberExpression; + if (memberExpression != null && + Resolvers.Properties(memberExpression.Expression.Type).Any(p => p.Name == (string)o)) + { + o = string.Format("{0} = '1'", o); + } + + return string.Format("not ({0})", o); + case ExpressionType.Convert: + if (expression.Method != null) + { + return Expression.Lambda(expression).Compile().DynamicInvoke(); + } + break; + } + + return VisitExpression(expression.Operand); + } + + /// + /// Processes a new expression. + /// + /// The new expression. + /// The result of the processing. + protected virtual object VisitNew(NewExpression expression) + { + var member = Expression.Convert(expression, typeof (object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + return getter(); + } + + /// + /// Processes a member access expression. + /// + /// The member access expression. + /// The result of the processing. + protected virtual object VisitMemberAccess(MemberExpression expression) + { + if (expression.Expression != null && expression.Expression.NodeType == ExpressionType.Parameter) + { + return MemberToColumn(expression); + } + + var member = Expression.Convert(expression, typeof (object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + return getter(); + } + + /// + /// Processes a constant expression. + /// + /// The constant expression. + /// The result of the processing. + protected virtual object VisitConstantExpression(ConstantExpression expression) + { + return expression.Value ?? "null"; + } + + /// + /// Proccesses a member expression. + /// + /// The member expression. + /// The result of the processing. + protected virtual string MemberToColumn(MemberExpression expression) + { + return Resolvers.Column((PropertyInfo)expression.Member); + } + + /// + /// Returns the expression operand for the specified expression type. + /// + /// The expression type for node of an expression tree. + /// The expression operand equivalent of the . + protected virtual string BindOperant(ExpressionType expressionType) + { + switch (expressionType) + { + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.AndAlso: + return "and"; + case ExpressionType.OrElse: + return "or"; + case ExpressionType.Add: + return "+"; + case ExpressionType.Subtract: + return "-"; + case ExpressionType.Multiply: + return "*"; + case ExpressionType.Divide: + return "/"; + case ExpressionType.Modulo: + return "MOD"; + case ExpressionType.Coalesce: + return "COALESCE"; + default: + return expressionType.ToString(); + } + } + + /// + /// Returns the current SQL query. + /// + /// The current SQL query. + public string ToSql() + { + return _whereBuilder.ToString(); + } + + /// + /// Returns the current SQL query. + /// + /// When this method returns, contains the parameters for the query. + /// The current SQL query. + public string ToSql(out DynamicParameters parameters) + { + parameters = _parameters; + return _whereBuilder.ToString(); + } + + /// + /// Returns the current SQL query. + /// + /// The current SQL query. + public override string ToString() + { + return _whereBuilder.ToString(); + } + } + + /// + /// Inserts the specified entity into the database and returns the id. + /// + /// The type of the entity. + /// The connection to the database. This can either be open or closed. + /// The entity to be inserted. + /// Optional transaction for the command. + /// The id of the inserted entity. + public static int Insert(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null) where TEntity : class + { + var type = typeof (TEntity); + + string sql; + if (!_insertQueryCache.TryGetValue(type, out sql)) + { + var tableName = Resolvers.Table(type); + var keyProperty = Resolvers.KeyProperty(type); + var typeProperties = Resolvers.Properties(type).Where(p => p != keyProperty).ToList(); + + var columnNames = typeProperties.Select(Resolvers.Column).ToArray(); + var paramNames = typeProperties.Select(p => "@" + p.Name).ToArray(); + + var builder = GetBuilder(connection); + + sql = builder.BuildInsert(tableName, columnNames, paramNames, keyProperty); + + _insertQueryCache[type] = sql; + } + + var result = connection.Query(sql, entity, transaction); + return result.Single(); + } + + /// + /// Updates the values of the specified entity in the database. + /// The return value indicates whether the operation succeeded. + /// + /// The type of the entity. + /// The connection to the database. This can either be open or closed. + /// The entity in the database. + /// Optional transaction for the command. + /// A value indicating whether the update operation succeeded. + public static bool Update(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null) + { + var type = typeof (TEntity); + + string sql; + if (!_updateQueryCache.TryGetValue(type, out sql)) + { + var tableName = Resolvers.Table(type); + var keyProperty = Resolvers.KeyProperty(type); + var typeProperties = Resolvers.Properties(type).Where(p => p != keyProperty).ToList(); + + var columnNames = typeProperties.Select(p => string.Format("{0} = @{1}", Resolvers.Column(p), p.Name)).ToArray(); + + sql = string.Format("update {0} set {1} where {2} = @{3}", + tableName, + string.Join(", ", columnNames), + Resolvers.Column(keyProperty), + keyProperty.Name); + + _updateQueryCache[type] = sql; + } + + return connection.Execute(sql, entity, transaction) > 0; + } + + /// + /// Deletes the specified entity from the database. + /// Returns a value indicating whether the operation succeeded. + /// + /// The type of the entity. + /// The connection to the database. This can either be open or closed. + /// The entity to be deleted. + /// Optional transaction for the command. + /// A value indicating whether the delete operation succeeded. + public static bool Delete(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null) + { + var type = typeof (TEntity); + + string sql; + if (!_deleteQueryCache.TryGetValue(type, out sql)) + { + var tableName = Resolvers.Table(type); + var keyProperty = Resolvers.KeyProperty(type); + var keyColumnName = Resolvers.Column(keyProperty); + + sql = string.Format("delete from {0} where {1} = @{2}", tableName, keyColumnName, keyProperty.Name); + + _deleteQueryCache[type] = sql; + } + + return connection.Execute(sql, entity, transaction) > 0; + } + + /// + /// Helper class for retrieving type metadata to build sql queries using configured resolvers. + /// + public static class Resolvers + { + private static readonly IDictionary _typeTableNameCache = new Dictionary(); + private static readonly IDictionary _columnNameCache = new Dictionary(); + private static readonly IDictionary _typeKeyPropertyCache = new Dictionary(); + private static readonly IDictionary _typePropertiesCache = new Dictionary(); + private static readonly IDictionary _typeForeignKeyPropertyCache = new Dictionary(); + + /// + /// Gets the key property for the specified type, using the configured . + /// + /// The to get the key property for. + /// The key property for . + public static PropertyInfo KeyProperty(Type type) + { + PropertyInfo keyProperty; + if (!_typeKeyPropertyCache.TryGetValue(type, out keyProperty)) + { + keyProperty = _keyPropertyResolver.ResolveKeyProperty(type); + _typeKeyPropertyCache[type] = keyProperty; + } + + return keyProperty; + } + + /// + /// Gets the foreign key property for the specified source type and including type + /// using the configure d. + /// + /// The source type which should contain the foreign key property. + /// The type of the foreign key relation. + /// The foreign key relationship type. + /// The foreign key property for and . + public static PropertyInfo ForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation) + { + var key = string.Format("{0};{1}", sourceType.FullName, includingType.FullName); + + ForeignKeyInfo foreignKeyInfo; + if (!_typeForeignKeyPropertyCache.TryGetValue(key, out foreignKeyInfo)) + { + // Resole the property and relation. + var foreignKeyProperty = _foreignKeyPropertyResolver.ResolveForeignKeyProperty(sourceType, includingType, out foreignKeyRelation); + + // Cache the info. + foreignKeyInfo = new ForeignKeyInfo(foreignKeyProperty, foreignKeyRelation); + _typeForeignKeyPropertyCache[key] = foreignKeyInfo; + } + + foreignKeyRelation = foreignKeyInfo.Relation; + return foreignKeyInfo.PropertyInfo; + } + + private class ForeignKeyInfo + { + public ForeignKeyInfo(PropertyInfo propertyInfo, ForeignKeyRelation relation) + { + PropertyInfo = propertyInfo; + Relation = relation; + } + + public PropertyInfo PropertyInfo { get; private set; } + + public ForeignKeyRelation Relation { get; private set; } + } + + /// + /// Gets the properties to be mapped for the specified type, using the configured . + /// + /// The to get the properties from. + /// >The collection of to be mapped properties of . + public static IEnumerable Properties(Type type) + { + PropertyInfo[] properties; + if (!_typePropertiesCache.TryGetValue(type, out properties)) + { + properties = _propertyResolver.ResolveProperties(type).ToArray(); + _typePropertiesCache[type] = properties; + } + + return properties; + } + + /// + /// Gets the name of the table in the database for the specified type, + /// using the configured . + /// + /// The to get the table name for. + /// The table name in the database for . + public static string Table(Type type) + { + string name; + if (!_typeTableNameCache.TryGetValue(type, out name)) + { + name = _tableNameResolver.ResolveTableName(type); + _typeTableNameCache[type] = name; + } + return name; + } + + /// + /// Gets the name of the column in the database for the specified type, + /// using the configured . + /// + /// The to get the column name for. + /// The column name in the database for . + public static string Column(PropertyInfo propertyInfo) + { + var key = string.Format("{0}.{1}", propertyInfo.DeclaringType, propertyInfo.Name); + + string columnName; + if (!_columnNameCache.TryGetValue(key, out columnName)) + { + columnName = _columnNameResolver.ResolveColumnName(propertyInfo); + _columnNameCache[key] = columnName; + } + + return columnName; + } + + /// + /// Provides access to default resolver implementations. + /// + public static class Default + { + /// + /// The default column name resolver. + /// + public static readonly IColumnNameResolver ColumnNameResolver = new DefaultColumnNameResolver(); + + /// + /// The default property resolver. + /// + public static readonly IPropertyResolver PropertyResolver = new DefaultPropertyResolver(); + + /// + /// The default key property resolver. + /// + public static readonly IKeyPropertyResolver KeyPropertyResolver = new DefaultKeyPropertyResolver(); + + /// + /// The default table name resolver. + /// + public static readonly ITableNameResolver TableNameResolver = new DefaultTableNameResolver(); + } + } + + #region Property resolving + private static IPropertyResolver _propertyResolver = new DefaultPropertyResolver(); + + /// + /// Defines methods for resolving the properties of entities. + /// Custom implementations can be registerd with . + /// + public interface IPropertyResolver + { + /// + /// Resolves the properties to be mapped for the specified type. + /// + /// The type to resolve the properties to be mapped for. + /// A collection of 's of the . + IEnumerable ResolveProperties(Type type); + } + + /// + /// Sets the implementation for resolving key of entities. + /// + /// An instance of . + public static void SetPropertyResolver(IPropertyResolver propertyResolver) + { + _propertyResolver = propertyResolver; + } + + /// + /// Represents the base class for property resolvers. + /// + public abstract class PropertyResolverBase : IPropertyResolver + { + private static readonly HashSet _primitiveTypes = new HashSet + { + typeof (object), + typeof (string), + typeof(Guid), + typeof (decimal), + typeof (double), + typeof (float), + typeof (DateTime), + typeof (TimeSpan) + }; + + /// + /// Resolves the properties to be mapped for the specified type. + /// + /// The type to resolve the properties to be mapped for. + /// A collection of 's of the . + public abstract IEnumerable ResolveProperties(Type type); + + /// + /// Gets a collection of types that are considered 'primitive' for Dommel but are not for the CLR. + /// Override this if you need your own implementation of this. + /// + protected virtual HashSet PrimitiveTypes + { + get + { + return _primitiveTypes; + } + } + + /// + /// Filters the complex types from the specified collection of properties. + /// + /// A collection of properties. + /// The properties that are considered 'primitive' of . + protected virtual IEnumerable FilterComplexTypes(IEnumerable properties) + { + foreach (var property in properties) + { + var type = property.PropertyType; + type = Nullable.GetUnderlyingType(type) ?? type; + +#if DOTNET5_4 + if (type.GetTypeInfo().IsPrimitive || type.GetTypeInfo().IsEnum || PrimitiveTypes.Contains(type)) +#else + if (type.IsPrimitive || type.IsEnum || PrimitiveTypes.Contains(type)) +#endif + { + yield return property; + } + } + } + } + + /// + /// Default implemenation of the interface. + /// + public class DefaultPropertyResolver : PropertyResolverBase + { + public override IEnumerable ResolveProperties(Type type) + { + return FilterComplexTypes(type.GetProperties()); + } + } + #endregion + + #region Key property resolving + private static IKeyPropertyResolver _keyPropertyResolver = new DefaultKeyPropertyResolver(); + + /// + /// Sets the implementation for resolving key properties of entities. + /// + /// An instance of . + public static void SetKeyPropertyResolver(IKeyPropertyResolver resolver) + { + _keyPropertyResolver = resolver; + } + + /// + /// Defines methods for resolving the key property of entities. + /// Custom implementations can be registerd with . + /// + public interface IKeyPropertyResolver + { + /// + /// Resolves the key property for the specified type. + /// + /// The type to resolve the key property for. + /// A instance of the key property of . + PropertyInfo ResolveKeyProperty(Type type); + } + + /// + /// Implements the interface by resolving key properties + /// with the [Key] attribute or with the name 'Id'. + /// + public class DefaultKeyPropertyResolver : IKeyPropertyResolver + { + /// + /// Finds the key property by looking for a property with the [Key] attribute or with the name 'Id'. + /// + public virtual PropertyInfo ResolveKeyProperty(Type type) + { + var allProps = Resolvers.Properties(type).ToList(); + + // Look for properties with the [Key] attribute. + var keyProps = allProps.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList(); + + if (keyProps.Count == 0) + { + // Search for properties named as 'Id' as fallback. + keyProps = allProps.Where(p => p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (keyProps.Count == 0) + { + throw new Exception(string.Format("Could not find the key property for type '{0}'.", type.FullName)); + } + + if (keyProps.Count > 1) + { + throw new Exception(string.Format("Multiple key properties were found for type '{0}'.", type.FullName)); + } + + return keyProps[0]; + } + } + #endregion + + #region Foreign key property resolving + /// + /// Describes a foreign key relationship. + /// + public enum ForeignKeyRelation + { + /// + /// Specifies a one-to-one relationship. + /// + OneToOne, + + /// + /// Specifies a one-to-many relationship. + /// + OneToMany, + + /// + /// Specifies a many-to-many relationship. + /// + ManyToMany + } + + private static IForeignKeyPropertyResolver _foreignKeyPropertyResolver = new DefaultForeignKeyPropertyResolver(); + + /// + /// Sets the implementation for resolving foreign key properties. + /// + /// An instance of . + public static void SetForeignKeyPropertyResolver(IForeignKeyPropertyResolver resolver) + { + _foreignKeyPropertyResolver = resolver; + } + + /// + /// Defines methods for resolving foreign key properties. + /// + public interface IForeignKeyPropertyResolver + { + /// + /// Resolves the foreign key property for the specified source type and including type. + /// + /// The source type which should contain the foreign key property. + /// The type of the foreign key relation. + /// The foreign key relationship type. + /// The foreign key property for and . + PropertyInfo ResolveForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation); + } + + /// + /// Implements the interface. + /// + public class DefaultForeignKeyPropertyResolver : IForeignKeyPropertyResolver + { + /// + /// Resolves the foreign key property for the specified source type and including type + /// by using + Id as property name. + /// + /// The source type which should contain the foreign key property. + /// The type of the foreign key relation. + /// The foreign key relationship type. + /// The foreign key property for and . + public virtual PropertyInfo ResolveForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation) + { + var oneToOneFk = ResolveOneToOne(sourceType, includingType); + if (oneToOneFk != null) + { + foreignKeyRelation = ForeignKeyRelation.OneToOne; + return oneToOneFk; + } + + var oneToManyFk = ResolveOneToMany(sourceType, includingType); + if (oneToManyFk != null) + { + foreignKeyRelation = ForeignKeyRelation.OneToMany; + return oneToManyFk; + } + + var msg = string.Format("Could not resolve foreign key property. Source type '{0}'; including type: '{1}'.", sourceType.FullName, includingType.FullName); + throw new Exception(msg); + } + + private static PropertyInfo ResolveOneToOne(Type sourceType, Type includingType) + { + // Look for the foreign key on the source type. + var foreignKeyName = includingType.Name + "Id"; + var foreignKeyProperty = sourceType.GetProperties().FirstOrDefault(p => p.Name == foreignKeyName); + + return foreignKeyProperty; + } + + private static PropertyInfo ResolveOneToMany(Type sourceType, Type includingType) + { + // Look for the foreign key on the including type. + var foreignKeyName = sourceType.Name + "Id"; + var foreignKeyProperty = includingType.GetProperties().FirstOrDefault(p => p.Name == foreignKeyName); + return foreignKeyProperty; + } + } + #endregion + + #region Table name resolving + private static ITableNameResolver _tableNameResolver = new DefaultTableNameResolver(); + + /// + /// Sets the implementation for resolving table names for entities. + /// + /// An instance of . + public static void SetTableNameResolver(ITableNameResolver resolver) + { + _tableNameResolver = resolver; + } + + /// + /// Defines methods for resolving table names of entities. + /// Custom implementations can be registerd with . + /// + public interface ITableNameResolver + { + /// + /// Resolves the table name for the specified type. + /// + /// The type to resolve the table name for. + /// A string containing the resolved table name for for . + string ResolveTableName(Type type); + } + + /// + /// Implements the interface by resolving table names + /// by making the type name plural and removing the 'I' prefix for interfaces. + /// + public class DefaultTableNameResolver : ITableNameResolver + { + /// + /// Resolves the table name by making the type plural (+ 's', Product -> Products) + /// and removing the 'I' prefix for interfaces. + /// + public virtual string ResolveTableName(Type type) + { + var name = type.Name + "s"; +#if DOTNET5_4 + if (type.GetTypeInfo().IsInterface && name.StartsWith("I")) +#else + if (type.IsInterface && name.StartsWith("I")) +#endif + { + name = name.Substring(1); + } + + // todo: add [Table] attribute support. + return name; + } + } + #endregion + + #region Column name resolving + private static IColumnNameResolver _columnNameResolver = new DefaultColumnNameResolver(); + + /// + /// Sets the implementation for resolving column names. + /// + /// An instance of . + public static void SetColumnNameResolver(IColumnNameResolver resolver) + { + _columnNameResolver = resolver; + } + + /// + /// Defines methods for resolving column names for entities. + /// Custom implementations can be registerd with . + /// + public interface IColumnNameResolver + { + /// + /// Resolves the column name for the specified property. + /// + /// The property of the entity. + /// The column name for the property. + string ResolveColumnName(PropertyInfo propertyInfo); + } + + /// + /// Implements the . + /// + public class DefaultColumnNameResolver : IColumnNameResolver + { + /// + /// Resolves the column name for the property. This is just the name of the property. + /// + public virtual string ResolveColumnName(PropertyInfo propertyInfo) + { + return propertyInfo.Name; + } + } + #endregion + + #region Sql builders + /// + /// Adds a custom implementation of + /// for the specified ADO.NET connection type. + /// + /// + /// The ADO.NET conncetion type to use the with. + /// Example: typeof(SqlConnection). + /// + /// An implementation of the . + public static void AddSqlBuilder(Type connectionType, ISqlBuilder builder) + { + _sqlBuilders[connectionType.Name.ToLower()] = builder; + } + + private static ISqlBuilder GetBuilder(IDbConnection connection) + { + var connectionName = connection.GetType().Name.ToLower(); + ISqlBuilder builder; + return _sqlBuilders.TryGetValue(connectionName, out builder) ? builder : new SqlServerSqlBuilder(); + } + + /// + /// Defines methods for building specialized SQL queries. + /// + public interface ISqlBuilder + { + /// + /// Builds an insert query using the specified table name, column names and parameter names. + /// A query to fetch the new id will be included as well. + /// + /// The name of the table to query. + /// The names of the columns in the table. + /// The names of the parameters in the database command. + /// The key property. This can be used to query a specific column for the new id. This is optional. + /// An insert query including a query to fetch the new id. + string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty); + } + + private sealed class SqlServerSqlBuilder : ISqlBuilder + { + public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) + { + return string.Format("set nocount on insert into {0} ({1}) values ({2}) select cast(scope_identity() as int)", + tableName, + string.Join(", ", columnNames), + string.Join(", ", paramNames)); + } + } + + private sealed class SqlServerCeSqlBuilder : ISqlBuilder + { + public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) + { + return string.Format("insert into {0} ({1}) values ({2}) select cast(@@IDENTITY as int)", + tableName, + string.Join(", ", columnNames), + string.Join(", ", paramNames)); + } + } + + private sealed class SqliteSqlBuilder : ISqlBuilder + { + public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) + { + return string.Format("insert into {0} ({1}) values ({2}); select last_insert_rowid() id", + tableName, + string.Join(", ", columnNames), + string.Join(", ", paramNames)); + } + } + + private sealed class MySqlSqlBuilder : ISqlBuilder + { + public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) + { + return string.Format("insert into {0} ({1}) values ({2}) select LAST_INSERT_ID() id", + tableName, + string.Join(", ", columnNames), + string.Join(", ", paramNames)); + } + } + + private sealed class PostgresSqlBuilder : ISqlBuilder + { + public string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty) + { + var sql = string.Format("insert into {0} ({1}) values ({2}) select last_insert_rowid() id", + tableName, + string.Join(", ", columnNames), + string.Join(", ", paramNames)); + + if (keyProperty != null) + { + var keyColumnName = Resolvers.Column(keyProperty); + + sql += " RETURNING " + keyColumnName; + } + else + { + // todo: what behavior is desired here? + throw new Exception("A key property is required for the PostgresSqlBuilder."); + } + + return sql; + } + } + #endregion + } +} diff --git a/src/Dommel/project.json b/src/Dommel/project.json index aa3bb579..22117efd 100644 --- a/src/Dommel/project.json +++ b/src/Dommel/project.json @@ -1,44 +1,42 @@ -{ - "version": "1.4.2", - "copyright": "© 2015 Henk Mollema", - "description": "Simple CRUD operations for Dapper.", - "authors": [ "henkmollema" ], - "tags": [ "dommel", "crud", "dapper", "database", "orm", "ado.net" ], - "projectUrl": "https://github.com/henkmollema/Dommel", - "licenseUrl": "http://opensource.org/licenses/MIT", - "compile": [ "../Dommel-net40/*.cs" ], - "dependencies": { - "Dapper": "1.41-beta5" - }, - "frameworks": { - "net40": { - "frameworkAssemblies": { - "System.ComponentModel.DataAnnotations": "4.0.0.0", - "System.Data": "4.0.0.0" - } - }, - "net45": { - "frameworkAssemblies": { - "System.ComponentModel.DataAnnotations": "4.0.0.0", - "System.Data": "4.0.0.0" - } - }, - "dnx451": { - "frameworkAssemblies": { - "System.ComponentModel.DataAnnotations": "4.0.0.0", - "System.Data": "4.0.0.0" - } - }, - "dnxcore50": { - "dependencies": { - "Microsoft.CSharp": "4.0.0", - "System.Collections": "4.0.10", - "System.ComponentModel.Annotations": "4.0.10", - "System.Data.Common": "4.0.0", - "System.Linq": "4.0.0", - "System.Reflection": "4.0.10", - "System.Reflection.Extensions": "4.0.0" - } - } - } -} +{ + "version": "1.5.0", + "copyright": "© 2016 Henk Mollema", + "description": "Simple CRUD operations for Dapper.", + "authors": [ "henkmollema" ], + "tags": [ "dommel", "crud", "dapper", "database", "orm", "ado.net" ], + "projectUrl": "https://github.com/henkmollema/Dommel", + "licenseUrl": "http://opensource.org/licenses/MIT", + "dependencies": { + "Dapper": "1.50.0-beta8" + }, + "frameworks": { + "net40": { + "frameworkAssemblies": { + "System.ComponentModel.DataAnnotations": "4.0.0.0", + "System.Data": "4.0.0.0" + } + }, + "net45": { + "frameworkAssemblies": { + "System.ComponentModel.DataAnnotations": "4.0.0.0", + "System.Data": "4.0.0.0" + } + }, + "net451": { + "frameworkAssemblies": { + "System.ComponentModel.DataAnnotations": "4.0.0.0", + "System.Data": "4.0.0.0" + } + }, + "dotnet5.4": { + "dependencies": { + "Microsoft.CSharp": "4.0.0", + "System.Collections": "4.0.10", + "System.ComponentModel.Annotations": "4.0.10", + "System.Data.Common": "4.0.0", + "System.Linq": "4.0.0", + "System.Reflection.Extensions": "4.0.0" + } + } + } +}