From 42283ab12f917af385d09943a91f135bce8eebe4 Mon Sep 17 00:00:00 2001 From: koljagava Date: Mon, 22 Nov 2021 11:52:32 +0100 Subject: [PATCH] First FluentTableMap implementation --- .vscode/launch.json | 42 + .vscode/tasks.json | 42 + Dapper.SimpleCRUD.sln | 28 + Dapper.SimpleCRUD/SimpleCRUD.cs | 500 +++--- .../Assert.cs | 53 + ...pper.SimpleCRUDFluentTableMap.Tests.csproj | 43 + .../Program.cs | 321 ++++ .../Tests.cs | 1570 +++++++++++++++++ .../Dapper.SimpleCRUDFluentTableMap.csproj | 22 + .../SimpleCRUDFluentTableMap.cs | 240 +++ 10 files changed, 2616 insertions(+), 245 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 Dapper.SimpleCRUDFluentTableMap.Tests/Assert.cs create mode 100644 Dapper.SimpleCRUDFluentTableMap.Tests/Dapper.SimpleCRUDFluentTableMap.Tests.csproj create mode 100644 Dapper.SimpleCRUDFluentTableMap.Tests/Program.cs create mode 100644 Dapper.SimpleCRUDFluentTableMap.Tests/Tests.cs create mode 100644 Dapper.SimpleCRUDFluentTableMap/Dapper.SimpleCRUDFluentTableMap.csproj create mode 100644 Dapper.SimpleCRUDFluentTableMap/SimpleCRUDFluentTableMap.cs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a1486bd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Dapper.SimpleCRUDTests/bin/Debug/netcoreapp3.1/Dapper.SimpleCRUDTests.dll", + "args": [], + "cwd": "${workspaceFolder}/Dapper.SimpleCRUDTests", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console) - FluentTableMap", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Dapper.SimpleCRUDFluentTableMap.Tests/bin/Debug/netcoreapp3.1/Dapper.SimpleCRUDFluentTableMap.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}/Dapper.SimpleCRUDFluentTableMap.Tests", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..fd866d6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Dapper.SimpleCRUDTests/Dapper.SimpleCRUDTests.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/Dapper.SimpleCRUDTests/Dapper.SimpleCRUDTests.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Dapper.SimpleCRUD.sln b/Dapper.SimpleCRUD.sln index b9a323e..850965b 100644 --- a/Dapper.SimpleCRUD.sln +++ b/Dapper.SimpleCRUD.sln @@ -14,6 +14,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SimpleCRUD", "Dapper EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SimpleCRUDTests", "Dapper.SimpleCRUDTests\Dapper.SimpleCRUDTests.csproj", "{165025ED-34C7-4519-BEDC-37B2859735AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.SimpleCRUDFluentTableMap", "Dapper.SimpleCRUDFluentTableMap\Dapper.SimpleCRUDFluentTableMap.csproj", "{F55831AC-F972-43C3-A742-EBB5D0908C8E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.SimpleCRUDFluentTableMap.Tests", "Dapper.SimpleCRUDFluentTableMap.Tests\Dapper.SimpleCRUDFluentTableMap.Tests.csproj", "{E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +52,30 @@ Global {165025ED-34C7-4519-BEDC-37B2859735AF}.Release|Mixed Platforms.Build.0 = Release|Any CPU {165025ED-34C7-4519-BEDC-37B2859735AF}.Release|x86.ActiveCfg = Release|Any CPU {165025ED-34C7-4519-BEDC-37B2859735AF}.Release|x86.Build.0 = Release|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Debug|x86.ActiveCfg = Debug|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Debug|x86.Build.0 = Debug|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Release|Any CPU.Build.0 = Release|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Release|x86.ActiveCfg = Release|Any CPU + {F55831AC-F972-43C3-A742-EBB5D0908C8E}.Release|x86.Build.0 = Release|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Debug|x86.ActiveCfg = Debug|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Debug|x86.Build.0 = Debug|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Release|Any CPU.Build.0 = Release|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Release|x86.ActiveCfg = Release|Any CPU + {E699CE93-AC81-4F3F-9BA5-2BC7C51DB87C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Dapper.SimpleCRUD/SimpleCRUD.cs b/Dapper.SimpleCRUD/SimpleCRUD.cs index 691075c..69fe753 100644 --- a/Dapper.SimpleCRUD/SimpleCRUD.cs +++ b/Dapper.SimpleCRUD/SimpleCRUD.cs @@ -7,7 +7,7 @@ using System.Reflection; using System.Text; using Microsoft.CSharp.RuntimeBinder; - + namespace Dapper { /// @@ -15,7 +15,7 @@ namespace Dapper /// public static partial class SimpleCRUD { - + static SimpleCRUD() { SetDialect(_dialect); @@ -25,16 +25,15 @@ static SimpleCRUD() private static string _encapsulation; private static string _getIdentitySql; private static string _getPagedListSql; - - private static readonly ConcurrentDictionary TableNames = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary ColumnNames = new ConcurrentDictionary(); - + private static readonly ConcurrentDictionary StringBuilderCacheDict = new ConcurrentDictionary(); private static bool StringBuilderCacheEnabled = true; - - private static ITableNameResolver _tableNameResolver = new TableNameResolver(); - private static IColumnNameResolver _columnNameResolver = new ColumnNameResolver(); - + + private static TableMapResolver _tableMapResolver = new TableMapResolver(); + private static IColumnPropertiesResolver _columnPropertiesResolver = _tableMapResolver; + private static ITableNameResolver _tableNameResolver = _tableMapResolver; + private static IColumnNameResolver _columnNameResolver = _tableMapResolver; + /// /// Append a Cached version of a strinbBuilderAction result based on a cacheKey /// @@ -48,14 +47,14 @@ private static void StringBuilderCache(StringBuilder sb, string cacheKey, Action sb.Append(value); return; } - + StringBuilder newSb = new StringBuilder(); stringBuilderAction(newSb); value = newSb.ToString(); StringBuilderCacheDict.AddOrUpdate(cacheKey, value, (t, v) => value); sb.Append(value); } - + /// /// Returns the current dialect name /// @@ -64,9 +63,9 @@ public static string GetDialect() { return _dialect.ToString(); } - + /// - /// Sets the database dialect + /// Sets the database dialect /// /// public static void SetDialect(Dialect dialect) @@ -111,7 +110,17 @@ public static void SetDialect(Dialect dialect) break; } } - + + /// + /// Sets the Column Map resolver + /// used to resolve column properties attributes + /// + /// The resolver to use when requesting the format of a table name + public static void SetColumnPropertiesResolver(IColumnPropertiesResolver resolver) + { + _columnPropertiesResolver = resolver; + } + /// /// Sets the table name resolver /// @@ -120,16 +129,17 @@ public static void SetTableNameResolver(ITableNameResolver resolver) { _tableNameResolver = resolver; } - + /// /// Sets the column name resolver /// /// The resolver to use when requesting the format of a column name + [Obsolete("use SetColumnPropertiesResolver instead")] public static void SetColumnNameResolver(IColumnNameResolver resolver) { _columnNameResolver = resolver; } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -148,24 +158,24 @@ public static T Get(this IDbConnection connection, object id, IDbTransaction { var currenttype = typeof(T); var idProps = GetIdProperties(currenttype).ToList(); - + if (!idProps.Any()) throw new ArgumentException("Get only supports an entity with a [Key] or Id property"); - + var name = GetTableName(currenttype); var sb = new StringBuilder(); sb.Append("Select "); //create a new empty instance of the type to get the base properties BuildSelect(sb, GetScaffoldableProperties().ToArray()); sb.AppendFormat(" from {0} where ", name); - + for (var i = 0; i < idProps.Count; i++) { if (i > 0) sb.Append(" and "); sb.AppendFormat("{0} = @{1}", GetColumnName(idProps[i]), idProps[i].Name); } - + var dynParms = new DynamicParameters(); if (idProps.Count == 1) dynParms.Add("@" + idProps.First().Name, id); @@ -174,13 +184,13 @@ public static T Get(this IDbConnection connection, object id, IDbTransaction foreach (var prop in idProps) dynParms.Add("@" + prop.Name, id.GetType().GetProperty(prop.Name).GetValue(id, null)); } - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("Get<{0}>: {1} with Id: {2}", currenttype, sb, id)); - + return connection.Query(sb.ToString(), dynParms, transaction, true, commandTimeout).FirstOrDefault(); } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -198,26 +208,26 @@ public static IEnumerable GetList(this IDbConnection connection, object wh { var currenttype = typeof(T); var name = GetTableName(currenttype); - + var sb = new StringBuilder(); var whereprops = GetAllProperties(whereConditions).ToArray(); sb.Append("Select "); //create a new empty instance of the type to get the base properties BuildSelect(sb, GetScaffoldableProperties().ToArray()); sb.AppendFormat(" from {0}", name); - + if (whereprops.Any()) { sb.Append(" where "); BuildWhere(sb, whereprops, whereConditions); } - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("GetList<{0}>: {1}", currenttype, sb)); - + return connection.Query(sb.ToString(), whereConditions, transaction, true, commandTimeout); } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -237,21 +247,21 @@ public static IEnumerable GetList(this IDbConnection connection, string co { var currenttype = typeof(T); var name = GetTableName(currenttype); - + var sb = new StringBuilder(); sb.Append("Select "); //create a new empty instance of the type to get the base properties BuildSelect(sb, GetScaffoldableProperties().ToArray()); sb.AppendFormat(" from {0}", name); - + sb.Append(" " + conditions); - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("GetList<{0}>: {1}", currenttype, sb)); - + return connection.Query(sb.ToString(), parameters, transaction, true, commandTimeout); } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -264,7 +274,7 @@ public static IEnumerable GetList(this IDbConnection connection) { return connection.GetList(new { }); } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -288,15 +298,15 @@ public static IEnumerable GetListPaged(this IDbConnection connection, int { if (string.IsNullOrEmpty(_getPagedListSql)) throw new Exception("GetListPage is not supported with the current SQL Dialect"); - + if (pageNumber < 1) throw new Exception("Page must be greater than 0"); - + var currenttype = typeof(T); var idProps = GetIdProperties(currenttype).ToList(); if (!idProps.Any()) throw new ArgumentException("Entity must have at least one [Key] property"); - + var name = GetTableName(currenttype); var sb = new StringBuilder(); var query = _getPagedListSql; @@ -304,7 +314,7 @@ public static IEnumerable GetListPaged(this IDbConnection connection, int { orderby = GetColumnName(idProps.First()); } - + //create a new empty instance of the type to get the base properties BuildSelect(sb, GetScaffoldableProperties().ToArray()); query = query.Replace("{SelectColumns}", sb.ToString()); @@ -314,13 +324,13 @@ public static IEnumerable GetListPaged(this IDbConnection connection, int query = query.Replace("{OrderBy}", orderby); query = query.Replace("{WhereClause}", conditions); query = query.Replace("{Offset}", ((pageNumber - 1) * rowsPerPage).ToString()); - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("GetListPaged<{0}>: {1}", currenttype, query)); - + return connection.Query(query, parameters, transaction, true, commandTimeout); } - + /// /// Inserts a row into the database /// By default inserts into the table matching the class name @@ -339,7 +349,7 @@ public static IEnumerable GetListPaged(this IDbConnection connection, int { return Insert(connection, entityToInsert, transaction, commandTimeout); } - + /// /// Inserts a row into the database, using ONLY the properties defined by TEntity /// By default inserts into the table matching the class name @@ -364,10 +374,10 @@ public static TKey Insert(this IDbConnection connection, TEntity .Invoke(null, new object[] { connection,entityToInsert,transaction,commandTimeout }); } var idProps = GetIdProperties(entityToInsert).ToList(); - + if (!idProps.Any()) throw new ArgumentException("Insert only supports an entity with a [Key] or Id property"); - + var keyHasPredefinedValue = false; var baseType = typeof(TKey); var underlyingType = Nullable.GetUnderlyingType(baseType); @@ -376,7 +386,7 @@ public static TKey Insert(this IDbConnection connection, TEntity { throw new Exception("Invalid return type"); } - + var name = GetTableName(entityToInsert); var sb = new StringBuilder(); sb.AppendFormat("insert into {0}", name); @@ -387,7 +397,7 @@ public static TKey Insert(this IDbConnection connection, TEntity sb.Append(" ("); BuildInsertValues(sb); sb.Append(")"); - + if (keytype == typeof(Guid)) { var guidvalue = (Guid)idProps.First().GetValue(entityToInsert, null); @@ -402,7 +412,7 @@ public static TKey Insert(this IDbConnection connection, TEntity } sb.Append(";select '" + idProps.First().GetValue(entityToInsert, null) + "' as id"); } - + if ((keytype == typeof(int) || keytype == typeof(long)) && Convert.ToInt64(idProps.First().GetValue(entityToInsert, null)) == 0) { sb.Append(";" + _getIdentitySql); @@ -411,19 +421,19 @@ public static TKey Insert(this IDbConnection connection, TEntity { keyHasPredefinedValue = true; } - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("Insert: {0}", sb)); - + var r = connection.Query(sb.ToString(), entityToInsert, transaction, true, commandTimeout); - + if (keytype == typeof(Guid) || keyHasPredefinedValue) { return (TKey)idProps.First().GetValue(entityToInsert, null); } return (TKey)r.First().id; } - + /// /// Updates a record or records in the database with only the properties of TEntity /// By default updates records in the table matching the class name @@ -451,25 +461,25 @@ public static int Update(this IDbConnection connection, TEntity entityT StringBuilderCache(masterSb, $"{typeof(TEntity).FullName}_Update", sb => { var idProps = GetIdProperties(entityToUpdate).ToList(); - + if (!idProps.Any()) throw new ArgumentException("Entity must have at least one [Key] or Id property"); - + var name = GetTableName(entityToUpdate); - + sb.AppendFormat("update {0}", name); - + sb.AppendFormat(" set "); BuildUpdateSet(entityToUpdate, sb); sb.Append(" where "); BuildWhere(sb, idProps, entityToUpdate); - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("Update: {0}", sb)); }); return connection.Execute(masterSb.ToString(), entityToUpdate, transaction, commandTimeout); } - + /// /// Deletes a record or records in the database that match the object passed in /// -By default deletes records in the table matching the class name @@ -488,25 +498,25 @@ public static int Delete(this IDbConnection connection, T entityToDelete, IDb var masterSb = new StringBuilder(); StringBuilderCache(masterSb, $"{typeof(T).FullName}_Delete", sb => { - + var idProps = GetIdProperties(entityToDelete).ToList(); - + if (!idProps.Any()) throw new ArgumentException("Entity must have at least one [Key] or Id property"); - + var name = GetTableName(entityToDelete); - + sb.AppendFormat("delete from {0}", name); - + sb.Append(" where "); BuildWhere(sb, idProps, entityToDelete); - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("Delete: {0}", sb)); }); return connection.Execute(masterSb.ToString(), entityToDelete, transaction, commandTimeout); } - + /// /// Deletes a record or records in the database by ID /// By default deletes records in the table matching the class name @@ -525,23 +535,23 @@ public static int Delete(this IDbConnection connection, object id, IDbTransac { var currenttype = typeof(T); var idProps = GetIdProperties(currenttype).ToList(); - - + + if (!idProps.Any()) throw new ArgumentException("Delete only supports an entity with a [Key] or Id property"); - + var name = GetTableName(currenttype); - + var sb = new StringBuilder(); sb.AppendFormat("Delete from {0} where ", name); - + for (var i = 0; i < idProps.Count; i++) { if (i > 0) sb.Append(" and "); sb.AppendFormat("{0} = @{1}", GetColumnName(idProps[i]), idProps[i].Name); } - + var dynParms = new DynamicParameters(); if (idProps.Count == 1) dynParms.Add("@" + idProps.First().Name, id); @@ -550,13 +560,13 @@ public static int Delete(this IDbConnection connection, object id, IDbTransac foreach (var prop in idProps) dynParms.Add("@" + prop.Name, id.GetType().GetProperty(prop.Name).GetValue(id, null)); } - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("Delete<{0}> {1}", currenttype, sb)); - + return connection.Execute(sb.ToString(), dynParms, transaction, commandTimeout); } - + /// /// Deletes a list of records in the database /// By default deletes records in the table matching the class name @@ -579,7 +589,7 @@ public static int DeleteList(this IDbConnection connection, object whereCondi { var currenttype = typeof(T); var name = GetTableName(currenttype); - + var whereprops = GetAllProperties(whereConditions).ToArray(); sb.AppendFormat("Delete from {0}", name); if (whereprops.Any()) @@ -587,13 +597,13 @@ public static int DeleteList(this IDbConnection connection, object whereCondi sb.Append(" where "); BuildWhere(sb, whereprops); } - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("DeleteList<{0}> {1}", currenttype, sb)); }); return connection.Execute(masterSb.ToString(), whereConditions, transaction, commandTimeout); } - + /// /// Deletes a list of records in the database /// By default deletes records in the table matching the class name @@ -619,19 +629,19 @@ public static int DeleteList(this IDbConnection connection, string conditions throw new ArgumentException("DeleteList requires a where clause"); if (!conditions.ToLower().Contains("where")) throw new ArgumentException("DeleteList requires a where clause and must contain the WHERE keyword"); - + var currenttype = typeof(T); var name = GetTableName(currenttype); - + sb.AppendFormat("Delete from {0}", name); sb.Append(" " + conditions); - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("DeleteList<{0}> {1}", currenttype, sb)); }); return connection.Execute(masterSb.ToString(), parameters, transaction, commandTimeout); } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -655,13 +665,13 @@ public static int RecordCount(this IDbConnection connection, string condition sb.Append("Select count(1)"); sb.AppendFormat(" from {0}", name); sb.Append(" " + conditions); - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("RecordCount<{0}>: {1}", currenttype, sb)); - + return connection.ExecuteScalar(sb.ToString(), parameters, transaction, commandTimeout); } - + /// /// By default queries the table matching the class name /// -Table name can be overridden by adding an attribute on your class [Table("YourTableName")] @@ -679,7 +689,7 @@ public static int RecordCount(this IDbConnection connection, object whereCond { var currenttype = typeof(T); var name = GetTableName(currenttype); - + var sb = new StringBuilder(); var whereprops = GetAllProperties(whereConditions).ToArray(); sb.Append("Select count(1)"); @@ -689,31 +699,31 @@ public static int RecordCount(this IDbConnection connection, object whereCond sb.Append(" where "); BuildWhere(sb, whereprops); } - + if (Debugger.IsAttached) Trace.WriteLine(String.Format("RecordCount<{0}>: {1}", currenttype, sb)); - + return connection.ExecuteScalar(sb.ToString(), whereConditions, transaction, commandTimeout); } - + //build update statement based on list on an entity private static void BuildUpdateSet(T entityToUpdate, StringBuilder masterSb) { StringBuilderCache(masterSb, $"{typeof(T).FullName}_BuildUpdateSet", sb => { var nonIdProps = GetUpdateableProperties(entityToUpdate).ToArray(); - + for (var i = 0; i < nonIdProps.Length; i++) { var property = nonIdProps[i]; - + sb.AppendFormat("{0} = @{1}", GetColumnName(property), property.Name); if (i < nonIdProps.Length - 1) sb.AppendFormat(", "); } }); } - + //build select clause based on list of properties skipping ones with the IgnoreSelect and NotMapped attribute private static void BuildSelect(StringBuilder masterSb, IEnumerable props) { @@ -724,27 +734,29 @@ private static void BuildSelect(StringBuilder masterSb, IEnumerable attr.GetType().Name == typeof(IgnoreSelectAttribute).Name || attr.GetType().Name == typeof(NotMappedAttribute).Name)) continue; - + var colProperties = _columnPropertiesResolver.ResolveColumnProperties(property); + + if (colProperties.IsIgnoredInSelect || colProperties.IsNotMapped) continue; + if (addedAny) sb.Append(","); - sb.Append(GetColumnName(property)); + + sb.Append(GetColumnName(property)); // use Encapsulate(colProperties.ColumnName) when _columnNameResolver is dropped //if there is a custom column name add an "as customcolumnname" to the item so it maps properly - if (property.GetCustomAttributes(true).SingleOrDefault(attr => attr.GetType().Name == typeof(ColumnAttribute).Name) != null) - sb.Append(" as " + Encapsulate(property.Name)); + if (!string.IsNullOrEmpty(colProperties.AliasName)) + sb.Append(" as " + Encapsulate(colProperties.AliasName)); addedAny = true; } }); } - + private static void BuildWhere(StringBuilder sb, IEnumerable idProps, object whereConditions = null) { var propertyInfos = idProps.ToArray(); for (var i = 0; i < propertyInfos.Count(); i++) { var useIsNull = false; - + //match up generic properties to source entity properties to allow fetching of the column attribute //the anonymous object used for search doesn't have the custom attributes attached to them so this allows us to build the correct where clause //by converting the model type to the database column name via the column attribute @@ -766,12 +778,12 @@ private static void BuildWhere(StringBuilder sb, IEnumerable(StringBuilder masterSb) { StringBuilderCache(masterSb, $"{typeof(T).FullName}_BuildInsertValues", sb => { - + var props = GetScaffoldableProperties().ToArray(); for (var i = 0; i < props.Count(); i++) { var property = props.ElementAt(i); + var colProperties = _columnPropertiesResolver.ResolveColumnProperties(property); if (property.PropertyType != typeof(Guid) && property.PropertyType != typeof(string) - && property.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(KeyAttribute).Name) - && property.GetCustomAttributes(true).All(attr => attr.GetType().Name != typeof(RequiredAttribute).Name)) + && colProperties.IsKey && !colProperties.IsRequired) continue; - if (property.GetCustomAttributes(true).Any(attr => - attr.GetType().Name == typeof(IgnoreInsertAttribute).Name || - attr.GetType().Name == typeof(NotMappedAttribute).Name || - attr.GetType().Name == typeof(ReadOnlyAttribute).Name && IsReadOnly(property)) - ) continue; - - if (property.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) && property.GetCustomAttributes(true).All(attr => attr.GetType().Name != typeof(RequiredAttribute).Name) && property.PropertyType != typeof(Guid)) continue; - + + if (colProperties.IsIgnoredInInsert || colProperties.IsNotMapped || colProperties.IsReadOnly) + continue; + + if (property.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) && !colProperties.IsRequired && property.PropertyType != typeof(Guid)) + continue; + sb.AppendFormat("@{0}", property.Name); if (i < props.Count() - 1) sb.Append(", "); @@ -807,7 +818,7 @@ private static void BuildInsertValues(StringBuilder masterSb) sb.Remove(sb.Length - 2, 2); }); } - + //build insert parameters which include all properties in the class that are not: //marked with the Editable(false) attribute //marked with the [Key] attribute @@ -819,22 +830,23 @@ private static void BuildInsertParameters(StringBuilder masterSb) StringBuilderCache(masterSb, $"{typeof(T).FullName}_BuildInsertParameters", sb => { var props = GetScaffoldableProperties().ToArray(); - + for (var i = 0; i < props.Count(); i++) { var property = props.ElementAt(i); + var colProperties = _columnPropertiesResolver.ResolveColumnProperties(property); + if (property.PropertyType != typeof(Guid) && property.PropertyType != typeof(string) - && property.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(KeyAttribute).Name) - && property.GetCustomAttributes(true).All(attr => attr.GetType().Name != typeof(RequiredAttribute).Name)) + && colProperties.IsKey && !colProperties.IsRequired) continue; - if (property.GetCustomAttributes(true).Any(attr => - attr.GetType().Name == typeof(IgnoreInsertAttribute).Name || - attr.GetType().Name == typeof(NotMappedAttribute).Name || - attr.GetType().Name == typeof(ReadOnlyAttribute).Name && IsReadOnly(property))) continue; - - if (property.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) && property.GetCustomAttributes(true).All(attr => attr.GetType().Name != typeof(RequiredAttribute).Name) && property.PropertyType != typeof(Guid)) continue; - - sb.Append(GetColumnName(property)); + + if (colProperties.IsIgnoredInInsert || colProperties.IsNotMapped || colProperties.IsReadOnly) + continue; + + if (property.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) && !colProperties.IsRequired && property.PropertyType != typeof(Guid)) + continue; + + sb.Append(Encapsulate(colProperties.ColumnName)); if (i < props.Count() - 1) sb.Append(", "); } @@ -849,53 +861,15 @@ private static IEnumerable GetAllProperties(T entity) where T : if (entity == null) return new PropertyInfo[0]; return entity.GetType().GetProperties(); } - + //Get all properties that are not decorated with the Editable(false) attribute private static IEnumerable GetScaffoldableProperties() { IEnumerable props = typeof(T).GetProperties(); - - props = props.Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(EditableAttribute).Name && !IsEditable(p)) == false); - - - return props.Where(p => p.PropertyType.IsSimpleType() || IsEditable(p)); + + return props.Where(p => _columnPropertiesResolver.ResolveColumnProperties(p).IsEditable); } - - //Determine if the Attribute has an AllowEdit key and return its boolean state - //fake the funk and try to mimic EditableAttribute in System.ComponentModel.DataAnnotations - //This allows use of the DataAnnotations property in the model and have the SimpleCRUD engine just figure it out without a reference - private static bool IsEditable(PropertyInfo pi) - { - var attributes = pi.GetCustomAttributes(false); - if (attributes.Length > 0) - { - dynamic write = attributes.FirstOrDefault(x => x.GetType().Name == typeof(EditableAttribute).Name); - if (write != null) - { - return write.AllowEdit; - } - } - return false; - } - - - //Determine if the Attribute has an IsReadOnly key and return its boolean state - //fake the funk and try to mimic ReadOnlyAttribute in System.ComponentModel - //This allows use of the DataAnnotations property in the model and have the SimpleCRUD engine just figure it out without a reference - private static bool IsReadOnly(PropertyInfo pi) - { - var attributes = pi.GetCustomAttributes(false); - if (attributes.Length > 0) - { - dynamic write = attributes.FirstOrDefault(x => x.GetType().Name == typeof(ReadOnlyAttribute).Name); - if (write != null) - { - return write.IsReadOnly; - } - } - return false; - } - + //Get all properties that are: //Not named Id //Not marked with the Key attribute @@ -905,20 +879,17 @@ private static bool IsReadOnly(PropertyInfo pi) private static IEnumerable GetUpdateableProperties(T entity) { var updateableProperties = GetScaffoldableProperties(); - //remove ones with ID - updateableProperties = updateableProperties.Where(p => !p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)); - //remove ones with key attribute - updateableProperties = updateableProperties.Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(KeyAttribute).Name) == false); - //remove ones that are readonly - updateableProperties = updateableProperties.Where(p => p.GetCustomAttributes(true).Any(attr => (attr.GetType().Name == typeof(ReadOnlyAttribute).Name) && IsReadOnly(p)) == false); - //remove ones with IgnoreUpdate attribute - updateableProperties = updateableProperties.Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(IgnoreUpdateAttribute).Name) == false); - //remove ones that are not mapped - updateableProperties = updateableProperties.Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(NotMappedAttribute).Name) == false); - - return updateableProperties; + return updateableProperties.Where(p => { + var cp = _columnPropertiesResolver.ResolveColumnProperties(p); + //remove ones with ID + return !p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) && + !cp.IsKey && + !cp.IsReadOnly && + !cp.IsIgnoredInUpdate && + !cp.IsNotMapped; + }); } - + //Get all properties that are named Id or have the Key attribute //For Inserts and updates we have a whole entity so this method is used private static IEnumerable GetIdProperties(object entity) @@ -926,15 +897,15 @@ private static IEnumerable GetIdProperties(object entity) var type = entity.GetType(); return GetIdProperties(type); } - + //Get all properties that are named Id or have the Key attribute //For Get(id) and Delete(id) we don't have an entity, just the type so this method is used private static IEnumerable GetIdProperties(Type type) { - var tp = type.GetProperties().Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(KeyAttribute).Name)).ToList(); + var tp = type.GetProperties().Where(p => _columnPropertiesResolver.ResolveColumnProperties(p).IsKey).ToList(); return tp.Any() ? tp : type.GetProperties().Where(p => p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)); } - + //Gets the table name for this entity //For Inserts and updates we have a whole entity so this method is used //Uses class name by default and overrides if the class has a Table attribute @@ -943,40 +914,22 @@ private static string GetTableName(object entity) var type = entity.GetType(); return GetTableName(type); } - + //Gets the table name for this type //For Get(id) and Delete(id) we don't have an entity, just the type so this method is used //Use dynamic type to be able to handle both our Table-attribute and the DataAnnotation //Uses class name by default and overrides if the class has a Table attribute private static string GetTableName(Type type) { - string tableName; - - if (TableNames.TryGetValue(type, out tableName)) - return tableName; - - tableName = _tableNameResolver.ResolveTableName(type); - - TableNames.AddOrUpdate(type, tableName, (t, v) => tableName); - - return tableName; + return _tableNameResolver.ResolveTableName(type); } - + private static string GetColumnName(PropertyInfo propertyInfo) - { - string columnName, key = string.Format("{0}.{1}", propertyInfo.DeclaringType, propertyInfo.Name); - - if (ColumnNames.TryGetValue(key, out columnName)) - return columnName; - - columnName = _columnNameResolver.ResolveColumnName(propertyInfo); - - ColumnNames.AddOrUpdate(key, columnName, (t, v) => columnName); - - return columnName; + { + return Encapsulate(_columnNameResolver.ResolveColumnName(propertyInfo)); } - - private static string Encapsulate(string databaseword) + + public static string Encapsulate(string databaseword) { return string.Format(_encapsulation, databaseword); } @@ -998,7 +951,7 @@ public static Guid SequentialGuid() bytes[4] = (byte)time.Second; return new Guid(bytes); } - + /// /// Database server dialects /// @@ -1011,23 +964,65 @@ public enum Dialect Oracle, DB2 } - + public interface ITableNameResolver { string ResolveTableName(Type type); } - + + [Obsolete("Implement IColumnPropertiesResolver instead.")] public interface IColumnNameResolver { string ResolveColumnName(PropertyInfo propertyInfo); } - - public class TableNameResolver : ITableNameResolver + + public interface IColumnPropertiesResolver { + IColumnProperties ResolveColumnProperties(PropertyInfo propertyInfo); + } + + public interface IColumnProperties + { + string AliasName { get; } + string ColumnName { get; } + bool IsKey { get; } + bool IsRequired { get; } + bool IsEditable { get; } + bool IsNotMapped { get; } + bool IsReadOnly { get; } + bool IsIgnoredInSelect { get; } + bool IsIgnoredInInsert { get; } + bool IsIgnoredInUpdate { get; } + } + + public class ColumnProperties : IColumnProperties + { + public string AliasName { get; set; } + public string ColumnName { get; set; } + public bool IsKey { get; set; } + public bool IsRequired { get; set; } + public bool IsEditable { get; set; } + public bool IsNotMapped { get; set; } + public bool IsReadOnly { get; set; } + public bool IsIgnoredInSelect { get; set; } + public bool IsIgnoredInInsert { get; set; } + public bool IsIgnoredInUpdate { get; set; } + } + + public class TableMapResolver : IColumnPropertiesResolver, ITableNameResolver, IColumnNameResolver + { + protected readonly Dictionary cacheTableNames = new Dictionary(); + protected readonly Dictionary cacheColumnsProperties = new Dictionary(); + public virtual string ResolveTableName(Type type) { + if (cacheTableNames.ContainsKey(type)) + { + return cacheTableNames[type]; + } + string tableName; - + if (GetDialect() == Dialect.DB2.ToString()) { tableName = type.Name; @@ -1036,7 +1031,7 @@ public virtual string ResolveTableName(Type type) { tableName = Encapsulate(type.Name); } - + var tableattr = type.GetCustomAttributes(true).SingleOrDefault(attr => attr.GetType().Name == typeof(TableAttribute).Name) as dynamic; if (tableattr != null) { @@ -1054,38 +1049,53 @@ public virtual string ResolveTableName(Type type) //Schema doesn't exist on this attribute. } } - + cacheTableNames.Add(type, tableName); return tableName; } - } - - public class ColumnNameResolver : IColumnNameResolver - { public virtual string ResolveColumnName(PropertyInfo propertyInfo) { - string columnName; - - if (GetDialect() == Dialect.DB2.ToString()) - { - columnName = propertyInfo.Name; - } - else + return ResolveColumnProperties(propertyInfo).ColumnName; + } + + public virtual IColumnProperties ResolveColumnProperties(PropertyInfo propertyInfo) + { + if (cacheColumnsProperties.ContainsKey(propertyInfo)) { - columnName = Encapsulate(propertyInfo.Name); + return cacheColumnsProperties[propertyInfo]; } - - var columnattr = propertyInfo.GetCustomAttributes(true).SingleOrDefault(attr => attr.GetType().Name == typeof(ColumnAttribute).Name) as dynamic; - if (columnattr != null) + + var columnName = propertyInfo.Name; + string aliasName = null; + + var attributes = propertyInfo.GetCustomAttributes(true); + var columnNameAttr = attributes.SingleOrDefault(attr => attr.GetType().Name == typeof(ColumnAttribute).Name) as dynamic; + if (columnNameAttr != null) { - columnName = Encapsulate(columnattr.Name); + aliasName = columnName; + columnName = columnNameAttr.Name; if (Debugger.IsAttached) Trace.WriteLine(String.Format("Column name for type overridden from {0} to {1}", propertyInfo.Name, columnName)); } - return columnName; + + var colProperties = new ColumnProperties + { + AliasName = aliasName, + ColumnName = columnName, + IsKey = attributes.Any(attr => attr.GetType().Name == typeof(KeyAttribute).Name), + IsRequired = attributes.Any(attr => attr.GetType().Name == typeof(RequiredAttribute).Name), + IsEditable = (attributes.FirstOrDefault(attr => attr.GetType().Name == typeof(EditableAttribute).Name) as dynamic)?.AllowEdit ?? propertyInfo.PropertyType.IsSimpleType(), + IsReadOnly = (attributes.FirstOrDefault(attr => attr.GetType().Name == typeof(ReadOnlyAttribute).Name) as dynamic)?.IsReadOnly ?? false, + IsIgnoredInSelect = attributes.Any(attr => attr.GetType().Name == typeof(IgnoreSelectAttribute).Name), + IsIgnoredInInsert = attributes.Any(attr => attr.GetType().Name == typeof(IgnoreInsertAttribute).Name), + IsIgnoredInUpdate = attributes.Any(attr => attr.GetType().Name == typeof(IgnoreUpdateAttribute).Name), + IsNotMapped = attributes.Any(attr => attr.GetType().Name == typeof(NotMappedAttribute).Name), + }; + cacheColumnsProperties.Add(propertyInfo, colProperties); + return colProperties; } } } - + /// /// Optional Table attribute. /// You can use the System.ComponentModel.DataAnnotations version in its place to specify the table name of a poco @@ -1110,7 +1120,7 @@ public TableAttribute(string tableName) /// public string Schema { get; set; } } - + /// /// Optional Column attribute. /// You can use the System.ComponentModel.DataAnnotations version in its place to specify the table name of a poco @@ -1131,7 +1141,7 @@ public ColumnAttribute(string columnName) /// public string Name { get; private set; } } - + /// /// Optional Key attribute. /// You can use the System.ComponentModel.DataAnnotations version in its place to specify the Primary Key of a poco @@ -1140,7 +1150,7 @@ public ColumnAttribute(string columnName) public class KeyAttribute : Attribute { } - + /// /// Optional NotMapped attribute. /// You can use the System.ComponentModel.DataAnnotations version in its place to specify that the property is not mapped @@ -1149,7 +1159,7 @@ public class KeyAttribute : Attribute public class NotMappedAttribute : Attribute { } - + /// /// Optional Key attribute. /// You can use the System.ComponentModel.DataAnnotations version in its place to specify a required property of a poco @@ -1158,7 +1168,7 @@ public class NotMappedAttribute : Attribute public class RequiredAttribute : Attribute { } - + /// /// Optional Editable attribute. /// You can use the System.ComponentModel.DataAnnotations version in its place to specify the properties that are editable @@ -1179,7 +1189,7 @@ public EditableAttribute(bool iseditable) /// public bool AllowEdit { get; private set; } } - + /// /// Optional Readonly attribute. /// You can use the System.ComponentModel version in its place to specify the properties that are editable @@ -1200,7 +1210,7 @@ public ReadOnlyAttribute(bool isReadOnly) /// public bool IsReadOnly { get; private set; } } - + /// /// Optional IgnoreSelect attribute. /// Custom for Dapper.SimpleCRUD to exclude a property from Select methods @@ -1209,7 +1219,7 @@ public ReadOnlyAttribute(bool isReadOnly) public class IgnoreSelectAttribute : Attribute { } - + /// /// Optional IgnoreInsert attribute. /// Custom for Dapper.SimpleCRUD to exclude a property from Insert methods @@ -1218,7 +1228,7 @@ public class IgnoreSelectAttribute : Attribute public class IgnoreInsertAttribute : Attribute { } - + /// /// Optional IgnoreUpdate attribute. /// Custom for Dapper.SimpleCRUD to exclude a property from Update methods @@ -1227,9 +1237,9 @@ public class IgnoreInsertAttribute : Attribute public class IgnoreUpdateAttribute : Attribute { } - + } - + internal static class TypeExtension { //You can't insert or update complex types. Lets filter them out. @@ -1261,7 +1271,7 @@ public static bool IsSimpleType(this Type type) }; return simpleTypes.Contains(type) || type.IsEnum; } - + public static string CacheKey(this IEnumerable props) { return string.Join(",",props.Select(p=> p.DeclaringType.FullName + "." + p.Name).ToArray()); diff --git a/Dapper.SimpleCRUDFluentTableMap.Tests/Assert.cs b/Dapper.SimpleCRUDFluentTableMap.Tests/Assert.cs new file mode 100644 index 0000000..609a6e2 --- /dev/null +++ b/Dapper.SimpleCRUDFluentTableMap.Tests/Assert.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Dapper.SimpleCRUDFluentTableMapTests +{ + /// + /// Assert extensions borrowed from Sam's code in DapperTests + /// + static class Assert + { + public static void IsEqualTo(this T obj, T other) + { + if (!obj.Equals(other)) + { + throw new ApplicationException(string.Format("{0} should be equal to {1}", obj, other)); + } + } + + public static void IsSequenceEqualTo(this IEnumerable obj, IEnumerable other) + { + if (!obj.SequenceEqual(other)) + { + throw new ApplicationException(string.Format("{0} should be equal to {1}", obj, other)); + } + } + + public static void IsFalse(this bool b) + { + if (b) + { + throw new ApplicationException("Expected false"); + } + } + + public static void IsTrue(this bool b) + { + if (!b) + { + throw new ApplicationException("Expected true"); + } + } + + public static void IsNull(this object obj) + { + if (obj != null) + { + throw new ApplicationException("Expected null"); + } + } + + } +} \ No newline at end of file diff --git a/Dapper.SimpleCRUDFluentTableMap.Tests/Dapper.SimpleCRUDFluentTableMap.Tests.csproj b/Dapper.SimpleCRUDFluentTableMap.Tests/Dapper.SimpleCRUDFluentTableMap.Tests.csproj new file mode 100644 index 0000000..2b6a2c3 --- /dev/null +++ b/Dapper.SimpleCRUDFluentTableMap.Tests/Dapper.SimpleCRUDFluentTableMap.Tests.csproj @@ -0,0 +1,43 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NETCORE;NETSTANDARD;NETSTANDARD2_1 + + + + NET472;NETFULL + + + diff --git a/Dapper.SimpleCRUDFluentTableMap.Tests/Program.cs b/Dapper.SimpleCRUDFluentTableMap.Tests/Program.cs new file mode 100644 index 0000000..fed2640 --- /dev/null +++ b/Dapper.SimpleCRUDFluentTableMap.Tests/Program.cs @@ -0,0 +1,321 @@ +using System; +using System.Data.SqlClient; +using System.Data.SQLite; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using IBM.Data.DB2.Core; +using MySql.Data.MySqlClient; +using Npgsql; + +namespace Dapper.SimpleCRUDFluentTableMapTests +{ + class Program + { + public const string SQLServerName = @".\sqlexpress"; + + static void Main() + { + Setup(); + RunTests(); + + SetupSqLite(); + RunTestsSqLite(); + + //PostgreSQL tests assume port 5432 with username postgres and password postgrespass + //they are commented out by default since postgres setup is required to run tests + //SetupPg(); + //RunTestsPg(); + + //MySQL tests assume port 3306 with username admin and password admin + //they are commented out by default since mysql setup is required to run tests + //SetupMySQL(); + //RunTestsMySQL(); + + //DB2 tests assume port 50000 with username db2admin and password db2admin + //they are commented out by default since db2 setup is required to run tests + //SetupDB2(); + //RunTestsDB2(); + } + + private static void Setup() + { + using (var connection = new SqlConnection($@"Data Source={SQLServerName};Initial Catalog=Master;Integrated Security=True")) + { + connection.Open(); + try + { + connection.Execute(@" DROP DATABASE DapperSimpleCrudTestDb; "); + } + catch (Exception) + { } + + connection.Execute(@" CREATE DATABASE DapperSimpleCrudTestDb; "); + } + + using (var connection = new SqlConnection($@"Data Source ={SQLServerName};Initial Catalog=DapperSimpleCrudTestDb;Integrated Security=True")) + { + connection.Open(); + connection.Execute(@" create table Users (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null, ScheduledDayOff int null, CreatedDate datetime DEFAULT(getdate())) "); + connection.Execute(@" create table Car (CarId int IDENTITY(1,1) not null, Id int null, Make nvarchar(100) not null, Model nvarchar(100) not null) "); + connection.Execute(@" create table BigCar (CarId bigint IDENTITY(2147483650,1) not null, Make nvarchar(100) not null, Model nvarchar(100) not null) "); + connection.Execute(@" create table City (Name nvarchar(100) not null, Population int not null) "); + connection.Execute(@" CREATE SCHEMA Log; "); + connection.Execute(@" create table Log.CarLog (Id int IDENTITY(1,1) not null, LogNotes nvarchar(100) NOT NULL) "); + connection.Execute(@" CREATE TABLE [dbo].[GUIDTest]([Id] [uniqueidentifier] NOT NULL,[name] [varchar](50) NOT NULL, CONSTRAINT [PK_GUIDTest] PRIMARY KEY CLUSTERED ([Id] ASC))"); + connection.Execute(@" create table StrangeColumnNames (ItemId int IDENTITY(1,1) not null Primary Key, word nvarchar(100) not null, colstringstrangeword nvarchar(100) not null, KeywordedProperty nvarchar(100) null)"); + connection.Execute(@" create table UserWithoutAutoIdentity (Id int not null Primary Key, Name nvarchar(100) not null, Age int not null) "); + connection.Execute(@" create table IgnoreColumns (Id int IDENTITY(1,1) not null Primary Key, IgnoreInsert nvarchar(100) null, IgnoreUpdate nvarchar(100) null, IgnoreSelect nvarchar(100) null, IgnoreAll nvarchar(100) null) "); + connection.Execute(@" CREATE TABLE GradingScale ([ScaleID] [int] IDENTITY(1,1) NOT NULL, [AppID] [int] NULL, [ScaleName] [nvarchar](50) NOT NULL, [IsDefault] [bit] NOT NULL)"); + connection.Execute(@" CREATE TABLE KeyMaster ([Key1] [int] NOT NULL, [Key2] [int] NOT NULL, CONSTRAINT [PK_KeyMaster] PRIMARY KEY CLUSTERED ([Key1] ASC, [Key2] ASC))"); + connection.Execute(@" CREATE TABLE [dbo].[stringtest]([stringkey] [varchar](50) NOT NULL,[name] [varchar](50) NOT NULL, CONSTRAINT [PK_stringkey] PRIMARY KEY CLUSTERED ([stringkey] ASC))"); + + } + Console.WriteLine("Created database"); + } + + private static void SetupPg() + { + using (var connection = new NpgsqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", "localhost", "5432", "postgres", "postgrespass", "postgres"))) + { + connection.Open(); + // drop database + connection.Execute("DROP DATABASE IF EXISTS testdb;"); + connection.Execute("CREATE DATABASE testdb WITH OWNER = postgres ENCODING = 'UTF8' CONNECTION LIMIT = -1;"); + } + System.Threading.Thread.Sleep(1000); + + using (var connection = new NpgsqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", "localhost", "5432", "postgres", "postgrespass", "testdb"))) + { + connection.Open(); + connection.Execute(@" create table Users (Id SERIAL PRIMARY KEY, Name varchar not null, Age int not null, ScheduledDayOff int null, CreatedDate date not null default CURRENT_DATE) "); + connection.Execute(@" create table Car (CarId SERIAL PRIMARY KEY, Id int null, Make varchar not null, Model varchar not null) "); + connection.Execute(@" create table BigCar (CarId BIGSERIAL PRIMARY KEY, Make varchar not null, Model varchar not null) "); + connection.Execute(@" alter sequence bigcar_carid_seq RESTART WITH 2147483650"); + connection.Execute(@" create table City (Name varchar not null, Population int not null) "); + connection.Execute(@" CREATE SCHEMA Log; "); + connection.Execute(@" create table Log.CarLog (Id SERIAL PRIMARY KEY, LogNotes varchar NOT NULL) "); + connection.Execute(@" CREATE TABLE GUIDTest(Id uuid PRIMARY KEY,name varchar NOT NULL)"); + connection.Execute(@" create table StrangeColumnNames (ItemId Serial PRIMARY KEY, word varchar not null, colstringstrangeword varchar, keywordedproperty varchar) "); + connection.Execute(@" create table UserWithoutAutoIdentity (Id int PRIMARY KEY, Name varchar not null, Age int not null) "); + + } + + } + + private static void SetupSqLite() + { + File.Delete(Directory.GetCurrentDirectory() + "\\MyDatabase.sqlite"); + SQLiteConnection.CreateFile("MyDatabase.sqlite"); + var connection = new SQLiteConnection("Data Source=MyDatabase.sqlite;Version=3;"); + using (connection) + { + connection.Open(); + connection.Execute(@" create table Users (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name nvarchar(100) not null, Age int not null, ScheduledDayOff int null, CreatedDate datetime default current_timestamp ) "); + connection.Execute(@" create table Car (CarId INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER null, Make nvarchar(100) not null, Model nvarchar(100) not null) "); + connection.Execute(@" create table BigCar (CarId INTEGER PRIMARY KEY AUTOINCREMENT, Make nvarchar(100) not null, Model nvarchar(100) not null) "); + connection.Execute(@" insert into BigCar (CarId,Make,Model) Values (2147483649,'car','car') "); + connection.Execute(@" create table City (Name nvarchar(100) not null, Population int not null) "); + connection.Execute(@" CREATE TABLE GUIDTest([Id] [uniqueidentifier] NOT NULL,[name] [varchar](50) NOT NULL, CONSTRAINT [PK_GUIDTest] PRIMARY KEY ([Id] ASC))"); + connection.Execute(@" create table StrangeColumnNames (ItemId INTEGER PRIMARY KEY AUTOINCREMENT, word nvarchar(100) not null, colstringstrangeword nvarchar(100) not null, KeywordedProperty nvarchar(100) null) "); + connection.Execute(@" create table UserWithoutAutoIdentity (Id INTEGER PRIMARY KEY, Name nvarchar(100) not null, Age int not null) "); + connection.Execute(@" create table IgnoreColumns (Id INTEGER PRIMARY KEY AUTOINCREMENT, IgnoreInsert nvarchar(100) null, IgnoreUpdate nvarchar(100) null, IgnoreSelect nvarchar(100) null, IgnoreAll nvarchar(100) null) "); + connection.Execute(@" CREATE TABLE KeyMaster (Key1 INTEGER NOT NULL, Key2 INTEGER NOT NULL, PRIMARY KEY ([Key1], [Key2]))"); + connection.Execute(@" CREATE TABLE stringtest (stringkey nvarchar(50) NOT NULL,name nvarchar(50) NOT NULL, PRIMARY KEY ([stringkey] ASC))"); + + } + } + + private static void SetupMySQL() + { + using (var connection = new MySqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", "localhost", "3306", "root", "admin", "sys"))) + { + connection.Open(); + // drop database + connection.Execute("DROP DATABASE IF EXISTS testdb;"); + connection.Execute("CREATE DATABASE testdb;"); + } + System.Threading.Thread.Sleep(1000); + + using (var connection = new MySqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", "localhost", "3306", "root", "admin", "testdb"))) + { + connection.Open(); + connection.Execute(@" create table Users (Id INTEGER PRIMARY KEY AUTO_INCREMENT, Name nvarchar(100) not null, Age int not null, ScheduledDayOff int null, CreatedDate datetime default current_timestamp ) "); + connection.Execute(@" create table Car (CarId INTEGER PRIMARY KEY AUTO_INCREMENT, Id INTEGER null, Make nvarchar(100) not null, Model nvarchar(100) not null) "); + connection.Execute(@" create table BigCar (CarId BIGINT PRIMARY KEY AUTO_INCREMENT, Make nvarchar(100) not null, Model nvarchar(100) not null) "); + connection.Execute(@" insert into BigCar (CarId,Make,Model) Values (2147483649,'car','car') "); + connection.Execute(@" create table City (Name nvarchar(100) not null, Population int not null) "); + connection.Execute(@" CREATE TABLE GUIDTest(Id CHAR(38) NOT NULL,name varchar(50) NOT NULL, CONSTRAINT PK_GUIDTest PRIMARY KEY (Id ASC))"); + connection.Execute(@" create table StrangeColumnNames (ItemId INTEGER PRIMARY KEY AUTO_INCREMENT, word nvarchar(100) not null, colstringstrangeword nvarchar(100) not null, KeywordedProperty nvarchar(100) null) "); + connection.Execute(@" create table UserWithoutAutoIdentity (Id INTEGER PRIMARY KEY, Name nvarchar(100) not null, Age int not null) "); + connection.Execute(@" create table IgnoreColumns (Id INTEGER PRIMARY KEY AUTO_INCREMENT, IgnoreInsert nvarchar(100) null, IgnoreUpdate nvarchar(100) null, IgnoreSelect nvarchar(100) null, IgnoreAll nvarchar(100) null) "); + connection.Execute(@" CREATE table KeyMaster (Key1 INTEGER NOT NULL, Key2 INTEGER NOT NULL, CONSTRAINT PK_KeyMaster PRIMARY KEY CLUSTERED (Key1 ASC, Key2 ASC))"); + } + + } + + + private static void RunTests() + { + var stopwatch = Stopwatch.StartNew(); + var sqltester = new Tests(SimpleCRUD.Dialect.SQLServer); + foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + var testwatch = Stopwatch.StartNew(); + Console.Write("Running " + method.Name + " in sql server"); + method.Invoke(sqltester, null); + testwatch.Stop(); + Console.WriteLine(" - OK! {0}ms", testwatch.ElapsedMilliseconds); + } + stopwatch.Stop(); + + // Write result + Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed); + + using (var connection = new SqlConnection($@"Data Source={SQLServerName};Initial Catalog=Master;Integrated Security=True")) + { + connection.Open(); + try + { + //drop any remaining connections, then drop the db. + connection.Execute(@" alter database DapperSimpleCrudTestDb set single_user with rollback immediate; DROP DATABASE DapperSimpleCrudTestDb; "); + } + catch (Exception) + { } + } + Console.Write("SQL Server testing complete."); + Console.ReadKey(); + } + + private static void RunTestsPg() + { + var stopwatch = Stopwatch.StartNew(); + var pgtester = new Tests(SimpleCRUD.Dialect.PostgreSQL); + foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + var testwatch = Stopwatch.StartNew(); + Console.Write("Running " + method.Name + " in PostgreSQL"); + method.Invoke(pgtester, null); + Console.WriteLine(" - OK! {0}ms", testwatch.ElapsedMilliseconds); + } + stopwatch.Stop(); + Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed); + + Console.Write("PostgreSQL testing complete."); + Console.ReadKey(); + } + + private static void RunTestsSqLite() + { + var stopwatch = Stopwatch.StartNew(); + var pgtester = new Tests(SimpleCRUD.Dialect.SQLite); + foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + //skip schema tests + if (method.Name.Contains("Schema")) continue; + var testwatch = Stopwatch.StartNew(); + Console.Write("Running " + method.Name + " in SQLite"); + method.Invoke(pgtester, null); + Console.WriteLine(" - OK! {0}ms", testwatch.ElapsedMilliseconds); + } + stopwatch.Stop(); + Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed); + Console.Write("SQLite testing complete."); + Console.ReadKey(); + } + + private static void RunTestsMySQL() + { + var stopwatch = Stopwatch.StartNew(); + var mysqltester = new Tests(SimpleCRUD.Dialect.MySQL); + foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + //skip schema tests + if (method.Name.Contains("Schema")) continue; + if (method.Name.Contains("Guid")) continue; + + var testwatch = Stopwatch.StartNew(); + Console.Write("Running " + method.Name + " in MySQL"); + method.Invoke(mysqltester, null); + Console.WriteLine(" - OK! {0}ms", testwatch.ElapsedMilliseconds); + } + stopwatch.Stop(); + Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed); + + Console.Write("MySQL testing complete."); + Console.ReadKey(); + } + private static void SetupDB2() + { + System.Security.SecureString secureString = new System.Security.SecureString(); + ProcessStartInfo processStartInfo; + string user = "db2admin"; + + foreach (char c in user) + { + secureString.AppendChar(c); + } + + processStartInfo = new ProcessStartInfo(); + processStartInfo.FileName = @"db2.exe"; + processStartInfo.UserName = user; + processStartInfo.Password = secureString; + processStartInfo.EnvironmentVariables["DB2CLP"] = "DB20FADE"; + processStartInfo.UseShellExecute = false; + processStartInfo.WorkingDirectory = @"C:\Program Files\IBM\SQLLIB\bin"; + + foreach (string command in (new string[] { "drop database testdb", "create database testdb" })) + { + Process process = new Process(); + + processStartInfo.Arguments = command; + + process.StartInfo = processStartInfo; + + process.Start(); + process.WaitForExit(); + } + + using (var connection = new DB2Connection(string.Format("Server={0};UID={1};PWD={2};Database={3};", "localhost:50000", user, user, "testdb"))) + { + connection.Open(); + + // Use the alias for pass the delete test. + connection.Execute(@"create table ""Users"" (Id int not null generated by default as identity, Name varchar(100) not null, Age int not null, ScheduledDayOff int, CreatedDate date not null default current_date, primary key(Id));"); + connection.Execute(@"create alias users for ""Users"""); + connection.Execute(@"create table Car (CarId int not null generated by default as identity, Id int, Make varchar(100) not null, Model varchar(100) not null, primary key(CarId));"); + connection.Execute(@"create table BigCar (CarId bigint not null generated by default as identity(start with 2147483650), Make varchar(100) not null, Model varchar(100) not null, primary key(CarId));"); + connection.Execute(@"create table City (Name varchar(100) not null, Population int not null);"); + connection.Execute(@"create schema ""Log"";"); + connection.Execute(@"create table ""Log"".""CarLog"" (Id int not null generated by default as identity, LogNotes varchar(100) not null, primary key(Id));"); + connection.Execute(@"create table GUIDTest(Id varchar(38) not null, name varchar(50) not null, primary key(Id));"); + connection.Execute(@"create table StrangeColumnNames (""ItemId"" int not null generated by default as identity, Word varchar(100) not null, ""colstringstrangeword"" varchar(100), ""KeywordedProperty"" varchar(100), primary key(""ItemId""))"); + connection.Execute(@"create table UserWithoutAutoIdentity (Id int not null generated by default as identity, Name varchar(100) not null, Age int not null, primary key(Id));"); + connection.Execute(@"create table IgnoreColumns (Id int not null generated by default as identity, IgnoreInsert varchar(100), IgnoreUpdate varchar(100), IgnoreSelect varchar(100), IgnoreAll varchar(100), primary key(Id));"); + connection.Execute(@"create table KeyMaster (Key1 int not null, Key2 int not null, PRIMARY KEY (Key1, Key2));"); + connection.Execute(@"create table stringtest (stringkey varchar(50) not null, name varchar(50) not null, primary key(stringkey))"); + } + } + + private static void RunTestsDB2() + { + var stopwatch = Stopwatch.StartNew(); + var db2tester = new Tests(SimpleCRUD.Dialect.DB2); + foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + //skip schema tests + if (method.Name.Contains("Schema")) continue; + if (method.Name.Contains("Guid")) continue; + var testwatch = Stopwatch.StartNew(); + Console.Write("Running " + method.Name + " in DB2"); + method.Invoke(db2tester, null); + Console.WriteLine(" - OK! {0}ms", testwatch.ElapsedMilliseconds); + } + stopwatch.Stop(); + Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed); + + Console.Write("DB2 testing complete."); + Console.ReadKey(); + } + } +} diff --git a/Dapper.SimpleCRUDFluentTableMap.Tests/Tests.cs b/Dapper.SimpleCRUDFluentTableMap.Tests/Tests.cs new file mode 100644 index 0000000..36ea417 --- /dev/null +++ b/Dapper.SimpleCRUDFluentTableMap.Tests/Tests.cs @@ -0,0 +1,1570 @@ +using System.Data; +using System.Data.SqlClient; +using System.Linq; +using System.Collections.Generic; +using System; +using System.Data.SQLite; +using MySql.Data.MySqlClient; +using Npgsql; +using IBM.Data.DB2.Core; + +namespace Dapper.SimpleCRUDFluentTableMapTests +{ + #region DTOClasses + public class UserEditableSettingsMapper : SimpleCRUDFluentTableMap.TableMapper + { + public UserEditableSettingsMapper() + { + ToTableName("Users"); + } + } + public class UserEditableSettings + { + public int Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + } + + public class UserMapper : SimpleCRUDFluentTableMap.TableMapper + { + public UserMapper() + { + ToTableName("Users"); + Map(u => u.CreatedDate).AsReadOnly(); + Map(u => u.NotMappedInt).AsNotMapped(); + } + } + + public class User : UserEditableSettings + { + //we modified so enums were automatically handled, we should also automatically handle nullable enums + public DayOfWeek? ScheduledDayOff { get; set; } + + public DateTime CreatedDate { get; set; } + + public int NotMappedInt { get; set; } + } + + public class User1Mapper : SimpleCRUDFluentTableMap.TableMapper + { + public User1Mapper() + { + ToTableName("Users"); + } + } + public class User1 + { + public int Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + public int? ScheduledDayOff { get; set; } + } + + public class CarMapper : SimpleCRUDFluentTableMap.TableMapper + { + public CarMapper() + { + Map(c => c.CarId).AsKey(); + Map(c => c.MakeWithModel).AsNotEditable(); + } + } + public class Car + { + #region DatabaseFields + public int CarId { get; set; } + public int? Id { get; set; } + public string Make { get; set; } + public string Model { get; set; } + #endregion + + #region RelatedTables + public List Users { get; set; } + #endregion + + #region AdditionalFields + public string MakeWithModel { get { return Make + " (" + Model + ")"; } } + #endregion + + } + + public class BigCarMapper : SimpleCRUDFluentTableMap.TableMapper + { + public BigCarMapper() + { + Map(c => c.CarId).AsKey(); + } + } + public class BigCar + { + #region DatabaseFields + public long CarId { get; set; } + public string Make { get; set; } + public string Model { get; set; } + #endregion + + } + + public class CarLogMapper : SimpleCRUDFluentTableMap.TableMapper + { + public CarLogMapper() + { + ToTableName("Log.CarLog"); + } + } + + public class CarLog + { + public int Id { get; set; } + public string LogNotes { get; set; } + } + + /// + /// This class should be used for failing tests, since no schema is specified and 'CarLog' is not on dbo + /// + public class SchemalessCarLogMapper : SimpleCRUDFluentTableMap.TableMapper + { + public SchemalessCarLogMapper() + { + ToTableName("CarLog"); + } + } + + public class SchemalessCarLog + { + public int Id { get; set; } + public string LogNotes { get; set; } + } + + public class CityMapper : SimpleCRUDFluentTableMap.TableMapper + { + public CityMapper() + { + Map(c => c.Name).AsKey(); + } + } + public class City + { + public string Name { get; set; } + public int Population { get; set; } + } + + public interface INameColumn + { + string Name { get; set; } + } + + public class CityWithINameMapper : SimpleCRUDFluentTableMap.TableMapper + { + public CityWithINameMapper() + { + ToTableName("City"); + Map(c => c.Name).AsKey(); // With FluentTableMap Mapper must be redefined + } + } + public class CityWithIName : City, INameColumn + { + + } + + public class UserWithINameMapper : SimpleCRUDFluentTableMap.TableMapper + { + public UserWithINameMapper() + { + ToTableName("Users"); + Map(u => u.CreatedDate).AsReadOnly(); // With FluentTableMap Mapper must be redefined + Map(u => u.NotMappedInt).AsNotMapped(); // With FluentTableMap Mapper must be redefined + } + } + public class UserWithIName : User, INameColumn + { + + } + + public class GUIDTestMapper : SimpleCRUDFluentTableMap.TableMapper + { + public GUIDTestMapper() + { + Map(g => g.Id).AsKey(); + } + } + public class GUIDTest + { + public Guid Id { get; set; } + public string Name { get; set; } + } + + public class StringTestMapper : SimpleCRUDFluentTableMap.TableMapper + { + public StringTestMapper() + { + Map(g => g.stringkey).AsKey(); + } + } + public class StringTest + { + public string stringkey { get; set; } + public string name { get; set; } + } + + public class StrangeColumnNamesMapper : SimpleCRUDFluentTableMap.TableMapper + { + public StrangeColumnNamesMapper() + { + Map(g => g.Id).AsKey().WithColumnName("ItemId"); + Map(g => g.StrangeWord).WithColumnName("colstringstrangeword"); + Map(g => g.Select).WithColumnName("KeywordedProperty"); + Map(g => g.ExtraProperty).AsNotEditable(); + } + } + public class StrangeColumnNames + { + public int Id { get; set; } + public string Word { get; set; } + public string StrangeWord { get; set; } + public string Select { get; set; } + public string ExtraProperty { get; set; } + } + + public class IgnoreColumnsMapper : SimpleCRUDFluentTableMap.TableMapper + { + public IgnoreColumnsMapper() + { + Map(g => g.Id).AsKey(); + Map(g => g.IgnoreInsert).AsIgnoredInInsert(); + Map(g => g.IgnoreUpdate).AsIgnoredInUpdate(); + Map(g => g.IgnoreSelect).AsIgnoredInSelect(); + Map(g => g.IgnoreAll).AsIgnoredInSelect().AsIgnoredInInsert().AsIgnoredInUpdate(); + } + } + public class IgnoreColumns + { + public int Id { get; set; } + public string IgnoreInsert { get; set; } + public string IgnoreUpdate { get; set; } + public string IgnoreSelect { get; set; } + public string IgnoreAll { get; set; } + } + + public class UserWithoutAutoIdentityMapper : SimpleCRUDFluentTableMap.TableMapper + { + public UserWithoutAutoIdentityMapper() + { + Map(u => u.Id).AsKey().AsRequired(); + } + } + public class UserWithoutAutoIdentity + { + public int Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + } + + public class KeyMasterMapper : SimpleCRUDFluentTableMap.TableMapper + { + public KeyMasterMapper() + { + Map(k => k.Key1).AsKey().AsRequired(); + Map(k => k.Key2).AsKey().AsRequired(); + } + } + public class KeyMaster + { + public int Key1 { get; set; } + public int Key2 { get; set; } + } + + #endregion + + public class Tests + { + public Tests(SimpleCRUD.Dialect dbtype) + { + _dbtype = dbtype; + SimpleCRUDFluentTableMap.AddAllMappingsFromAssembliesLoaded(); + SimpleCRUDFluentTableMap.RegisterSimpleCrudFluentTableMapResolver(); + } + private SimpleCRUD.Dialect _dbtype; + + private IDbConnection GetOpenConnection() + { + + IDbConnection connection; + if (_dbtype == SimpleCRUD.Dialect.PostgreSQL) + { + connection = new NpgsqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", "localhost", "5432", "postgres", "postgrespass", "testdb")); + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.PostgreSQL); + } + else if (_dbtype == SimpleCRUD.Dialect.SQLite) + { + connection = new SQLiteConnection("Data Source=MyDatabase.sqlite;Version=3;"); + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.SQLite); + } + else if (_dbtype == SimpleCRUD.Dialect.MySQL) + { + connection = new MySqlConnection(String.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};", "localhost", "3306", "root", "admin", "testdb")); + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.MySQL); + } + else if (_dbtype == SimpleCRUD.Dialect.DB2) + { + connection = new DB2Connection(string.Format("Server={0};UID={1};PWD={2};Database={3};", "localhost:50000", "db2admin", "db2admin", "testdb")); + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.DB2); + } + else + { + connection = new SqlConnection($@"Data Source={Program.SQLServerName};Initial Catalog=DapperSimpleCrudTestDb;Integrated Security=True;MultipleActiveResultSets=true;"); + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.SQLServer); + } + + connection.Open(); + return connection; + } + + //basic tests + public void TestInsertWithSpecifiedTableName() + { + using (var connection = GetOpenConnection()) + { + + var id = connection.Insert(new User { Name = "TestInsertWithSpecifiedTableName", Age = 10 }); + var user = connection.Get(id); + user.Name.IsEqualTo("TestInsertWithSpecifiedTableName"); + connection.Delete(id); + + } + } + + public void TestMassInsert() + { + //With cached strinb builder, this tests runs 2.5X faster (From 400ms to 180ms) + using (var connection = GetOpenConnection()) + using (var transaction = connection.BeginTransaction()) + { + for (int i = 0; i < 1000; i++) + { + var id = connection.Insert(new User { Name = $"Name #{i}", Age = i }, transaction); + } + } + } + + public void TestMassUpdate() //356 + { + //With cached strinb builder, this tests runs 2.5X faster (From 375ms to 140ms) + using (var connection = GetOpenConnection()) + using (var transaction = connection.BeginTransaction()) + { + var id = connection.Insert(new User { Name = "New User", Age = 0 }, transaction); + var user = connection.Get(id, transaction); + + for (int i = 1; i <= 1000; i++) + { + user.Age = i; + connection.Update(user, transaction); + } + } + } + + public void TestInsertUsingBigIntPrimaryKey() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new BigCar { Make = "Big", Model = "Car" }); + connection.Delete(id); + + } + } + + public void TestInsertUsingGenericLimitedFields() + { + using (var connection = GetOpenConnection()) + { + //arrange + var user = new User { Name = "User1", Age = 10, ScheduledDayOff = DayOfWeek.Friday }; + + //act + var id = connection.Insert(user); + + //assert + var insertedUser = connection.Get(id); + insertedUser.ScheduledDayOff.IsNull(); + + connection.Delete(id); + } + } + + public void TestSimpleGet() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "UserTestSimpleGet", Age = 10 }); + var user = connection.Get(id); + user.Name.IsEqualTo("UserTestSimpleGet"); + connection.Delete(id); + + } + } + + public void TestDeleteById() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "UserTestDeleteById", Age = 10 }); + connection.Delete(id); + connection.Get(id).IsNull(); + } + } + + public void TestDeleteByObject() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestDeleteByObject", Age = 10 }); + var user = connection.Get(id); + connection.Delete(user); + connection.Get(id).IsNull(); + } + } + + public void TestSimpleGetList() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestSimpleGetList1", Age = 10 }); + connection.Insert(new User { Name = "TestSimpleGetList2", Age = 10 }); + var user = connection.GetList(new { }); + user.Count().IsEqualTo(2); + connection.Execute("Delete from Users"); + } + } + + public void TestFilteredGetList() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestFilteredGetList1", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetList2", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetList3", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetList4", Age = 11 }); + + var user = connection.GetList(new { Age = 10 }); + user.Count().IsEqualTo(3); + connection.Execute("Delete from Users"); + } + } + + + public void TestFilteredGetListWithMultipleKeys() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new KeyMaster { Key1 = 1, Key2 = 1 }); + connection.Insert(new KeyMaster { Key1 = 1, Key2 = 2 }); + connection.Insert(new KeyMaster { Key1 = 1, Key2 = 3 }); + connection.Insert(new KeyMaster { Key1 = 2, Key2 = 4 }); + + var keyMasters = connection.GetList(new { Key1 = 1 }); + keyMasters.Count().IsEqualTo(3); + connection.Execute("Delete from KeyMaster"); + } + } + + + public void TestFilteredWithSQLGetList() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestFilteredWithSQLGetList1", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredWithSQLGetList2", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredWithSQLGetList3", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredWithSQLGetList4", Age = 11 }); + + var user = connection.GetList("where Name like 'TestFilteredWithSQLGetList%' and Age = 10"); + user.Count().IsEqualTo(3); + connection.Execute("Delete from Users"); + } + } + + public void TestGetListWithNullWhere() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestGetListWithNullWhere", Age = 10 }); + var user = connection.GetList(null); + user.Count().IsEqualTo(1); + connection.Execute("Delete from Users"); + } + } + + public void TestGetListWithoutWhere() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestGetListWithoutWhere", Age = 10 }); + var user = connection.GetList(); + user.Count().IsEqualTo(1); + connection.Execute("Delete from Users"); + } + } + + public void TestsGetListWithParameters() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestsGetListWithParameters1", Age = 10 }); + connection.Insert(new User { Name = "TestsGetListWithParameters2", Age = 10 }); + connection.Insert(new User { Name = "TestsGetListWithParameters3", Age = 10 }); + connection.Insert(new User { Name = "TestsGetListWithParameters4", Age = 11 }); + + var user = connection.GetList("where Age > @Age", new { Age = 10 }); + user.Count().IsEqualTo(1); + connection.Execute("Delete from Users"); + } + } + + public void TestGetWithReadonlyProperty() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestGetWithReadonlyProperty", Age = 10 }); + var user = connection.Get(id); + user.CreatedDate.Year.IsEqualTo(DateTime.Now.Year); + connection.Execute("Delete from Users"); + } + } + + public void TestInsertWithReadonlyProperty() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestInsertWithReadonlyProperty", Age = 10, CreatedDate = new DateTime(2001, 1, 1) }); + var user = connection.Get(id); + //the date can't be 2001 - it should be the autogenerated date from the database + user.CreatedDate.Year.IsEqualTo(DateTime.Now.Year); + connection.Execute("Delete from Users"); + } + } + + public void TestUpdateWithReadonlyProperty() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestUpdateWithReadonlyProperty", Age = 10 }); + var user = connection.Get(id); + user.Age = 11; + user.CreatedDate = new DateTime(2001, 1, 1); + connection.Update(user); + user = connection.Get(id); + //don't allow changing created date since it has a readonly attribute + user.CreatedDate.Year.IsEqualTo(DateTime.Now.Year); + connection.Execute("Delete from Users"); + } + } + + public void TestGetWithNotMappedProperty() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestGetWithNotMappedProperty", Age = 10, NotMappedInt = 1000 }); + var user = connection.Get(id); + user.NotMappedInt.IsEqualTo(0); + connection.Execute("Delete from Users"); + } + } + + public void TestInsertWithNotMappedProperty() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestInsertWithNotMappedProperty", Age = 10, CreatedDate = new DateTime(2001, 1, 1), NotMappedInt = 1000 }); + var user = connection.Get(id); + user.NotMappedInt.IsEqualTo(0); + connection.Execute("Delete from Users"); + } + } + + public void TestUpdateWithNotMappedProperty() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestUpdateWithNotMappedProperty", Age = 10 }); + var user = connection.Get(id); + user.Age = 11; + user.CreatedDate = new DateTime(2001, 1, 1); + user.NotMappedInt = 1234; + connection.Update(user); + user = connection.Get(id); + + user.NotMappedInt.IsEqualTo(0); + + connection.Execute("Delete from Users"); + } + } + + public void TestInsertWithSpecifiedKey() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new Car { Make = "Honda", Model = "Civic" }); + id.IsEqualTo(1); + } + } + + public void TestInsertWithExtraPropertiesShouldSkipNonSimpleTypesAndPropertiesMarkedEditableFalse() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new Car { Make = "Honda", Model = "Civic", Users = new List { new User { Age = 12, Name = "test" } } }); + id.IsEqualTo(2); + } + } + + public void TestUpdate() + { + using (var connection = GetOpenConnection()) + { + var newid = (int)connection.Insert(new Car { Make = "Honda", Model = "Civic" }); + var newitem = connection.Get(newid); + newitem.Make = "Toyota"; + connection.Update(newitem); + var updateditem = connection.Get(newid); + updateditem.Make.IsEqualTo("Toyota"); + connection.Delete(newid); + } + } + + /// + /// We expect scheduled day off to NOT be updated, since it's not a property of UserEditableSettings + /// + public void TestUpdateUsingGenericLimitedFields() + { + using (var connection = GetOpenConnection()) + { + //arrange + var user = new User { Name = "User1", Age = 10, ScheduledDayOff = DayOfWeek.Friday }; + user.Id = connection.Insert(user) ?? 0; + + user.ScheduledDayOff = DayOfWeek.Thursday; + var userAsEditableSettings = (UserEditableSettings)user; + userAsEditableSettings.Name = "User++"; + + connection.Update(userAsEditableSettings); + + //act + var insertedUser = connection.Get(user.Id); + + //assert + insertedUser.Name.IsEqualTo("User++"); + insertedUser.ScheduledDayOff.IsEqualTo(DayOfWeek.Friday); + + connection.Delete(user.Id); + } + } + + public void TestDeleteByObjectWithAttributes() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new Car { Make = "Honda", Model = "Civic" }); + var car = connection.Get(id); + connection.Delete(car); + connection.Get(id).IsNull(); + } + } + + public void TestDeleteByMultipleKeyObjectWithAttributes() + { + using (var connection = GetOpenConnection()) + { + var keyMaster = new KeyMaster { Key1 = 1, Key2 = 2 }; + connection.Insert(keyMaster); + var car = connection.Get(new { Key1 = 1, Key2 = 2 }); + connection.Delete(car); + connection.Get(keyMaster).IsNull(); + } + } + + public void TestComplexTypesMarkedEditableAreSaved() + { + using (var connection = GetOpenConnection()) + { + var id = (int)connection.Insert(new User { Name = "User", Age = 11, ScheduledDayOff = DayOfWeek.Friday }); + var user1 = connection.Get(id); + user1.ScheduledDayOff.IsEqualTo(DayOfWeek.Friday); + connection.Delete(user1); + } + } + + public void TestNullableSimpleTypesAreSaved() + { + using (var connection = GetOpenConnection()) + { + var id = (int)connection.Insert(new User1 { Name = "User", Age = 11, ScheduledDayOff = 2 }); + var user1 = connection.Get(id); + user1.ScheduledDayOff.IsEqualTo(2); + connection.Delete(user1); + } + } + + public void TestInsertIntoDifferentSchema() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new CarLog { LogNotes = "blah blah blah" }); + id.IsEqualTo(1); + connection.Delete(id); + + } + } + + public void TestGetFromDifferentSchema() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new CarLog { LogNotes = "TestGetFromDifferentSchema" }); + var carlog = connection.Get(id); + carlog.LogNotes.IsEqualTo("TestGetFromDifferentSchema"); + connection.Delete(id); + } + } + + public void TestTryingToGetFromTableInSchemaWithoutDataAnnotationShouldFail() + { + using (var connection = GetOpenConnection()) + { + try + { + connection.Get(1); + } + catch (Exception) + { + //we expect to get an exception, so return + return; + } + + //if we get here without throwing an exception, the test failed. + throw new ApplicationException("Expected exception"); + } + } + + public void TestGetFromTableWithNonIntPrimaryKey() + { + using (var connection = GetOpenConnection()) + { + //note - there's not support for inserts without a non-int id, so drop down to a normal execute + connection.Execute("INSERT INTO CITY (NAME, POPULATION) VALUES ('Morgantown', 31000)"); + var city = connection.Get("Morgantown"); + city.Population.IsEqualTo(31000); + } + } + + public void TestDeleteFromTableWithNonIntPrimaryKey() + { + using (var connection = GetOpenConnection()) + { + //note - there's not support for inserts without a non-int id, so drop down to a normal execute + connection.Execute("INSERT INTO CITY (NAME, POPULATION) VALUES ('Fairmont', 18737)"); + connection.Delete("Fairmont").IsEqualTo(1); + } + } + + public void TestNullableEnumInsert() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "Enum-y", Age = 10, ScheduledDayOff = DayOfWeek.Thursday }); + var user = connection.GetList(new { Name = "Enum-y" }).FirstOrDefault() ?? new User(); + user.ScheduledDayOff.IsEqualTo(DayOfWeek.Thursday); + connection.Delete(user.Id); + } + } + + //dialect test + + public void TestChangeDialect() + { + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.SQLServer); + SimpleCRUD.GetDialect().IsEqualTo(SimpleCRUD.Dialect.SQLServer.ToString()); + SimpleCRUD.SetDialect(SimpleCRUD.Dialect.PostgreSQL); + SimpleCRUD.GetDialect().IsEqualTo(SimpleCRUD.Dialect.PostgreSQL.ToString()); + } + + + // A GUID is being created and returned on insert but never actually + //applied to the insert query. + + //This can be seen on a table where the key + //is a GUID and defaults to (newid()) and no GUID is provided on the + //insert. Dapper will generate a GUID but it is not applied so the GUID is + //generated by newid() but the Dapper GUID is returned instead which is + //incorrect. + + + //GUID primary key tests + + public void TestInsertIntoTableWithUnspecifiedGuidKey() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new GUIDTest { Name = "GuidUser" }); + id.GetType().Name.IsEqualTo("Guid"); + var record = connection.Get(id); + record.Name.IsEqualTo("GuidUser"); + connection.Delete(id); + } + } + + public void TestInsertIntoTableWithGuidKey() + { + using (var connection = GetOpenConnection()) + { + var guid = new Guid("1a6fb33d-7141-47a0-b9fa-86a1a1945da9"); + var id = connection.Insert(new GUIDTest { Name = "InsertIntoTableWithGuidKey", Id = guid }); + id.IsEqualTo(guid); + connection.Delete(id); + } + } + + public void TestGetRecordWithGuidKey() + { + using (var connection = GetOpenConnection()) + { + var guid = new Guid("2a6fb33d-7141-47a0-b9fa-86a1a1945da9"); + connection.Insert(new GUIDTest { Name = "GetRecordWithGuidKey", Id = guid }); + var id = connection.GetList().First().Id; + var record = connection.Get(id); + record.Name.IsEqualTo("GetRecordWithGuidKey"); + connection.Delete(id); + + } + } + + public void TestDeleteRecordWithGuidKey() + { + using (var connection = GetOpenConnection()) + { + var guid = new Guid("3a6fb33d-7141-47a0-b9fa-86a1a1945da9"); + connection.Insert(new GUIDTest { Name = "DeleteRecordWithGuidKey", Id = guid }); + var id = connection.GetList().First().Id; + connection.Delete(id); + connection.Get(id).IsNull(); + } + } + public void TestInsertIntoTableWithStringKey() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new StringTest { stringkey = "123xyz", name = "Bob" }); + id.IsEqualTo("123xyz"); + connection.Delete(id); + } + } + +#if !NET40 + //async tests + public void TestMultiInsertASync() + { + using (var connection = GetOpenConnection()) + { + connection.InsertAsync(new User { Name = "TestMultiInsertASync1", Age = 10 }); + connection.InsertAsync(new User { Name = "TestMultiInsertASync2", Age = 10 }); + connection.InsertAsync(new User { Name = "TestMultiInsertASync3", Age = 10 }); + connection.InsertAsync(new User { Name = "TestMultiInsertASync4", Age = 11 }); + System.Threading.Thread.Sleep(300); + //tiny wait to let the inserts happen + var list = connection.GetList(new { Age = 10 }); + list.Count().IsEqualTo(3); + connection.Execute("Delete from Users"); + + } + } + + public async void MultiInsertWithGuidAsync() + { + using (var connection = GetOpenConnection()) + { + await connection.InsertAsync(new GUIDTest { Name = "MultiInsertWithGuidAsync" }); + await connection.InsertAsync(new GUIDTest { Name = "MultiInsertWithGuidAsync" }); + await connection.InsertAsync(new GUIDTest { Name = "MultiInsertWithGuidAsync" }); + await connection.InsertAsync(new GUIDTest { Name = "MultiInsertWithGuidAsync" }); + //tiny wait to let the inserts happen + System.Threading.Thread.Sleep(300); + var list = connection.GetList(new { Name = "MultiInsertWithGuidAsync" }); + list.Count().IsEqualTo(4); + connection.Execute("Delete from GUIDTest"); + } + } + + public void TestSimpleGetAsync() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestSimpleGetAsync", Age = 10 }); + var user = connection.GetAsync(id); + user.Result.Name.IsEqualTo("TestSimpleGetAsync"); + connection.Delete(id); + } + } + + public void TestMultipleKeyGetAsync() + { + using (var connection = GetOpenConnection()) + { + var keyMaster = new KeyMaster { Key1 = 1, Key2 = 2 }; + connection.Insert(keyMaster); + var result = connection.GetAsync(new { Key1 = 1, Key2 = 2 }); + result.Result.Key1.IsEqualTo(1); + result.Result.Key2.IsEqualTo(2); + connection.Delete(keyMaster); + } + } + + public async void TestDeleteByIdAsync() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "UserAsyncDelete", Age = 10 }); + await connection.DeleteAsync(id); + //tiny wait to let the delete happen + connection.Get(id).IsNull(); + } + } + + public void TestDeleteByObjectAsync() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new User { Name = "TestDeleteByObjectAsync", Age = 10 }); + var user = connection.Get(id); + connection.DeleteAsync(user); + connection.Get(id).IsNull(); + connection.Delete(id); + } + } + + public void TestDeleteByMultipleKeyObject() + { + using (var connection = GetOpenConnection()) + { + var keyMaster = new KeyMaster { Key1 = 1, Key2 = 2 }; + connection.Insert(keyMaster); + connection.Get(keyMaster); + connection.Delete(new { Key1 = 1, Key2 = 2 }); + connection.Get(keyMaster).IsNull(); + connection.Delete(keyMaster); + } + } + + public void TestSimpleGetListAsync() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestSimpleGetListAsync1", Age = 10 }); + connection.Insert(new User { Name = "TestSimpleGetListAsync2", Age = 10 }); + var user = connection.GetListAsync(new { }); + user.Result.Count().IsEqualTo(2); + connection.Execute("Delete from Users"); + } + } + + public void TestFilteredGetListAsync() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestFilteredGetListAsync1", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetListAsync2", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetListAsync3", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetListAsync4", Age = 11 }); + + var user = connection.GetListAsync(new { Age = 10 }); + user.Result.Count().IsEqualTo(3); + connection.Execute("Delete from Users"); + } + } + + public void TestFilteredGetListParametersAsync() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestFilteredGetListParametersAsync1", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetListParametersAsync2", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetListParametersAsync3", Age = 10 }); + connection.Insert(new User { Name = "TestFilteredGetListParametersAsync4", Age = 11 }); + + var user = connection.GetListAsync("where Age = @Age", new { Age = 10 }); + user.Result.Count().IsEqualTo(3); + connection.Execute("Delete from Users"); + } + } + + public void TestRecordCountAsync() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(30); + connection.RecordCountAsync().Result.IsEqualTo(30); + + connection.RecordCountAsync("where age = 10 or age = 11").Result.IsEqualTo(2); + + + connection.Execute("Delete from Users"); + } + + } + + public void TestRecordCountByObjectAsync() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(30); + connection.RecordCountAsync().Result.IsEqualTo(30); + + connection.RecordCountAsync(new { age = 10 }).Result.IsEqualTo(1); + + + connection.Execute("Delete from Users"); + } + + } + + public void TestInsertWithSpecifiedPrimaryKeyAsync() + { + using (var connection = GetOpenConnection()) + { + var id = connection.InsertAsync(new UserWithoutAutoIdentity() { Id = 999, Name = "User999Async", Age = 10 }); + id.Result.IsEqualTo(999); + var user = connection.GetAsync(999); + user.Result.Name.IsEqualTo("User999Async"); + connection.Execute("Delete from UserWithoutAutoIdentity"); + } + } + + + public async void TestInsertWithMultiplePrimaryKeysAsync() + { + using (var connection = GetOpenConnection()) + { + var keyMaster = new KeyMaster { Key1 = 1, Key2 = 2 }; + await connection.InsertAsync(keyMaster); + var result = connection.GetAsync(new { Key1 = 1, Key2 = 2 }); + result.Result.Key1.IsEqualTo(1); + result.Result.Key2.IsEqualTo(2); + connection.Execute("Delete from KeyMaster"); + } + } + public void TestInsertUsingGenericLimitedFieldsAsync() + { + using (var connection = GetOpenConnection()) + { + //arrange + var user = new User { Name = "User1", Age = 10, ScheduledDayOff = DayOfWeek.Friday }; + + //act + var idTask = connection.InsertAsync(user); + idTask.Wait(); + var id = idTask.Result; + + //assert + var insertedUser = connection.Get(id); + insertedUser.ScheduledDayOff.IsNull(); + + connection.Delete(id); + } + } + + /// + /// We expect scheduled day off to NOT be updated, since it's not a property of UserEditableSettings + /// + public void TestUpdateUsingGenericLimitedFieldsAsync() + { + using (var connection = GetOpenConnection()) + { + //arrange + var user = new User { Name = "User1", Age = 10, ScheduledDayOff = DayOfWeek.Friday }; + user.Id = connection.Insert(user) ?? 0; + + user.ScheduledDayOff = DayOfWeek.Thursday; + var userAsEditableSettings = (UserEditableSettings)user; + userAsEditableSettings.Name = "User++"; + + connection.UpdateAsync(userAsEditableSettings).Wait(); + + //act + var insertedUser = connection.Get(user.Id); + + //assert + insertedUser.Name.IsEqualTo("User++"); + insertedUser.ScheduledDayOff.IsEqualTo(DayOfWeek.Friday); + + connection.Delete(user.Id); + } + } +#endif + //column attribute tests + + public void TestInsertWithSpecifiedColumnName() + { + using (var connection = GetOpenConnection()) + { + var itemId = connection.Insert(new StrangeColumnNames { Word = "InsertWithSpecifiedColumnName", StrangeWord = "Strange 1" }); + itemId.IsEqualTo(1); + connection.Delete(itemId); + + } + } + + public void TestDeleteByObjectWithSpecifiedColumnName() + { + using (var connection = GetOpenConnection()) + { + var itemId = connection.Insert(new StrangeColumnNames { Word = "TestDeleteByObjectWithSpecifiedColumnName", StrangeWord = "Strange 1" }); + var strange = connection.Get(itemId); + connection.Delete(strange); + connection.Get(itemId).IsNull(); + } + } + + public void TestSimpleGetListWithSpecifiedColumnName() + { + using (var connection = GetOpenConnection()) + { + var id1 = connection.Insert(new StrangeColumnNames { Word = "TestSimpleGetListWithSpecifiedColumnName1", StrangeWord = "Strange 2", }); + var id2 = connection.Insert(new StrangeColumnNames { Word = "TestSimpleGetListWithSpecifiedColumnName2", StrangeWord = "Strange 3", }); + var strange = connection.GetList(new { }); + strange.First().StrangeWord.IsEqualTo("Strange 2"); + strange.Count().IsEqualTo(2); + connection.Delete(id1); + connection.Delete(id2); + } + } + + public void TestUpdateWithSpecifiedColumnName() + { + using (var connection = GetOpenConnection()) + { + var newid = (int)connection.Insert(new StrangeColumnNames { Word = "Word Insert", StrangeWord = "Strange Insert" }); + var newitem = connection.Get(newid); + newitem.Word = "Word Update"; + connection.Update(newitem); + var updateditem = connection.Get(newid); + updateditem.Word.IsEqualTo("Word Update"); + connection.Delete(newid); + } + } + + public void TestFilteredGetListWithSpecifiedColumnName() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new StrangeColumnNames { Word = "Word 5", StrangeWord = "Strange 1", }); + connection.Insert(new StrangeColumnNames { Word = "Word 6", StrangeWord = "Strange 2", }); + connection.Insert(new StrangeColumnNames { Word = "Word 7", StrangeWord = "Strange 2", }); + connection.Insert(new StrangeColumnNames { Word = "Word 8", StrangeWord = "Strange 2", }); + + var strange = connection.GetList(new { StrangeWord = "Strange 2" }); + strange.Count().IsEqualTo(3); + connection.Execute("Delete from StrangeColumnNames"); + } + } + + public void TestGetListPaged() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetListPaged(2, 10, null, null); + resultlist.Count().IsEqualTo(10); + resultlist.Skip(4).First().Name.IsEqualTo("Person 14"); + connection.Execute("Delete from Users"); + } + } + + public void TestGetListPagedWithParameters() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetListPaged(1, 30, "where Age > @Age", null, new { Age = 14 }); + resultlist.Count().IsEqualTo(15); + resultlist.First().Name.IsEqualTo("Person 15"); + connection.Execute("Delete from Users"); + } + } + + + public void TestGetListPagedWithSpecifiedPrimaryKey() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new StrangeColumnNames { Word = "Word " + x, StrangeWord = "Strange " + x }); + x++; + } while (x < 30); + + var resultlist = connection.GetListPaged(2, 10, null, null); + resultlist.Count().IsEqualTo(10); + resultlist.Skip(4).First().Word.IsEqualTo("Word 14"); + connection.Execute("Delete from StrangeColumnNames"); + } + } + public void TestGetListPagedWithWhereClause() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist1 = connection.GetListPaged(1, 3, "Where Name LIKE 'Person 2%'", "age desc"); + resultlist1.Count().IsEqualTo(3); + + var resultlist = connection.GetListPaged(2, 3, "Where Name LIKE 'Person 2%'", "age desc"); + resultlist.Count().IsEqualTo(3); + resultlist.Skip(1).First().Name.IsEqualTo("Person 25"); + + connection.Execute("Delete from Users"); + } + } + + public void TestDeleteListWithWhereClause() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + connection.DeleteList("Where age > 9"); + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(10); + connection.Execute("Delete from Users"); + } + } + + public void TestDeleteListWithWhereObject() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 10); + + connection.DeleteList(new { age = 9 }); + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(9); + connection.Execute("Delete from Users"); + } + } + + public void TestDeleteListWithParameters() + { + using (var connection = GetOpenConnection()) + { + int x = 1; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 10); + + connection.DeleteList("where age >= @Age", new { Age = 9 }); + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(8); + connection.Execute("Delete from Users"); + } + } + + public void TestRecordCountWhereClause() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(30); + connection.RecordCount().IsEqualTo(30); + + connection.RecordCount("where age = 10 or age = 11").IsEqualTo(2); + + + connection.Execute("Delete from Users"); + } + + } + + public void TestRecordCountWhereObject() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(30); + connection.RecordCount().IsEqualTo(30); + + connection.RecordCount(new { age = 10 }).IsEqualTo(1); + + + connection.Execute("Delete from Users"); + } + + } + + public void TestRecordCountParameters() + { + using (var connection = GetOpenConnection()) + { + int x = 0; + do + { + connection.Insert(new User { Name = "Person " + x, Age = x, CreatedDate = DateTime.Now, ScheduledDayOff = DayOfWeek.Thursday }); + x++; + } while (x < 30); + + var resultlist = connection.GetList(); + resultlist.Count().IsEqualTo(30); + connection.RecordCount("where Age > 15").IsEqualTo(14); + + + connection.Execute("Delete from Users"); + } + + } + + public void TestInsertWithSpecifiedPrimaryKey() + { + using (var connection = GetOpenConnection()) + { + var id = connection.Insert(new UserWithoutAutoIdentity() { Id = 999, Name = "User999", Age = 10 }); + id.IsEqualTo(999); + var user = connection.Get(999); + user.Name.IsEqualTo("User999"); + connection.Execute("Delete from UserWithoutAutoIdentity"); + } + } + + public void TestGetListNullableWhere() + { + using (var connection = GetOpenConnection()) + { + connection.Insert(new User { Name = "TestGetListWithoutWhere", Age = 10, ScheduledDayOff = DayOfWeek.Friday }); + connection.Insert(new User { Name = "TestGetListWithoutWhere", Age = 10 }); + + //test with null property + var list = connection.GetList(new { ScheduledDayOff = (DayOfWeek?)null }); + list.Count().IsEqualTo(1); + + + // test with db.null value + list = connection.GetList(new { ScheduledDayOff = DBNull.Value }); + list.Count().IsEqualTo(1); + + connection.Execute("Delete from Users"); + } + } + + public void TestInsertUsingInterface() + { + using (var connection = GetOpenConnection()) + using (var transaction = connection.BeginTransaction()) + { + INameColumn newUser = new UserWithIName + { + Age = 40, + Name = "Jonathan Larouche", + ScheduledDayOff = DayOfWeek.Sunday, + CreatedDate = new DateTime(2000, 1, 1) + }; + + connection.Insert(newUser, transaction); + + INameColumn newCity = new CityWithIName + { + Name = "Montreal", + Population = 5675 + }; + + connection.Insert(newCity, transaction); + + var user = connection.GetList(new { Name = "Jonathan Larouche" }, transaction).FirstOrDefault(); + user.Age.IsEqualTo(40); + var city = connection.GetList(new { Name = "Montreal" }, transaction).FirstOrDefault(); + city.Population.IsEqualTo(5675); + + } + } + + public async void TestInsertAsyncUsingInterface() + { + using (var connection = GetOpenConnection()) + using (var transaction = connection.BeginTransaction()) + { + INameColumn newUser = new UserWithIName + { + Age = 40, + Name = "Jonathan Larouche", + ScheduledDayOff = DayOfWeek.Sunday, + CreatedDate = new DateTime(2000, 1, 1) + }; + + await connection.InsertAsync(newUser, transaction); + + var user = connection.GetList(new { Name = "Jonathan Larouche" }, transaction).FirstOrDefault(); + user.Age.IsEqualTo(40); + + } + } + + public void TestUpdateUsingInterface() + { + using (var connection = GetOpenConnection()) + using (var transaction = connection.BeginTransaction()) + { + INameColumn newUser = new UserWithIName + { + Age = 40, + Name = "Jonathan Larouche", + ScheduledDayOff = DayOfWeek.Sunday, + CreatedDate = new DateTime(2000, 1, 1) + }; + + ((UserWithIName)newUser).Id = connection.Insert(newUser, transaction).Value; + ((UserWithIName)newUser).Age = 41; + connection.Update(newUser, transaction); + + INameColumn newCity = new CityWithIName + { + Name = "Montreal", + Population = 5675 + }; + + connection.Insert(newCity, transaction); + ((CityWithIName)newCity).Population = 6000; + connection.Update(newCity, transaction); + + var user = connection.GetList(new { Name = "Jonathan Larouche" }, transaction).FirstOrDefault(); + user.Age.IsEqualTo(41); + var city = connection.GetList(new { Name = "Montreal" }, transaction).FirstOrDefault(); + city.Population.IsEqualTo(6000); + + } + } + + public async void TestUpdateAsyncUsingInterface() + { + using (var connection = GetOpenConnection()) + using (var transaction = connection.BeginTransaction()) + { + INameColumn newUser = new UserWithIName + { + Age = 40, + Name = "Jonathan Larouche", + ScheduledDayOff = DayOfWeek.Sunday, + CreatedDate = new DateTime(2000, 1, 1) + }; + + ((UserWithIName)newUser).Id = connection.Insert(newUser, transaction).Value; + ((UserWithIName)newUser).Age = 41; + await connection.UpdateAsync(newUser, transaction); + + INameColumn newCity = new CityWithIName + { + Name = "Montreal", + Population = 5675 + }; + + connection.Insert(newCity, transaction); + ((CityWithIName)newCity).Population = 6000; + await connection.UpdateAsync(newCity, transaction); + + var user = connection.GetList(new { Name = "Jonathan Larouche" }, transaction).FirstOrDefault(); + user.Age.IsEqualTo(41); + var city = connection.GetList(new { Name = "Montreal" }, transaction).FirstOrDefault(); + city.Population.IsEqualTo(6000); + + } + } + + //ignore attribute tests + //i cheated here and stuffed all of these in one test + //didn't implement in postgres or mysql tests yet + public void IgnoreProperties() + { + using (var connection = GetOpenConnection()) + { + var itemId = connection.Insert(new IgnoreColumns() { IgnoreInsert = "OriginalInsert", IgnoreUpdate = "OriginalUpdate", IgnoreSelect = "OriginalSelect", IgnoreAll = "OriginalAll" }); + var item = connection.Get(itemId); + //verify insert column was ignored + item.IgnoreInsert.IsNull(); + + //verify select value wasn't selected + item.IgnoreSelect.IsNull(); + + //verify the column is really there via straight dapper + var fromDapper = connection.Query("Select * from IgnoreColumns where Id = @Id", new { id = itemId }).First(); + fromDapper.IgnoreSelect.IsEqualTo("OriginalSelect"); + + //change value and update + item.IgnoreUpdate = "ChangedUpdate"; + connection.Update(item); + + //verify that update didn't take effect + item = connection.Get(itemId); + item.IgnoreUpdate.IsEqualTo("OriginalUpdate"); + + var allColumnDapper = connection.Query("Select IgnoreAll from IgnoreColumns where Id = @Id", new { id = itemId }).First(); + allColumnDapper.IgnoreAll.IsNull(); + + connection.Delete(itemId); + } + } + + } +} diff --git a/Dapper.SimpleCRUDFluentTableMap/Dapper.SimpleCRUDFluentTableMap.csproj b/Dapper.SimpleCRUDFluentTableMap/Dapper.SimpleCRUDFluentTableMap.csproj new file mode 100644 index 0000000..8ffe4d6 --- /dev/null +++ b/Dapper.SimpleCRUDFluentTableMap/Dapper.SimpleCRUDFluentTableMap.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.0 + + + + + + + + + + + + NETCORE;NETSTANDARD;NETSTANDARD2_0 + + + + bin\Release\netstandard2.0\Dapper.SimpleCRUDFluentTableMap.xml + + diff --git a/Dapper.SimpleCRUDFluentTableMap/SimpleCRUDFluentTableMap.cs b/Dapper.SimpleCRUDFluentTableMap/SimpleCRUDFluentTableMap.cs new file mode 100644 index 0000000..d6ef4e3 --- /dev/null +++ b/Dapper.SimpleCRUDFluentTableMap/SimpleCRUDFluentTableMap.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Dapper +{ + public static class SimpleCRUDFluentTableMap + { + public interface ITableMapper + { + string TableName { get; } + IDictionary ColumnPropertiesMaps { get; } + } + + private static Dictionary tableMappers = new Dictionary(); + + // public static void AddMappings(IEnumerable> mappers) + // { + // foreach(var mapper in mappers) { + // AddMapping(mapper); + // } + // } + + public static void AddMapping(TableMapper mapper) + { + if (mapper == null) + throw new ArgumentNullException(nameof(mapper)); + if (tableMappers.ContainsKey(typeof(T))) + { + throw new ArgumentException($"mapper of type {mapper.GetType().FullName} already registered"); + } + tableMappers.Add(typeof(T), mapper); + } + + public static void AddAllMappingsFromAssembliesLoaded() + { + tableMappers.Clear(); + var iType = typeof(ITableMapper); + foreach(var m in AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => iType.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract && !x.IsGenericType)) + { + var dt = m.BaseType.GetGenericArguments().First(); + tableMappers.Add(dt, (ITableMapper)Activator.CreateInstance(m)); + } + } + + public static void RegisterSimpleCrudFluentTableMapResolver(){ + var fluentResolver = new FluentTableMapResolver(); + Dapper.SimpleCRUD.SetColumnPropertiesResolver(fluentResolver); + Dapper.SimpleCRUD.SetTableNameResolver(fluentResolver); + Dapper.SimpleCRUD.SetColumnNameResolver(fluentResolver); + } + + internal static bool IsSimpleType(this Type type) + { + var underlyingType = Nullable.GetUnderlyingType(type); + type = underlyingType ?? type; + var simpleTypes = new List + { + typeof(byte), + typeof(sbyte), + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(float), + typeof(double), + typeof(decimal), + typeof(bool), + typeof(string), + typeof(char), + typeof(Guid), + typeof(DateTime), + typeof(DateTimeOffset), + typeof(TimeSpan), + typeof(byte[]) + }; + return simpleTypes.Contains(type) || type.IsEnum; + } + + public class FluentColumnProperties : Dapper.SimpleCRUD.IColumnProperties + { + public string AliasName { get; private set; } + public string ColumnName { get; private set; } + public bool IsKey { get; private set; } + public bool IsRequired { get; private set; } + public bool IsEditable { get; private set; } + public bool IsNotMapped { get; private set; } + public bool IsReadOnly { get; private set; } + public bool IsIgnoredInSelect { get; private set; } + public bool IsIgnoredInInsert { get; private set; } + public bool IsIgnoredInUpdate { get; private set; } + public FluentColumnProperties(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + throw new ArgumentNullException(nameof(propertyInfo)); + this.ColumnName = propertyInfo.Name; + IsEditable = propertyInfo.PropertyType.IsSimpleType(); + } + + public FluentColumnProperties WithColumnName(string columnName) + { + if (string.IsNullOrEmpty(columnName)) + { + throw new ArgumentNullException(nameof(columnName)); + } + this.AliasName = this.ColumnName; + this.ColumnName = columnName; + return this; + } + + public FluentColumnProperties AsKey() + { + this.IsKey = true; + return this; + } + public FluentColumnProperties AsRequired() + { + this.IsRequired = true; + return this; + } + public FluentColumnProperties AsReadOnly() + { + this.IsReadOnly = true; + return this; + } + public FluentColumnProperties AsNotMapped() + { + this.IsNotMapped = true; + return this; + } + public FluentColumnProperties AsEditable() + { + this.IsEditable = true; + return this; + } + public FluentColumnProperties AsNotEditable() + { + this.IsEditable = false; + return this; + } + public FluentColumnProperties AsIgnoredInSelect() + { + this.IsIgnoredInSelect = true; + return this; + } + public FluentColumnProperties AsIgnoredInUpdate() + { + this.IsIgnoredInUpdate = true; + return this; + } + public FluentColumnProperties AsIgnoredInInsert() + { + this.IsIgnoredInInsert = true; + return this; + } + } + + public class TableMapper : ITableMapper + { + public string TableName { get; private set; } + + public IDictionary ColumnPropertiesMaps { get; private set; } + + public TableMapper() + { + TableName = typeof(T).Name; + ColumnPropertiesMaps = new Dictionary(); + } + + protected void ToTableName(string tableName) + { + if (string.IsNullOrEmpty(tableName)) + { + throw new ArgumentNullException(nameof(tableName)); + } + TableName = string.Join(".", tableName.Split('.').Select(s => Dapper.SimpleCRUD.Encapsulate(s))); + } + protected FluentColumnProperties Map(Expression> expression) + { + var pInfo = GetPropertyInfo(expression); + if (ColumnPropertiesMaps.ContainsKey(pInfo)) + throw new InvalidOperationException($"property {pInfo.Name} already Mapped"); + var colProperties = new FluentColumnProperties(pInfo); + ColumnPropertiesMaps.Add(pInfo, colProperties); + return colProperties; + } + + /// + /// Gets the corresponding from an . + /// + /// The expression that selects the property to get info on. + /// The property info collected from the expression. + /// When is null. + /// The expression doesn't indicate a valid property." + /// https://stackoverflow.com/questions/17115634/get-propertyinfo-of-a-parameter-passed-as-lambda-expression + private PropertyInfo GetPropertyInfo(Expression> expression) + { + switch (expression?.Body) { + case null: + throw new ArgumentNullException(nameof(expression)); + case UnaryExpression unaryExp when unaryExp.Operand is MemberExpression memberExp: + return (PropertyInfo)memberExp.Member; + case MemberExpression memberExp: + return (PropertyInfo)memberExp.Member; + default: + throw new ArgumentException($"The expression doesn't indicate a valid property. [ {expression} ]"); + } + } + } + + public class FluentTableMapResolver : Dapper.SimpleCRUD.IColumnPropertiesResolver, Dapper.SimpleCRUD.ITableNameResolver, Dapper.SimpleCRUD.IColumnNameResolver { + public string ResolveTableName(Type type) + { + ITableMapper tm = null; + tableMappers.TryGetValue(type, out tm); + return tm?.TableName ?? type.Name; + } + public Dapper.SimpleCRUD.IColumnProperties ResolveColumnProperties(PropertyInfo propertyInfo) + { + ITableMapper tm = null; + tableMappers.TryGetValue(propertyInfo.ReflectedType, out tm); + var colProperties = tm?.ColumnPropertiesMaps + .Where(cpm => cpm.Key.Name == propertyInfo.Name) + .Select(cmp => cmp.Value) + .FirstOrDefault(); + return colProperties ?? new FluentColumnProperties(propertyInfo); + } + public string ResolveColumnName(PropertyInfo propertyInfo) + { + return ResolveColumnProperties(propertyInfo).ColumnName; + } + } + } +} \ No newline at end of file