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
================================================
<Project Sdk="Microsoft.Build.Traversal/2.0.24">
<ItemGroup>
<ProjectReference Include="*/*.csproj" />
<ProjectReference Include="tests/**/*.csproj" />
</ItemGroup>
</Project>
================================================
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
================================================
<Project>
<PropertyGroup>
<Copyright>2019 Stack Exchange, Inc.</Copyright>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>../Dapper.snk</AssemblyOriginatorKeyFile>
<PackageId>$(AssemblyName)</PackageId>
<PackageReleaseNotes>https://dapperlib.github.io/Dapper.Contrib/</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/DapperLib/Dapper.Contrib</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageIcon>Dapper.png</PackageIcon>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/DapperLib/Dapper.Contrib</RepositoryUrl>
<Deterministic>false</Deterministic>
<NoWarn>$(NOWARN);IDE0056;IDE0057;IDE0079</NoWarn>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<DefaultLanguage>en-US</DefaultLanguage>
<IncludeSymbols>false</IncludeSymbols>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<Deterministic>true</Deterministic>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release' and '$(SourceRoot)'==''">
<SourceRoot Include="$(MSBuildThisFileDirectory)/"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" IncludeAssets="runtime; build; native; contentfiles; analyzers" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="all" />
</ItemGroup>
</Project>
================================================
FILE: Directory.Build.targets
================================================
<Project>
<!-- workaround for deterministic builds; see https://github.com/clairernovotny/DeterministicBuilds -->
<PropertyGroup>
<TargetFrameworkMonikerAssemblyAttributesPath>$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))</TargetFrameworkMonikerAssemblyAttributesPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedFiles Include="$(GeneratedAssemblyInfoFile)"/>
</ItemGroup>
</Project>
================================================
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
========================================
[](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/) | [](https://www.nuget.org/packages/Dapper.Contrib/) | [](https://www.nuget.org/packages/Dapper.Contrib/) | [](https://www.nuget.org/packages/Dapper.Contrib/) | [](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<T>(id);
IEnumerable<T> GetAll<T>();
int Insert<T>(T obj);
int Insert<T>(Enumerable<T> list);
bool Update<T>(T obj);
bool Update<T>(Enumerable<T> list);
bool Delete<T>(T obj);
bool Delete<T>(Enumerable<T> list);
bool DeleteAll<T>();
```
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<Car>(1);
```
or a list of all entities in the table.
```csharp
var cars = connection.GetAll<Car>();
```
`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<Car>();
```
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<Car>(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
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
================================================
FILE: src/Dapper.Contrib/Dapper.Contrib.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Dapper.Contrib</AssemblyName>
<Title>Dapper.Contrib</Title>
<PackageTags>orm;sql;micro-orm;dapper</PackageTags>
<Description>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.</Description>
<Authors>Sam Saffron;Johan Danforth</Authors>
<TargetFrameworks>net461;netstandard2.0;net5.0</TargetFrameworks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CA1050</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.78" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
================================================
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
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">Interface type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Entity of T</returns>
public static async Task<T> GetAsync<T>(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<T>(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<T>(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault();
if (!((await connection.QueryAsync<dynamic>(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary<string, object> res))
{
return null;
}
var obj = ProxyGenerator.GetInterfaceProxy<T>();
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;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">Interface or type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Entity of T</returns>
public static Task<IEnumerable<T>> GetAllAsync<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof(List<T>);
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
{
GetSingleKey<T>(nameof(GetAll));
var name = GetTableName(type);
sql = "SELECT * FROM " + name;
GetQueries[cacheType.TypeHandle] = sql;
}
if (!type.IsInterface)
{
return connection.QueryAsync<T>(sql, null, transaction, commandTimeout);
}
return GetAllAsyncImpl<T>(connection, transaction, commandTimeout, sql, type);
}
private static async Task<IEnumerable<T>> GetAllAsyncImpl<T>(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<T>();
foreach (IDictionary<string, object> res in result)
{
var obj = ProxyGenerator.GetInterfaceProxy<T>();
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;
}
/// <summary>
/// Inserts an entity into table "Ts" asynchronously using Task and returns identity id.
/// </summary>
/// <typeparam name="T">The type being inserted.</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="sqlAdapter">The specific ISqlAdapter to use, auto-detected based on connection if null</param>
/// <returns>Identity of inserted entity</returns>
public static Task<int> InsertAsync<T>(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);
}
/// <summary>
/// Updates entity in table "Ts" asynchronously using Task, checks if the entity is modified if the entity is tracked by the Get() extension.
/// </summary>
/// <typeparam name="T">Type to be updated</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static async Task<bool> UpdateAsync<T>(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;
}
/// <summary>
/// Delete entity in table "Ts" asynchronously using Task.
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToDelete">Entity to delete</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if deleted, false if not found</returns>
public static async Task<bool> DeleteAsync<T>(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;
}
/// <summary>
/// Delete all entities in the table related to the type T asynchronously using Task.
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if deleted, false if none found</returns>
public static async Task<bool> DeleteAllAsync<T>(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
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert);
}
public partial class SqlServerAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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<dynamic>("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
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName,
string columnList, string parameterList, IEnumerable<PropertyInfo> 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<dynamic>("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
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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<string, object>)results.First())[p.Name.ToLower()];
p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
}
public partial class SQLiteAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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
{
/// <summary>
/// The Dapper.Contrib extensions for Dapper
/// </summary>
public static partial class SqlMapperExtensions
{
/// <summary>
/// Defined a proxy object with a possibly dirty state.
/// </summary>
public interface IProxy //must be kept public
{
/// <summary>
/// Whether the object has been changed.
/// </summary>
bool IsDirty { get; set; }
}
/// <summary>
/// Defines a table name mapper for getting table names from types.
/// </summary>
public interface ITableNameMapper
{
/// <summary>
/// Gets a table name from a given <see cref="Type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> to get a name from.</param>
/// <returns>The table name for the given <paramref name="type"/>.</returns>
string GetTableName(Type type);
}
/// <summary>
/// The function to get a database type from the given <see cref="IDbConnection"/>.
/// </summary>
/// <param name="connection">The connection to get a database type name from.</param>
public delegate string GetDatabaseTypeDelegate(IDbConnection connection);
/// <summary>
/// The function to get a table name from a given <see cref="Type"/>
/// </summary>
/// <param name="type">The <see cref="Type"/> to get a table name for.</param>
public delegate string TableNameMapperDelegate(Type type);
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ExplicitKeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ComputedProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter();
private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary
= new Dictionary<string, ISqlAdapter>(6)
{
["sqlconnection"] = new SqlServerAdapter(),
["sqlceconnection"] = new SqlCeServerAdapter(),
["npgsqlconnection"] = new PostgresAdapter(),
["sqliteconnection"] = new SQLiteAdapter(),
["mysqlconnection"] = new MySqlAdapter(),
["fbconnection"] = new FbAdapter()
};
private static List<PropertyInfo> ComputedPropertiesCache(Type type)
{
if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> 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<PropertyInfo> ExplicitKeyPropertiesCache(Type type)
{
if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> 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<PropertyInfo> KeyPropertiesCache(Type type)
{
if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> 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<PropertyInfo> TypePropertiesCache(Type type)
{
if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> 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<T>(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}<T> 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}<T> only supports an entity with a [Key] or an [ExplicitKey] property");
return keys.Count > 0 ? keys[0] : explicitKeys[0];
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">Interface or type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Entity of T</returns>
public static T Get<T>(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<T>(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<string, object> res))
{
return null;
}
obj = ProxyGenerator.GetInterfaceProxy<T>();
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<T>(sql, dynParams, transaction, commandTimeout: commandTimeout).FirstOrDefault();
}
return obj;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">Interface or type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Entity of T</returns>
public static IEnumerable<T> GetAll<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof(List<T>);
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
{
GetSingleKey<T>(nameof(GetAll));
var name = GetTableName(type);
sql = "select * from " + name;
GetQueries[cacheType.TypeHandle] = sql;
}
if (!type.IsInterface) return connection.Query<T>(sql, null, transaction, commandTimeout: commandTimeout);
var result = connection.Query(sql);
var list = new List<T>();
foreach (IDictionary<string, object> res in result)
{
var obj = ProxyGenerator.GetInterfaceProxy<T>();
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;
}
/// <summary>
/// Specify a custom table name mapper based on the POCO type name
/// </summary>
#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<TableAttribute>(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;
}
/// <summary>
/// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list.
/// </summary>
/// <typeparam name="T">The type to insert.</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert, can be list of entities</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Identity of inserted entity, or number of inserted rows if inserting a list</returns>
public static long Insert<T>(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;
}
/// <summary>
/// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
/// </summary>
/// <typeparam name="T">Type to be updated</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static bool Update<T>(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;
}
/// <summary>
/// Delete entity in table "Ts".
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToDelete">Entity to delete</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if deleted, false if not found</returns>
public static bool Delete<T>(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;
}
/// <summary>
/// Delete all entities in the table related to the type T.
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if deleted, false if none found</returns>
public static bool DeleteAll<T>(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;
}
/// <summary>
/// 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.
/// </summary>
#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<Type, Type> TypeCache = new Dictionary<Type, Type>();
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<T>()
{
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<T>(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<T>(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
{
//Define the field and the property
var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(propertyName,
System.Reflection.PropertyAttributes.None,
propType,
new[] { 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<object>());
property.SetCustomAttribute(attributeBuilder);
}
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(T).GetMethod("get_" + propertyName);
var setMethod = typeof(T).GetMethod("set_" + propertyName);
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
}
}
}
/// <summary>
/// Defines the name of a table to use in Dapper.Contrib commands.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
/// <summary>
/// Creates a table mapping to a specific name for Dapper.Contrib commands
/// </summary>
/// <param name="tableName">The name of this table in the database.</param>
public TableAttribute(string tableName)
{
Name = tableName;
}
/// <summary>
/// The name of the table in the database
/// </summary>
public string Name { get; set; }
}
/// <summary>
/// Specifies that this field is a primary key in the database
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class KeyAttribute : Attribute
{
}
/// <summary>
/// Specifies that this field is an explicitly set primary key in the database
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ExplicitKeyAttribute : Attribute
{
}
/// <summary>
/// Specifies whether a field is writable in the database.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class WriteAttribute : Attribute
{
/// <summary>
/// Specifies whether a field is writable in the database.
/// </summary>
/// <param name="write">Whether a field is writable in the database.</param>
public WriteAttribute(bool write)
{
Write = write;
}
/// <summary>
/// Whether a field is writable in the database.
/// </summary>
public bool Write { get; }
}
/// <summary>
/// Specifies that this is a computed column.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ComputedAttribute : Attribute
{
}
}
/// <summary>
/// The interface for all Dapper.Contrib database operations
/// Implementing this is each provider's model.
/// </summary>
public partial interface ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert);
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
void AppendColumnName(StringBuilder sb, string columnName);
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
}
/// <summary>
/// The SQL Server database adapter.
/// </summary>
public partial class SqlServerAdapter : ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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;
}
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}]", columnName);
}
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
}
}
/// <summary>
/// The SQL Server Compact Edition database adapter.
/// </summary>
public partial class SqlCeServerAdapter : ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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;
}
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}]", columnName);
}
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
}
}
/// <summary>
/// The MySQL database adapter.
/// </summary>
public partial class MySqlAdapter : ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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);
}
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("`{0}`", columnName);
}
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("`{0}` = @{1}", columnName, columnName);
}
}
/// <summary>
/// The Postgres database adapter.
/// </summary>
public partial class PostgresAdapter : ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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<string, object>)results[0])[p.Name.ToLower()];
p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\"", columnName);
}
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
}
}
/// <summary>
/// The SQLite database adapter.
/// </summary>
public partial class SQLiteAdapter : ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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;
}
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\"", columnName);
}
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
}
}
/// <summary>
/// The Firebase SQL adapter.
/// </summary>
public partial class FbAdapter : ISqlAdapter
{
/// <summary>
/// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="transaction">The transaction to use.</param>
/// <param name="commandTimeout">The command timeout to use.</param>
/// <param name="tableName">The table to insert into.</param>
/// <param name="columnList">The columns to set with this insert.</param>
/// <param name="parameterList">The parameters to set for this insert.</param>
/// <param name="keyProperties">The key columns in this table.</param>
/// <param name="entityToInsert">The entity to insert.</param>
/// <returns>The Id of the row created.</returns>
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> 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);
}
/// <summary>
/// Adds the name of a column.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}", columnName);
}
/// <summary>
/// Adds a column equality to a parameter.
/// </summary>
/// <param name="sb">The string builder to append to.</param>
/// <param name="columnName">The column name.</param>
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0} = @{1}", columnName, columnName);
}
}
================================================
FILE: tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Dapper.Tests.Contrib</AssemblyName>
<Description>Dapper Contrib Test Suite</Description>
<TargetFrameworks>netcoreapp3.1;net462;net5.0</TargetFrameworks>
<NoWarn>$(NoWarn);CA1816;IDE0063;xUnit1004</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="Test.DB.sdf" />
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper.SqlBuilder" Version="2.0.78" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.0" />
<PackageReference Include="MySqlConnector" Version="1.1.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
================================================
FILE: tests/Dapper.Tests.Contrib/Helpers/Attributes.cs
================================================
using System;
using Xunit.Sdk;
namespace Dapper.Tests
{
/// <summary>
/// <para>Override for <see cref="Xunit.FactAttribute"/> that truncates our DisplayName down.</para>
/// <para>
/// 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.
/// </para>
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Dapper.Tests.FactDiscoverer", "Dapper.Tests.Contrib")]
public class FactAttribute : Xunit.FactAttribute
{
}
/// <summary>
/// <para>Override for <see cref="Xunit.TheoryAttribute"/> that truncates our DisplayName down.</para>
/// <para>
/// 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).
/// </para>
/// </summary>
[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<T>(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<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
=> new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, dataRow) };
protected override IEnumerable<IXunitTestCase> CreateTestCasesForSkip(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, string skipReason)
=> new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) };
protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
=> new[] { new SkippableTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) };
protected override IEnumerable<IXunitTestCase> 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<RunSummary> 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<RunSummary> 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<GenericType<string>>();
var objectToInsert = new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "something"
};
await connection.InsertAsync(objectToInsert);
Assert.Single(connection.GetAll<GenericType<string>>());
var objectsToInsert = new List<GenericType<string>>(2)
{
new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "1",
},
new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "2",
}
};
await connection.InsertAsync(objectsToInsert);
var list = connection.GetAll<GenericType<string>>();
Assert.Equal(3, list.Count());
}
}
[Fact]
public async Task TypeWithGenericParameterCanBeUpdatedAsync()
{
using (var connection = GetOpenConnection())
{
var objectToInsert = new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "something"
};
await connection.InsertAsync(objectToInsert);
objectToInsert.Name = "somethingelse";
await connection.UpdateAsync(objectToInsert);
var updatedObject = connection.Get<GenericType<string>>(objectToInsert.Id);
Assert.Equal(objectToInsert.Name, updatedObject.Name);
}
}
[Fact]
public async Task TypeWithGenericParameterCanBeDeletedAsync()
{
using (var connection = GetOpenConnection())
{
var objectToInsert = new GenericType<string>
{
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<ObjectX>(Guid.NewGuid().ToString()).ConfigureAwait(false);
Assert.Null(retrieved);
}
}
/// <summary>
/// Tests for issue #351
/// </summary>
[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<int>("Select Count(*) From ObjectX").ConfigureAwait(false)).First();
await connection.InsertAsync(o1).ConfigureAwait(false);
var list1 = (await connection.QueryAsync<ObjectX>("select * from ObjectX").ConfigureAwait(false)).ToList();
Assert.Equal(list1.Count, originalxCount + 1);
o1 = await connection.GetAsync<ObjectX>(guid).ConfigureAwait(false);
Assert.Equal(o1.ObjectXId, guid);
o1.Name = "Bar";
await connection.UpdateAsync(o1).ConfigureAwait(false);
o1 = await connection.GetAsync<ObjectX>(guid).ConfigureAwait(false);
Assert.Equal("Bar", o1.Name);
await connection.DeleteAsync(o1).ConfigureAwait(false);
o1 = await connection.GetAsync<ObjectX>(guid).ConfigureAwait(false);
Assert.Null(o1);
const int id = 42;
var o2 = new ObjectY { ObjectYId = id, Name = "Foo" };
var originalyCount = connection.Query<int>("Select Count(*) From ObjectY").First();
await connection.InsertAsync(o2).ConfigureAwait(false);
var list2 = (await connection.QueryAsync<ObjectY>("select * from ObjectY").ConfigureAwait(false)).ToList();
Assert.Equal(list2.Count, originalyCount + 1);
o2 = await connection.GetAsync<ObjectY>(id).ConfigureAwait(false);
Assert.Equal(o2.ObjectYId, id);
o2.Name = "Bar";
await connection.UpdateAsync(o2).ConfigureAwait(false);
o2 = await connection.GetAsync<ObjectY>(id).ConfigureAwait(false);
Assert.Equal("Bar", o2.Name);
await connection.DeleteAsync(o2).ConfigureAwait(false);
o2 = await connection.GetAsync<ObjectY>(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<Car>(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<Car>(id).ConfigureAwait(false)).Name);
Assert.True(await connection.DeleteAsync(new Car { Id = id }).ConfigureAwait(false));
Assert.Null(await connection.GetAsync<Car>(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<User>(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<User>(30).ConfigureAwait(false));
var originalCount = (await connection.QueryAsync<int>("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<IUser>(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<IUser>(id).ConfigureAwait(false);
Assert.Equal("Bob", user.Name);
//get a user with no tracking
var notrackedUser = await connection.GetAsync<User>(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<User>(id).ConfigureAwait(false)).Name);
Assert.Equal((await connection.QueryAsync<User>("select * from Users").ConfigureAwait(false)).Count(), originalCount + 1);
Assert.True(await connection.DeleteAsync(user).ConfigureAwait(false));
Assert.Equal((await connection.QueryAsync<User>("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<User>().ConfigureAwait(false);
Assert.Null(await connection.GetAsync<IUser>(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<User>().ConfigureAwait(false);
var rand = new Random(8675309);
var data = new List<User>(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<int>(justId.RawSql, justId.Parameters).ConfigureAwait(false);
var users = await connection.QueryAsync<User>(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<User>().ConfigureAwait(false);
await connection.InsertAsync(new User { Age = 5, Name = "Testy McTestington" }).ConfigureAwait(false);
if ((await connection.QueryAsync<int>(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<T>(Func<IEnumerable<User>, T> helper)
where T : class
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
await connection.DeleteAllAsync<User>().ConfigureAwait(false);
var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false);
Assert.Equal(total, numberOfEntities);
users = connection.Query<User>("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<T>(Func<IEnumerable<User>, T> helper)
where T : class
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
await connection.DeleteAllAsync<User>().ConfigureAwait(false);
var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false);
Assert.Equal(total, numberOfEntities);
users = connection.Query<User>("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<User>("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<T>(Func<IEnumerable<User>, T> helper)
where T : class
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
await connection.DeleteAllAsync<User>().ConfigureAwait(false);
var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false);
Assert.Equal(total, numberOfEntities);
users = connection.Query<User>("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<User>("select * from Users").ToList();
Assert.Equal(users.Count, numberOfEntities - 10);
}
}
[Fact]
public async Task GetAllAsync()
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
await connection.DeleteAllAsync<User>().ConfigureAwait(false);
var total = await connection.InsertAsync(users).ConfigureAwait(false);
Assert.Equal(total, numberOfEntities);
users = (List<User>)await connection.GetAllAsync<User>().ConfigureAwait(false);
Assert.Equal(users.Count, numberOfEntities);
var iusers = await connection.GetAllAsync<IUser>().ConfigureAwait(false);
Assert.Equal(iusers.ToList().Count, numberOfEntities);
}
}
/// <summary>
/// Test for issue #933
/// </summary>
[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<INullableDate>(id1).ConfigureAwait(false);
Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value);
var value2 = await connection.GetAsync<INullableDate>(id2).ConfigureAwait(false);
Assert.True(value2.DateValue == null);
var value3 = await connection.GetAllAsync<INullableDate>().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<User>().ConfigureAwait(false);
var id = await connection.InsertAsync(new Result { Name = "Adam", Order = 1 }).ConfigureAwait(false);
var result = await connection.GetAsync<Result>(id).ConfigureAwait(false);
Assert.Equal(1, result.Order);
}
}
[Fact]
public async Task DeleteAllAsync()
{
using (var connection = GetOpenConnection())
{
await connection.DeleteAllAsync<User>().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<User>().ConfigureAwait(false);
Assert.Null(await connection.GetAsync<User>(id1).ConfigureAwait(false));
Assert.Null(await connection.GetAsync<User>(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<T>
{
[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<GenericType<string>>();
var objectToInsert = new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "something"
};
connection.Insert(objectToInsert);
Assert.Single(connection.GetAll<GenericType<string>>());
var objectsToInsert = new List<GenericType<string>>(2)
{
new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "1",
},
new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "2",
}
};
connection.Insert(objectsToInsert);
var list = connection.GetAll<GenericType<string>>();
Assert.Equal(3, list.Count());
}
}
[Fact]
public void TypeWithGenericParameterCanBeUpdated()
{
using (var connection = GetOpenConnection())
{
var objectToInsert = new GenericType<string>
{
Id = Guid.NewGuid().ToString(),
Name = "something"
};
connection.Insert(objectToInsert);
objectToInsert.Name = "somethingelse";
connection.Update(objectToInsert);
var updatedObject = connection.Get<GenericType<string>>(objectToInsert.Id);
Assert.Equal(objectToInsert.Name, updatedObject.Name);
}
}
[Fact]
public void TypeWithGenericParameterCanBeDeleted()
{
using (var connection = GetOpenConnection())
{
var objectToInsert = new GenericType<string>
{
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<ObjectX>();
var objectXId = Guid.NewGuid().ToString();
var insertObject = new ObjectX
{
ObjectXId = objectXId,
Name = "Someone else"
};
connection.Insert(insertObject);
var list = connection.GetAll<ObjectX>();
Assert.Single(list);
}
}
/// <summary>
/// Tests for issue #351
/// </summary>
[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<int>("Select Count(*) From ObjectX").First();
connection.Insert(o1);
var list1 = connection.Query<ObjectX>("select * from ObjectX").ToList();
Assert.Equal(list1.Count, originalxCount + 1);
o1 = connection.Get<ObjectX>(guid);
Assert.Equal(o1.ObjectXId, guid);
o1.Name = "Bar";
connection.Update(o1);
o1 = connection.Get<ObjectX>(guid);
Assert.Equal("Bar", o1.Name);
connection.Delete(o1);
o1 = connection.Get<ObjectX>(guid);
Assert.Null(o1);
const int id = 42;
var o2 = new ObjectY { ObjectYId = id, Name = "Foo" };
var originalyCount = connection.Query<int>("Select Count(*) From ObjectY").First();
connection.Insert(o2);
var list2 = connection.Query<ObjectY>("select * from ObjectY").ToList();
Assert.Equal(list2.Count, originalyCount + 1);
o2 = connection.Get<ObjectY>(id);
Assert.Equal(o2.ObjectYId, id);
o2.Name = "Bar";
connection.Update(o2);
o2 = connection.Get<ObjectY>(id);
Assert.Equal("Bar", o2.Name);
connection.Delete(o2);
o2 = connection.Get<ObjectY>(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<ObjectX>().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<ObjectZ>("select * from ObjectZ").ToList();
Assert.Single(list2);
o2 = connection.Get<ObjectZ>(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<Stuff>(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<Stuff>("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<Car>(id);
Assert.NotNull(car);
Assert.Equal("Volvo", car.Name);
Assert.Equal("Volvo", connection.Get<Car>(id).Name);
Assert.True(connection.Update(new Car { Id = (int)id, Name = "Saab" }));
Assert.Equal("Saab", connection.Get<Car>(id).Name);
Assert.True(connection.Delete(new Car { Id = (int)id }));
Assert.Null(connection.Get<Car>(id));
}
}
[Fact]
public void TestSimpleGet()
{
using (var connection = GetOpenConnection())
{
var id = connection.Insert(new User { Name = "Adama", Age = 10 });
var user = connection.Get<User>(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<User>();
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<T>(Func<IEnumerable<User>, T> helper)
where T : class
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(helper(users));
Assert.Equal(total, numberOfEntities);
users = connection.Query<User>("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<T>(Func<IEnumerable<User>, T> helper)
where T : class
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(helper(users));
Assert.Equal(total, numberOfEntities);
users = connection.Query<User>("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<User>("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<T>(Func<IEnumerable<User>, T> helper)
where T : class
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(helper(users));
Assert.Equal(total, numberOfEntities);
users = connection.Query<User>("select * from Users").ToList();
Assert.Equal(users.Count, numberOfEntities);
var usersToDelete = users.Take(10).ToList();
connection.Delete(helper(usersToDelete));
users = connection.Query<User>("select * from Users").ToList();
Assert.Equal(users.Count, numberOfEntities - 10);
}
}
[Fact]
public void InsertGetUpdate()
{
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
Assert.Null(connection.Get<User>(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<IUser>(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<IUser>(id);
Assert.Equal("Bob", user.Name);
//get a user with no tracking
var notrackedUser = connection.Get<User>(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<User>(id).Name);
Assert.Single(connection.Query<User>("select * from Users"));
Assert.True(connection.Delete(user));
Assert.Empty(connection.Query<User>("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<User>();
Assert.IsNull(connection.Get<User>(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<Person>();
}
}
[Fact]
public void GetAll()
{
const int numberOfEntities = 10;
var users = new List<User>(numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(users);
Assert.Equal(total, numberOfEntities);
users = connection.GetAll<User>().ToList();
Assert.Equal(users.Count, numberOfEntities);
var iusers = connection.GetAll<IUser>().ToList();
Assert.Equal(iusers.Count, numberOfEntities);
for (var i = 0; i < numberOfEntities; i++)
Assert.Equal(iusers[i].Age, i);
}
}
/// <summary>
/// Test for issue #933
/// </summary>
[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<INullableDate>(id1);
Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value);
var value2 = connection.Get<INullableDate>(id2);
Assert.True(value2.DateValue == null);
var value3 = connection.GetAll<INullableDate>().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<Car>(id, tran);
var orgName = car.Name;
car.Name = "Another car";
connection.Update(car, tran);
tran.Rollback();
car = connection.Get<Car>(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<Car>(id)); //returns null - car with that id should not exist
}
}
}
#endif
[Fact]
public void InsertCheckKey()
{
using (var connection = GetOpenConnection())
{
Assert.Null(connection.Get<IUser>(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<User>(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<int>(justId.RawSql, justId.Parameters);
var users = connection.Query<User>(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<User>();
connection.Insert(new User { Age = 5, Name = "Testy McTestington" });
if (connection.Query<int>(template.RawSql, template.Parameters).Single() != 1)
throw new Exception("Query failed");
}
}
[Fact]
public void InsertFieldWithReservedName()
{
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var id = connection.Insert(new Result() { Name = "Adam", Order = 1 });
var result = connection.Get<Result>(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<User>());
Assert.Null(connection.Get<User>(id1));
Assert.Null(connection.Get<User>(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
================================================
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<SignAssembly>false</SignAssembly>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<DebugType>Full</DebugType>
<DefineConstants Condition="'$(OS)' == 'Windows_NT'">$(DefineConstants);WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Dapper.Contrib/Dapper.Contrib.csproj" />
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Data.Linq" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<!-- <Content Include="xunit.runner.json" Condition="'$(OS)' == 'Unix'" CopyToOutputDirectory="PreserveNewest" /> -->
</ItemGroup>
</Project>
================================================
FILE: tests/Directory.Build.targets
================================================
<Project>
<PropertyGroup>
<IsTestProject Condition="'$(OS)' != 'Windows_NT' AND $(TargetFramework.StartsWith('net4'))">false</IsTestProject>
</PropertyGroup>
</Project>
================================================
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
}
}
}
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
SYMBOL INDEX (212 symbols across 7 files)
FILE: src/Dapper.Contrib/SqlMapperExtensions.Async.cs
class SqlMapperExtensions (line 12) | public static partial class SqlMapperExtensions
method GetAsync (line 25) | public static async Task<T> GetAsync<T>(this IDbConnection connection,...
method GetAllAsync (line 81) | public static Task<IEnumerable<T>> GetAllAsync<T>(this IDbConnection c...
method GetAllAsyncImpl (line 102) | private static async Task<IEnumerable<T>> GetAllAsyncImpl<T>(IDbConnec...
method InsertAsync (line 139) | public static Task<int> InsertAsync<T>(this IDbConnection connection, ...
method UpdateAsync (line 209) | public static async Task<bool> UpdateAsync<T>(this IDbConnection conne...
method DeleteAsync (line 280) | public static async Task<bool> DeleteAsync<T>(this IDbConnection conne...
method DeleteAllAsync (line 336) | public static async Task<bool> DeleteAllAsync<T>(this IDbConnection co...
type ISqlAdapter (line 346) | public partial interface ISqlAdapter
method InsertAsync (line 360) | Task<int> InsertAsync(IDbConnection connection, IDbTransaction transac...
class SqlServerAdapter (line 363) | public partial class SqlServerAdapter
method InsertAsync (line 377) | public async Task<int> InsertAsync(IDbConnection connection, IDbTransa...
class SqlCeServerAdapter (line 396) | public partial class SqlCeServerAdapter
method InsertAsync (line 410) | public async Task<int> InsertAsync(IDbConnection connection, IDbTransa...
class MySqlAdapter (line 429) | public partial class MySqlAdapter
method InsertAsync (line 443) | public async Task<int> InsertAsync(IDbConnection connection, IDbTransa...
class PostgresAdapter (line 462) | public partial class PostgresAdapter
method InsertAsync (line 476) | public async Task<int> InsertAsync(IDbConnection connection, IDbTransa...
class SQLiteAdapter (line 515) | public partial class SQLiteAdapter
method InsertAsync (line 529) | public async Task<int> InsertAsync(IDbConnection connection, IDbTransa...
class FbAdapter (line 545) | public partial class FbAdapter
method InsertAsync (line 559) | public async Task<int> InsertAsync(IDbConnection connection, IDbTransa...
FILE: src/Dapper.Contrib/SqlMapperExtensions.cs
class SqlMapperExtensions (line 18) | public static partial class SqlMapperExtensions
type IProxy (line 23) | public interface IProxy //must be kept public
type ITableNameMapper (line 34) | public interface ITableNameMapper
method GetTableName (line 41) | string GetTableName(Type type);
method ComputedPropertiesCache (line 74) | private static List<PropertyInfo> ComputedPropertiesCache(Type type)
method ExplicitKeyPropertiesCache (line 87) | private static List<PropertyInfo> ExplicitKeyPropertiesCache(Type type)
method KeyPropertiesCache (line 100) | private static List<PropertyInfo> KeyPropertiesCache(Type type)
method TypePropertiesCache (line 123) | private static List<PropertyInfo> TypePropertiesCache(Type type)
method IsWriteable (line 135) | private static bool IsWriteable(PropertyInfo pi)
method GetSingleKey (line 144) | private static PropertyInfo GetSingleKey<T>(string method)
method Get (line 170) | public static T Get<T>(this IDbConnection connection, dynamic id, IDbT...
method GetAll (line 232) | public static IEnumerable<T> GetAll<T>(this IDbConnection connection, ...
method GetTableName (line 280) | private static string GetTableName(Type type)
method Insert (line 320) | public static long Insert<T>(this IDbConnection connection, T entityTo...
method Update (line 399) | public static bool Update<T>(this IDbConnection connection, T entityTo...
method Delete (line 470) | public static bool Delete<T>(this IDbConnection connection, T entityTo...
method DeleteAll (line 526) | public static bool DeleteAll<T>(this IDbConnection connection, IDbTran...
method GetFormatter (line 543) | private static ISqlAdapter GetFormatter(IDbConnection connection)
class ProxyGenerator (line 553) | private static class ProxyGenerator
method GetAsmBuilder (line 557) | private static AssemblyBuilder GetAsmBuilder(string name)
method GetInterfaceProxy (line 566) | public static T GetInterfaceProxy<T>()
method CreateIsDirtyProperty (line 604) | private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuil...
method CreateProperty (line 645) | private static void CreateProperty<T>(TypeBuilder typeBuilder, strin...
class TableAttribute (line 706) | [AttributeUsage(AttributeTargets.Class)]
method TableAttribute (line 713) | public TableAttribute(string tableName)
class KeyAttribute (line 727) | [AttributeUsage(AttributeTargets.Property)]
class ExplicitKeyAttribute (line 735) | [AttributeUsage(AttributeTargets.Property)]
class WriteAttribute (line 743) | [AttributeUsage(AttributeTargets.Property)]
method WriteAttribute (line 750) | public WriteAttribute(bool write)
class ComputedAttribute (line 764) | [AttributeUsage(AttributeTargets.Property)]
type ISqlAdapter (line 774) | public partial interface ISqlAdapter
method Insert (line 788) | int Insert(IDbConnection connection, IDbTransaction transaction, int? ...
method AppendColumnName (line 795) | void AppendColumnName(StringBuilder sb, string columnName);
method AppendColumnNameEqualsValue (line 801) | void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
class SqlServerAdapter (line 807) | public partial class SqlServerAdapter : ISqlAdapter
method Insert (line 821) | public int Insert(IDbConnection connection, IDbTransaction transaction...
method AppendColumnName (line 844) | public void AppendColumnName(StringBuilder sb, string columnName)
method AppendColumnNameEqualsValue (line 854) | public void AppendColumnNameEqualsValue(StringBuilder sb, string colum...
class SqlCeServerAdapter (line 863) | public partial class SqlCeServerAdapter : ISqlAdapter
method Insert (line 877) | public int Insert(IDbConnection connection, IDbTransaction transaction...
method AppendColumnName (line 900) | public void AppendColumnName(StringBuilder sb, string columnName)
method AppendColumnNameEqualsValue (line 910) | public void AppendColumnNameEqualsValue(StringBuilder sb, string colum...
class MySqlAdapter (line 919) | public partial class MySqlAdapter : ISqlAdapter
method Insert (line 933) | public int Insert(IDbConnection connection, IDbTransaction transaction...
method AppendColumnName (line 955) | public void AppendColumnName(StringBuilder sb, string columnName)
method AppendColumnNameEqualsValue (line 965) | public void AppendColumnNameEqualsValue(StringBuilder sb, string colum...
class PostgresAdapter (line 974) | public partial class PostgresAdapter : ISqlAdapter
method Insert (line 988) | public int Insert(IDbConnection connection, IDbTransaction transaction...
method AppendColumnName (line 1031) | public void AppendColumnName(StringBuilder sb, string columnName)
method AppendColumnNameEqualsValue (line 1041) | public void AppendColumnNameEqualsValue(StringBuilder sb, string colum...
class SQLiteAdapter (line 1050) | public partial class SQLiteAdapter : ISqlAdapter
method Insert (line 1064) | public int Insert(IDbConnection connection, IDbTransaction transaction...
method AppendColumnName (line 1084) | public void AppendColumnName(StringBuilder sb, string columnName)
method AppendColumnNameEqualsValue (line 1094) | public void AppendColumnNameEqualsValue(StringBuilder sb, string colum...
class FbAdapter (line 1103) | public partial class FbAdapter : ISqlAdapter
method Insert (line 1117) | public int Insert(IDbConnection connection, IDbTransaction transaction...
method AppendColumnName (line 1141) | public void AppendColumnName(StringBuilder sb, string columnName)
method AppendColumnNameEqualsValue (line 1151) | public void AppendColumnNameEqualsValue(StringBuilder sb, string colum...
FILE: tests/Dapper.Tests.Contrib/Helpers/Attributes.cs
class FactAttribute (line 14) | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
class TheoryAttribute (line 30) | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
class FactLongRunningAttribute (line 34) | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
method FactLongRunningAttribute (line 37) | public FactLongRunningAttribute()
FILE: tests/Dapper.Tests.Contrib/Helpers/XunitSkippable.cs
class Skip (line 12) | public static class Skip
method Inconclusive (line 14) | public static void Inconclusive(string reason = "inconclusive")
method If (line 17) | public static void If<T>(object obj, string reason = null)
class SkipTestException (line 25) | public class SkipTestException : Exception
method SkipTestException (line 27) | public SkipTestException(string reason) : base(reason)
class FactDiscoverer (line 33) | public class FactDiscoverer : Xunit.Sdk.FactDiscoverer
method FactDiscoverer (line 35) | public FactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagn...
method CreateTestCase (line 37) | protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscove...
class TheoryDiscoverer (line 41) | public class TheoryDiscoverer : Xunit.Sdk.TheoryDiscoverer
method TheoryDiscoverer (line 43) | public TheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(dia...
method CreateTestCasesForDataRow (line 45) | protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataR...
method CreateTestCasesForSkip (line 48) | protected override IEnumerable<IXunitTestCase> CreateTestCasesForSkip(...
method CreateTestCasesForTheory (line 51) | protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheor...
method CreateTestCasesForSkippedDataRow (line 54) | protected override IEnumerable<IXunitTestCase> CreateTestCasesForSkipp...
class SkippableTestCase (line 58) | public class SkippableTestCase : XunitTestCase
method GetDisplayName (line 60) | protected override string GetDisplayName(IAttributeInfo factAttribute,...
method SkippableTestCase (line 63) | [Obsolete("Called by the de-serializer; should only be called by deriv...
method SkippableTestCase (line 66) | public SkippableTestCase(IMessageSink diagnosticMessageSink, TestMetho...
method RunAsync (line 71) | public override async Task<RunSummary> RunAsync(
class SkippableTheoryTestCase (line 84) | public class SkippableTheoryTestCase : XunitTheoryTestCase
method GetDisplayName (line 86) | protected override string GetDisplayName(IAttributeInfo factAttribute,...
method SkippableTheoryTestCase (line 89) | [Obsolete("Called by the de-serializer; should only be called by deriv...
method SkippableTheoryTestCase (line 92) | public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, Tes...
method RunAsync (line 95) | public override async Task<RunSummary> RunAsync(
class NamedSkippedDataRowTestCase (line 108) | public class NamedSkippedDataRowTestCase : XunitSkippedDataRowTestCase
method GetDisplayName (line 110) | protected override string GetDisplayName(IAttributeInfo factAttribute,...
method NamedSkippedDataRowTestCase (line 113) | [Obsolete("Called by the de-serializer; should only be called by deriv...
method NamedSkippedDataRowTestCase (line 116) | public NamedSkippedDataRowTestCase(IMessageSink diagnosticMessageSink,...
class SkippableMessageBus (line 120) | public class SkippableMessageBus : IMessageBus
method SkippableMessageBus (line 123) | public SkippableMessageBus(IMessageBus innerBus) => InnerBus = innerBus;
method Dispose (line 127) | public void Dispose() { }
method QueueMessage (line 129) | public bool QueueMessage(IMessageSinkMessage message)
class XUnitExtensions (line 144) | internal static class XUnitExtensions
method StripName (line 146) | internal static string StripName(this string name) =>
method Update (line 149) | public static RunSummary Update(this RunSummary summary, SkippableMess...
FILE: tests/Dapper.Tests.Contrib/TestSuite.Async.cs
class TestSuite (line 12) | public abstract partial class TestSuite
method TypeWithGenericParameterCanBeInsertedAsync (line 14) | [Fact]
method TypeWithGenericParameterCanBeUpdatedAsync (line 49) | [Fact]
method TypeWithGenericParameterCanBeDeletedAsync (line 69) | [Fact]
method GetAsyncSucceedsAfterDeleteAsyncWhenExplicitKeyPresent (line 86) | [Fact]
method InsertGetUpdateDeleteWithExplicitKeyAsync (line 100) | [Fact]
method TableNameAsync (line 139) | [Fact]
method TestSimpleGetAsync (line 156) | [Fact]
method InsertGetUpdateAsync (line 169) | [Fact]
method InsertCheckKeyAsync (line 208) | [Fact]
method BuilderSelectClauseAsync (line 222) | [Fact]
method BuilderTemplateWithoutCompositionAsync (line 256) | [Fact]
method InsertEnumerableAsync (line 276) | [Fact]
method InsertArrayAsync (line 282) | [Fact]
method InsertListAsync (line 288) | [Fact]
method InsertHelperAsync (line 294) | private async Task InsertHelperAsync<T>(Func<IEnumerable<User>, T> hel...
method UpdateEnumerableAsync (line 314) | [Fact]
method UpdateArrayAsync (line 320) | [Fact]
method UpdateListAsync (line 326) | [Fact]
method UpdateHelperAsync (line 332) | private async Task UpdateHelperAsync<T>(Func<IEnumerable<User>, T> hel...
method DeleteEnumerableAsync (line 359) | [Fact]
method DeleteArrayAsync (line 365) | [Fact]
method DeleteListAsync (line 371) | [Fact]
method DeleteHelperAsync (line 377) | private async Task DeleteHelperAsync<T>(Func<IEnumerable<User>, T> hel...
method GetAllAsync (line 402) | [Fact]
method GetAsyncAndGetAllAsyncWithNullableValues (line 427) | [Fact]
method InsertFieldWithReservedNameAsync (line 448) | [Fact]
method DeleteAllAsync (line 461) | [Fact]
FILE: tests/Dapper.Tests.Contrib/TestSuite.cs
class ObjectX (line 12) | [Table("ObjectX")]
class ObjectY (line 20) | [Table("ObjectY")]
class ObjectZ (line 28) | [Table("ObjectZ")]
type IUser (line 36) | public interface IUser
class User (line 44) | public class User : IUser
type INullableDate (line 51) | public interface INullableDate
class NullableDate (line 58) | public class NullableDate : INullableDate
class Person (line 64) | public class Person
class Stuff (line 70) | [Table("Stuff")]
class Car (line 79) | [Table("Automobiles")]
class Result (line 88) | [Table("Results")]
class GenericType (line 96) | [Table("GenericType")]
class TestSuite (line 104) | public abstract partial class TestSuite
method GetConnection (line 106) | public abstract IDbConnection GetConnection();
method GetConnectionString (line 108) | protected static string GetConnectionString(string name, string defaul...
method GetOpenConnection (line 111) | private IDbConnection GetOpenConnection()
method TypeWithGenericParameterCanBeInserted (line 118) | [Fact]
method TypeWithGenericParameterCanBeUpdated (line 153) | [Fact]
method TypeWithGenericParameterCanBeDeleted (line 173) | [Fact]
method Issue418 (line 190) | [Fact]
method InsertGetUpdateDeleteWithExplicitKey (line 222) | [Fact]
method GetAllWithExplicitKey (line 261) | [Fact]
method InsertGetUpdateDeleteWithExplicitKeyNamedId (line 276) | [Fact]
method ShortIdentity (line 291) | [Fact]
method NullDateTime (line 305) | [Fact]
method TableName (line 318) | [Fact]
method TestSimpleGet (line 336) | [Fact]
method TestClosedConnection (line 349) | [Fact]
method InsertEnumerable (line 360) | [Fact]
method InsertArray (line 366) | [Fact]
method InsertList (line 372) | [Fact]
method InsertHelper (line 378) | private void InsertHelper<T>(Func<IEnumerable<User>, T> helper)
method UpdateEnumerable (line 398) | [Fact]
method UpdateArray (line 404) | [Fact]
method UpdateList (line 410) | [Fact]
method UpdateHelper (line 416) | private void UpdateHelper<T>(Func<IEnumerable<User>, T> helper)
method DeleteEnumerable (line 443) | [Fact]
method DeleteArray (line 449) | [Fact]
method DeleteList (line 455) | [Fact]
method DeleteHelper (line 461) | private void DeleteHelper<T>(Func<IEnumerable<User>, T> helper)
method InsertGetUpdate (line 486) | [Fact]
method InsertWithCustomDbType (line 525) | [Fact(Skip = "Not parallel friendly - thinking about how to test this")]
method InsertWithCustomTableNameMapper (line 557) | [Fact]
method GetAll (line 586) | [Fact]
method GetAndGetAllWithNullableValues (line 613) | [Fact]
method Transactions (line 633) | [Fact]
method TransactionScope (line 652) | [Fact]
method InsertCheckKey (line 669) | [Fact]
method BuilderSelectClause (line 681) | [Fact]
method BuilderTemplateWithoutComposition (line 712) | [Fact]
method InsertFieldWithReservedName (line 731) | [Fact]
method DeleteAll (line 744) | [Fact]
FILE: tests/Dapper.Tests.Contrib/TestSuites.cs
class SkippableFactAttribute (line 16) | [XunitTestCaseDiscoverer("Dapper.Tests.SkippableFactDiscoverer", "Dapper...
class SqlServerTestSuite (line 22) | public class SqlServerTestSuite : TestSuite
method GetConnection (line 28) | public override IDbConnection GetConnection() => new SqlConnection(Con...
method SqlServerTestSuite (line 30) | static SqlServerTestSuite()
class MySqlServerTestSuite (line 61) | public class MySqlServerTestSuite : TestSuite
method GetConnection (line 66) | public override IDbConnection GetConnection()
method MySqlServerTestSuite (line 74) | static MySqlServerTestSuite()
class SQLiteTestSuite (line 115) | public class SQLiteTestSuite : TestSuite
method GetConnection (line 119) | public override IDbConnection GetConnection() => new SqliteConnection(...
method SQLiteTestSuite (line 121) | static SQLiteTestSuite()
class SqlCETestSuite (line 146) | public class SqlCETestSuite : TestSuite
method GetConnection (line 150) | public override IDbConnection GetConnection() => new SqlCeConnection(C...
method SqlCETestSuite (line 152) | static SqlCETestSuite()
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (187K chars).
[
{
"path": ".editorconfig",
"chars": 2849,
"preview": "# EditorConfig is awesome:http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Don't use tabs for indent"
},
{
"path": ".gitattributes",
"chars": 768,
"preview": "* text=auto\n\n*.doc diff=astextplain\n*.DOC\tdiff=astextplain\n*.docx\tdiff=astextplain\n*.DOCX\tdiff=astextplain\n*.dot\tdiff="
},
{
"path": ".github/workflows/main.yml",
"chars": 1913,
"preview": "name: Main Build\n\non:\n pull_request:\n push:\n branches:\n - main\n paths:\n - '*'\n - '!/docs/*' # Don't run"
},
{
"path": ".gitignore",
"chars": 209,
"preview": "/*.suo\n.vs/\n.vscode/\nbin/\nobj/\n/*.user\n_Resharper*\n.hgtags\nNuGet.exe\n*.user\n*.nupkg\n.nupkgs/\n.docstats\n*.ide/\n*.lock.jso"
},
{
"path": "Build.csproj",
"chars": 192,
"preview": "<Project Sdk=\"Microsoft.Build.Traversal/2.0.24\">\r\n <ItemGroup>\r\n <ProjectReference Include=\"*/*.csproj\" />\r\n <Pro"
},
{
"path": "Dapper.sln",
"chars": 2995,
"preview": "\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 16\r\nVisualStudioVersion = 16.0.2"
},
{
"path": "Directory.Build.props",
"chars": 1910,
"preview": "<Project>\n <PropertyGroup>\n <Copyright>2019 Stack Exchange, Inc.</Copyright>\n\n <TreatWarningsAsErrors>true</Treat"
},
{
"path": "Directory.Build.targets",
"chars": 487,
"preview": "<Project>\n <!-- workaround for deterministic builds; see https://github.com/clairernovotny/DeterministicBuilds -->\n <P"
},
{
"path": "License.txt",
"chars": 110,
"preview": "The Dapper.Contrib library and tools are licenced under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0"
},
{
"path": "Readme.md",
"chars": 4747,
"preview": "Dapper.Contrib - a simple object mapper for .Net\n========================================\n[]\nparam(\n [bool] $CreatePackages,\n [bool] $RunTests = $true,\n [string] "
},
{
"path": "docs/_config.yml",
"chars": 26,
"preview": "theme: jekyll-theme-cayman"
},
{
"path": "docs/index.md",
"chars": 614,
"preview": "# Dapper.Contrib - Extensions for Dapper\n\n## Overview\n\nA brief guide is available [on github](https://github.com/DapperL"
},
{
"path": "nuget.config",
"chars": 193,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n <packageSources>\n <clear />\n <add key=\"NuGet\" value=\"htt"
},
{
"path": "src/Dapper.Contrib/Dapper.Contrib.csproj",
"chars": 1094,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n <PropertyGroup>\r\n <AssemblyName>Dapper.Contrib</AssemblyName>\r\n <Title>Dappe"
},
{
"path": "src/Dapper.Contrib/SqlMapperExtensions.Async.cs",
"chars": 29343,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Data;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusi"
},
{
"path": "src/Dapper.Contrib/SqlMapperExtensions.cs",
"chars": 53702,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Data;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusi"
},
{
"path": "tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj",
"chars": 932,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <AssemblyName>Dapper.Tests.Contrib</AssemblyName>\n <Descript"
},
{
"path": "tests/Dapper.Tests.Contrib/Helpers/Attributes.cs",
"chars": 1881,
"preview": "using System;\r\nusing Xunit.Sdk;\r\n\r\nnamespace Dapper.Tests\r\n{\r\n /// <summary>\r\n /// <para>Override for <see cref=\""
},
{
"path": "tests/Dapper.Tests.Contrib/Helpers/XunitSkippable.cs",
"chars": 8168,
"preview": "using System;\r\n\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading;\r\nusing System.Threading"
},
{
"path": "tests/Dapper.Tests.Contrib/TestSuite.Async.cs",
"chars": 20763,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\n\r\nusing Dapper.Con"
},
{
"path": "tests/Dapper.Tests.Contrib/TestSuite.cs",
"chars": 26566,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Data;\r\nusing System.Linq;\r\nusing Dapper.Contrib.Extensio"
},
{
"path": "tests/Dapper.Tests.Contrib/TestSuites.cs",
"chars": 10252,
"preview": "using Microsoft.Data.Sqlite;\r\nusing MySqlConnector;\r\nusing System;\r\nusing System.Data;\r\nusing System.Data.SqlClient;\r\nu"
},
{
"path": "tests/Dapper.Tests.Contrib/xunit.runner.json",
"chars": 100,
"preview": "{\n \"$schema\": \"https://xunit.net/schema/current/xunit.runner.schema.json\",\n \"shadowCopy\": false\n}"
},
{
"path": "tests/Directory.Build.props",
"chars": 1692,
"preview": "<Project>\n <Import Project=\"$(MSBuildThisFileDirectory)..\\Directory.Build.props\" />\n <PropertyGroup>\n <OutputType>L"
},
{
"path": "tests/Directory.Build.targets",
"chars": 176,
"preview": "<Project>\n <PropertyGroup>\n <IsTestProject Condition=\"'$(OS)' != 'Windows_NT' AND $(TargetFramework.StartsWith('net4"
},
{
"path": "tests/docker-compose.yml",
"chars": 583,
"preview": "version: \"3\"\nservices:\n mysql:\n image: mysql:8\n container_name: mysql\n ports:\n - 3306:3306\n environmen"
},
{
"path": "version.json",
"chars": 297,
"preview": "{\n \"version\": \"2.0\",\n \"assemblyVersion\": \"2.0.0.0\",\n \"publicReleaseRefSpec\": [\n \"^refs/heads/main$\",\n \"^refs/ta"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the DapperLib/Dapper.Contrib GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (172.3 KB), approximately 38.1k tokens, and a symbol index with 212 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.