Repository: DapperLib/Dapper.Contrib Branch: main Commit: cf24f6bdc577 Files: 31 Total size: 172.3 KB Directory structure: gitextract_c51ukdyb/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── Build.csproj ├── Dapper.sln ├── Dapper.snk ├── Directory.Build.props ├── Directory.Build.targets ├── License.txt ├── Readme.md ├── appveyor.yml ├── build.cmd ├── build.ps1 ├── docs/ │ ├── _config.yml │ └── index.md ├── nuget.config ├── src/ │ └── Dapper.Contrib/ │ ├── Dapper.Contrib.csproj │ ├── SqlMapperExtensions.Async.cs │ └── SqlMapperExtensions.cs ├── tests/ │ ├── Dapper.Tests.Contrib/ │ │ ├── Dapper.Tests.Contrib.csproj │ │ ├── Helpers/ │ │ │ ├── Attributes.cs │ │ │ └── XunitSkippable.cs │ │ ├── TestSuite.Async.cs │ │ ├── TestSuite.cs │ │ ├── TestSuites.cs │ │ └── xunit.runner.json │ ├── Directory.Build.props │ ├── Directory.Build.targets │ └── docker-compose.yml └── version.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome:http://EditorConfig.org # top-most EditorConfig file root = true # Don't use tabs for indentation. [*] indent_style = space # (Please don't specify an indent_size here; that has too many unintended consequences.) # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 insert_final_newline = true charset = utf-8-bom # Xml project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # JSON files [*.json] indent_size = 2 # Dotnet code style settings: [*.{cs,vb}] # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion # Use language keywords instead of framework type names for type references dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion # Suggest more modern language features when available dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion # CSharp code style settings: [*.cs] # Prefer "var" everywhere #csharp_style_var_for_built_in_types = true:suggestion #csharp_style_var_when_type_is_apparent = false:suggestion #csharp_style_var_elsewhere = true:suggestion # Prefer method-like constructs to have a expression-body csharp_style_expression_bodied_methods = true:none csharp_style_expression_bodied_constructors = true:none csharp_style_expression_bodied_operators = true:none # Prefer property-like constructs to have an expression-body csharp_style_expression_bodied_properties = true:none csharp_style_expression_bodied_indexers = true:none csharp_style_expression_bodied_accessors = true:none # Suggest more modern language features when available csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true ================================================ FILE: .gitattributes ================================================ * text=auto *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.jpg binary *.png binary *.gif binary *.cs -text diff=csharp *.vb -text *.c -text *.cpp -text *.cxx -text *.h -text *.hxx -text *.py -text *.rb -text *.java -text *.html -text *.htm -text *.css -text *.scss -text *.sass -text *.less -text *.js -text *.lisp -text *.clj -text *.sql -text *.php -text *.lua -text *.m -text *.asm -text *.erl -text *.fs -text *.fsx -text *.hs -text *.csproj -text merge=union *.vbproj -text merge=union *.fsproj -text merge=union *.dbproj -text merge=union *.sln -text merge=union ================================================ FILE: .github/workflows/main.yml ================================================ name: Main Build on: pull_request: push: branches: - main paths: - '*' - '!/docs/*' # Don't run workflow when files are only in the /docs directory jobs: vm-job: name: Ubuntu runs-on: ubuntu-latest services: postgres: image: postgres ports: - 5432/tcp env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 sqlserver: image: mcr.microsoft.com/mssql/server:2019-latest ports: - 1433/tcp env: ACCEPT_EULA: Y SA_PASSWORD: "Password." mysql: image: mysql ports: - 3306/tcp env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test steps: - name: Checkout code uses: actions/checkout@v1 - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: Dapper.Contrib Tests run: dotnet test tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj -c Release --logger GitHubActions /p:CI=true env: MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true ================================================ FILE: .gitignore ================================================ /*.suo .vs/ .vscode/ bin/ obj/ /*.user _Resharper* .hgtags NuGet.exe *.user *.nupkg .nupkgs/ .docstats *.ide/ *.lock.json *.coverage Test.DB.* TestResults/ .dotnet/* BenchmarkDotNet.Artifacts/ .idea/ .DS_Store ================================================ FILE: Build.csproj ================================================ ================================================ FILE: Dapper.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28917.182 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig appveyor.yml = appveyor.yml build.ps1 = build.ps1 Directory.Build.props = Directory.Build.props docs\index.md = docs\index.md License.txt = License.txt nuget.config = nuget.config Readme.md = Readme.md version.json = version.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Contrib", "Dapper.Contrib\Dapper.Contrib.csproj", "{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Contrib", "tests\Dapper.Tests.Contrib\Dapper.Tests.Contrib.csproj", "{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" ProjectSection(SolutionItems) = preProject tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets tests\docker-compose.yml = tests\docker-compose.yml EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Release|Any CPU.Build.0 = Release|Any CPU {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Debug|Any CPU.Build.0 = Debug|Any CPU {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {4E409F8F-CFBB-4332-8B0A-FD5A283051FD} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A} = {568BD46C-1C65-4D44-870C-12CD72563262} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} EndGlobalSection EndGlobal ================================================ FILE: Directory.Build.props ================================================ 2019 Stack Exchange, Inc. true true ../Dapper.snk $(AssemblyName) https://dapperlib.github.io/Dapper.Contrib/ https://github.com/DapperLib/Dapper.Contrib Apache-2.0 Dapper.png git https://github.com/DapperLib/Dapper.Contrib false $(NOWARN);IDE0056;IDE0057;IDE0079 true embedded en-US false true 9.0 true true true ================================================ FILE: Directory.Build.targets ================================================ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) ================================================ FILE: License.txt ================================================ The Dapper.Contrib library and tools are licenced under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 ================================================ FILE: Readme.md ================================================ Dapper.Contrib - a simple object mapper for .Net ======================================== [![Build status](https://ci.appveyor.com/api/projects/status/1w448i6nfxd14w75?svg=true)](https://ci.appveyor.com/project/StackExchange/dapper-contrib) Release Notes ------------- Located at [dapperlib.github.io/Dapper.Contrib](https://dapperlib.github.io/Dapper.Contrib/) Packages -------- MyGet Pre-release feed: https://www.myget.org/gallery/dapper | Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | | ------- | ------------ | ----------------- | --------- | ----- | | [Dapper.Contrib](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/v/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/vpre/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/dt/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.Contrib.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.Contrib) | Features -------- Dapper.Contrib contains a number of helper methods for inserting, getting, updating and deleting records. The full list of extension methods in Dapper.Contrib right now are: ```csharp T Get(id); IEnumerable GetAll(); int Insert(T obj); int Insert(Enumerable list); bool Update(T obj); bool Update(Enumerable list); bool Delete(T obj); bool Delete(Enumerable list); bool DeleteAll(); ``` For these extensions to work, the entity in question _MUST_ have a key property. Dapper will automatically use a property named "`id`" (case-insensitive) as the key property, if one is present. ```csharp public class Car { public int Id { get; set; } // Works by convention public string Name { get; set; } } ``` If the entity doesn't follow this convention, decorate a specific property with a `[Key]` or `[ExplicitKey]` attribute. ```csharp public class User { [Key] int TheId { get; set; } string Name { get; set; } int Age { get; set; } } ``` `[Key]` should be used for database-generated keys (e.g. autoincrement columns), while `[ExplicitKey]` should be used for explicit keys generated in code. `Get` methods ------- Get one specific entity based on id ```csharp var car = connection.Get(1); ``` or a list of all entities in the table. ```csharp var cars = connection.GetAll(); ``` `Insert` methods ------- Insert one entity ```csharp connection.Insert(new Car { Name = "Volvo" }); ``` or a list of entities. ```csharp connection.Insert(cars); ``` `Update` methods ------- Update one specific entity ```csharp connection.Update(new Car() { Id = 1, Name = "Saab" }); ``` or update a list of entities. ```csharp connection.Update(cars); ``` `Delete` methods ------- Delete an entity by the specified `[Key]` property ```csharp connection.Delete(new Car() { Id = 1 }); ``` a list of entities ```csharp connection.Delete(cars); ``` or _ALL_ entities in the table. ```csharp connection.DeleteAll(); ``` Special Attributes ---------- Dapper.Contrib makes use of some optional attributes: * `[Table("Tablename")]` - use another table name instead of the (by default pluralized) name of the class ```csharp [Table ("emps")] public class Employee { public int Id { get; set; } public string Name { get; set; } } ``` * `[Key]` - this property represents a database-generated identity/key ```csharp public class Employee { [Key] public int EmployeeId { get; set; } public string Name { get; set; } } ``` * `[ExplicitKey]` - this property represents an explicit identity/key which is *not* automatically generated by the database ```csharp public class Employee { [ExplicitKey] public Guid EmployeeId { get; set; } public string Name { get; set; } } ``` * `[Write(true/false)]` - this property is (not) writeable * `[Computed]` - this property is computed and should not be part of updates Limitations and caveats ------- ### SQLite `SQLiteConnection` exposes an `Update` event that clashes with the `Update` extension provided by Dapper.Contrib. There are 2 ways to deal with this. 1. Call the `Update` method explicitly from `SqlMapperExtensions` ```Csharp SqlMapperExtensions.Update(_conn, new Employee { Id = 1, Name = "Mercedes" }); ``` 2. Make the method signature unique by passing a type parameter to `Update` ```Csharp connection.Update(new Car() { Id = 1, Name = "Maruti" }); ``` ================================================ FILE: appveyor.yml ================================================ image: Visual Studio 2019 skip_branch_with_pr: true skip_tags: true skip_commits: files: - '**/*.md' environment: Appveyor: true # Postgres POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6 PGUSER: postgres PGPASSWORD: Password12! POSTGRES_ENV_POSTGRES_USER: postgres POSTGRES_ENV_POSTGRES_PASSWORD: Password12! POSTGRES_ENV_POSTGRES_DB: test # MySQL MYSQL_PATH: C:\Program Files\MySql\MySQL Server 5.7 MYSQL_PWD: Password12! MYSQL_ENV_MYSQL_USER: root MYSQL_ENV_MYSQL_PASSWORD: Password12! MYSQL_ENV_MYSQL_DATABASE: test # Connection strings for tests: MySqlConnectionString: Server=localhost;Database=test;Uid=root;Pwd=Password12! OLEDBConnectionString: Provider=SQLOLEDB;Data Source=(local)\SQL2019;Initial Catalog=tempdb;User Id=sa;Password=Password12! PostgesConnectionString: Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test SqlServerConnectionString: Server=(local)\SQL2019;Database=tempdb;User ID=sa;Password=Password12! services: - mysql - postgresql init: - git config --global core.autocrlf input - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH% - net start MSSQL$SQL2019 nuget: disable_publish_on_pr: true build_script: # Postgres - createdb test # MySQL - mysql -e "create database test;" --user=root # Our stuff - ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true test: off artifacts: - path: .\.nupkgs\*.nupkg deploy: - provider: NuGet server: https://www.myget.org/F/stackoverflow/api/v2 on: branch: main api_key: secure: P/UHxq2DEs0GI1SoDXDesHjRVsSVgdywz5vmsnhFQQY5aJgO3kP+QfhwfhXz19Rw symbol_server: https://www.myget.org/F/stackoverflow/symbols/api/v2/package - provider: NuGet server: https://www.myget.org/F/dapper/api/v2 on: branch: main api_key: secure: PV7ERAltWWLhy7AT2h+Vb5c1BM9/WFgvggb+rKyQ8hDg3fYqpZauYdidOOgt2lp4 symbol_server: https://www.myget.org/F/dapper/api/v2/package ================================================ FILE: build.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" ================================================ FILE: build.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] param( [bool] $CreatePackages, [bool] $RunTests = $true, [string] $PullRequestNumber ) Write-Host "Run Parameters:" -ForegroundColor Cyan Write-Host " CreatePackages: $CreatePackages" Write-Host " RunTests: $RunTests" Write-Host " dotnet --version:" (dotnet --version) $packageOutputFolder = "$PSScriptRoot\.nupkgs" if ($PullRequestNumber) { Write-Host "Building for a pull request (#$PullRequestNumber), skipping packaging." -ForegroundColor Yellow $CreatePackages = $false } Write-Host "Building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta" dotnet build ".\Build.csproj" -c Release /p:CI=true Write-Host "Done building." -ForegroundColor "Green" if ($RunTests) { Write-Host "Running tests: Build.csproj traversal (all frameworks)" -ForegroundColor "Magenta" dotnet test ".\Build.csproj" -c Release --no-build if ($LastExitCode -ne 0) { Write-Host "Error with tests, aborting build." -Foreground "Red" Exit 1 } Write-Host "Tests passed!" -ForegroundColor "Green" } if ($CreatePackages) { New-Item -ItemType Directory -Path $packageOutputFolder -Force | Out-Null Write-Host "Clearing existing $packageOutputFolder..." -NoNewline Get-ChildItem $packageOutputFolder | Remove-Item Write-Host "done." -ForegroundColor "Green" Write-Host "Building all packages" -ForegroundColor "Green" dotnet pack ".\Build.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:CI=true } Write-Host "Build Complete." -ForegroundColor "Green" ================================================ FILE: docs/_config.yml ================================================ theme: jekyll-theme-cayman ================================================ FILE: docs/index.md ================================================ # Dapper.Contrib - Extensions for Dapper ## Overview A brief guide is available [on github](https://github.com/DapperLib/Dapper.Contrib/blob/main/Readme.md) ## Installation From NuGet: Install-Package Dapper.Contrib Note: to get the latest pre-release build, add ` -Pre` to the end of the command. ## Release Notes ### Unreleased (note: new PRs will not be merged until they add release note wording here) ### Previous Releases Prior to v2.0.90, Dapper.Contrib was part of the main repository - please see release notes at [https://dapperlib.github.io/Dapper/](https://dapperlib.github.io/Dapper/) ================================================ FILE: nuget.config ================================================  ================================================ FILE: src/Dapper.Contrib/Dapper.Contrib.csproj ================================================  Dapper.Contrib Dapper.Contrib orm;sql;micro-orm;dapper The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities. Sam Saffron;Johan Danforth net461;netstandard2.0;net5.0 false $(NoWarn);CA1050 ================================================ FILE: src/Dapper.Contrib/SqlMapperExtensions.Async.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Dapper; namespace Dapper.Contrib.Extensions { public static partial class SqlMapperExtensions { /// /// Returns a single entity by a single id from table "Ts" asynchronously using Task. T must be of interface type. /// Id must be marked with [Key] attribute. /// Created entity is tracked/intercepted for changes and used by the Update() extension. /// /// Interface type to create and populate /// Open SqlConnection /// Id of the entity to get, must be marked with [Key] attribute /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Entity of T public static async Task GetAsync(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) { var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); sql = $"SELECT * FROM {name} WHERE {key.Name} = @id"; GetQueries[type.TypeHandle] = sql; } var dynParams = new DynamicParameters(); dynParams.Add("@id", id); if (!type.IsInterface) return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); if (!((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary res)) { return null; } var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; if (val == null) continue; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); } else { property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } } ((IProxy)obj).IsDirty = false; //reset change tracking and return return obj; } /// /// Returns a list of entities from table "Ts". /// Id of T must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Entity of T public static Task> GetAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); var cacheType = typeof(List); if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql)) { GetSingleKey(nameof(GetAll)); var name = GetTableName(type); sql = "SELECT * FROM " + name; GetQueries[cacheType.TypeHandle] = sql; } if (!type.IsInterface) { return connection.QueryAsync(sql, null, transaction, commandTimeout); } return GetAllAsyncImpl(connection, transaction, commandTimeout, sql, type); } private static async Task> GetAllAsyncImpl(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string sql, Type type) where T : class { var result = await connection.QueryAsync(sql, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var list = new List(); foreach (IDictionary res in result) { var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; if (val == null) continue; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); } else { property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); } return list; } /// /// Inserts an entity into table "Ts" asynchronously using Task and returns identity id. /// /// The type being inserted. /// Open SqlConnection /// Entity to insert /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// The specific ISqlAdapter to use, auto-detected based on connection if null /// Identity of inserted entity public static Task InsertAsync(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class { var type = typeof(T); sqlAdapter ??= GetFormatter(connection); var isList = false; if (type.IsArray) { isList = true; type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { isList = true; type = type.GetGenericArguments()[0]; } } var name = GetTableName(type); var sbColumnList = new StringBuilder(null); var allProperties = TypePropertiesCache(type); var keyProperties = KeyPropertiesCache(type).ToList(); var computedProperties = ComputedPropertiesCache(type); var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed[i]; sqlAdapter.AppendColumnName(sbColumnList, property.Name); if (i < allPropertiesExceptKeyAndComputed.Count - 1) sbColumnList.Append(", "); } var sbParameterList = new StringBuilder(null); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed[i]; sbParameterList.AppendFormat("@{0}", property.Name); if (i < allPropertiesExceptKeyAndComputed.Count - 1) sbParameterList.Append(", "); } if (!isList) //single entity { return sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); } //insert list of entities var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})"; return connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout); } /// /// Updates entity in table "Ts" asynchronously using Task, checks if the entity is modified if the entity is tracked by the Get() extension. /// /// Type to be updated /// Open SqlConnection /// Entity to be updated /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if updated, false if not found or not modified (tracked entities) public static async Task UpdateAsync(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { if ((entityToUpdate is IProxy proxy) && !proxy.IsDirty) { return false; } var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { type = type.GetGenericArguments()[0]; } } var keyProperties = KeyPropertiesCache(type).ToList(); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); var sb = new StringBuilder(); sb.AppendFormat("update {0} set ", name); var allProperties = TypePropertiesCache(type); keyProperties.AddRange(explicitKeyProperties); var computedProperties = ComputedPropertiesCache(type); var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); var adapter = GetFormatter(connection); for (var i = 0; i < nonIdProps.Count; i++) { var property = nonIdProps[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < nonIdProps.Count - 1) sb.Append(", "); } sb.Append(" where "); for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < keyProperties.Count - 1) sb.Append(" and "); } var updated = await connection.ExecuteAsync(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction).ConfigureAwait(false); return updated > 0; } /// /// Delete entity in table "Ts" asynchronously using Task. /// /// Type of entity /// Open SqlConnection /// Entity to delete /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if deleted, false if not found public static async Task DeleteAsync(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { if (entityToDelete == null) throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete)); var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { type = type.GetGenericArguments()[0]; } } var keyProperties = KeyPropertiesCache(type); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); var allKeyProperties = keyProperties.Concat(explicitKeyProperties).ToList(); var sb = new StringBuilder(); sb.AppendFormat("DELETE FROM {0} WHERE ", name); var adapter = GetFormatter(connection); for (var i = 0; i < allKeyProperties.Count; i++) { var property = allKeyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < allKeyProperties.Count - 1) sb.Append(" AND "); } var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false); return deleted > 0; } /// /// Delete all entities in the table related to the type T asynchronously using Task. /// /// Type of entity /// Open SqlConnection /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if deleted, false if none found public static async Task DeleteAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); var statement = "DELETE FROM " + GetTableName(type); var deleted = await connection.ExecuteAsync(statement, null, transaction, commandTimeout).ConfigureAwait(false); return deleted > 0; } } } public partial interface ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); } public partial class SqlServerAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) values ({parameterList}); SELECT SCOPE_IDENTITY() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var first = await multi.ReadFirstOrDefaultAsync().ConfigureAwait(false); if (first == null || first.id == null) return 0; var id = (int)first.id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class SqlCeServerAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var r = (await connection.QueryAsync("SELECT @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false)).ToList(); if (r[0] == null || r[0].id == null) return 0; var id = (int)r[0].id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class MySqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var r = await connection.QueryAsync("SELECT LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var id = r.First().id; if (id == null) return 0; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return Convert.ToInt32(id); var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } } public partial class PostgresAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var sb = new StringBuilder(); sb.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", tableName, columnList, parameterList); // If no primary key then safe to assume a join table with not too much data to return var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Length == 0) { sb.Append(" RETURNING *"); } else { sb.Append(" RETURNING "); bool first = true; foreach (var property in propertyInfos) { if (!first) sb.Append(", "); first = false; sb.Append(property.Name); } } var results = await connection.QueryAsync(sb.ToString(), entityToInsert, transaction, commandTimeout).ConfigureAwait(false); // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys var id = 0; foreach (var p in propertyInfos) { var value = ((IDictionary)results.First())[p.Name.ToLower()]; p.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } return id; } } public partial class SQLiteAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var id = (int)(await multi.ReadFirstAsync().ConfigureAwait(false)).id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class FbAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); var keyName = propertyInfos[0].Name; var r = await connection.QueryAsync($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var id = r.First().ID; if (id == null) return 0; if (propertyInfos.Length == 0) return Convert.ToInt32(id); var idp = propertyInfos[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } } ================================================ FILE: src/Dapper.Contrib/SqlMapperExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Collections.Concurrent; using System.Reflection.Emit; using System.Threading; using Dapper; namespace Dapper.Contrib.Extensions { /// /// The Dapper.Contrib extensions for Dapper /// public static partial class SqlMapperExtensions { /// /// Defined a proxy object with a possibly dirty state. /// public interface IProxy //must be kept public { /// /// Whether the object has been changed. /// bool IsDirty { get; set; } } /// /// Defines a table name mapper for getting table names from types. /// public interface ITableNameMapper { /// /// Gets a table name from a given . /// /// The to get a name from. /// The table name for the given . string GetTableName(Type type); } /// /// The function to get a database type from the given . /// /// The connection to get a database type name from. public delegate string GetDatabaseTypeDelegate(IDbConnection connection); /// /// The function to get a table name from a given /// /// The to get a table name for. public delegate string TableNameMapperDelegate(Type type); private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); private static readonly Dictionary AdapterDictionary = new Dictionary(6) { ["sqlconnection"] = new SqlServerAdapter(), ["sqlceconnection"] = new SqlCeServerAdapter(), ["npgsqlconnection"] = new PostgresAdapter(), ["sqliteconnection"] = new SQLiteAdapter(), ["mysqlconnection"] = new MySqlAdapter(), ["fbconnection"] = new FbAdapter() }; private static List ComputedPropertiesCache(Type type) { if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable pi)) { return pi.ToList(); } var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); ComputedProperties[type.TypeHandle] = computedProperties; return computedProperties; } private static List ExplicitKeyPropertiesCache(Type type) { if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi)) { return pi.ToList(); } var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; return explicitKeyProperties; } private static List KeyPropertiesCache(Type type) { if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi)) { return pi.ToList(); } var allProperties = TypePropertiesCache(type); var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList(); if (keyProperties.Count == 0) { var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase)); if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) { keyProperties.Add(idProp); } } KeyProperties[type.TypeHandle] = keyProperties; return keyProperties; } private static List TypePropertiesCache(Type type) { if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable pis)) { return pis.ToList(); } var properties = type.GetProperties().Where(IsWriteable).ToArray(); TypeProperties[type.TypeHandle] = properties; return properties.ToList(); } private static bool IsWriteable(PropertyInfo pi) { var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); if (attributes.Count != 1) return true; var writeAttribute = (WriteAttribute)attributes[0]; return writeAttribute.Write; } private static PropertyInfo GetSingleKey(string method) { var type = typeof(T); var keys = KeyPropertiesCache(type); var explicitKeys = ExplicitKeyPropertiesCache(type); var keyCount = keys.Count + explicitKeys.Count; if (keyCount > 1) throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property. [Key] Count: {keys.Count}, [ExplicitKey] Count: {explicitKeys.Count}"); if (keyCount == 0) throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); return keys.Count > 0 ? keys[0] : explicitKeys[0]; } /// /// Returns a single entity by a single id from table "Ts". /// Id must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection /// Id of the entity to get, must be marked with [Key] attribute /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Entity of T public static T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) { var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); sql = $"select * from {name} where {key.Name} = @id"; GetQueries[type.TypeHandle] = sql; } var dynParams = new DynamicParameters(); dynParams.Add("@id", id); T obj; if (type.IsInterface) { if (!(connection.Query(sql, dynParams).FirstOrDefault() is IDictionary res)) { return null; } obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; if (val == null) continue; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); } else { property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } } ((IProxy)obj).IsDirty = false; //reset change tracking and return } else { obj = connection.Query(sql, dynParams, transaction, commandTimeout: commandTimeout).FirstOrDefault(); } return obj; } /// /// Returns a list of entities from table "Ts". /// Id of T must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Entity of T public static IEnumerable GetAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); var cacheType = typeof(List); if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql)) { GetSingleKey(nameof(GetAll)); var name = GetTableName(type); sql = "select * from " + name; GetQueries[cacheType.TypeHandle] = sql; } if (!type.IsInterface) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout); var result = connection.Query(sql); var list = new List(); foreach (IDictionary res in result) { var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; if (val == null) continue; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); } else { property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); } return list; } /// /// Specify a custom table name mapper based on the POCO type name /// #pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static TableNameMapperDelegate TableNameMapper; #pragma warning restore CA2211 // Non-constant fields should not be visible private static string GetTableName(Type type) { if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name; if (TableNameMapper != null) { name = TableNameMapper(type); } else { //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework var tableAttrName = type.GetCustomAttribute(false)?.Name ?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; if (tableAttrName != null) { name = tableAttrName; } else { name = type.Name + "s"; if (type.IsInterface && name.StartsWith("I")) name = name.Substring(1); } } TypeTableName[type.TypeHandle] = name; return name; } /// /// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list. /// /// The type to insert. /// Open SqlConnection /// Entity to insert, can be list of entities /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Identity of inserted entity, or number of inserted rows if inserting a list public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var isList = false; var type = typeof(T); if (type.IsArray) { isList = true; type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { isList = true; type = type.GetGenericArguments()[0]; } } var name = GetTableName(type); var sbColumnList = new StringBuilder(null); var allProperties = TypePropertiesCache(type); var keyProperties = KeyPropertiesCache(type); var computedProperties = ComputedPropertiesCache(type); var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); var adapter = GetFormatter(connection); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed[i]; adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336 if (i < allPropertiesExceptKeyAndComputed.Count - 1) sbColumnList.Append(", "); } var sbParameterList = new StringBuilder(null); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed[i]; sbParameterList.AppendFormat("@{0}", property.Name); if (i < allPropertiesExceptKeyAndComputed.Count - 1) sbParameterList.Append(", "); } int returnVal; var wasClosed = connection.State == ConnectionState.Closed; if (wasClosed) connection.Open(); if (!isList) //single entity { returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); } else { //insert list of entities var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})"; returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout); } if (wasClosed) connection.Close(); return returnVal; } /// /// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension. /// /// Type to be updated /// Open SqlConnection /// Entity to be updated /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if updated, false if not found or not modified (tracked entities) public static bool Update(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { if (entityToUpdate is IProxy proxy && !proxy.IsDirty) { return false; } var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { type = type.GetGenericArguments()[0]; } } var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); var sb = new StringBuilder(); sb.AppendFormat("update {0} set ", name); var allProperties = TypePropertiesCache(type); keyProperties.AddRange(explicitKeyProperties); var computedProperties = ComputedPropertiesCache(type); var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); var adapter = GetFormatter(connection); for (var i = 0; i < nonIdProps.Count; i++) { var property = nonIdProps[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 if (i < nonIdProps.Count - 1) sb.Append(", "); } sb.Append(" where "); for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 if (i < keyProperties.Count - 1) sb.Append(" and "); } var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); return updated > 0; } /// /// Delete entity in table "Ts". /// /// Type of entity /// Open SqlConnection /// Entity to delete /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if deleted, false if not found public static bool Delete(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { if (entityToDelete == null) throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete)); var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { type = type.GetGenericArguments()[0]; } } var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); keyProperties.AddRange(explicitKeyProperties); var sb = new StringBuilder(); sb.AppendFormat("delete from {0} where ", name); var adapter = GetFormatter(connection); for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 if (i < keyProperties.Count - 1) sb.Append(" and "); } var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); return deleted > 0; } /// /// Delete all entities in the table related to the type T. /// /// Type of entity /// Open SqlConnection /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if deleted, false if none found public static bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); var name = GetTableName(type); var statement = $"delete from {name}"; var deleted = connection.Execute(statement, null, transaction, commandTimeout); return deleted > 0; } /// /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. /// #pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static GetDatabaseTypeDelegate GetDatabaseType; #pragma warning restore CA2211 // Non-constant fields should not be visible private static ISqlAdapter GetFormatter(IDbConnection connection) { var name = GetDatabaseType?.Invoke(connection).ToLower() ?? connection.GetType().Name.ToLower(); return AdapterDictionary.TryGetValue(name, out var adapter) ? adapter : DefaultAdapter; } private static class ProxyGenerator { private static readonly Dictionary TypeCache = new Dictionary(); private static AssemblyBuilder GetAsmBuilder(string name) { #if !NET461 return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #else return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #endif } public static T GetInterfaceProxy() { Type typeOfT = typeof(T); if (TypeCache.TryGetValue(typeOfT, out Type k)) { return (T)Activator.CreateInstance(k); } var assemblyBuilder = GetAsmBuilder(typeOfT.Name); var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter var interfaceType = typeof(IProxy); var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), TypeAttributes.Public | TypeAttributes.Class); typeBuilder.AddInterfaceImplementation(typeOfT); typeBuilder.AddInterfaceImplementation(interfaceType); //create our _isDirty field, which implements IProxy var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder); // Generate a field for each property, which implements the T foreach (var property in typeof(T).GetProperties()) { var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute); CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); } #if NETSTANDARD2_0 var generatedType = typeBuilder.CreateTypeInfo().AsType(); #else var generatedType = typeBuilder.CreateType(); #endif TypeCache.Add(typeOfT, generatedType); return (T)Activator.CreateInstance(generatedType); } private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) { var propType = typeof(bool); var field = typeBuilder.DefineField("_" + nameof(IProxy.IsDirty), propType, FieldAttributes.Private); var property = typeBuilder.DefineProperty(nameof(IProxy.IsDirty), System.Reflection.PropertyAttributes.None, propType, new[] { propType }); const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; // Define the "get" and "set" accessor methods var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + nameof(IProxy.IsDirty), getSetAttr, propType, Type.EmptyTypes); var currGetIl = currGetPropMthdBldr.GetILGenerator(); currGetIl.Emit(OpCodes.Ldarg_0); currGetIl.Emit(OpCodes.Ldfld, field); currGetIl.Emit(OpCodes.Ret); var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + nameof(IProxy.IsDirty), getSetAttr, null, new[] { propType }); var currSetIl = currSetPropMthdBldr.GetILGenerator(); currSetIl.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_1); currSetIl.Emit(OpCodes.Stfld, field); currSetIl.Emit(OpCodes.Ret); property.SetGetMethod(currGetPropMthdBldr); property.SetSetMethod(currSetPropMthdBldr); var getMethod = typeof(IProxy).GetMethod("get_" + nameof(IProxy.IsDirty)); var setMethod = typeof(IProxy).GetMethod("set_" + nameof(IProxy.IsDirty)); typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); return currSetPropMthdBldr; } private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) { //Define the field and the property var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); var property = typeBuilder.DefineProperty(propertyName, System.Reflection.PropertyAttributes.None, propType, new[] { propType }); const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig; // Define the "get" and "set" accessor methods var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, getSetAttr, propType, Type.EmptyTypes); var currGetIl = currGetPropMthdBldr.GetILGenerator(); currGetIl.Emit(OpCodes.Ldarg_0); currGetIl.Emit(OpCodes.Ldfld, field); currGetIl.Emit(OpCodes.Ret); var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, getSetAttr, null, new[] { propType }); //store value in private field and set the isdirty flag var currSetIl = currSetPropMthdBldr.GetILGenerator(); currSetIl.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_1); currSetIl.Emit(OpCodes.Stfld, field); currSetIl.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldc_I4_1); currSetIl.Emit(OpCodes.Call, setIsDirtyMethod); currSetIl.Emit(OpCodes.Ret); //TODO: Should copy all attributes defined by the interface? if (isIdentity) { var keyAttribute = typeof(KeyAttribute); var myConstructorInfo = keyAttribute.GetConstructor(Type.EmptyTypes); var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, Array.Empty()); property.SetCustomAttribute(attributeBuilder); } property.SetGetMethod(currGetPropMthdBldr); property.SetSetMethod(currSetPropMthdBldr); var getMethod = typeof(T).GetMethod("get_" + propertyName); var setMethod = typeof(T).GetMethod("set_" + propertyName); typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); } } } /// /// Defines the name of a table to use in Dapper.Contrib commands. /// [AttributeUsage(AttributeTargets.Class)] public class TableAttribute : Attribute { /// /// Creates a table mapping to a specific name for Dapper.Contrib commands /// /// The name of this table in the database. public TableAttribute(string tableName) { Name = tableName; } /// /// The name of the table in the database /// public string Name { get; set; } } /// /// Specifies that this field is a primary key in the database /// [AttributeUsage(AttributeTargets.Property)] public class KeyAttribute : Attribute { } /// /// Specifies that this field is an explicitly set primary key in the database /// [AttributeUsage(AttributeTargets.Property)] public class ExplicitKeyAttribute : Attribute { } /// /// Specifies whether a field is writable in the database. /// [AttributeUsage(AttributeTargets.Property)] public class WriteAttribute : Attribute { /// /// Specifies whether a field is writable in the database. /// /// Whether a field is writable in the database. public WriteAttribute(bool write) { Write = write; } /// /// Whether a field is writable in the database. /// public bool Write { get; } } /// /// Specifies that this is a computed column. /// [AttributeUsage(AttributeTargets.Property)] public class ComputedAttribute : Attribute { } } /// /// The interface for all Dapper.Contrib database operations /// Implementing this is each provider's model. /// public partial interface ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. void AppendColumnName(StringBuilder sb, string columnName); /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); } /// /// The SQL Server database adapter. /// public partial class SqlServerAdapter : ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id"; var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); var first = multi.Read().FirstOrDefault(); if (first == null || first.id == null) return 0; var id = (int)first.id; var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Length == 0) return id; var idProperty = propertyInfos[0]; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; } /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. public void AppendColumnName(StringBuilder sb, string columnName) { sb.AppendFormat("[{0}]", columnName); } /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { sb.AppendFormat("[{0}] = @{1}", columnName, columnName); } } /// /// The SQL Server Compact Edition database adapter. /// public partial class SqlCeServerAdapter : ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ToList(); if (r[0].id == null) return 0; var id = (int)r[0].id; var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Length == 0) return id; var idProperty = propertyInfos[0]; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; } /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. public void AppendColumnName(StringBuilder sb, string columnName) { sb.AppendFormat("[{0}]", columnName); } /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { sb.AppendFormat("[{0}] = @{1}", columnName, columnName); } } /// /// The MySQL database adapter. /// public partial class MySqlAdapter : ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout); var id = r.First().id; if (id == null) return 0; var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Length == 0) return Convert.ToInt32(id); var idp = propertyInfos[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. public void AppendColumnName(StringBuilder sb, string columnName) { sb.AppendFormat("`{0}`", columnName); } /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { sb.AppendFormat("`{0}` = @{1}", columnName, columnName); } } /// /// The Postgres database adapter. /// public partial class PostgresAdapter : ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var sb = new StringBuilder(); sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); // If no primary key then safe to assume a join table with not too much data to return var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Length == 0) { sb.Append(" RETURNING *"); } else { sb.Append(" RETURNING "); var first = true; foreach (var property in propertyInfos) { if (!first) sb.Append(", "); first = false; sb.Append(property.Name); } } var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys var id = 0; foreach (var p in propertyInfos) { var value = ((IDictionary)results[0])[p.Name.ToLower()]; p.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } return id; } /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. public void AppendColumnName(StringBuilder sb, string columnName) { sb.AppendFormat("\"{0}\"", columnName); } /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); } } /// /// The SQLite database adapter. /// public partial class SQLiteAdapter : ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); var id = (int)multi.Read().First().id; var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Length == 0) return id; var idProperty = propertyInfos[0]; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; } /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. public void AppendColumnName(StringBuilder sb, string columnName) { sb.AppendFormat("\"{0}\"", columnName); } /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); } } /// /// The Firebase SQL adapter. /// public partial class FbAdapter : ISqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); var keyName = propertyInfos[0].Name; var r = connection.Query($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout); var id = r.First().ID; if (id == null) return 0; if (propertyInfos.Length == 0) return Convert.ToInt32(id); var idp = propertyInfos[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } /// /// Adds the name of a column. /// /// The string builder to append to. /// The column name. public void AppendColumnName(StringBuilder sb, string columnName) { sb.AppendFormat("{0}", columnName); } /// /// Adds a column equality to a parameter. /// /// The string builder to append to. /// The column name. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { sb.AppendFormat("{0} = @{1}", columnName, columnName); } } ================================================ FILE: tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj ================================================  Dapper.Tests.Contrib Dapper Contrib Test Suite netcoreapp3.1;net462;net5.0 $(NoWarn);CA1816;IDE0063;xUnit1004 PreserveNewest ================================================ FILE: tests/Dapper.Tests.Contrib/Helpers/Attributes.cs ================================================ using System; using Xunit.Sdk; namespace Dapper.Tests { /// /// Override for that truncates our DisplayName down. /// /// Attribute that is applied to a method to indicate that it is a fact that should /// be run by the test runner. It can also be extended to support a customized definition /// of a test method. /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Dapper.Tests.FactDiscoverer", "Dapper.Tests.Contrib")] public class FactAttribute : Xunit.FactAttribute { } /// /// Override for that truncates our DisplayName down. /// /// Marks a test method as being a data theory. Data theories are tests which are /// fed various bits of data from a data source, mapping to parameters on the test /// method. If the data source contains multiple rows, then the test method is executed /// multiple times (once with each data row). Data is provided by attributes which /// derive from Xunit.Sdk.DataAttribute (notably, Xunit.InlineDataAttribute and Xunit.MemberDataAttribute). /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Dapper.Tests.TheoryDiscoverer", "Dapper.Tests.Contrib")] public class TheoryAttribute : Xunit.TheoryAttribute { } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class FactLongRunningAttribute : FactAttribute { public FactLongRunningAttribute() { #if !LONG_RUNNING Skip = "Long running"; #endif } public string Url { get; private set; } } } ================================================ FILE: tests/Dapper.Tests.Contrib/Helpers/XunitSkippable.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit.Sdk; namespace Dapper.Tests { public static class Skip { public static void Inconclusive(string reason = "inconclusive") => throw new SkipTestException(reason); public static void If(object obj, string reason = null) where T : class { if (obj is T) Skip.Inconclusive(reason ?? $"not valid for {typeof(T).FullName}"); } } #pragma warning disable RCS1194 // Implement exception constructors. public class SkipTestException : Exception { public SkipTestException(string reason) : base(reason) { } } #pragma warning restore RCS1194 // Implement exception constructors. public class FactDiscoverer : Xunit.Sdk.FactDiscoverer { public FactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) => new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); } public class TheoryDiscoverer : Xunit.Sdk.TheoryDiscoverer { public TheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) => new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, dataRow) }; protected override IEnumerable CreateTestCasesForSkip(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, string skipReason) => new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) => new[] { new SkippableTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; protected override IEnumerable CreateTestCasesForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason) => new[] { new NamedSkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow) }; } public class SkippableTestCase : XunitTestCase { protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => base.GetDisplayName(factAttribute, displayName).StripName(); [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public SkippableTestCase() { } public SkippableTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[] testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) { } public override async Task RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { var skipMessageBus = new SkippableMessageBus(messageBus); var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource).ConfigureAwait(false); return result.Update(skipMessageBus); } } public class SkippableTheoryTestCase : XunitTheoryTestCase { protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => base.GetDisplayName(factAttribute, displayName).StripName(); [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public SkippableTheoryTestCase() { } public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) { } public override async Task RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { var skipMessageBus = new SkippableMessageBus(messageBus); var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource).ConfigureAwait(false); return result.Update(skipMessageBus); } } public class NamedSkippedDataRowTestCase : XunitSkippedDataRowTestCase { protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => base.GetDisplayName(factAttribute, displayName).StripName(); [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public NamedSkippedDataRowTestCase() { } public NamedSkippedDataRowTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, string skipReason, object[] testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, skipReason, testMethodArguments) { } } public class SkippableMessageBus : IMessageBus { private readonly IMessageBus InnerBus; public SkippableMessageBus(IMessageBus innerBus) => InnerBus = innerBus; public int DynamicallySkippedTestCount { get; private set; } public void Dispose() { } public bool QueueMessage(IMessageSinkMessage message) { if (message is ITestFailed testFailed) { var exceptionType = testFailed.ExceptionTypes.FirstOrDefault(); if (exceptionType == typeof(SkipTestException).FullName) { DynamicallySkippedTestCount++; return InnerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); } } return InnerBus.QueueMessage(message); } } internal static class XUnitExtensions { internal static string StripName(this string name) => name.Replace("Dapper.Tests.", ""); public static RunSummary Update(this RunSummary summary, SkippableMessageBus bus) { if (bus.DynamicallySkippedTestCount > 0) { summary.Failed -= bus.DynamicallySkippedTestCount; summary.Skipped += bus.DynamicallySkippedTestCount; } return summary; } } } ================================================ FILE: tests/Dapper.Tests.Contrib/TestSuite.Async.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Dapper.Contrib.Extensions; using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; using Xunit; namespace Dapper.Tests.Contrib { public abstract partial class TestSuite { [Fact] public async Task TypeWithGenericParameterCanBeInsertedAsync() { using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync>(); var objectToInsert = new GenericType { Id = Guid.NewGuid().ToString(), Name = "something" }; await connection.InsertAsync(objectToInsert); Assert.Single(connection.GetAll>()); var objectsToInsert = new List>(2) { new GenericType { Id = Guid.NewGuid().ToString(), Name = "1", }, new GenericType { Id = Guid.NewGuid().ToString(), Name = "2", } }; await connection.InsertAsync(objectsToInsert); var list = connection.GetAll>(); Assert.Equal(3, list.Count()); } } [Fact] public async Task TypeWithGenericParameterCanBeUpdatedAsync() { using (var connection = GetOpenConnection()) { var objectToInsert = new GenericType { Id = Guid.NewGuid().ToString(), Name = "something" }; await connection.InsertAsync(objectToInsert); objectToInsert.Name = "somethingelse"; await connection.UpdateAsync(objectToInsert); var updatedObject = connection.Get>(objectToInsert.Id); Assert.Equal(objectToInsert.Name, updatedObject.Name); } } [Fact] public async Task TypeWithGenericParameterCanBeDeletedAsync() { using (var connection = GetOpenConnection()) { var objectToInsert = new GenericType { Id = Guid.NewGuid().ToString(), Name = "something" }; await connection.InsertAsync(objectToInsert); bool deleted = await connection.DeleteAsync(objectToInsert); Assert.True(deleted); } } [Fact] public async Task GetAsyncSucceedsAfterDeleteAsyncWhenExplicitKeyPresent() { using (var connection = GetOpenConnection()) { await connection.DeleteAsync(new ObjectX { ObjectXId = Guid.NewGuid().ToString() }).ConfigureAwait(false); var retrieved = await connection.GetAsync(Guid.NewGuid().ToString()).ConfigureAwait(false); Assert.Null(retrieved); } } /// /// Tests for issue #351 /// [Fact] public async Task InsertGetUpdateDeleteWithExplicitKeyAsync() { using (var connection = GetOpenConnection()) { var guid = Guid.NewGuid().ToString(); var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" }; var originalxCount = (await connection.QueryAsync("Select Count(*) From ObjectX").ConfigureAwait(false)).First(); await connection.InsertAsync(o1).ConfigureAwait(false); var list1 = (await connection.QueryAsync("select * from ObjectX").ConfigureAwait(false)).ToList(); Assert.Equal(list1.Count, originalxCount + 1); o1 = await connection.GetAsync(guid).ConfigureAwait(false); Assert.Equal(o1.ObjectXId, guid); o1.Name = "Bar"; await connection.UpdateAsync(o1).ConfigureAwait(false); o1 = await connection.GetAsync(guid).ConfigureAwait(false); Assert.Equal("Bar", o1.Name); await connection.DeleteAsync(o1).ConfigureAwait(false); o1 = await connection.GetAsync(guid).ConfigureAwait(false); Assert.Null(o1); const int id = 42; var o2 = new ObjectY { ObjectYId = id, Name = "Foo" }; var originalyCount = connection.Query("Select Count(*) From ObjectY").First(); await connection.InsertAsync(o2).ConfigureAwait(false); var list2 = (await connection.QueryAsync("select * from ObjectY").ConfigureAwait(false)).ToList(); Assert.Equal(list2.Count, originalyCount + 1); o2 = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal(o2.ObjectYId, id); o2.Name = "Bar"; await connection.UpdateAsync(o2).ConfigureAwait(false); o2 = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal("Bar", o2.Name); await connection.DeleteAsync(o2).ConfigureAwait(false); o2 = await connection.GetAsync(id).ConfigureAwait(false); Assert.Null(o2); } } [Fact] public async Task TableNameAsync() { using (var connection = GetOpenConnection()) { // tests against "Automobiles" table (Table attribute) var id = await connection.InsertAsync(new Car { Name = "VolvoAsync" }).ConfigureAwait(false); var car = await connection.GetAsync(id).ConfigureAwait(false); Assert.NotNull(car); Assert.Equal("VolvoAsync", car.Name); Assert.True(await connection.UpdateAsync(new Car { Id = id, Name = "SaabAsync" }).ConfigureAwait(false)); Assert.Equal("SaabAsync", (await connection.GetAsync(id).ConfigureAwait(false)).Name); Assert.True(await connection.DeleteAsync(new Car { Id = id }).ConfigureAwait(false)); Assert.Null(await connection.GetAsync(id).ConfigureAwait(false)); } } [Fact] public async Task TestSimpleGetAsync() { using (var connection = GetOpenConnection()) { var id = await connection.InsertAsync(new User { Name = "Adama", Age = 10 }).ConfigureAwait(false); var user = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal(id, user.Id); Assert.Equal("Adama", user.Name); await connection.DeleteAsync(user).ConfigureAwait(false); } } [Fact] public async Task InsertGetUpdateAsync() { using (var connection = GetOpenConnection()) { Assert.Null(await connection.GetAsync(30).ConfigureAwait(false)); var originalCount = (await connection.QueryAsync("select Count(*) from Users").ConfigureAwait(false)).First(); var id = await connection.InsertAsync(new User { Name = "Adam", Age = 10 }).ConfigureAwait(false); //get a user with "isdirty" tracking var user = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal("Adam", user.Name); Assert.False(await connection.UpdateAsync(user).ConfigureAwait(false)); //returns false if not updated, based on tracking user.Name = "Bob"; Assert.True(await connection.UpdateAsync(user).ConfigureAwait(false)); //returns true if updated, based on tracking user = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal("Bob", user.Name); //get a user with no tracking var notrackedUser = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal("Bob", notrackedUser.Name); Assert.True(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); //returns true, even though user was not changed notrackedUser.Name = "Cecil"; Assert.True(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); Assert.Equal("Cecil", (await connection.GetAsync(id).ConfigureAwait(false)).Name); Assert.Equal((await connection.QueryAsync("select * from Users").ConfigureAwait(false)).Count(), originalCount + 1); Assert.True(await connection.DeleteAsync(user).ConfigureAwait(false)); Assert.Equal((await connection.QueryAsync("select * from Users").ConfigureAwait(false)).Count(), originalCount); Assert.False(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); //returns false, user not found Assert.True(await connection.InsertAsync(new User { Name = "Adam", Age = 10 }).ConfigureAwait(false) > originalCount + 1); } } [Fact] public async Task InsertCheckKeyAsync() { using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); Assert.Null(await connection.GetAsync(3).ConfigureAwait(false)); var user = new User { Name = "Adamb", Age = 10 }; var id = await connection.InsertAsync(user).ConfigureAwait(false); Assert.Equal(user.Id, id); } } [Fact] public async Task BuilderSelectClauseAsync() { using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var rand = new Random(8675309); var data = new List(100); for (var i = 0; i < 100; i++) { var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; data.Add(nU); nU.Id = await connection.InsertAsync(nU).ConfigureAwait(false); } var builder = new SqlBuilder(); var justId = builder.AddTemplate("SELECT /**select**/ FROM Users"); var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users"); builder.Select("Id"); var ids = await connection.QueryAsync(justId.RawSql, justId.Parameters).ConfigureAwait(false); var users = await connection.QueryAsync(all.RawSql, all.Parameters).ConfigureAwait(false); foreach (var u in data) { if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select"); if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age)) throw new Exception("Missing users in select"); } } } [Fact] public async Task BuilderTemplateWithoutCompositionAsync() { var builder = new SqlBuilder(); var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new { age = 5 }); if (template.RawSql == null) throw new Exception("RawSql null"); if (template.Parameters == null) throw new Exception("Parameters null"); using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); await connection.InsertAsync(new User { Age = 5, Name = "Testy McTestington" }).ConfigureAwait(false); if ((await connection.QueryAsync(template.RawSql, template.Parameters).ConfigureAwait(false)).Single() != 1) throw new Exception("Query failed"); } } [Fact] public async Task InsertEnumerableAsync() { await InsertHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); } [Fact] public async Task InsertArrayAsync() { await InsertHelperAsync(src => src.ToArray()).ConfigureAwait(false); } [Fact] public async Task InsertListAsync() { await InsertHelperAsync(src => src.ToList()).ConfigureAwait(false); } private async Task InsertHelperAsync(Func, T> helper) where T : class { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false); Assert.Equal(total, numberOfEntities); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities); } } [Fact] public async Task UpdateEnumerableAsync() { await UpdateHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); } [Fact] public async Task UpdateArrayAsync() { await UpdateHelperAsync(src => src.ToArray()).ConfigureAwait(false); } [Fact] public async Task UpdateListAsync() { await UpdateHelperAsync(src => src.ToList()).ConfigureAwait(false); } private async Task UpdateHelperAsync(Func, T> helper) where T : class { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false); Assert.Equal(total, numberOfEntities); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities); foreach (var user in users) { user.Name += " updated"; } await connection.UpdateAsync(helper(users)).ConfigureAwait(false); var name = connection.Query("select * from Users").First().Name; Assert.Contains("updated", name); } } [Fact] public async Task DeleteEnumerableAsync() { await DeleteHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); } [Fact] public async Task DeleteArrayAsync() { await DeleteHelperAsync(src => src.ToArray()).ConfigureAwait(false); } [Fact] public async Task DeleteListAsync() { await DeleteHelperAsync(src => src.ToList()).ConfigureAwait(false); } private async Task DeleteHelperAsync(Func, T> helper) where T : class { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false); Assert.Equal(total, numberOfEntities); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities); var usersToDelete = users.Take(10).ToList(); await connection.DeleteAsync(helper(usersToDelete)).ConfigureAwait(false); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities - 10); } } [Fact] public async Task GetAllAsync() { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var total = await connection.InsertAsync(users).ConfigureAwait(false); Assert.Equal(total, numberOfEntities); users = (List)await connection.GetAllAsync().ConfigureAwait(false); Assert.Equal(users.Count, numberOfEntities); var iusers = await connection.GetAllAsync().ConfigureAwait(false); Assert.Equal(iusers.ToList().Count, numberOfEntities); } } /// /// Test for issue #933 /// [Fact] public async void GetAsyncAndGetAllAsyncWithNullableValues() { using (var connection = GetOpenConnection()) { var id1 = connection.Insert(new NullableDate { DateValue = new DateTime(2011, 07, 14) }); var id2 = connection.Insert(new NullableDate { DateValue = null }); var value1 = await connection.GetAsync(id1).ConfigureAwait(false); Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value); var value2 = await connection.GetAsync(id2).ConfigureAwait(false); Assert.True(value2.DateValue == null); var value3 = await connection.GetAllAsync().ConfigureAwait(false); var valuesList = value3.ToList(); Assert.Equal(new DateTime(2011, 07, 14), valuesList[0].DateValue.Value); Assert.True(valuesList[1].DateValue == null); } } [Fact] public async Task InsertFieldWithReservedNameAsync() { using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var id = await connection.InsertAsync(new Result { Name = "Adam", Order = 1 }).ConfigureAwait(false); var result = await connection.GetAsync(id).ConfigureAwait(false); Assert.Equal(1, result.Order); } } [Fact] public async Task DeleteAllAsync() { using (var connection = GetOpenConnection()) { await connection.DeleteAllAsync().ConfigureAwait(false); var id1 = await connection.InsertAsync(new User { Name = "Alice", Age = 32 }).ConfigureAwait(false); var id2 = await connection.InsertAsync(new User { Name = "Bob", Age = 33 }).ConfigureAwait(false); await connection.DeleteAllAsync().ConfigureAwait(false); Assert.Null(await connection.GetAsync(id1).ConfigureAwait(false)); Assert.Null(await connection.GetAsync(id2).ConfigureAwait(false)); } } } } ================================================ FILE: tests/Dapper.Tests.Contrib/TestSuite.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using Dapper.Contrib.Extensions; using Xunit; using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; namespace Dapper.Tests.Contrib { [Table("ObjectX")] public class ObjectX { [ExplicitKey] public string ObjectXId { get; set; } public string Name { get; set; } } [Table("ObjectY")] public class ObjectY { [ExplicitKey] public int ObjectYId { get; set; } public string Name { get; set; } } [Table("ObjectZ")] public class ObjectZ { [ExplicitKey] public int Id { get; set; } public string Name { get; set; } } public interface IUser { [Key] int Id { get; set; } string Name { get; set; } int Age { get; set; } } public class User : IUser { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } public interface INullableDate { [Key] int Id { get; set; } DateTime? DateValue { get; set; } } public class NullableDate : INullableDate { public int Id { get; set; } public DateTime? DateValue { get; set; } } public class Person { public int Id { get; set; } public string Name { get; set; } } [Table("Stuff")] public class Stuff { [Key] public short TheId { get; set; } public string Name { get; set; } public DateTime? Created { get; set; } } [Table("Automobiles")] public class Car { public int Id { get; set; } public string Name { get; set; } [Computed] public string Computed { get; set; } } [Table("Results")] public class Result { public int Id { get; set; } public string Name { get; set; } public int Order { get; set; } } [Table("GenericType")] public class GenericType { [ExplicitKey] public string Id { get; set; } public string Name { get; set; } } public abstract partial class TestSuite { public abstract IDbConnection GetConnection(); protected static string GetConnectionString(string name, string defaultConnectionString) => Environment.GetEnvironmentVariable(name) ?? defaultConnectionString; private IDbConnection GetOpenConnection() { var connection = GetConnection(); connection.Open(); return connection; } [Fact] public void TypeWithGenericParameterCanBeInserted() { using (var connection = GetOpenConnection()) { connection.DeleteAll>(); var objectToInsert = new GenericType { Id = Guid.NewGuid().ToString(), Name = "something" }; connection.Insert(objectToInsert); Assert.Single(connection.GetAll>()); var objectsToInsert = new List>(2) { new GenericType { Id = Guid.NewGuid().ToString(), Name = "1", }, new GenericType { Id = Guid.NewGuid().ToString(), Name = "2", } }; connection.Insert(objectsToInsert); var list = connection.GetAll>(); Assert.Equal(3, list.Count()); } } [Fact] public void TypeWithGenericParameterCanBeUpdated() { using (var connection = GetOpenConnection()) { var objectToInsert = new GenericType { Id = Guid.NewGuid().ToString(), Name = "something" }; connection.Insert(objectToInsert); objectToInsert.Name = "somethingelse"; connection.Update(objectToInsert); var updatedObject = connection.Get>(objectToInsert.Id); Assert.Equal(objectToInsert.Name, updatedObject.Name); } } [Fact] public void TypeWithGenericParameterCanBeDeleted() { using (var connection = GetOpenConnection()) { var objectToInsert = new GenericType { Id = Guid.NewGuid().ToString(), Name = "something" }; connection.Insert(objectToInsert); bool deleted = connection.Delete(objectToInsert); Assert.True(deleted); } } [Fact] public void Issue418() { using (var connection = GetOpenConnection()) { //update first (will fail) then insert //added for bug #418 var updateObject = new ObjectX { ObjectXId = Guid.NewGuid().ToString(), Name = "Someone" }; var updates = connection.Update(updateObject); Assert.False(updates); connection.DeleteAll(); var objectXId = Guid.NewGuid().ToString(); var insertObject = new ObjectX { ObjectXId = objectXId, Name = "Someone else" }; connection.Insert(insertObject); var list = connection.GetAll(); Assert.Single(list); } } /// /// Tests for issue #351 /// [Fact] public void InsertGetUpdateDeleteWithExplicitKey() { using (var connection = GetOpenConnection()) { var guid = Guid.NewGuid().ToString(); var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" }; var originalxCount = connection.Query("Select Count(*) From ObjectX").First(); connection.Insert(o1); var list1 = connection.Query("select * from ObjectX").ToList(); Assert.Equal(list1.Count, originalxCount + 1); o1 = connection.Get(guid); Assert.Equal(o1.ObjectXId, guid); o1.Name = "Bar"; connection.Update(o1); o1 = connection.Get(guid); Assert.Equal("Bar", o1.Name); connection.Delete(o1); o1 = connection.Get(guid); Assert.Null(o1); const int id = 42; var o2 = new ObjectY { ObjectYId = id, Name = "Foo" }; var originalyCount = connection.Query("Select Count(*) From ObjectY").First(); connection.Insert(o2); var list2 = connection.Query("select * from ObjectY").ToList(); Assert.Equal(list2.Count, originalyCount + 1); o2 = connection.Get(id); Assert.Equal(o2.ObjectYId, id); o2.Name = "Bar"; connection.Update(o2); o2 = connection.Get(id); Assert.Equal("Bar", o2.Name); connection.Delete(o2); o2 = connection.Get(id); Assert.Null(o2); } } [Fact] public void GetAllWithExplicitKey() { using (var connection = GetOpenConnection()) { var guid = Guid.NewGuid().ToString(); var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" }; connection.Insert(o1); var objectXs = connection.GetAll().ToList(); Assert.True(objectXs.Count > 0); Assert.Equal(1, objectXs.Count(x => x.ObjectXId == guid)); } } [Fact] public void InsertGetUpdateDeleteWithExplicitKeyNamedId() { using (var connection = GetOpenConnection()) { const int id = 42; var o2 = new ObjectZ { Id = id, Name = "Foo" }; connection.Insert(o2); var list2 = connection.Query("select * from ObjectZ").ToList(); Assert.Single(list2); o2 = connection.Get(id); Assert.Equal(o2.Id, id); } } [Fact] public void ShortIdentity() { using (var connection = GetOpenConnection()) { const string name = "First item"; var id = connection.Insert(new Stuff { Name = name }); Assert.True(id > 0); // 1-n are valid here, due to parallel tests var item = connection.Get(id); Assert.Equal(item.TheId, (short)id); Assert.Equal(item.Name, name); } } [Fact] public void NullDateTime() { using (var connection = GetOpenConnection()) { connection.Insert(new Stuff { Name = "First item" }); connection.Insert(new Stuff { Name = "Second item", Created = DateTime.Now }); var stuff = connection.Query("select * from Stuff").ToList(); Assert.Null(stuff[0].Created); Assert.NotNull(stuff.Last().Created); } } [Fact] public void TableName() { using (var connection = GetOpenConnection()) { // tests against "Automobiles" table (Table attribute) var id = connection.Insert(new Car { Name = "Volvo" }); var car = connection.Get(id); Assert.NotNull(car); Assert.Equal("Volvo", car.Name); Assert.Equal("Volvo", connection.Get(id).Name); Assert.True(connection.Update(new Car { Id = (int)id, Name = "Saab" })); Assert.Equal("Saab", connection.Get(id).Name); Assert.True(connection.Delete(new Car { Id = (int)id })); Assert.Null(connection.Get(id)); } } [Fact] public void TestSimpleGet() { using (var connection = GetOpenConnection()) { var id = connection.Insert(new User { Name = "Adama", Age = 10 }); var user = connection.Get(id); Assert.Equal(user.Id, (int)id); Assert.Equal("Adama", user.Name); connection.Delete(user); } } [Fact] public void TestClosedConnection() { using (var connection = GetConnection()) { Assert.True(connection.Insert(new User { Name = "Adama", Age = 10 }) > 0); var users = connection.GetAll(); Assert.NotEmpty(users); } } [Fact] public void InsertEnumerable() { InsertHelper(src => src.AsEnumerable()); } [Fact] public void InsertArray() { InsertHelper(src => src.ToArray()); } [Fact] public void InsertList() { InsertHelper(src => src.ToList()); } private void InsertHelper(Func, T> helper) where T : class { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { connection.DeleteAll(); var total = connection.Insert(helper(users)); Assert.Equal(total, numberOfEntities); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities); } } [Fact] public void UpdateEnumerable() { UpdateHelper(src => src.AsEnumerable()); } [Fact] public void UpdateArray() { UpdateHelper(src => src.ToArray()); } [Fact] public void UpdateList() { UpdateHelper(src => src.ToList()); } private void UpdateHelper(Func, T> helper) where T : class { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { connection.DeleteAll(); var total = connection.Insert(helper(users)); Assert.Equal(total, numberOfEntities); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities); foreach (var user in users) { user.Name += " updated"; } connection.Update(helper(users)); var name = connection.Query("select * from Users").First().Name; Assert.Contains("updated", name); } } [Fact] public void DeleteEnumerable() { DeleteHelper(src => src.AsEnumerable()); } [Fact] public void DeleteArray() { DeleteHelper(src => src.ToArray()); } [Fact] public void DeleteList() { DeleteHelper(src => src.ToList()); } private void DeleteHelper(Func, T> helper) where T : class { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { connection.DeleteAll(); var total = connection.Insert(helper(users)); Assert.Equal(total, numberOfEntities); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities); var usersToDelete = users.Take(10).ToList(); connection.Delete(helper(usersToDelete)); users = connection.Query("select * from Users").ToList(); Assert.Equal(users.Count, numberOfEntities - 10); } } [Fact] public void InsertGetUpdate() { using (var connection = GetOpenConnection()) { connection.DeleteAll(); Assert.Null(connection.Get(3)); //insert with computed attribute that should be ignored connection.Insert(new Car { Name = "Volvo", Computed = "this property should be ignored" }); var id = connection.Insert(new User { Name = "Adam", Age = 10 }); //get a user with "isdirty" tracking var user = connection.Get(id); Assert.Equal("Adam", user.Name); Assert.False(connection.Update(user)); //returns false if not updated, based on tracking user.Name = "Bob"; Assert.True(connection.Update(user)); //returns true if updated, based on tracking user = connection.Get(id); Assert.Equal("Bob", user.Name); //get a user with no tracking var notrackedUser = connection.Get(id); Assert.Equal("Bob", notrackedUser.Name); Assert.True(connection.Update(notrackedUser)); //returns true, even though user was not changed notrackedUser.Name = "Cecil"; Assert.True(connection.Update(notrackedUser)); Assert.Equal("Cecil", connection.Get(id).Name); Assert.Single(connection.Query("select * from Users")); Assert.True(connection.Delete(user)); Assert.Empty(connection.Query("select * from Users")); Assert.False(connection.Update(notrackedUser)); //returns false, user not found } } #if SQLCE [Fact(Skip = "Not parallel friendly - thinking about how to test this")] public void InsertWithCustomDbType() { SqlMapperExtensions.GetDatabaseType = conn => "SQLiteConnection"; bool sqliteCodeCalled = false; using (var connection = GetOpenConnection()) { connection.DeleteAll(); Assert.IsNull(connection.Get(3)); try { connection.Insert(new User { Name = "Adam", Age = 10 }); } catch (SqlCeException ex) { sqliteCodeCalled = ex.Message.IndexOf("There was an error parsing the query", StringComparison.OrdinalIgnoreCase) >= 0; } // ReSharper disable once EmptyGeneralCatchClause catch (Exception) { } } SqlMapperExtensions.GetDatabaseType = null; if (!sqliteCodeCalled) { throw new Exception("Was expecting sqlite code to be called"); } } #endif [Fact] public void InsertWithCustomTableNameMapper() { SqlMapperExtensions.TableNameMapper = type => { switch (type.Name) { case "Person": return "People"; default: var tableattr = type.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic; if (tableattr != null) return tableattr.Name; var name = type.Name + "s"; if (type.IsInterface && name.StartsWith("I")) return name.Substring(1); return name; } }; using (var connection = GetOpenConnection()) { var id = connection.Insert(new Person { Name = "Mr Mapper" }); Assert.Equal(1, id); connection.GetAll(); } } [Fact] public void GetAll() { const int numberOfEntities = 10; var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); using (var connection = GetOpenConnection()) { connection.DeleteAll(); var total = connection.Insert(users); Assert.Equal(total, numberOfEntities); users = connection.GetAll().ToList(); Assert.Equal(users.Count, numberOfEntities); var iusers = connection.GetAll().ToList(); Assert.Equal(iusers.Count, numberOfEntities); for (var i = 0; i < numberOfEntities; i++) Assert.Equal(iusers[i].Age, i); } } /// /// Test for issue #933 /// [Fact] public void GetAndGetAllWithNullableValues() { using (var connection = GetOpenConnection()) { var id1 = connection.Insert(new NullableDate { DateValue = new DateTime(2011, 07, 14) }); var id2 = connection.Insert(new NullableDate { DateValue = null }); var value1 = connection.Get(id1); Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value); var value2 = connection.Get(id2); Assert.True(value2.DateValue == null); var value3 = connection.GetAll().ToList(); Assert.Equal(new DateTime(2011, 07, 14), value3[0].DateValue.Value); Assert.True(value3[1].DateValue == null); } } [Fact] public void Transactions() { using (var connection = GetOpenConnection()) { var id = connection.Insert(new Car { Name = "one car" }); //insert outside transaction var tran = connection.BeginTransaction(); var car = connection.Get(id, tran); var orgName = car.Name; car.Name = "Another car"; connection.Update(car, tran); tran.Rollback(); car = connection.Get(id); //updates should have been rolled back Assert.Equal(car.Name, orgName); } } #if TRANSCOPE [Fact] public void TransactionScope() { using (var txscope = new TransactionScope()) { using (var connection = GetOpenConnection()) { var id = connection.Insert(new Car { Name = "one car" }); //inser car within transaction txscope.Dispose(); //rollback Assert.Null(connection.Get(id)); //returns null - car with that id should not exist } } } #endif [Fact] public void InsertCheckKey() { using (var connection = GetOpenConnection()) { Assert.Null(connection.Get(3)); User user = new User { Name = "Adamb", Age = 10 }; int id = (int)connection.Insert(user); Assert.Equal(user.Id, id); } } [Fact] public void BuilderSelectClause() { using (var connection = GetOpenConnection()) { var rand = new Random(8675309); var data = new List(100); for (int i = 0; i < 100; i++) { var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; data.Add(nU); nU.Id = (int)connection.Insert(nU); } var builder = new SqlBuilder(); var justId = builder.AddTemplate("SELECT /**select**/ FROM Users"); var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users"); builder.Select("Id"); var ids = connection.Query(justId.RawSql, justId.Parameters); var users = connection.Query(all.RawSql, all.Parameters); foreach (var u in data) { if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select"); if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age)) throw new Exception("Missing users in select"); } } } [Fact] public void BuilderTemplateWithoutComposition() { var builder = new SqlBuilder(); var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new { age = 5 }); if (template.RawSql == null) throw new Exception("RawSql null"); if (template.Parameters == null) throw new Exception("Parameters null"); using (var connection = GetOpenConnection()) { connection.DeleteAll(); connection.Insert(new User { Age = 5, Name = "Testy McTestington" }); if (connection.Query(template.RawSql, template.Parameters).Single() != 1) throw new Exception("Query failed"); } } [Fact] public void InsertFieldWithReservedName() { using (var connection = GetOpenConnection()) { connection.DeleteAll(); var id = connection.Insert(new Result() { Name = "Adam", Order = 1 }); var result = connection.Get(id); Assert.Equal(1, result.Order); } } [Fact] public void DeleteAll() { using (var connection = GetOpenConnection()) { var id1 = connection.Insert(new User { Name = "Alice", Age = 32 }); var id2 = connection.Insert(new User { Name = "Bob", Age = 33 }); Assert.True(connection.DeleteAll()); Assert.Null(connection.Get(id1)); Assert.Null(connection.Get(id2)); } } } } ================================================ FILE: tests/Dapper.Tests.Contrib/TestSuites.cs ================================================ using Microsoft.Data.Sqlite; using MySqlConnector; using System; using System.Data; using System.Data.SqlClient; using System.IO; using Xunit; using Xunit.Sdk; namespace Dapper.Tests.Contrib { // The test suites here implement TestSuiteBase so that each provider runs // the entire set of tests without declarations per method // If we want to support a new provider, they need only be added here - not in multiple places [XunitTestCaseDiscoverer("Dapper.Tests.SkippableFactDiscoverer", "Dapper.Tests.Contrib")] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class SkippableFactAttribute : FactAttribute { } public class SqlServerTestSuite : TestSuite { private const string DbName = "tempdb"; public static string ConnectionString => GetConnectionString("SqlServerConnectionString", $"Data Source=.;Initial Catalog={DbName};Integrated Security=True"); public override IDbConnection GetConnection() => new SqlConnection(ConnectionString); static SqlServerTestSuite() { using (var connection = new SqlConnection(ConnectionString)) { // ReSharper disable once AccessToDisposedClosure void dropTable(string name) => connection.Execute($"IF OBJECT_ID('{name}', 'U') IS NOT NULL DROP TABLE [{name}]; "); connection.Open(); dropTable("Stuff"); connection.Execute("CREATE TABLE Stuff (TheId int IDENTITY(1,1) not null, Name nvarchar(100) not null, Created DateTime null);"); dropTable("People"); connection.Execute("CREATE TABLE People (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null);"); dropTable("Users"); connection.Execute("CREATE TABLE Users (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null);"); dropTable("Automobiles"); connection.Execute("CREATE TABLE Automobiles (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null);"); dropTable("Results"); connection.Execute("CREATE TABLE Results (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, [Order] int not null);"); dropTable("ObjectX"); connection.Execute("CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null);"); dropTable("ObjectY"); connection.Execute("CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null);"); dropTable("ObjectZ"); connection.Execute("CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); dropTable("GenericType"); connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null);"); dropTable("NullableDates"); connection.Execute("CREATE TABLE NullableDates (Id int IDENTITY(1,1) not null, DateValue DateTime null);"); } } } public class MySqlServerTestSuite : TestSuite { public static string ConnectionString { get; } = GetConnectionString("MySqlConnectionString", "Server=localhost;Database=tests;Uid=test;Pwd=pass;UseAffectedRows=false;"); public override IDbConnection GetConnection() { if (_skip) Skip.Inconclusive("Skipping MySQL Tests - no server."); return new MySqlConnection(ConnectionString); } private static readonly bool _skip; static MySqlServerTestSuite() { try { using (var connection = new MySqlConnection(ConnectionString)) { // ReSharper disable once AccessToDisposedClosure void dropTable(string name) => connection.Execute($"DROP TABLE IF EXISTS `{name}`;"); connection.Open(); dropTable("Stuff"); connection.Execute("CREATE TABLE Stuff (TheId int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, Created DateTime null);"); dropTable("People"); connection.Execute("CREATE TABLE People (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null);"); dropTable("Users"); connection.Execute("CREATE TABLE Users (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, Age int not null);"); dropTable("Automobiles"); connection.Execute("CREATE TABLE Automobiles (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null);"); dropTable("Results"); connection.Execute("CREATE TABLE Results (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, `Order` int not null);"); dropTable("ObjectX"); connection.Execute("CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null);"); dropTable("ObjectY"); connection.Execute("CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null);"); dropTable("ObjectZ"); connection.Execute("CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); dropTable("GenericType"); connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null);"); dropTable("NullableDates"); connection.Execute("CREATE TABLE NullableDates (Id int not null AUTO_INCREMENT PRIMARY KEY, DateValue DateTime);"); } } catch (MySqlException e) { if (e.Message.Contains("Unable to connect")) _skip = true; else throw; } } } public class SQLiteTestSuite : TestSuite { private const string FileName = "Test.DB.sqlite"; public static string ConnectionString => $"Filename=./{FileName};Mode=ReadWriteCreate;"; public override IDbConnection GetConnection() => new SqliteConnection(ConnectionString); static SQLiteTestSuite() { if (File.Exists(FileName)) { File.Delete(FileName); } using (var connection = new SqliteConnection(ConnectionString)) { connection.Open(); connection.Execute("CREATE TABLE Stuff (TheId integer primary key autoincrement not null, Name nvarchar(100) not null, Created DateTime null) "); connection.Execute("CREATE TABLE People (Id integer primary key autoincrement not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE Users (Id integer primary key autoincrement not null, Name nvarchar(100) not null, Age int not null) "); connection.Execute("CREATE TABLE Automobiles (Id integer primary key autoincrement not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE Results (Id integer primary key autoincrement not null, Name nvarchar(100) not null, [Order] int not null) "); connection.Execute("CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE ObjectY (ObjectYId integer not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE ObjectZ (Id integer not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE NullableDates (Id integer primary key autoincrement not null, DateValue DateTime) "); } } } #if SQLCE public class SqlCETestSuite : TestSuite { const string FileName = "Test.DB.sdf"; public static string ConnectionString => $"Data Source={FileName};"; public override IDbConnection GetConnection() => new SqlCeConnection(ConnectionString); static SqlCETestSuite() { if (File.Exists(FileName)) { File.Delete(FileName); } var engine = new SqlCeEngine(ConnectionString); engine.CreateDatabase(); using (var connection = new SqlCeConnection(ConnectionString)) { connection.Open(); connection.Execute(@"CREATE TABLE Stuff (TheId int IDENTITY(1,1) not null, Name nvarchar(100) not null, Created DateTime null) "); connection.Execute(@"CREATE TABLE People (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE Users (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null) "); connection.Execute(@"CREATE TABLE Automobiles (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE Results (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, [Order] int not null) "); connection.Execute(@"CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE NullableDates (Id int IDENTITY(1,1) not null, DateValue DateTime null) "); } Console.WriteLine("Created database"); } } #endif } ================================================ FILE: tests/Dapper.Tests.Contrib/xunit.runner.json ================================================ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "shadowCopy": false } ================================================ FILE: tests/Directory.Build.props ================================================ Library false false false false true Full $(DefineConstants);WINDOWS ================================================ FILE: tests/Directory.Build.targets ================================================ false ================================================ FILE: tests/docker-compose.yml ================================================ version: "3" services: mysql: image: mysql:8 container_name: mysql ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test postgres: image: postgres:alpine container_name: postgres ports: - 5432:5432 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test sqlserver: image: mcr.microsoft.com/mssql/server:2019-latest container_name: sql-server-db ports: - 1433:1433 environment: ACCEPT_EULA: Y SA_PASSWORD: "Password." ================================================ FILE: version.json ================================================ { "version": "2.0", "assemblyVersion": "2.0.0.0", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/tags/v\\d+\\.\\d+" ], "nugetPackageVersion": { "semVer": 2 }, "cloudBuild": { "buildNumber": { "enabled": true, "setVersionVariables": true } } }