Repository: DapperLib/Dapper.Contrib
Branch: main
Commit: cf24f6bdc577
Files: 31
Total size: 172.3 KB
Directory structure:
gitextract_c51ukdyb/
├── .editorconfig
├── .gitattributes
├── .github/
│ └── workflows/
│ └── main.yml
├── .gitignore
├── Build.csproj
├── Dapper.sln
├── Dapper.snk
├── Directory.Build.props
├── Directory.Build.targets
├── License.txt
├── Readme.md
├── appveyor.yml
├── build.cmd
├── build.ps1
├── docs/
│ ├── _config.yml
│ └── index.md
├── nuget.config
├── src/
│ └── Dapper.Contrib/
│ ├── Dapper.Contrib.csproj
│ ├── SqlMapperExtensions.Async.cs
│ └── SqlMapperExtensions.cs
├── tests/
│ ├── Dapper.Tests.Contrib/
│ │ ├── Dapper.Tests.Contrib.csproj
│ │ ├── Helpers/
│ │ │ ├── Attributes.cs
│ │ │ └── XunitSkippable.cs
│ │ ├── TestSuite.Async.cs
│ │ ├── TestSuite.cs
│ │ ├── TestSuites.cs
│ │ └── xunit.runner.json
│ ├── Directory.Build.props
│ ├── Directory.Build.targets
│ └── docker-compose.yml
└── version.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome:http://EditorConfig.org
# top-most EditorConfig file
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
#csharp_style_var_for_built_in_types = true:suggestion
#csharp_style_var_when_type_is_apparent = false:suggestion
#csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a expression-body
csharp_style_expression_bodied_methods = true:none
csharp_style_expression_bodied_constructors = true:none
csharp_style_expression_bodied_operators = true:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
================================================
FILE: .gitattributes
================================================
* text=auto
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.jpg binary
*.png binary
*.gif binary
*.cs -text diff=csharp
*.vb -text
*.c -text
*.cpp -text
*.cxx -text
*.h -text
*.hxx -text
*.py -text
*.rb -text
*.java -text
*.html -text
*.htm -text
*.css -text
*.scss -text
*.sass -text
*.less -text
*.js -text
*.lisp -text
*.clj -text
*.sql -text
*.php -text
*.lua -text
*.m -text
*.asm -text
*.erl -text
*.fs -text
*.fsx -text
*.hs -text
*.csproj -text merge=union
*.vbproj -text merge=union
*.fsproj -text merge=union
*.dbproj -text merge=union
*.sln -text merge=union
================================================
FILE: .github/workflows/main.yml
================================================
name: Main Build
on:
pull_request:
push:
branches:
- main
paths:
- '*'
- '!/docs/*' # Don't run workflow when files are only in the /docs directory
jobs:
vm-job:
name: Ubuntu
runs-on: ubuntu-latest
services:
postgres:
image: postgres
ports:
- 5432/tcp
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
sqlserver:
image: mcr.microsoft.com/mssql/server:2019-latest
ports:
- 1433/tcp
env:
ACCEPT_EULA: Y
SA_PASSWORD: "Password."
mysql:
image: mysql
ports:
- 3306/tcp
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
steps:
- name: Checkout code
uses: actions/checkout@v1
- name: .NET Build
run: dotnet build Build.csproj -c Release /p:CI=true
- name: Dapper.Contrib Tests
run: dotnet test tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj -c Release --logger GitHubActions /p:CI=true
env:
MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true
OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.;
PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres;
SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.;
- name: .NET Lib Pack
run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true
================================================
FILE: .gitignore
================================================
/*.suo
.vs/
.vscode/
bin/
obj/
/*.user
_Resharper*
.hgtags
NuGet.exe
*.user
*.nupkg
.nupkgs/
.docstats
*.ide/
*.lock.json
*.coverage
Test.DB.*
TestResults/
.dotnet/*
BenchmarkDotNet.Artifacts/
.idea/
.DS_Store
================================================
FILE: Build.csproj
================================================
================================================
FILE: Dapper.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28917.182
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
appveyor.yml = appveyor.yml
build.ps1 = build.ps1
Directory.Build.props = Directory.Build.props
docs\index.md = docs\index.md
License.txt = License.txt
nuget.config = nuget.config
Readme.md = Readme.md
version.json = version.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Contrib", "Dapper.Contrib\Dapper.Contrib.csproj", "{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Contrib", "tests\Dapper.Tests.Contrib\Dapper.Tests.Contrib.csproj", "{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}"
ProjectSection(SolutionItems) = preProject
tests\Directory.Build.props = tests\Directory.Build.props
tests\Directory.Build.targets = tests\Directory.Build.targets
tests\docker-compose.yml = tests\docker-compose.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Release|Any CPU.Build.0 = Release|Any CPU
{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4E409F8F-CFBB-4332-8B0A-FD5A283051FD} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}
{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A} = {568BD46C-1C65-4D44-870C-12CD72563262}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710}
EndGlobalSection
EndGlobal
================================================
FILE: Directory.Build.props
================================================
2019 Stack Exchange, Inc.truetrue../Dapper.snk$(AssemblyName)https://dapperlib.github.io/Dapper.Contrib/https://github.com/DapperLib/Dapper.ContribApache-2.0Dapper.pnggithttps://github.com/DapperLib/Dapper.Contribfalse$(NOWARN);IDE0056;IDE0057;IDE0079trueembeddeden-USfalsetrue9.0truetruetrue
================================================
FILE: Directory.Build.targets
================================================
$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))
================================================
FILE: License.txt
================================================
The Dapper.Contrib library and tools are licenced under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
================================================
FILE: Readme.md
================================================
Dapper.Contrib - a simple object mapper for .Net
========================================
[](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(id);
IEnumerable GetAll();
int Insert(T obj);
int Insert(Enumerable list);
bool Update(T obj);
bool Update(Enumerable list);
bool Delete(T obj);
bool Delete(Enumerable list);
bool DeleteAll();
```
For these extensions to work, the entity in question _MUST_ have a
key property. Dapper will automatically use a property named "`id`"
(case-insensitive) as the key property, if one is present.
```csharp
public class Car
{
public int Id { get; set; } // Works by convention
public string Name { get; set; }
}
```
If the entity doesn't follow this convention, decorate
a specific property with a `[Key]` or `[ExplicitKey]` attribute.
```csharp
public class User
{
[Key]
int TheId { get; set; }
string Name { get; set; }
int Age { get; set; }
}
```
`[Key]` should be used for database-generated keys (e.g. autoincrement columns),
while `[ExplicitKey]` should be used for explicit keys generated in code.
`Get` methods
-------
Get one specific entity based on id
```csharp
var car = connection.Get(1);
```
or a list of all entities in the table.
```csharp
var cars = connection.GetAll();
```
`Insert` methods
-------
Insert one entity
```csharp
connection.Insert(new Car { Name = "Volvo" });
```
or a list of entities.
```csharp
connection.Insert(cars);
```
`Update` methods
-------
Update one specific entity
```csharp
connection.Update(new Car() { Id = 1, Name = "Saab" });
```
or update a list of entities.
```csharp
connection.Update(cars);
```
`Delete` methods
-------
Delete an entity by the specified `[Key]` property
```csharp
connection.Delete(new Car() { Id = 1 });
```
a list of entities
```csharp
connection.Delete(cars);
```
or _ALL_ entities in the table.
```csharp
connection.DeleteAll();
```
Special Attributes
----------
Dapper.Contrib makes use of some optional attributes:
* `[Table("Tablename")]` - use another table name instead of the (by default pluralized) name of the class
```csharp
[Table ("emps")]
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
```
* `[Key]` - this property represents a database-generated identity/key
```csharp
public class Employee
{
[Key]
public int EmployeeId { get; set; }
public string Name { get; set; }
}
```
* `[ExplicitKey]` - this property represents an explicit identity/key which is
*not* automatically generated by the database
```csharp
public class Employee
{
[ExplicitKey]
public Guid EmployeeId { get; set; }
public string Name { get; set; }
}
```
* `[Write(true/false)]` - this property is (not) writeable
* `[Computed]` - this property is computed and should not be part of updates
Limitations and caveats
-------
### SQLite
`SQLiteConnection` exposes an `Update` event that clashes with the `Update`
extension provided by Dapper.Contrib. There are 2 ways to deal with this.
1. Call the `Update` method explicitly from `SqlMapperExtensions`
```Csharp
SqlMapperExtensions.Update(_conn, new Employee { Id = 1, Name = "Mercedes" });
```
2. Make the method signature unique by passing a type parameter to `Update`
```Csharp
connection.Update(new Car() { Id = 1, Name = "Maruti" });
```
================================================
FILE: appveyor.yml
================================================
image: Visual Studio 2019
skip_branch_with_pr: true
skip_tags: true
skip_commits:
files:
- '**/*.md'
environment:
Appveyor: true
# Postgres
POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6
PGUSER: postgres
PGPASSWORD: Password12!
POSTGRES_ENV_POSTGRES_USER: postgres
POSTGRES_ENV_POSTGRES_PASSWORD: Password12!
POSTGRES_ENV_POSTGRES_DB: test
# MySQL
MYSQL_PATH: C:\Program Files\MySql\MySQL Server 5.7
MYSQL_PWD: Password12!
MYSQL_ENV_MYSQL_USER: root
MYSQL_ENV_MYSQL_PASSWORD: Password12!
MYSQL_ENV_MYSQL_DATABASE: test
# Connection strings for tests:
MySqlConnectionString: Server=localhost;Database=test;Uid=root;Pwd=Password12!
OLEDBConnectionString: Provider=SQLOLEDB;Data Source=(local)\SQL2019;Initial Catalog=tempdb;User Id=sa;Password=Password12!
PostgesConnectionString: Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test
SqlServerConnectionString: Server=(local)\SQL2019;Database=tempdb;User ID=sa;Password=Password12!
services:
- mysql
- postgresql
init:
- git config --global core.autocrlf input
- SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH%
- net start MSSQL$SQL2019
nuget:
disable_publish_on_pr: true
build_script:
# Postgres
- createdb test
# MySQL
- mysql -e "create database test;" --user=root
# Our stuff
- ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true
test: off
artifacts:
- path: .\.nupkgs\*.nupkg
deploy:
- provider: NuGet
server: https://www.myget.org/F/stackoverflow/api/v2
on:
branch: main
api_key:
secure: P/UHxq2DEs0GI1SoDXDesHjRVsSVgdywz5vmsnhFQQY5aJgO3kP+QfhwfhXz19Rw
symbol_server: https://www.myget.org/F/stackoverflow/symbols/api/v2/package
- provider: NuGet
server: https://www.myget.org/F/dapper/api/v2
on:
branch: main
api_key:
secure: PV7ERAltWWLhy7AT2h+Vb5c1BM9/WFgvggb+rKyQ8hDg3fYqpZauYdidOOgt2lp4
symbol_server: https://www.myget.org/F/dapper/api/v2/package
================================================
FILE: build.cmd
================================================
@ECHO OFF
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE"
================================================
FILE: build.ps1
================================================
[CmdletBinding(PositionalBinding=$false)]
param(
[bool] $CreatePackages,
[bool] $RunTests = $true,
[string] $PullRequestNumber
)
Write-Host "Run Parameters:" -ForegroundColor Cyan
Write-Host " CreatePackages: $CreatePackages"
Write-Host " RunTests: $RunTests"
Write-Host " dotnet --version:" (dotnet --version)
$packageOutputFolder = "$PSScriptRoot\.nupkgs"
if ($PullRequestNumber) {
Write-Host "Building for a pull request (#$PullRequestNumber), skipping packaging." -ForegroundColor Yellow
$CreatePackages = $false
}
Write-Host "Building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta"
dotnet build ".\Build.csproj" -c Release /p:CI=true
Write-Host "Done building." -ForegroundColor "Green"
if ($RunTests) {
Write-Host "Running tests: Build.csproj traversal (all frameworks)" -ForegroundColor "Magenta"
dotnet test ".\Build.csproj" -c Release --no-build
if ($LastExitCode -ne 0) {
Write-Host "Error with tests, aborting build." -Foreground "Red"
Exit 1
}
Write-Host "Tests passed!" -ForegroundColor "Green"
}
if ($CreatePackages) {
New-Item -ItemType Directory -Path $packageOutputFolder -Force | Out-Null
Write-Host "Clearing existing $packageOutputFolder..." -NoNewline
Get-ChildItem $packageOutputFolder | Remove-Item
Write-Host "done." -ForegroundColor "Green"
Write-Host "Building all packages" -ForegroundColor "Green"
dotnet pack ".\Build.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:CI=true
}
Write-Host "Build Complete." -ForegroundColor "Green"
================================================
FILE: docs/_config.yml
================================================
theme: jekyll-theme-cayman
================================================
FILE: docs/index.md
================================================
# Dapper.Contrib - Extensions for Dapper
## Overview
A brief guide is available [on github](https://github.com/DapperLib/Dapper.Contrib/blob/main/Readme.md)
## Installation
From NuGet:
Install-Package Dapper.Contrib
Note: to get the latest pre-release build, add ` -Pre` to the end of the command.
## Release Notes
### Unreleased
(note: new PRs will not be merged until they add release note wording here)
### Previous Releases
Prior to v2.0.90, Dapper.Contrib was part of the main repository - please see release notes at [https://dapperlib.github.io/Dapper/](https://dapperlib.github.io/Dapper/)
================================================
FILE: nuget.config
================================================
================================================
FILE: src/Dapper.Contrib/Dapper.Contrib.csproj
================================================
Dapper.ContribDapper.Contriborm;sql;micro-orm;dapperThe official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities.Sam Saffron;Johan Danforthnet461;netstandard2.0;net5.0false$(NoWarn);CA1050
================================================
FILE: src/Dapper.Contrib/SqlMapperExtensions.Async.cs
================================================
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Dapper;
namespace Dapper.Contrib.Extensions
{
public static partial class SqlMapperExtensions
{
///
/// Returns a single entity by a single id from table "Ts" asynchronously using Task. T must be of interface type.
/// Id must be marked with [Key] attribute.
/// Created entity is tracked/intercepted for changes and used by the Update() extension.
///
/// Interface type to create and populate
/// Open SqlConnection
/// Id of the entity to get, must be marked with [Key] attribute
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Entity of T
public static async Task GetAsync(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
if (!GetQueries.TryGetValue(type.TypeHandle, out string sql))
{
var key = GetSingleKey(nameof(GetAsync));
var name = GetTableName(type);
sql = $"SELECT * FROM {name} WHERE {key.Name} = @id";
GetQueries[type.TypeHandle] = sql;
}
var dynParams = new DynamicParameters();
dynParams.Add("@id", id);
if (!type.IsInterface)
return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault();
if (!((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary res))
{
return null;
}
var obj = ProxyGenerator.GetInterfaceProxy();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
if (val == null) continue;
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var genericType = Nullable.GetUnderlyingType(property.PropertyType);
if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null);
}
else
{
property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
}
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
return obj;
}
///
/// Returns a list of entities from table "Ts".
/// Id of T must be marked with [Key] attribute.
/// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
///
/// Interface or type to create and populate
/// Open SqlConnection
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Entity of T
public static Task> GetAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof(List);
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
{
GetSingleKey(nameof(GetAll));
var name = GetTableName(type);
sql = "SELECT * FROM " + name;
GetQueries[cacheType.TypeHandle] = sql;
}
if (!type.IsInterface)
{
return connection.QueryAsync(sql, null, transaction, commandTimeout);
}
return GetAllAsyncImpl(connection, transaction, commandTimeout, sql, type);
}
private static async Task> GetAllAsyncImpl(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string sql, Type type) where T : class
{
var result = await connection.QueryAsync(sql, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false);
var list = new List();
foreach (IDictionary res in result)
{
var obj = ProxyGenerator.GetInterfaceProxy();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
if (val == null) continue;
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var genericType = Nullable.GetUnderlyingType(property.PropertyType);
if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null);
}
else
{
property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
}
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
list.Add(obj);
}
return list;
}
///
/// Inserts an entity into table "Ts" asynchronously using Task and returns identity id.
///
/// The type being inserted.
/// Open SqlConnection
/// Entity to insert
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// The specific ISqlAdapter to use, auto-detected based on connection if null
/// Identity of inserted entity
public static Task InsertAsync(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null,
int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class
{
var type = typeof(T);
sqlAdapter ??= GetFormatter(connection);
var isList = false;
if (type.IsArray)
{
isList = true;
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
isList = true;
type = type.GetGenericArguments()[0];
}
}
var name = GetTableName(type);
var sbColumnList = new StringBuilder(null);
var allProperties = TypePropertiesCache(type);
var keyProperties = KeyPropertiesCache(type).ToList();
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
sqlAdapter.AppendColumnName(sbColumnList, property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbColumnList.Append(", ");
}
var sbParameterList = new StringBuilder(null);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
sbParameterList.AppendFormat("@{0}", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbParameterList.Append(", ");
}
if (!isList) //single entity
{
return sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
//insert list of entities
var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})";
return connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout);
}
///
/// Updates entity in table "Ts" asynchronously using Task, checks if the entity is modified if the entity is tracked by the Get() extension.
///
/// Type to be updated
/// Open SqlConnection
/// Entity to be updated
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if updated, false if not found or not modified (tracked entities)
public static async Task UpdateAsync(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if ((entityToUpdate is IProxy proxy) && !proxy.IsDirty)
{
return false;
}
var type = typeof(T);
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
type = type.GetGenericArguments()[0];
}
}
var keyProperties = KeyPropertiesCache(type).ToList();
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
keyProperties.AddRange(explicitKeyProperties);
var computedProperties = ComputedPropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < nonIdProps.Count; i++)
{
var property = nonIdProps[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name);
if (i < nonIdProps.Count - 1)
sb.Append(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count; i++)
{
var property = keyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name);
if (i < keyProperties.Count - 1)
sb.Append(" and ");
}
var updated = await connection.ExecuteAsync(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction).ConfigureAwait(false);
return updated > 0;
}
///
/// Delete entity in table "Ts" asynchronously using Task.
///
/// Type of entity
/// Open SqlConnection
/// Entity to delete
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if deleted, false if not found
public static async Task DeleteAsync(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToDelete == null)
throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete));
var type = typeof(T);
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
type = type.GetGenericArguments()[0];
}
}
var keyProperties = KeyPropertiesCache(type);
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
var allKeyProperties = keyProperties.Concat(explicitKeyProperties).ToList();
var sb = new StringBuilder();
sb.AppendFormat("DELETE FROM {0} WHERE ", name);
var adapter = GetFormatter(connection);
for (var i = 0; i < allKeyProperties.Count; i++)
{
var property = allKeyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name);
if (i < allKeyProperties.Count - 1)
sb.Append(" AND ");
}
var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false);
return deleted > 0;
}
///
/// Delete all entities in the table related to the type T asynchronously using Task.
///
/// Type of entity
/// Open SqlConnection
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if deleted, false if none found
public static async Task DeleteAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var statement = "DELETE FROM " + GetTableName(type);
var deleted = await connection.ExecuteAsync(statement, null, transaction, commandTimeout).ConfigureAwait(false);
return deleted > 0;
}
}
}
public partial interface ISqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert);
}
public partial class SqlServerAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"INSERT INTO {tableName} ({columnList}) values ({parameterList}); SELECT SCOPE_IDENTITY() id";
var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
var first = await multi.ReadFirstOrDefaultAsync().ConfigureAwait(false);
if (first == null || first.id == null) return 0;
var id = (int)first.id;
var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (pi.Length == 0) return id;
var idp = pi[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return id;
}
}
public partial class SqlCeServerAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})";
await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
var r = (await connection.QueryAsync("SELECT @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false)).ToList();
if (r[0] == null || r[0].id == null) return 0;
var id = (int)r[0].id;
var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (pi.Length == 0) return id;
var idp = pi[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return id;
}
}
public partial class MySqlAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName,
string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})";
await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
var r = await connection.QueryAsync("SELECT LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false);
var id = r.First().id;
if (id == null) return 0;
var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (pi.Length == 0) return Convert.ToInt32(id);
var idp = pi[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return Convert.ToInt32(id);
}
}
public partial class PostgresAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var sb = new StringBuilder();
sb.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", tableName, columnList, parameterList);
// If no primary key then safe to assume a join table with not too much data to return
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length == 0)
{
sb.Append(" RETURNING *");
}
else
{
sb.Append(" RETURNING ");
bool first = true;
foreach (var property in propertyInfos)
{
if (!first)
sb.Append(", ");
first = false;
sb.Append(property.Name);
}
}
var results = await connection.QueryAsync(sb.ToString(), entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
// Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys
var id = 0;
foreach (var p in propertyInfos)
{
var value = ((IDictionary)results.First())[p.Name.ToLower()];
p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
}
public partial class SQLiteAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id";
var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
var id = (int)(await multi.ReadFirstAsync().ConfigureAwait(false)).id;
var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (pi.Length == 0) return id;
var idp = pi[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return id;
}
}
public partial class FbAdapter
{
///
/// Inserts into the database, returning the Id of the row created.
///
/// The connection to use.
/// The transaction to use.
/// The command timeout to use.
/// The table to insert into.
/// The columns to set with this insert.
/// The parameters to set for this insert.
/// The key columns in this table.
/// The entity to insert.
/// The Id of the row created.
public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
{
var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
var keyName = propertyInfos[0].Name;
var r = await connection.QueryAsync($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false);
var id = r.First().ID;
if (id == null) return 0;
if (propertyInfos.Length == 0) return Convert.ToInt32(id);
var idp = propertyInfos[0];
idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
return Convert.ToInt32(id);
}
}
================================================
FILE: src/Dapper.Contrib/SqlMapperExtensions.cs
================================================
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Collections.Concurrent;
using System.Reflection.Emit;
using System.Threading;
using Dapper;
namespace Dapper.Contrib.Extensions
{
///
/// The Dapper.Contrib extensions for Dapper
///
public static partial class SqlMapperExtensions
{
///
/// Defined a proxy object with a possibly dirty state.
///
public interface IProxy //must be kept public
{
///
/// Whether the object has been changed.
///
bool IsDirty { get; set; }
}
///
/// Defines a table name mapper for getting table names from types.
///
public interface ITableNameMapper
{
///
/// Gets a table name from a given .
///
/// The to get a name from.
/// The table name for the given .
string GetTableName(Type type);
}
///
/// The function to get a database type from the given .
///
/// The connection to get a database type name from.
public delegate string GetDatabaseTypeDelegate(IDbConnection connection);
///
/// The function to get a table name from a given
///
/// The to get a table name for.
public delegate string TableNameMapperDelegate(Type type);
private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>();
private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary();
private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary();
private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter();
private static readonly Dictionary AdapterDictionary
= new Dictionary(6)
{
["sqlconnection"] = new SqlServerAdapter(),
["sqlceconnection"] = new SqlCeServerAdapter(),
["npgsqlconnection"] = new PostgresAdapter(),
["sqliteconnection"] = new SQLiteAdapter(),
["mysqlconnection"] = new MySqlAdapter(),
["fbconnection"] = new FbAdapter()
};
private static List ComputedPropertiesCache(Type type)
{
if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable pi))
{
return pi.ToList();
}
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
ComputedProperties[type.TypeHandle] = computedProperties;
return computedProperties;
}
private static List ExplicitKeyPropertiesCache(Type type)
{
if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi))
{
return pi.ToList();
}
var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList();
ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties;
return explicitKeyProperties;
}
private static List KeyPropertiesCache(Type type)
{
if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi))
{
return pi.ToList();
}
var allProperties = TypePropertiesCache(type);
var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
if (keyProperties.Count == 0)
{
var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase));
if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute))
{
keyProperties.Add(idProp);
}
}
KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties;
}
private static List TypePropertiesCache(Type type)
{
if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable pis))
{
return pis.ToList();
}
var properties = type.GetProperties().Where(IsWriteable).ToArray();
TypeProperties[type.TypeHandle] = properties;
return properties.ToList();
}
private static bool IsWriteable(PropertyInfo pi)
{
var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
if (attributes.Count != 1) return true;
var writeAttribute = (WriteAttribute)attributes[0];
return writeAttribute.Write;
}
private static PropertyInfo GetSingleKey(string method)
{
var type = typeof(T);
var keys = KeyPropertiesCache(type);
var explicitKeys = ExplicitKeyPropertiesCache(type);
var keyCount = keys.Count + explicitKeys.Count;
if (keyCount > 1)
throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property. [Key] Count: {keys.Count}, [ExplicitKey] Count: {explicitKeys.Count}");
if (keyCount == 0)
throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property");
return keys.Count > 0 ? keys[0] : explicitKeys[0];
}
///
/// Returns a single entity by a single id from table "Ts".
/// Id must be marked with [Key] attribute.
/// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
///
/// Interface or type to create and populate
/// Open SqlConnection
/// Id of the entity to get, must be marked with [Key] attribute
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Entity of T
public static T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
if (!GetQueries.TryGetValue(type.TypeHandle, out string sql))
{
var key = GetSingleKey(nameof(Get));
var name = GetTableName(type);
sql = $"select * from {name} where {key.Name} = @id";
GetQueries[type.TypeHandle] = sql;
}
var dynParams = new DynamicParameters();
dynParams.Add("@id", id);
T obj;
if (type.IsInterface)
{
if (!(connection.Query(sql, dynParams).FirstOrDefault() is IDictionary res))
{
return null;
}
obj = ProxyGenerator.GetInterfaceProxy();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
if (val == null) continue;
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var genericType = Nullable.GetUnderlyingType(property.PropertyType);
if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null);
}
else
{
property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
}
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
}
else
{
obj = connection.Query(sql, dynParams, transaction, commandTimeout: commandTimeout).FirstOrDefault();
}
return obj;
}
///
/// Returns a list of entities from table "Ts".
/// Id of T must be marked with [Key] attribute.
/// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
///
/// Interface or type to create and populate
/// Open SqlConnection
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Entity of T
public static IEnumerable GetAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof(List);
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
{
GetSingleKey(nameof(GetAll));
var name = GetTableName(type);
sql = "select * from " + name;
GetQueries[cacheType.TypeHandle] = sql;
}
if (!type.IsInterface) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout);
var result = connection.Query(sql);
var list = new List();
foreach (IDictionary res in result)
{
var obj = ProxyGenerator.GetInterfaceProxy();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
if (val == null) continue;
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var genericType = Nullable.GetUnderlyingType(property.PropertyType);
if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null);
}
else
{
property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
}
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
list.Add(obj);
}
return list;
}
///
/// Specify a custom table name mapper based on the POCO type name
///
#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API
public static TableNameMapperDelegate TableNameMapper;
#pragma warning restore CA2211 // Non-constant fields should not be visible
private static string GetTableName(Type type)
{
if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name;
if (TableNameMapper != null)
{
name = TableNameMapper(type);
}
else
{
//NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework
var tableAttrName =
type.GetCustomAttribute(false)?.Name
?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name;
if (tableAttrName != null)
{
name = tableAttrName;
}
else
{
name = type.Name + "s";
if (type.IsInterface && name.StartsWith("I"))
name = name.Substring(1);
}
}
TypeTableName[type.TypeHandle] = name;
return name;
}
///
/// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list.
///
/// The type to insert.
/// Open SqlConnection
/// Entity to insert, can be list of entities
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// Identity of inserted entity, or number of inserted rows if inserting a list
public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var isList = false;
var type = typeof(T);
if (type.IsArray)
{
isList = true;
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
isList = true;
type = type.GetGenericArguments()[0];
}
}
var name = GetTableName(type);
var sbColumnList = new StringBuilder(null);
var allProperties = TypePropertiesCache(type);
var keyProperties = KeyPropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbColumnList.Append(", ");
}
var sbParameterList = new StringBuilder(null);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
sbParameterList.AppendFormat("@{0}", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbParameterList.Append(", ");
}
int returnVal;
var wasClosed = connection.State == ConnectionState.Closed;
if (wasClosed) connection.Open();
if (!isList) //single entity
{
returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
else
{
//insert list of entities
var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})";
returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
}
if (wasClosed) connection.Close();
return returnVal;
}
///
/// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
///
/// Type to be updated
/// Open SqlConnection
/// Entity to be updated
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if updated, false if not found or not modified (tracked entities)
public static bool Update(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToUpdate is IProxy proxy && !proxy.IsDirty)
{
return false;
}
var type = typeof(T);
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
type = type.GetGenericArguments()[0];
}
}
var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
keyProperties.AddRange(explicitKeyProperties);
var computedProperties = ComputedPropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < nonIdProps.Count; i++)
{
var property = nonIdProps[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < nonIdProps.Count - 1)
sb.Append(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count; i++)
{
var property = keyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < keyProperties.Count - 1)
sb.Append(" and ");
}
var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
return updated > 0;
}
///
/// Delete entity in table "Ts".
///
/// Type of entity
/// Open SqlConnection
/// Entity to delete
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if deleted, false if not found
public static bool Delete(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToDelete == null)
throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete));
var type = typeof(T);
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
type = type.GetGenericArguments()[0];
}
}
var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
keyProperties.AddRange(explicitKeyProperties);
var sb = new StringBuilder();
sb.AppendFormat("delete from {0} where ", name);
var adapter = GetFormatter(connection);
for (var i = 0; i < keyProperties.Count; i++)
{
var property = keyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < keyProperties.Count - 1)
sb.Append(" and ");
}
var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout);
return deleted > 0;
}
///
/// Delete all entities in the table related to the type T.
///
/// Type of entity
/// Open SqlConnection
/// The transaction to run under, null (the default) if none
/// Number of seconds before command execution timeout
/// true if deleted, false if none found
public static bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var name = GetTableName(type);
var statement = $"delete from {name}";
var deleted = connection.Execute(statement, null, transaction, commandTimeout);
return deleted > 0;
}
///
/// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object).
/// Please note that this callback is global and will be used by all the calls that require a database specific adapter.
///
#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API
public static GetDatabaseTypeDelegate GetDatabaseType;
#pragma warning restore CA2211 // Non-constant fields should not be visible
private static ISqlAdapter GetFormatter(IDbConnection connection)
{
var name = GetDatabaseType?.Invoke(connection).ToLower()
?? connection.GetType().Name.ToLower();
return AdapterDictionary.TryGetValue(name, out var adapter)
? adapter
: DefaultAdapter;
}
private static class ProxyGenerator
{
private static readonly Dictionary TypeCache = new Dictionary();
private static AssemblyBuilder GetAsmBuilder(string name)
{
#if !NET461
return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run);
#else
return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run);
#endif
}
public static T GetInterfaceProxy()
{
Type typeOfT = typeof(T);
if (TypeCache.TryGetValue(typeOfT, out Type k))
{
return (T)Activator.CreateInstance(k);
}
var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
var interfaceType = typeof(IProxy);
var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.AddInterfaceImplementation(typeOfT);
typeBuilder.AddInterfaceImplementation(interfaceType);
//create our _isDirty field, which implements IProxy
var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder);
// Generate a field for each property, which implements the T
foreach (var property in typeof(T).GetProperties())
{
var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute);
CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
}
#if NETSTANDARD2_0
var generatedType = typeBuilder.CreateTypeInfo().AsType();
#else
var generatedType = typeBuilder.CreateType();
#endif
TypeCache.Add(typeOfT, generatedType);
return (T)Activator.CreateInstance(generatedType);
}
private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
{
var propType = typeof(bool);
var field = typeBuilder.DefineField("_" + nameof(IProxy.IsDirty), propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(nameof(IProxy.IsDirty),
System.Reflection.PropertyAttributes.None,
propType,
new[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName
| MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + nameof(IProxy.IsDirty),
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIl = currGetPropMthdBldr.GetILGenerator();
currGetIl.Emit(OpCodes.Ldarg_0);
currGetIl.Emit(OpCodes.Ldfld, field);
currGetIl.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + nameof(IProxy.IsDirty),
getSetAttr,
null,
new[] { propType });
var currSetIl = currSetPropMthdBldr.GetILGenerator();
currSetIl.Emit(OpCodes.Ldarg_0);
currSetIl.Emit(OpCodes.Ldarg_1);
currSetIl.Emit(OpCodes.Stfld, field);
currSetIl.Emit(OpCodes.Ret);
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(IProxy).GetMethod("get_" + nameof(IProxy.IsDirty));
var setMethod = typeof(IProxy).GetMethod("set_" + nameof(IProxy.IsDirty));
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
return currSetPropMthdBldr;
}
private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
{
//Define the field and the property
var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(propertyName,
System.Reflection.PropertyAttributes.None,
propType,
new[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public
| MethodAttributes.Virtual
| MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIl = currGetPropMthdBldr.GetILGenerator();
currGetIl.Emit(OpCodes.Ldarg_0);
currGetIl.Emit(OpCodes.Ldfld, field);
currGetIl.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
getSetAttr,
null,
new[] { propType });
//store value in private field and set the isdirty flag
var currSetIl = currSetPropMthdBldr.GetILGenerator();
currSetIl.Emit(OpCodes.Ldarg_0);
currSetIl.Emit(OpCodes.Ldarg_1);
currSetIl.Emit(OpCodes.Stfld, field);
currSetIl.Emit(OpCodes.Ldarg_0);
currSetIl.Emit(OpCodes.Ldc_I4_1);
currSetIl.Emit(OpCodes.Call, setIsDirtyMethod);
currSetIl.Emit(OpCodes.Ret);
//TODO: Should copy all attributes defined by the interface?
if (isIdentity)
{
var keyAttribute = typeof(KeyAttribute);
var myConstructorInfo = keyAttribute.GetConstructor(Type.EmptyTypes);
var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, Array.Empty