diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..ab8863289
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: [mgravell, dapperlib]
+custom: ["https://www.buymeacoffee.com/marcgravell"]
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..19273c5e6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,32 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug, needs-triage
+assignees: ''
+
+---
+
+**Check your library version, and try updating**
+To help, we're going to need to know your library version. If it isn't the latest: *go do that* - it might
+fix the problem, and even if it doesn't: you're going to need to update if we find a problem and fix it,
+so you might as well get ready for that now.
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected and actual behavior**
+A clear and concise description of what you expected to happen, and what actually happens.
+
+**Additional context**
+Add any other context about the problem here:
+- what DB backend (and version) are you using, if relevant?
+- what ADO.NET provider (and version) are you using, if relevant?
+- what OS and .NET runtime (and version) are you using, if relevant?
diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml
new file mode 100644
index 000000000..5e3b83b36
--- /dev/null
+++ b/.github/workflows/cla.yml
@@ -0,0 +1,43 @@
+name: "CLA Assistant"
+on:
+ issue_comment:
+ types: [created]
+ pull_request_target:
+ types: [opened,closed,synchronize]
+
+# explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings
+permissions:
+ actions: write
+ contents: write
+ pull-requests: write
+ statuses: write
+
+jobs:
+ CLAAssistant:
+ runs-on: ubuntu-latest
+ steps:
+ - name: "CLA Assistant"
+ if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
+ uses: contributor-assistant/github-action@v2.3.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # the below token should have repo scope and must be manually added by you in the repository's secret
+ # This token is required only if you have configured to store the signatures in a remote repository/organization
+ PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ with:
+ path-to-signatures: 'signatures/version1/cla.json'
+ path-to-document: 'https://raw.githubusercontent.com/DapperLib/Dapper/main/NonCLA.md' # e.g. a CLA or a DCO document
+ # branch should not be protected
+ branch: 'main'
+ # allowlist: user1,bot*
+
+ # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken
+ #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
+ #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository)
+ #create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
+ #signed-commit-message: 'For example: $contributorName has signed the CLA in $owner/$repo#$pullRequestNo'
+ #custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign'
+ #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
+ #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
+ #lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
+ #use-dco-flag: true - If you are using DCO instead of CLA
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 000000000..3145873c1
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,57 @@
+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: Setup dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+ - name: .NET Build
+ run: dotnet build Build.csproj -c Release /p:CI=true
+ - name: Dapper Tests
+ run: dotnet test tests/Dapper.Tests/Dapper.Tests.csproj -c Release --logger GitHubActions -p:CI=true -p:TestTfmsInParallel=false
+ 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
diff --git a/.gitignore b/.gitignore
index 360431748..02815d7d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,7 @@ Test.DB.*
TestResults/
Dapper.Tests/*.sdf
Dapper.Tests/SqlServerTypes/
-.dotnet/*
\ No newline at end of file
+.dotnet/*
+BenchmarkDotNet.Artifacts/
+.idea/
+.DS_Store
\ No newline at end of file
diff --git a/Build.csproj b/Build.csproj
new file mode 100644
index 000000000..c2ed16c13
--- /dev/null
+++ b/Build.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj
deleted file mode 100644
index add00a547..000000000
--- a/Dapper.Contrib/Dapper.Contrib.csproj
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
- Dapper.Contrib
- orm;sql;micro-orm;dapper
- Dapper.Contrib
- The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities.
- Sam Saffron;Johan Danforth
- net451;netstandard1.3;netstandard2.0
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Dapper.Contrib/Readme.md b/Dapper.Contrib/Readme.md
deleted file mode 100644
index eeeb0838f..000000000
--- a/Dapper.Contrib/Readme.md
+++ /dev/null
@@ -1,172 +0,0 @@
-Dapper.Contrib - more extensions for dapper
-===========================================
-
-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 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" });
- ```
diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs
deleted file mode 100644
index 8d283f07a..000000000
--- a/Dapper.Contrib/SqlMapperExtensions.Async.cs
+++ /dev/null
@@ -1,533 +0,0 @@
-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 .NET 4.5 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 dynParms = new DynamicParameters();
- dynParms.Add("@id", id);
-
- if (!type.IsInterface())
- return (await connection.QueryAsync(sql, dynParms, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault();
-
- var res = (await connection.QueryAsync(sql, dynParms).ConfigureAwait(false)).FirstOrDefault() as IDictionary;
-
- if (res == null)
- return null;
-
- var obj = ProxyGenerator.GetInterfaceProxy();
-
- foreach (var property in TypePropertiesCache(type))
- {
- var val = res[property.Name];
- property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
- }
-
- ((IProxy)obj).IsDirty = false; //reset change tracking and return
-
- return obj;
- }
-
- ///
- /// Returns a list of entites 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).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];
- 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 .NET 4.5 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 = sqlAdapter ?? GetFormatter(connection);
-
- var isList = false;
- if (type.IsArray)
- {
- isList = true;
- type = type.GetElementType();
- }
- else if (type.IsGenericType() && type.GetTypeInfo().ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
- {
- 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();
-
- 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 .NET 4.5 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())
- {
- 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 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.AppendFormat(", ");
- }
- 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.AppendFormat(" 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 .NET 4.5 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())
- {
- 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);
- keyProperties.AddRange(explicitKeyProperties);
-
- var sb = new StringBuilder();
- sb.AppendFormat("DELETE FROM {0} WHERE ", name);
-
- for (var i = 0; i < keyProperties.Count; i++)
- {
- var property = keyProperties[i];
- sb.AppendFormat("{0} = @{1}", property.Name, property.Name);
- if (i < keyProperties.Count - 1)
- sb.AppendFormat(" 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 .NET 4.5 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 = multi.Read().FirstOrDefault();
- 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 assinging 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)multi.Read().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 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);
- }
-}
diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs
deleted file mode 100644
index 696dd6e9d..000000000
--- a/Dapper.Contrib/SqlMapperExtensions.cs
+++ /dev/null
@@ -1,1115 +0,0 @@
-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 Dapper;
-
-#if NETSTANDARD1_3
-using DataException = System.InvalidOperationException;
-#else
-using System.Threading;
-#endif
-
-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 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
- {
- ["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");
- 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 dynParms = new DynamicParameters();
- dynParms.Add("@id", id);
-
- T obj;
-
- if (type.IsInterface())
- {
- var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary;
-
- if (res == null)
- return null;
-
- obj = ProxyGenerator.GetInterfaceProxy();
-
- foreach (var property in TypePropertiesCache(type))
- {
- var val = res[property.Name];
- property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
- }
-
- ((IProxy)obj).IsDirty = false; //reset change tracking and return
- }
- else
- {
- obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault();
- }
- return obj;
- }
-
- ///
- /// Returns a list of entites 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];
- 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
- ///
- public static TableNameMapperDelegate TableNameMapper;
-
- 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 should be able to handle both our own Table-attribute as well as the one in EntityFramework
- var tableAttr = type
-#if NETSTANDARD1_3
- .GetTypeInfo()
-#endif
- .GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic;
- if (tableAttr != null)
- {
- name = tableAttr.Name;
- }
- 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() && type.GetTypeInfo().ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
- {
- 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())
- {
- 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.AppendFormat(", ");
- }
- 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.AppendFormat(" 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())
- {
- 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.AppendFormat(" 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.
- ///
- public static GetDatabaseTypeDelegate GetDatabaseType;
-
- private static ISqlAdapter GetFormatter(IDbConnection connection)
- {
- var name = GetDatabaseType?.Invoke(connection).ToLower()
- ?? connection.GetType().Name.ToLower();
-
- return !AdapterDictionary.ContainsKey(name)
- ? DefaultAdapter
- : AdapterDictionary[name];
- }
-
- private static class ProxyGenerator
- {
- private static readonly Dictionary TypeCache = new Dictionary();
-
- private static AssemblyBuilder GetAsmBuilder(string name)
- {
-#if NETSTANDARD1_3 || NETSTANDARD2_0
- 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 NETSTANDARD1_3 || 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(new Type[] { });
- var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new 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);
- }
- }
- }
-
- ///
- /// Defines the name of a table to use in Dapper.Contrib commands.
- ///
- [AttributeUsage(AttributeTargets.Class)]
- public class TableAttribute : Attribute
- {
- ///
- /// Creates a table mapping to a specific name for Dapper.Contrib commands
- ///
- /// The name of this table in the database.
- public TableAttribute(string tableName)
- {
- Name = tableName;
- }
-
- ///
- /// The name of the table in the database
- ///
- public string Name { get; set; }
- }
-
- ///
- /// Specifies that this field is a primary key in the database
- ///
- [AttributeUsage(AttributeTargets.Property)]
- public class KeyAttribute : Attribute
- {
- }
-
- ///
- /// Specifies that this field is a explicitly set primary key in the database
- ///
- [AttributeUsage(AttributeTargets.Property)]
- public class ExplicitKeyAttribute : Attribute
- {
- }
-
- ///
- /// Specifies whether a field is writable in the database.
- ///
- [AttributeUsage(AttributeTargets.Property)]
- public class WriteAttribute : Attribute
- {
- ///
- /// Specifies whether a field is writable in the database.
- ///
- /// Whether a field is writable in the database.
- public WriteAttribute(bool write)
- {
- Write = write;
- }
-
- ///
- /// Whether a field is writable in the database.
- ///
- public bool Write { get; }
- }
-
- ///
- /// Specifies that this is a computed column.
- ///
- [AttributeUsage(AttributeTargets.Property)]
- public class ComputedAttribute : Attribute
- {
- }
-}
-
-///
-/// The interface for all Dapper.Contrib database operations
-/// Implementing this is each provider's model.
-///
-public partial interface ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert);
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- void AppendColumnName(StringBuilder sb, string columnName);
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
-}
-
-///
-/// The SQL Server database adapter.
-///
-public partial class SqlServerAdapter : ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
- {
- var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id";
- var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
-
- var first = multi.Read().FirstOrDefault();
- if (first == null || first.id == null) return 0;
-
- var id = (int)first.id;
- var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
- if (propertyInfos.Length == 0) return id;
-
- var idProperty = propertyInfos[0];
- idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
-
- return id;
- }
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnName(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("[{0}]", columnName);
- }
-
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
- }
-}
-
-///
-/// The SQL Server Compact Edition database adapter.
-///
-public partial class SqlCeServerAdapter : ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
- {
- var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
- connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
- var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ToList();
-
- if (r[0].id == null) return 0;
- var id = (int)r[0].id;
-
- var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
- if (propertyInfos.Length == 0) return id;
-
- var idProperty = propertyInfos[0];
- idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
-
- return id;
- }
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnName(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("[{0}]", columnName);
- }
-
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
- }
-}
-
-///
-/// The MySQL database adapter.
-///
-public partial class MySqlAdapter : ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
- {
- var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
- connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
- var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout);
-
- var id = r.First().id;
- if (id == null) return 0;
- var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
- if (propertyInfos.Length == 0) return Convert.ToInt32(id);
-
- var idp = propertyInfos[0];
- idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
-
- return Convert.ToInt32(id);
- }
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnName(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("`{0}`", columnName);
- }
-
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("`{0}` = @{1}", columnName, columnName);
- }
-}
-
-///
-/// The Postgres database adapter.
-///
-public partial class PostgresAdapter : ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
- {
- var sb = new StringBuilder();
- sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
-
- // If no primary key then safe to assume a join table with not too much data to return
- var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
- if (propertyInfos.Length == 0)
- {
- sb.Append(" RETURNING *");
- }
- else
- {
- sb.Append(" RETURNING ");
- var first = true;
- foreach (var property in propertyInfos)
- {
- if (!first)
- sb.Append(", ");
- first = false;
- sb.Append(property.Name);
- }
- }
-
- var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList();
-
- // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys
- var id = 0;
- foreach (var p in propertyInfos)
- {
- var value = ((IDictionary)results[0])[p.Name.ToLower()];
- p.SetValue(entityToInsert, value, null);
- if (id == 0)
- id = Convert.ToInt32(value);
- }
- return id;
- }
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnName(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("\"{0}\"", columnName);
- }
-
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
- }
-}
-
-///
-/// The SQLite database adapter.
-///
-public partial class SQLiteAdapter : ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
- {
- var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id";
- var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
-
- var id = (int)multi.Read().First().id;
- var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
- if (propertyInfos.Length == 0) return id;
-
- var idProperty = propertyInfos[0];
- idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
-
- return id;
- }
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnName(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("\"{0}\"", columnName);
- }
-
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
- }
-}
-
-///
-/// The Firebase SQL adapeter.
-///
-public partial class FbAdapter : ISqlAdapter
-{
- ///
- /// Inserts into the database, returning the Id of the row created.
- ///
- /// The connection to use.
- /// The transaction to use.
- /// The command timeout to use.
- /// The table to insert into.
- /// The columns to set with this insert.
- /// The parameters to set for this insert.
- /// The key columns in this table.
- /// The entity to insert.
- /// The Id of the row created.
- public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert)
- {
- var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
- connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
-
- var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
- var keyName = propertyInfos[0].Name;
- var r = connection.Query($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout);
-
- var id = r.First().ID;
- if (id == null) return 0;
- if (propertyInfos.Length == 0) return Convert.ToInt32(id);
-
- var idp = propertyInfos[0];
- idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
-
- return Convert.ToInt32(id);
- }
-
- ///
- /// Adds the name of a column.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnName(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("{0}", columnName);
- }
-
- ///
- /// Adds a column equality to a parameter.
- ///
- /// The string builder to append to.
- /// The column name.
- public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
- {
- sb.AppendFormat("{0} = @{1}", columnName, columnName);
- }
-}
diff --git a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj
index fdc7382c0..f016f490a 100644
--- a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj
+++ b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj
@@ -4,25 +4,20 @@
Dapper: Entity Framework type handlers (with a strong name)
Extension handlers for entity framework
Marc Gravell;Nick Craver
- net451
+ net461
../Dapper.snk
true
true
Dapper.EntityFramework.StrongName
orm;sql;micro-orm
+ enable
-
-
-
-
-
-
-
-
+
+
diff --git a/Dapper.EntityFramework/Dapper.EntityFramework.csproj b/Dapper.EntityFramework/Dapper.EntityFramework.csproj
index 8d2976e78..0496d9c8f 100644
--- a/Dapper.EntityFramework/Dapper.EntityFramework.csproj
+++ b/Dapper.EntityFramework/Dapper.EntityFramework.csproj
@@ -5,18 +5,18 @@
Dapper entity framework type handlers
1.50.2
Marc Gravell;Nick Craver
- net451
+ net461
orm;sql;micro-orm
+ enable
-
-
-
-
-
-
-
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
\ No newline at end of file
diff --git a/Dapper.EntityFramework/DbGeographyHandler.cs b/Dapper.EntityFramework/DbGeographyHandler.cs
index c0355dc91..67893a386 100644
--- a/Dapper.EntityFramework/DbGeographyHandler.cs
+++ b/Dapper.EntityFramework/DbGeographyHandler.cs
@@ -27,10 +27,10 @@ public class DbGeographyHandler : SqlMapper.TypeHandler
///
/// The parameter to configure.
/// Parameter value.
- public override void SetValue(IDbDataParameter parameter, DbGeography value)
+ public override void SetValue(IDbDataParameter parameter, DbGeography? value)
{
- object parsed = null;
- if (value != null)
+ object? parsed = null;
+ if (value is not null)
{
parsed = SqlGeography.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId);
}
@@ -46,12 +46,12 @@ public override void SetValue(IDbDataParameter parameter, DbGeography value)
///
/// The value from the database.
/// The typed value.
- public override DbGeography Parse(object value)
+ public override DbGeography? Parse(object? value)
{
- if (value == null || value is DBNull) return null;
+ if (value is null || value is DBNull) return null;
if (value is SqlGeography geo)
{
- return DbGeography.FromBinary(geo.STAsBinary().Value);
+ return DbGeography.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value);
}
return DbGeography.FromText(value.ToString());
}
diff --git a/Dapper.EntityFramework/DbGeometryHandler.cs b/Dapper.EntityFramework/DbGeometryHandler.cs
index bd8582529..835ecfacb 100644
--- a/Dapper.EntityFramework/DbGeometryHandler.cs
+++ b/Dapper.EntityFramework/DbGeometryHandler.cs
@@ -27,17 +27,17 @@ public class DbGeometryHandler : SqlMapper.TypeHandler
///
/// The parameter to configure.
/// Parameter value.
- public override void SetValue(IDbDataParameter parameter, DbGeometry value)
+ public override void SetValue(IDbDataParameter parameter, DbGeometry? value)
{
- object parsed = null;
- if (value != null)
+ object? parsed = null;
+ if (value is not null)
{
parsed = SqlGeometry.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId);
}
parameter.Value = parsed ?? DBNull.Value;
- if (parameter is SqlParameter)
+ if (parameter is SqlParameter sqlP)
{
- ((SqlParameter)parameter).UdtTypeName = "geometry";
+ sqlP.UdtTypeName = "geometry";
}
}
@@ -46,12 +46,12 @@ public override void SetValue(IDbDataParameter parameter, DbGeometry value)
///
/// The value from the database.
/// The typed value.
- public override DbGeometry Parse(object value)
+ public override DbGeometry? Parse(object? value)
{
- if (value == null || value is DBNull) return null;
+ if (value is null || value is DBNull) return null;
if (value is SqlGeometry geo)
{
- return DbGeometry.FromBinary(geo.STAsBinary().Value);
+ return DbGeometry.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value);
}
return DbGeometry.FromText(value.ToString());
}
diff --git a/Dapper.EntityFramework/PublicAPI.Shipped.txt b/Dapper.EntityFramework/PublicAPI.Shipped.txt
new file mode 100644
index 000000000..1f97829c4
--- /dev/null
+++ b/Dapper.EntityFramework/PublicAPI.Shipped.txt
@@ -0,0 +1,13 @@
+#nullable enable
+Dapper.EntityFramework.DbGeographyHandler
+Dapper.EntityFramework.DbGeographyHandler.DbGeographyHandler() -> void
+Dapper.EntityFramework.DbGeometryHandler
+Dapper.EntityFramework.DbGeometryHandler.DbGeometryHandler() -> void
+Dapper.EntityFramework.Handlers
+override Dapper.EntityFramework.DbGeographyHandler.Parse(object? value) -> System.Data.Entity.Spatial.DbGeography?
+override Dapper.EntityFramework.DbGeographyHandler.SetValue(System.Data.IDbDataParameter! parameter, System.Data.Entity.Spatial.DbGeography? value) -> void
+override Dapper.EntityFramework.DbGeometryHandler.Parse(object? value) -> System.Data.Entity.Spatial.DbGeometry?
+override Dapper.EntityFramework.DbGeometryHandler.SetValue(System.Data.IDbDataParameter! parameter, System.Data.Entity.Spatial.DbGeometry? value) -> void
+static Dapper.EntityFramework.Handlers.Register() -> void
+static readonly Dapper.EntityFramework.DbGeographyHandler.Default -> Dapper.EntityFramework.DbGeographyHandler!
+static readonly Dapper.EntityFramework.DbGeometryHandler.Default -> Dapper.EntityFramework.DbGeometryHandler!
\ No newline at end of file
diff --git a/Dapper.EntityFramework/PublicAPI.Unshipped.txt b/Dapper.EntityFramework/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000..91b0e1a43
--- /dev/null
+++ b/Dapper.EntityFramework/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
\ No newline at end of file
diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs
new file mode 100644
index 000000000..71f1b92c9
--- /dev/null
+++ b/Dapper.ProviderTools/BulkCopy.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Concurrent;
+using System.Data;
+using System.Data.Common;
+using System.Linq.Expressions;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Dapper.ProviderTools.Internal;
+
+namespace Dapper.ProviderTools
+{
+ ///
+ /// Provides provider-agnostic access to bulk-copy services
+ ///
+ public abstract class BulkCopy : IDisposable
+ {
+ ///
+ /// Attempt to create a BulkCopy instance for the connection provided
+ ///
+ public static BulkCopy? TryCreate(DbConnection connection)
+ {
+ if (connection is null) return null;
+ var type = connection.GetType();
+ if (!s_bcpFactory.TryGetValue(type, out var func))
+ {
+ s_bcpFactory[type] = func = CreateBcpFactory(type);
+ }
+ var obj = func?.Invoke(connection);
+ return DynamicBulkCopy.Create(obj);
+ }
+
+ ///
+ /// Create a BulkCopy instance for the connection provided
+ ///
+ public static BulkCopy Create(DbConnection connection)
+ {
+ var bcp = TryCreate(connection);
+ if (bcp is null)
+ {
+ if (connection is null) throw new ArgumentNullException(nameof(connection));
+ throw new NotSupportedException("Unable to create BulkCopy for " + connection.GetType().FullName);
+ }
+ return bcp;
+ }
+
+ /////
+ ///// Provide an external registration for a given connection type
+ /////
+ //public static void Register(Type type, Func factory)
+ //{
+ // throw new NotImplementedException();
+ //}
+
+ private static readonly ConcurrentDictionary?> s_bcpFactory
+ = new ConcurrentDictionary?>();
+
+ internal static Func? CreateBcpFactory(Type connectionType)
+ {
+ try
+ {
+ var match = Regex.Match(connectionType.Name, "^(.+)Connection$");
+ if (match.Success)
+ {
+ var prefix = match.Groups[1].Value;
+ var bcpType = connectionType.Assembly.GetType($"{connectionType.Namespace}.{prefix}BulkCopy");
+ if (bcpType is not null)
+ {
+ var ctor = bcpType.GetConstructor(new[] { connectionType });
+ if (ctor is null) return null;
+
+ var p = Expression.Parameter(typeof(DbConnection), "conn");
+ var body = Expression.New(ctor, Expression.Convert(p, connectionType));
+ return Expression.Lambda>(body, p).Compile();
+ }
+ }
+ }
+ catch { }
+ return null;
+ }
+
+ ///
+ /// Name of the destination table on the server.
+ ///
+ public abstract string DestinationTableName { get; set; }
+ ///
+ /// Write a set of data to the server
+ ///
+ public abstract void WriteToServer(DataTable source);
+ ///
+ /// Write a set of data to the server
+ ///
+ public abstract void WriteToServer(DataRow[] source);
+ ///
+ /// Write a set of data to the server
+ ///
+ public abstract void WriteToServer(IDataReader source);
+ ///
+ /// Write a set of data to the server
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")]
+ public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken = default);
+ ///
+ /// Write a set of data to the server
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")]
+ public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken = default);
+ ///
+ /// Write a set of data to the server
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")]
+ public abstract Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken = default);
+ ///
+ /// Add a mapping between two columns by name
+ ///
+ public abstract void AddColumnMapping(string sourceColumn, string destinationColumn);
+ ///
+ /// Add a mapping between two columns by position
+ ///
+ public abstract void AddColumnMapping(int sourceColumn, int destinationColumn);
+ ///
+ /// The underlying untyped object providing the bulk-copy service
+ ///
+ public abstract object Wrapped { get; }
+
+ ///
+ /// Enables or disables streaming from a data-reader
+ ///
+ public bool EnableStreaming { get; set; }
+ ///
+ /// Number of rows in each batch
+ ///
+ public int BatchSize { get; set; }
+ ///
+ /// Number of seconds for the operation to complete before it times out.
+ ///
+ public int BulkCopyTimeout { get; set; }
+
+ ///
+ /// Release any resources associated with this instance
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Release any resources associated with this instance
+ ///
+ protected abstract void Dispose(bool disposing);
+ }
+}
diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj
new file mode 100644
index 000000000..176887317
--- /dev/null
+++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj
@@ -0,0 +1,24 @@
+
+
+ Dapper.ProviderTools
+ orm;sql;micro-orm
+ Dapper Provider Tools
+ Provider-agnostic ADO.NET helper utilities
+ Marc Gravell
+ net461;netstandard2.0;net8.0
+ true
+ enable
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
diff --git a/Dapper.ProviderTools/DbConnectionExtensions.cs b/Dapper.ProviderTools/DbConnectionExtensions.cs
new file mode 100644
index 000000000..5960fdef5
--- /dev/null
+++ b/Dapper.ProviderTools/DbConnectionExtensions.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Concurrent;
+using System.Data.Common;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Dapper.ProviderTools
+{
+ ///
+ /// Helper utilities for working with database connections
+ ///
+ public static class DbConnectionExtensions
+ {
+ ///
+ /// Attempt to get the client connection id for a given connection
+ ///
+ public static bool TryGetClientConnectionId(this DbConnection connection, out Guid clientConnectionId)
+ {
+ clientConnectionId = default;
+ return connection is not null && ByTypeHelpers.Get(connection.GetType()).TryGetClientConnectionId(
+ connection, out clientConnectionId);
+ }
+
+ ///
+ /// Clear all pools associated with the provided connection type
+ ///
+ public static bool TryClearAllPools(this DbConnection connection)
+ => connection is not null && ByTypeHelpers.Get(connection.GetType()).TryClearAllPools();
+
+ ///
+ /// Clear the pools associated with the provided connection
+ ///
+ public static bool TryClearPool(this DbConnection connection)
+ => connection is not null && ByTypeHelpers.Get(connection.GetType()).TryClearPool(connection);
+
+ private sealed class ByTypeHelpers
+ {
+ private static readonly ConcurrentDictionary s_byType
+ = new ConcurrentDictionary();
+ private readonly Func? _getClientConnectionId;
+
+ private readonly Action? _clearPool;
+ private readonly Action? _clearAllPools;
+
+ public bool TryGetClientConnectionId(DbConnection connection, out Guid clientConnectionId)
+ {
+ if (_getClientConnectionId is null)
+ {
+ clientConnectionId = default;
+ return false;
+ }
+ clientConnectionId = _getClientConnectionId(connection);
+ return true;
+ }
+
+ public bool TryClearPool(DbConnection connection)
+ {
+ if (_clearPool is null) return false;
+ _clearPool(connection);
+ return true;
+ }
+
+ public bool TryClearAllPools()
+ {
+ if (_clearAllPools is null) return false;
+ _clearAllPools();
+ return true;
+ }
+
+ public static ByTypeHelpers Get(Type type)
+ {
+ if (!s_byType.TryGetValue(type, out var value))
+ {
+ s_byType[type] = value = new ByTypeHelpers(type);
+ }
+ return value;
+ }
+
+ private ByTypeHelpers(Type type)
+ {
+ _getClientConnectionId = TryGetInstanceProperty("ClientConnectionId", type);
+
+ try
+ {
+ var clearAllPools = type.GetMethod("ClearAllPools", BindingFlags.Public | BindingFlags.Static,
+ null, Type.EmptyTypes, null);
+ if (clearAllPools is not null)
+ {
+ _clearAllPools = (Action)Delegate.CreateDelegate(typeof(Action), clearAllPools);
+ }
+ }
+ catch { }
+
+ try
+ {
+ var clearPool = type.GetMethod("ClearPool", BindingFlags.Public | BindingFlags.Static,
+ null, new[] { type }, null);
+ if (clearPool is not null)
+ {
+ var p = Expression.Parameter(typeof(DbConnection), "connection");
+ var body = Expression.Call(clearPool, Expression.Convert(p, type));
+ var lambda = Expression.Lambda>(body, p);
+ _clearPool = lambda.Compile();
+ }
+ }
+ catch { }
+ }
+
+ private static Func? TryGetInstanceProperty(string name, Type type)
+ {
+ try
+ {
+ var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
+ if (prop is null || !prop.CanRead) return null;
+ if (prop.PropertyType != typeof(T)) return null;
+
+ var p = Expression.Parameter(typeof(DbConnection), "connection");
+ var body = Expression.Property(Expression.Convert(p, type), prop);
+ var lambda = Expression.Lambda>(body, p);
+ return lambda.Compile();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+ }
+}
diff --git a/Dapper.ProviderTools/DbExceptionExtensions.cs b/Dapper.ProviderTools/DbExceptionExtensions.cs
new file mode 100644
index 000000000..d1b6d8e2b
--- /dev/null
+++ b/Dapper.ProviderTools/DbExceptionExtensions.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Concurrent;
+using System.Data.Common;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Dapper.ProviderTools
+{
+ ///
+ /// Helper utilities for working with database exceptions
+ ///
+ public static class DbExceptionExtensions
+ {
+ ///
+ /// Indicates whether the provided exception has an integer Number property with the supplied value
+ ///
+ public static bool IsNumber(this DbException exception, int number)
+ => exception is not null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number);
+
+
+ private sealed class ByTypeHelpers
+ {
+ private static readonly ConcurrentDictionary s_byType
+ = new ConcurrentDictionary();
+ private readonly Func? _getNumber;
+
+ public bool IsNumber(DbException exception, int number)
+ => _getNumber is not null && _getNumber(exception) == number;
+
+ public static ByTypeHelpers Get(Type type)
+ {
+ if (!s_byType.TryGetValue(type, out var value))
+ {
+ s_byType[type] = value = new ByTypeHelpers(type);
+ }
+ return value;
+ }
+
+ private ByTypeHelpers(Type type)
+ {
+ _getNumber = TryGetInstanceProperty("Number", type);
+ }
+
+ private static Func? TryGetInstanceProperty(string name, Type type)
+ {
+ try
+ {
+ var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
+ if (prop is null || !prop.CanRead) return null;
+ if (prop.PropertyType != typeof(T)) return null;
+
+ var p = Expression.Parameter(typeof(DbException), "exception");
+ var body = Expression.Property(Expression.Convert(p, type), prop);
+ var lambda = Expression.Lambda>(body, p);
+ return lambda.Compile();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+ }
+}
diff --git a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs
new file mode 100644
index 000000000..d8fd7b5d8
--- /dev/null
+++ b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Data;
+using System.Data.Common;
+using System.Threading;
+using System.Threading.Tasks;
+#nullable enable
+namespace Dapper.ProviderTools.Internal
+{
+ internal sealed class DynamicBulkCopy : BulkCopy
+ {
+ internal static BulkCopy? Create(object? wrapped)
+ => wrapped is null ? null : new DynamicBulkCopy(wrapped);
+
+ private DynamicBulkCopy(object wrapped)
+ => _wrapped = wrapped;
+
+ private readonly dynamic _wrapped;
+
+ public override string DestinationTableName
+ {
+ get => _wrapped.DestinationTableName;
+ set => _wrapped.DestinationTableName = value;
+ }
+
+ public override object Wrapped => _wrapped;
+
+ public override void AddColumnMapping(string sourceColumn, string destinationColumn)
+ => _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn);
+
+ public override void AddColumnMapping(int sourceColumn, int destinationColumn)
+ => _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn);
+
+ public override void WriteToServer(DataTable source)
+ => _wrapped.WriteToServer(source);
+ public override void WriteToServer(DataRow[] source)
+ => _wrapped.WriteToServer(source);
+
+ public override void WriteToServer(IDataReader source)
+ => _wrapped.WriteToServer(source);
+
+ public override Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken)
+ => _wrapped.WriteToServer(source, cancellationToken);
+
+ public override Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken)
+ => _wrapped.WriteToServer(source, cancellationToken);
+ public override Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken)
+ => _wrapped.WriteToServer(source, cancellationToken);
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_wrapped is IDisposable d)
+ {
+ try { d.Dispose(); } catch { }
+ }
+ }
+ }
+ }
+}
diff --git a/Dapper.ProviderTools/PublicAPI.Shipped.txt b/Dapper.ProviderTools/PublicAPI.Shipped.txt
new file mode 100644
index 000000000..a8329b713
--- /dev/null
+++ b/Dapper.ProviderTools/PublicAPI.Shipped.txt
@@ -0,0 +1,30 @@
+#nullable enable
+abstract Dapper.ProviderTools.BulkCopy.AddColumnMapping(int sourceColumn, int destinationColumn) -> void
+abstract Dapper.ProviderTools.BulkCopy.AddColumnMapping(string! sourceColumn, string! destinationColumn) -> void
+abstract Dapper.ProviderTools.BulkCopy.DestinationTableName.get -> string!
+abstract Dapper.ProviderTools.BulkCopy.DestinationTableName.set -> void
+abstract Dapper.ProviderTools.BulkCopy.Dispose(bool disposing) -> void
+abstract Dapper.ProviderTools.BulkCopy.Wrapped.get -> object!
+abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.DataRow![]! source) -> void
+abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.DataTable! source) -> void
+abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.IDataReader! source) -> void
+abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.Common.DbDataReader! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.DataRow![]! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.DataTable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+Dapper.ProviderTools.BulkCopy
+Dapper.ProviderTools.BulkCopy.BatchSize.get -> int
+Dapper.ProviderTools.BulkCopy.BatchSize.set -> void
+Dapper.ProviderTools.BulkCopy.BulkCopy() -> void
+Dapper.ProviderTools.BulkCopy.BulkCopyTimeout.get -> int
+Dapper.ProviderTools.BulkCopy.BulkCopyTimeout.set -> void
+Dapper.ProviderTools.BulkCopy.Dispose() -> void
+Dapper.ProviderTools.BulkCopy.EnableStreaming.get -> bool
+Dapper.ProviderTools.BulkCopy.EnableStreaming.set -> void
+Dapper.ProviderTools.DbConnectionExtensions
+Dapper.ProviderTools.DbExceptionExtensions
+static Dapper.ProviderTools.BulkCopy.Create(System.Data.Common.DbConnection! connection) -> Dapper.ProviderTools.BulkCopy!
+static Dapper.ProviderTools.BulkCopy.TryCreate(System.Data.Common.DbConnection! connection) -> Dapper.ProviderTools.BulkCopy?
+static Dapper.ProviderTools.DbConnectionExtensions.TryClearAllPools(this System.Data.Common.DbConnection! connection) -> bool
+static Dapper.ProviderTools.DbConnectionExtensions.TryClearPool(this System.Data.Common.DbConnection! connection) -> bool
+static Dapper.ProviderTools.DbConnectionExtensions.TryGetClientConnectionId(this System.Data.Common.DbConnection! connection, out System.Guid clientConnectionId) -> bool
+static Dapper.ProviderTools.DbExceptionExtensions.IsNumber(this System.Data.Common.DbException! exception, int number) -> bool
\ No newline at end of file
diff --git a/Dapper.ProviderTools/PublicAPI.Unshipped.txt b/Dapper.ProviderTools/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000..91b0e1a43
--- /dev/null
+++ b/Dapper.ProviderTools/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
\ No newline at end of file
diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj
index 5712fff9a..7d5f3ff5a 100644
--- a/Dapper.Rainbow/Dapper.Rainbow.csproj
+++ b/Dapper.Rainbow/Dapper.Rainbow.csproj
@@ -6,24 +6,17 @@
Trivial micro-orm implemented on Dapper, provides with CRUD helpers.
Sam Saffron
2017 Sam Saffron
- net451;netstandard1.3
+ net461;netstandard2.0;net5.0
false
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/Dapper.Rainbow/Database.Async.cs b/Dapper.Rainbow/Database.Async.cs
index d247e9e18..e1a78c47b 100644
--- a/Dapper.Rainbow/Database.Async.cs
+++ b/Dapper.Rainbow/Database.Async.cs
@@ -34,7 +34,7 @@ public partial class Table
///
/// The Id of the record to update.
/// The new record.
- /// The number of affeced rows.
+ /// The number of affected rows.
public Task UpdateAsync(TId id, dynamic data)
{
List paramNames = GetParamNames((object)data);
@@ -88,7 +88,7 @@ public Task> AllAsync() =>
/// The parameters to use.
/// The number of rows affected.
public Task ExecuteAsync(string sql, dynamic param = null) =>
- _connection.ExecuteAsync(sql, param as object, _transaction, _commandTimeout);
+ _connection.ExecuteAsync(sql, param as object, Transaction, _commandTimeout);
///
/// Asynchronously queries the current database.
@@ -98,7 +98,7 @@ public Task ExecuteAsync(string sql, dynamic param = null) =>
/// The parameters to use.
/// An enumerable of for the rows fetched.
public Task> QueryAsync(string sql, dynamic param = null) =>
- _connection.QueryAsync(sql, param as object, _transaction, _commandTimeout);
+ _connection.QueryAsync(sql, param as object, Transaction, _commandTimeout);
///
/// Asynchronously queries the current database for a single record.
@@ -108,10 +108,10 @@ public Task> QueryAsync(string sql, dynamic param = null) =>
/// The parameters to use.
/// An enumerable of for the rows fetched.
public Task QueryFirstOrDefaultAsync(string sql, dynamic param = null) =>
- _connection.QueryFirstOrDefaultAsync(sql, param as object, _transaction, _commandTimeout);
+ _connection.QueryFirstOrDefaultAsync(sql, param as object, Transaction, _commandTimeout);
///
- /// Perform a asynchronous multi-mapping query with 2 input types.
+ /// Perform an asynchronous multi-mapping query with 2 input types.
/// This returns a single type, combined from the raw types via .
///
/// The first type in the recordset.
@@ -129,7 +129,7 @@ public Task> QueryAsync(string sq
_connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout);
///
- /// Perform a asynchronous multi-mapping query with 3 input types.
+ /// Perform an asynchronous multi-mapping query with 3 input types.
/// This returns a single type, combined from the raw types via .
///
/// The first type in the recordset.
@@ -148,7 +148,7 @@ public Task> QueryAsync(s
_connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout);
///
- /// Perform a asynchronous multi-mapping query with 4 input types.
+ /// Perform an asynchronous multi-mapping query with 4 input types.
/// This returns a single type, combined from the raw types via .
///
/// The first type in the recordset.
@@ -168,7 +168,7 @@ public Task> QueryAsync
- /// Perform a asynchronous multi-mapping query with 5 input types.
+ /// Perform an asynchronous multi-mapping query with 5 input types.
/// This returns a single type, combined from the raw types via .
///
/// The first type in the recordset.
@@ -189,13 +189,13 @@ public Task> QueryAsync
- /// Execute a query asynchronously using .NET 4.5 Task.
+ /// Execute a query asynchronously using Task.
///
/// The SQL to execute.
/// The parameters to use.
/// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object>
public Task> QueryAsync(string sql, dynamic param = null) =>
- _connection.QueryAsync(sql, param as object, _transaction);
+ _connection.QueryAsync(sql, param as object, Transaction);
///
/// Execute a command that returns multiple result sets, and access each in turn.
diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs
index 7bd0f91c4..74ab9ba2f 100644
--- a/Dapper.Rainbow/Database.cs
+++ b/Dapper.Rainbow/Database.cs
@@ -45,7 +45,7 @@ public string TableName
{
get
{
- tableName = tableName ?? database.DetermineTableName(likelyTableName);
+ tableName ??= database.DetermineTableName(likelyTableName);
return tableName;
}
}
@@ -142,7 +142,9 @@ internal static List GetParamNames(object o)
foreach (var prop in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetGetMethod(false) != null))
{
var attribs = prop.GetCustomAttributes(typeof(IgnorePropertyAttribute), true);
+#pragma warning disable IDE0019 // Use pattern matching - complex enough here
var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute;
+#pragma warning restore IDE0019 // Use pattern matching
if (attr == null || (!attr.Value))
{
paramNames.Add(prop.Name);
@@ -173,7 +175,16 @@ public Table(Database database, string likelyTableName)
private DbConnection _connection;
private int _commandTimeout;
- private DbTransaction _transaction;
+
+ ///
+ /// Get access to the underlying transaction
+ ///
+ public DbTransaction Transaction { get; private set; }
+
+ ///
+ /// Get underlying database connection.
+ ///
+ public DbConnection Connection => _connection;
///
/// Initializes the database.
@@ -194,7 +205,7 @@ internal void InitDatabase(DbConnection connection, int commandTimeout)
{
_connection = connection;
_commandTimeout = commandTimeout;
- tableConstructor = tableConstructor ?? CreateTableConstructorForTable();
+ tableConstructor ??= CreateTableConstructorForTable();
tableConstructor(this as TDatabase);
}
@@ -208,9 +219,11 @@ internal virtual Action CreateTableConstructorForTable()
/// Begins a transaction in this database.
///
/// The isolation level to use.
- public void BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted)
+ /// The transaction created
+ public DbTransaction BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted)
{
- _transaction = _connection.BeginTransaction(isolation);
+ Transaction = _connection.BeginTransaction(isolation);
+ return Transaction;
}
///
@@ -218,8 +231,8 @@ public void BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommi
///
public void CommitTransaction()
{
- _transaction.Commit();
- _transaction = null;
+ Transaction.Commit();
+ Transaction = null;
}
///
@@ -227,8 +240,8 @@ public void CommitTransaction()
///
public void RollbackTransaction()
{
- _transaction.Rollback();
- _transaction = null;
+ Transaction.Rollback();
+ Transaction = null;
}
///
@@ -252,7 +265,7 @@ protected Action CreateTableConstructor(params Type[] tableTypes)
var il = dm.GetILGenerator();
var setters = GetType().GetProperties()
- .Where(p => p.PropertyType.IsGenericType() && tableTypes.Contains(p.PropertyType.GetGenericTypeDefinition()))
+ .Where(p => p.PropertyType.IsGenericType && tableTypes.Contains(p.PropertyType.GetGenericTypeDefinition()))
.Select(p => Tuple.Create(
p.GetSetMethod(true),
p.PropertyType.GetConstructor(new[] { typeof(TDatabase), typeof(string) }),
@@ -315,7 +328,7 @@ private bool TableExists(string name)
name = name.Replace("[", "");
name = name.Replace("]", "");
- if (name.Contains("."))
+ if (name.IndexOf('.') > 0)
{
var parts = name.Split('.');
if (parts.Length == 2)
@@ -329,7 +342,7 @@ private bool TableExists(string name)
if (!string.IsNullOrEmpty(schemaName)) builder.Append("TABLE_SCHEMA = @schemaName AND ");
builder.Append("TABLE_NAME = @name");
- return _connection.Query(builder.ToString(), new { schemaName, name }, _transaction).Count() == 1;
+ return _connection.Query(builder.ToString(), new { schemaName, name }, Transaction).Count() == 1;
}
///
@@ -339,7 +352,7 @@ private bool TableExists(string name)
/// The parameters to use.
/// The number of rows affected.
public int Execute(string sql, dynamic param = null) =>
- _connection.Execute(sql, param as object, _transaction, _commandTimeout);
+ _connection.Execute(sql, param as object, Transaction, _commandTimeout);
///
/// Queries the current database.
@@ -350,7 +363,7 @@ public int Execute(string sql, dynamic param = null) =>
/// Whether to buffer the results.
/// An enumerable of for the rows fetched.
public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) =>
- _connection.Query(sql, param as object, _transaction, buffered, _commandTimeout);
+ _connection.Query(sql, param as object, Transaction, buffered, _commandTimeout);
///
/// Queries the current database for a single record.
@@ -360,7 +373,7 @@ public IEnumerable Query(string sql, dynamic param = null, bool buffered =
/// The parameters to use.
/// An enumerable of for the rows fetched.
public T QueryFirstOrDefault(string sql, dynamic param = null) =>
- _connection.QueryFirstOrDefault(sql, param as object, _transaction, _commandTimeout);
+ _connection.QueryFirstOrDefault(sql, param as object, Transaction, _commandTimeout);
///
/// Perform a multi-mapping query with 2 input types.
@@ -448,7 +461,7 @@ public IEnumerable QueryWhether the results should be buffered in memory.
/// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object>
public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) =>
- _connection.Query(sql, param as object, _transaction, buffered);
+ _connection.Query(sql, param as object, Transaction, buffered);
///
/// Execute a command that returns multiple result sets, and access each in turn.
@@ -464,15 +477,16 @@ public SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbT
///
/// Disposes the current database, rolling back current transactions.
///
- public void Dispose()
+ public virtual void Dispose()
{
- if (_connection.State != ConnectionState.Closed)
+ var connection = _connection;
+ if (connection.State != ConnectionState.Closed)
{
- _transaction?.Rollback();
-
- _connection.Close();
_connection = null;
+ Transaction = null;
+ connection?.Close();
}
+ GC.SuppressFinalize(this);
}
}
}
diff --git a/Dapper.Rainbow/Snapshotter.cs b/Dapper.Rainbow/Snapshotter.cs
index ef6f8b0d5..c4e44a2e9 100644
--- a/Dapper.Rainbow/Snapshotter.cs
+++ b/Dapper.Rainbow/Snapshotter.cs
@@ -12,7 +12,7 @@ namespace Dapper
public static class Snapshotter
{
///
- /// Starts the snapshot of an objec by making a copy of the current state.
+ /// Starts the snapshot of an object by making a copy of its current state.
///
/// The type of object to snapshot.
/// The object to snapshot.
@@ -69,14 +69,14 @@ public DynamicParameters Diff()
private static T Clone(T myObject)
{
- cloner = cloner ?? GenerateCloner();
+ cloner ??= GenerateCloner();
return cloner(myObject);
}
private static DynamicParameters Diff(T original, T current)
{
var dm = new DynamicParameters();
- differ = differ ?? GenerateDiffer();
+ differ ??= GenerateDiffer();
foreach (var pair in differ(original, current))
{
dm.Add(pair.Name, pair.NewValue);
@@ -91,15 +91,15 @@ private static List RelevantProperties()
p.GetSetMethod(true) != null
&& p.GetGetMethod(true) != null
&& (p.PropertyType == typeof(string)
- || p.PropertyType.IsValueType()
- || (p.PropertyType.IsGenericType() && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
+ || p.PropertyType.IsValueType
+ || (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
).ToList();
}
private static bool AreEqual(U first, U second)
{
- if (EqualityComparer.Default.Equals(first, default(U)) && EqualityComparer.Default.Equals(second, default(U))) return true;
- if (EqualityComparer.Default.Equals(first, default(U))) return false;
+ if (EqualityComparer.Default.Equals(first, default) && EqualityComparer.Default.Equals(second, default)) return true;
+ if (EqualityComparer.Default.Equals(first, default)) return false;
return first.Equals(second);
}
@@ -109,13 +109,13 @@ private static Func> GenerateDiffer()
var il = dm.GetILGenerator();
// change list
- il.DeclareLocal(typeof(List));
- il.DeclareLocal(typeof(Change));
- il.DeclareLocal(typeof(object)); // boxed change
+ var list = il.DeclareLocal(typeof(List));
+ var change = il.DeclareLocal(typeof(Change));
+ var boxed = il.DeclareLocal(typeof(object)); // boxed change
il.Emit(OpCodes.Newobj, typeof(List).GetConstructor(Type.EmptyTypes));
// [list]
- il.Emit(OpCodes.Stloc_0);
+ il.Emit(OpCodes.Stloc, list);
foreach (var prop in RelevantProperties())
{
@@ -138,7 +138,7 @@ private static Func> GenerateDiffer()
// [original prop val, current prop val, current prop val boxed]
}
- il.Emit(OpCodes.Stloc_2);
+ il.Emit(OpCodes.Stloc, boxed);
// [original prop val, current prop val]
il.EmitCall(OpCodes.Call, typeof(Snapshot).GetMethod(nameof(AreEqual), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(new Type[] { prop.PropertyType }), null);
@@ -153,7 +153,7 @@ private static Func> GenerateDiffer()
il.Emit(OpCodes.Dup);
// [change,change]
- il.Emit(OpCodes.Stloc_1);
+ il.Emit(OpCodes.Stloc, change);
// [change]
il.Emit(OpCodes.Ldstr, prop.Name);
@@ -161,18 +161,18 @@ private static Func> GenerateDiffer()
il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_Name"));
// []
- il.Emit(OpCodes.Ldloc_1);
+ il.Emit(OpCodes.Ldloc, change);
// [change]
- il.Emit(OpCodes.Ldloc_2);
+ il.Emit(OpCodes.Ldloc, boxed);
// [change, boxed]
il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_NewValue"));
// []
- il.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldloc, list);
// [change list]
- il.Emit(OpCodes.Ldloc_1);
+ il.Emit(OpCodes.Ldloc, change);
// [change list, change]
il.Emit(OpCodes.Callvirt, typeof(List).GetMethod("Add"));
// []
@@ -180,7 +180,7 @@ private static Func> GenerateDiffer()
il.MarkLabel(skip);
}
- il.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldloc, list);
// [change list]
il.Emit(OpCodes.Ret);
@@ -191,18 +191,18 @@ private static Func> GenerateDiffer()
private static Func GenerateCloner()
{
var dm = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
- var ctor = typeof(T).GetConstructor(new Type[] { });
+ var ctor = typeof(T).GetConstructor(Type.EmptyTypes);
var il = dm.GetILGenerator();
- il.DeclareLocal(typeof(T));
+ var typed = il.DeclareLocal(typeof(T));
il.Emit(OpCodes.Newobj, ctor);
- il.Emit(OpCodes.Stloc_0);
+ il.Emit(OpCodes.Stloc, typed);
foreach (var prop in RelevantProperties())
{
- il.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldloc, typed);
// [clone]
il.Emit(OpCodes.Ldarg_0);
// [clone, source]
@@ -213,7 +213,7 @@ private static Func GenerateCloner()
}
// Load new constructed obj on eval stack -> 1 item on stack
- il.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldloc, typed);
// Return constructed object. --> 0 items on stack
il.Emit(OpCodes.Ret);
diff --git a/Dapper.Rainbow/readme.md b/Dapper.Rainbow/readme.md
new file mode 100644
index 000000000..c1f34b67a
--- /dev/null
+++ b/Dapper.Rainbow/readme.md
@@ -0,0 +1,118 @@
+# Using Dapper.Rainbow in C# for CRUD Operations
+
+This guide outlines how to use `Dapper.Rainbow` in C# for CRUD operations.
+
+## 1. Setting Up
+
+Add Dapper and Dapper.Rainbow to your project via NuGet:
+
+```powershell
+Install-Package Dapper -Version x.x.x
+Install-Package Dapper.Rainbow -Version x.x.x
+```
+
+*Replace `x.x.x` with the latest version numbers.*
+
+## 2. Database Setup and Requirements
+
+For `Dapper.Rainbow` to function correctly, ensure each table has a primary key column named `Id`.
+
+Example `Users` table schema:
+
+```sql
+CREATE TABLE Users (
+ Id INT IDENTITY(1,1) PRIMARY KEY,
+ Name VARCHAR(100),
+ Email VARCHAR(100)
+);
+```
+
+## 3. Establishing Database Connection
+
+Open a connection to your database:
+
+```csharp
+using System.Data.SqlClient;
+
+var connectionString = "your_connection_string_here";
+using var connection = new SqlConnection(connectionString);
+connection.Open(); // Open the connection
+```
+
+## 4. Defining Your Database Context
+
+Define a class for your database context:
+
+```csharp
+using Dapper;
+using System.Data;
+
+public class MyDatabase : Database
+{
+ public Table Users { get; set; }
+}
+
+public class User
+{
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Email { get; set; }
+}
+```
+
+## 5. Performing CRUD Operations
+
+### Insert
+
+```csharp
+var db = new MyDatabase { Connection = connection };
+var newUser = new User { Name = "John Doe", Email = "john.doe@example.com" };
+var insertedUser = db.Users.Insert(newUser);
+```
+
+### Select
+
+Fetch users by ID or all users:
+
+```csharp
+var user = db.Users.Get(id); // Single user by ID
+var users = connection.Query("SELECT * FROM Users"); // All users
+```
+
+### Update
+
+```csharp
+var userToUpdate = db.Users.Get(id);
+userToUpdate.Email = "new.email@example.com";
+db.Users.Update(userToUpdate);
+```
+
+### Delete
+
+```csharp
+db.Users.Delete(id);
+```
+
+## 6. Working with Foreign Keys
+
+Example schema for a `Posts` table with a foreign key to `Users`:
+
+```sql
+CREATE TABLE Posts (
+ Id INT IDENTITY(1,1) PRIMARY KEY,
+ UserId INT,
+ Content VARCHAR(255),
+ FOREIGN KEY (UserId) REFERENCES Users(Id)
+);
+```
+
+Inserting a parent (`User`) and a child (`Post`) row:
+
+```csharp
+var newUser = new User { Name = "Jane Doe", Email = "jane.doe@example.com" };
+var userId = db.Users.Insert(newUser);
+
+var newPost = new Post { UserId = userId, Content = "Hello, World!" };
+db.Connection.Insert(newPost); // Using Dapper for the child table
+```
+
diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj
index 173c24d0c..a455a5941 100644
--- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj
+++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj
@@ -5,21 +5,24 @@
Dapper SqlBuilder component
The Dapper SqlBuilder component, for building SQL queries dynamically.
Sam Saffron, Johan Danforth
- net451;netstandard1.3
-
+ net461;netstandard2.0;net8.0
false
false
+ enable
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
-
-
-
-
-
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/Dapper.SqlBuilder/PublicAPI.Shipped.txt b/Dapper.SqlBuilder/PublicAPI.Shipped.txt
new file mode 100644
index 000000000..9baefbb74
--- /dev/null
+++ b/Dapper.SqlBuilder/PublicAPI.Shipped.txt
@@ -0,0 +1,22 @@
+#nullable enable
+Dapper.SqlBuilder
+Dapper.SqlBuilder.AddClause(string! name, string! sql, object? parameters, string! joiner, string! prefix = "", string! postfix = "", bool isInclusive = false) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.AddParameters(dynamic! parameters) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.AddTemplate(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder.Template!
+Dapper.SqlBuilder.GroupBy(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.Having(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.InnerJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.Intersect(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.Join(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.LeftJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.OrderBy(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.OrWhere(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.RightJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.Select(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.Set(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
+Dapper.SqlBuilder.SqlBuilder() -> void
+Dapper.SqlBuilder.Template
+Dapper.SqlBuilder.Template.Parameters.get -> object?
+Dapper.SqlBuilder.Template.RawSql.get -> string!
+Dapper.SqlBuilder.Template.Template(Dapper.SqlBuilder! builder, string! sql, dynamic? parameters) -> void
+Dapper.SqlBuilder.Where(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder!
\ No newline at end of file
diff --git a/Dapper.SqlBuilder/PublicAPI.Unshipped.txt b/Dapper.SqlBuilder/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000..91b0e1a43
--- /dev/null
+++ b/Dapper.SqlBuilder/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
\ No newline at end of file
diff --git a/Dapper.SqlBuilder/Readme.md b/Dapper.SqlBuilder/Readme.md
new file mode 100644
index 000000000..5a0968f9f
--- /dev/null
+++ b/Dapper.SqlBuilder/Readme.md
@@ -0,0 +1,128 @@
+Dapper.SqlBuilder - a simple sql formatter for .Net
+========================================
+[](https://ci.appveyor.com/project/StackExchange/dapper-SqlBuilder)
+
+Packages
+--------
+
+MyGet Pre-release feed: https://www.myget.org/gallery/dapper
+
+| Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet |
+| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| [Dapper.SqlBuilder](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [](https://www.myget.org/feed/dapper/package/nuget/Dapper.SqlBuilder) |
+
+Features
+--------
+
+Dapper.SqlBuilder contains a number of helper methods for generating sql.
+
+The list of extension methods in Dapper.SqlBuilder right now are:
+
+```csharp
+SqlBuilder AddParameters(dynamic parameters);
+SqlBuilder Select(string sql, dynamic parameters = null);
+SqlBuilder Where(string sql, dynamic parameters = null);
+SqlBuilder OrWhere(string sql, dynamic parameters = null);
+SqlBuilder OrderBy(string sql, dynamic parameters = null);
+SqlBuilder GroupBy(string sql, dynamic parameters = null);
+SqlBuilder Having(string sql, dynamic parameters = null);
+SqlBuilder Set(string sql, dynamic parameters = null);
+SqlBuilder Join(string sql, dynamic parameters = null);
+SqlBuilder InnerJoin(string sql, dynamic parameters = null);
+SqlBuilder LeftJoin(string sql, dynamic parameters = null);
+SqlBuilder RightJoin(string sql, dynamic parameters = null);
+SqlBuilder Intersect(string sql, dynamic parameters = null);
+```
+
+
+Template
+--------
+
+SqlBuilder allows you to generate N SQL templates from a composed query, it can easily format sql when you are attaching parameters and how, e.g:
+```csharp
+var builder = new SqlBuilder()
+ .Where("a = @a", new { a = 1 })
+ .Where("b = @b", new { b = 2 })
+ .OrderBy("a")
+ .OrderBy("b");
+var counter = builder.AddTemplate("select count(*) from table /**where**/");
+var selector = builder.AddTemplate("select * from table /**where**/ /**orderby**/");
+var count = cnn.Query(counter.RawSql, counter.Parameters).Single();
+var rows = cnn.Query(selector.RawSql, selector.Parameters);
+```
+
+it's same as
+```csharp
+var count = cnn.Query("select count(*) from table where a = @a and b = @b", new { a = 1, b = 1 });
+var rows = cnn.Query("select * from table where a = @a and b = @b order by a, b", new { a = 1, b = 1 });
+```
+
+Dynamic Filter Paging Example
+----------
+
+```csharp
+var builder = new SqlBuilder();
+var selectTemplate = builder.AddTemplate(@"select X.* from (
+ select us.*, ROW_NUMBER() OVER (/**orderby**/) AS RowNumber
+ from Users us
+ /**where**/
+ ) as X
+ where RowNumber between @start and @finish", new { start, finish });
+var countTemplate = builder.AddTemplate(@"select count(*) from Users /**where**/");
+
+if (userId.HasValue())
+ builder.Where($"t.userId = @{nameof(userId)}", new { userId });
+if (isCancel)
+ builder.Where($"t.isCancel = @{nameof(isCancel)}", new { isCancel });
+
+builder.OrderBy(string.Format("t.id {0}", orderDesc ? "desc" : "asc"));
+
+var users = conn.Query(selectTemplate.RawSql, selectTemplate.Parameters);
+var count = conn.ExecuteScalar(countTemplate.RawSql, countTemplate.Parameters);
+//..etc..
+```
+
+Limitations and caveats
+--------
+
+### Combining the Where and OrWhere methods
+
+The OrWhere method currently groups all `and` and `or` clauses by type,
+then join the groups with `and` or `or` depending on the first call.
+This may result in possibly unexpected outcomes.
+See also [issue 647](https://github.com/DapperLib/Dapper/issues/647).
+
+#### Example Where first
+
+When providing the following clauses
+```csharp
+sql.Where("a = @a1");
+sql.OrWhere("b = @b1");
+sql.Where("a = @a2");
+sql.OrWhere("b = @b2");
+```
+
+SqlBuilder will generate sql
+```sql
+a = @a1 AND a = @a2 AND ( b = @b1 OR b = @b2 )
+```
+
+and not say
+```sql
+a = @a1 OR b = @b1 AND a = @a2 OR b = @b2
+```
+
+#### Example OrWhere first
+
+When providing the following clauses
+```csharp
+sql.OrWhere("b = @b1");
+sql.Where("a = @a1");
+sql.OrWhere("b = @b2");
+sql.Where("a = @a2");
+```
+
+SqlBuilder will generate sql
+```sql
+a = @a1 OR a = @a2 OR ( b = @b1 OR b = @b2 )
+```
diff --git a/Dapper.SqlBuilder/SqlBuilder.cs b/Dapper.SqlBuilder/SqlBuilder.cs
index c83097f42..cb45d0582 100644
--- a/Dapper.SqlBuilder/SqlBuilder.cs
+++ b/Dapper.SqlBuilder/SqlBuilder.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
@@ -9,14 +10,20 @@ public class SqlBuilder
private readonly Dictionary _data = new Dictionary();
private int _seq;
- private class Clause
+ private sealed class Clause
{
- public string Sql { get; set; }
- public object Parameters { get; set; }
- public bool IsInclusive { get; set; }
+ public Clause(string sql, object? parameters, bool isInclusive)
+ {
+ Sql = sql;
+ Parameters = parameters;
+ IsInclusive = isInclusive;
+ }
+ public string Sql { get; }
+ public object? Parameters { get; }
+ public bool IsInclusive { get; }
}
- private class Clauses : List
+ private sealed class Clauses : List
{
private readonly string _joiner, _prefix, _postfix;
@@ -29,11 +36,9 @@ public Clauses(string joiner, string prefix = "", string postfix = "")
public string ResolveClauses(DynamicParameters p)
{
- foreach (var item in this)
- {
- p.AddDynamicParams(item.Parameters);
- }
- return this.Any(a => a.IsInclusive)
+ ForEach(item => p.AddDynamicParams(item.Parameters));
+
+ return Exists(a => a.IsInclusive)
? _prefix +
string.Join(_joiner,
this.Where(a => !a.IsInclusive)
@@ -52,10 +57,10 @@ public class Template
{
private readonly string _sql;
private readonly SqlBuilder _builder;
- private readonly object _initParams;
+ private readonly object? _initParams;
private int _dataSeq = -1; // Unresolved
- public Template(SqlBuilder builder, string sql, dynamic parameters)
+ public Template(SqlBuilder builder, string sql, dynamic? parameters)
{
_initParams = parameters;
_sql = sql;
@@ -85,69 +90,73 @@ private void ResolveSql()
}
}
- private string rawSql;
- private object parameters;
+ private string? rawSql;
+ private object? parameters;
public string RawSql
{
- get { ResolveSql(); return rawSql; }
+ get { ResolveSql(); return rawSql!; }
}
- public object Parameters
+ public object? Parameters
{
get { ResolveSql(); return parameters; }
}
}
- public Template AddTemplate(string sql, dynamic parameters = null) =>
- new Template(this, sql, parameters);
+ public Template AddTemplate(string sql, dynamic? parameters = null) =>
+ new Template(this, sql, (object?)parameters);
- protected SqlBuilder AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false)
+ protected SqlBuilder AddClause(string name, string sql, object? parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false)
{
- if (!_data.TryGetValue(name, out Clauses clauses))
+ if (!_data.TryGetValue(name, out var clauses))
{
clauses = new Clauses(joiner, prefix, postfix);
_data[name] = clauses;
}
- clauses.Add(new Clause { Sql = sql, Parameters = parameters, IsInclusive = isInclusive });
+ clauses.Add(new Clause(sql, parameters, isInclusive));
_seq++;
return this;
}
- public SqlBuilder Intersect(string sql, dynamic parameters = null) =>
- AddClause("intersect", sql, parameters, "\nINTERSECT\n ", "\n ", "\n", false);
+ public SqlBuilder Intersect(string sql, dynamic? parameters = null) =>
+ AddClause("intersect", sql, (object?)parameters, "\nINTERSECT\n ", "\n ", "\n", false);
- public SqlBuilder InnerJoin(string sql, dynamic parameters = null) =>
- AddClause("innerjoin", sql, parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false);
+ public SqlBuilder InnerJoin(string sql, dynamic? parameters = null) =>
+ AddClause("innerjoin", sql, (object?)parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false);
- public SqlBuilder LeftJoin(string sql, dynamic parameters = null) =>
- AddClause("leftjoin", sql, parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false);
+ public SqlBuilder LeftJoin(string sql, dynamic? parameters = null) =>
+ AddClause("leftjoin", sql, (object?)parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false);
- public SqlBuilder RightJoin(string sql, dynamic parameters = null) =>
- AddClause("rightjoin", sql, parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false);
+ public SqlBuilder RightJoin(string sql, dynamic? parameters = null) =>
+ AddClause("rightjoin", sql, (object?)parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false);
- public SqlBuilder Where(string sql, dynamic parameters = null) =>
- AddClause("where", sql, parameters, " AND ", "WHERE ", "\n", false);
+ public SqlBuilder Where(string sql, dynamic? parameters = null) =>
+ AddClause("where", sql, (object?)parameters, " AND ", "WHERE ", "\n", false);
- public SqlBuilder OrWhere(string sql, dynamic parameters = null) =>
- AddClause("where", sql, parameters, " OR ", "WHERE ", "\n", true);
+ public SqlBuilder OrWhere(string sql, dynamic? parameters = null) =>
+ AddClause("where", sql, (object?)parameters, " OR ", "WHERE ", "\n", true);
- public SqlBuilder OrderBy(string sql, dynamic parameters = null) =>
- AddClause("orderby", sql, parameters, " , ", "ORDER BY ", "\n", false);
+ public SqlBuilder OrderBy(string sql, dynamic? parameters = null) =>
+ AddClause("orderby", sql, (object?)parameters, " , ", "ORDER BY ", "\n", false);
- public SqlBuilder Select(string sql, dynamic parameters = null) =>
- AddClause("select", sql, parameters, " , ", "", "\n", false);
+ public SqlBuilder Select(string sql, dynamic? parameters = null) =>
+ AddClause("select", sql, (object?)parameters, " , ", "", "\n", false);
public SqlBuilder AddParameters(dynamic parameters) =>
- AddClause("--parameters", "", parameters, "", "", "", false);
+ AddClause("--parameters", "", (object?)parameters, "", "", "", false);
+
+ public SqlBuilder Join(string sql, dynamic? parameters = null) =>
+ AddClause("join", sql, (object?)parameters, "\nJOIN ", "\nJOIN ", "\n", false);
+
+ public SqlBuilder GroupBy(string sql, dynamic? parameters = null) =>
+ AddClause("groupby", sql, (object?)parameters, " , ", "\nGROUP BY ", "\n", false);
- public SqlBuilder Join(string sql, dynamic parameters = null) =>
- AddClause("join", sql, parameters, "\nJOIN ", "\nJOIN ", "\n", false);
+ public SqlBuilder Having(string sql, dynamic? parameters = null) =>
+ AddClause("having", sql, (object?)parameters, "\nAND ", "HAVING ", "\n", false);
- public SqlBuilder GroupBy(string sql, dynamic parameters = null) =>
- AddClause("groupby", sql, parameters, " , ", "\nGROUP BY ", "\n", false);
+ public SqlBuilder Set(string sql, dynamic? parameters = null) =>
+ AddClause("set", sql, (object?)parameters, " , ", "SET ", "\n", false);
- public SqlBuilder Having(string sql, dynamic parameters = null) =>
- AddClause("having", sql, parameters, "\nAND ", "HAVING ", "\n", false);
}
}
diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj
index 22d0a419e..705385fd6 100644
--- a/Dapper.StrongName/Dapper.StrongName.csproj
+++ b/Dapper.StrongName/Dapper.StrongName.csproj
@@ -3,33 +3,28 @@
Dapper.StrongName
orm;sql;micro-orm
Dapper (Strong Named)
- A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..
+ A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects.
Sam Saffron;Marc Gravell;Nick Craver
- net451;netstandard1.3;netstandard2.0
+ net461;netstandard2.0;net8.0;net10.0
true
true
+ enable
+ true
+ $(DefineConstants);STRONG_NAME
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj
deleted file mode 100644
index ea2efb865..000000000
--- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
- Dapper.Tests.Contrib
- Dapper.Tests.Contrib
- Dapper Contrib Test Suite
- portable
- Exe
- false
- netcoreapp1.0;netcoreapp2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/Dapper.Tests.Contrib/TestSuite.Async.cs
deleted file mode 100644
index 7c57a3634..000000000
--- a/Dapper.Tests.Contrib/TestSuite.Async.cs
+++ /dev/null
@@ -1,386 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-using Dapper.Contrib.Extensions;
-using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute;
-using Xunit;
-
-namespace Dapper.Tests.Contrib
-{
- public abstract partial class TestSuite
- {
- [Fact]
- public async Task TypeWithGenericParameterCanBeInsertedAsync()
- {
- using (var connection = GetOpenConnection())
- {
- await connection.DeleteAllAsync>();
- var objectToInsert = new GenericType
- {
- Id = Guid.NewGuid().ToString(),
- Name = "something"
- };
- await connection.InsertAsync(objectToInsert);
-
- Assert.Single(connection.GetAll>());
-
- var objectsToInsert = new List>
- {
- new GenericType
- {
- Id = Guid.NewGuid().ToString(),
- Name = "1",
- },
- new GenericType
- {
- Id = Guid.NewGuid().ToString(),
- Name = "2",
- }
- };
-
- await connection.InsertAsync(objectsToInsert);
- var list = connection.GetAll>();
- Assert.Equal(3, list.Count());
- }
- }
-
- ///
- /// Tests for issue #351
- ///
- [Fact]
- public async Task InsertGetUpdateDeleteWithExplicitKeyAsync()
- {
- using (var connection = GetOpenConnection())
- {
- var guid = Guid.NewGuid().ToString();
- var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" };
- var originalxCount = (await connection.QueryAsync("Select Count(*) From ObjectX").ConfigureAwait(false)).First();
- await connection.InsertAsync(o1).ConfigureAwait(false);
- var list1 = (await connection.QueryAsync("select * from ObjectX").ConfigureAwait(false)).ToList();
- Assert.Equal(list1.Count, originalxCount + 1);
- o1 = await connection.GetAsync(guid).ConfigureAwait(false);
- Assert.Equal(o1.ObjectXId, guid);
- o1.Name = "Bar";
- await connection.UpdateAsync(o1).ConfigureAwait(false);
- o1 = await connection.GetAsync(guid).ConfigureAwait(false);
- Assert.Equal("Bar", o1.Name);
- await connection.DeleteAsync(o1).ConfigureAwait(false);
- o1 = await connection.GetAsync(guid).ConfigureAwait(false);
- Assert.Null(o1);
-
- const int id = 42;
- var o2 = new ObjectY { ObjectYId = id, Name = "Foo" };
- var originalyCount = connection.Query("Select Count(*) From ObjectY").First();
- await connection.InsertAsync(o2).ConfigureAwait(false);
- var list2 = (await connection.QueryAsync("select * from ObjectY").ConfigureAwait(false)).ToList();
- Assert.Equal(list2.Count, originalyCount + 1);
- o2 = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Equal(o2.ObjectYId, id);
- o2.Name = "Bar";
- await connection.UpdateAsync(o2).ConfigureAwait(false);
- o2 = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Equal("Bar", o2.Name);
- await connection.DeleteAsync(o2).ConfigureAwait(false);
- o2 = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Null(o2);
- }
- }
-
- [Fact]
- public async Task TableNameAsync()
- {
- using (var connection = GetOpenConnection())
- {
- // tests against "Automobiles" table (Table attribute)
- var id = await connection.InsertAsync(new Car { Name = "VolvoAsync" }).ConfigureAwait(false);
- var car = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.NotNull(car);
- Assert.Equal("VolvoAsync", car.Name);
- Assert.True(await connection.UpdateAsync(new Car { Id = id, Name = "SaabAsync" }).ConfigureAwait(false));
- Assert.Equal("SaabAsync", (await connection.GetAsync(id).ConfigureAwait(false)).Name);
- Assert.True(await connection.DeleteAsync(new Car { Id = id }).ConfigureAwait(false));
- Assert.Null(await connection.GetAsync(id).ConfigureAwait(false));
- }
- }
-
- [Fact]
- public async Task TestSimpleGetAsync()
- {
- using (var connection = GetOpenConnection())
- {
- var id = await connection.InsertAsync(new User { Name = "Adama", Age = 10 }).ConfigureAwait(false);
- var user = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Equal(id, user.Id);
- Assert.Equal("Adama", user.Name);
- await connection.DeleteAsync(user).ConfigureAwait(false);
- }
- }
-
- [Fact]
- public async Task InsertGetUpdateAsync()
- {
- using (var connection = GetOpenConnection())
- {
- Assert.Null(await connection.GetAsync(30).ConfigureAwait(false));
-
- var originalCount = (await connection.QueryAsync("select Count(*) from Users").ConfigureAwait(false)).First();
-
- var id = await connection.InsertAsync(new User { Name = "Adam", Age = 10 }).ConfigureAwait(false);
-
- //get a user with "isdirty" tracking
- var user = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Equal("Adam", user.Name);
- Assert.False(await connection.UpdateAsync(user).ConfigureAwait(false)); //returns false if not updated, based on tracking
- user.Name = "Bob";
- Assert.True(await connection.UpdateAsync(user).ConfigureAwait(false)); //returns true if updated, based on tracking
- user = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Equal("Bob", user.Name);
-
- //get a user with no tracking
- var notrackedUser = await connection.GetAsync(id).ConfigureAwait(false);
- Assert.Equal("Bob", notrackedUser.Name);
- Assert.True(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false));
- //returns true, even though user was not changed
- notrackedUser.Name = "Cecil";
- Assert.True(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false));
- Assert.Equal("Cecil", (await connection.GetAsync(id).ConfigureAwait(false)).Name);
-
- Assert.Equal((await connection.QueryAsync("select * from Users").ConfigureAwait(false)).Count(), originalCount + 1);
- Assert.True(await connection.DeleteAsync(user).ConfigureAwait(false));
- Assert.Equal((await connection.QueryAsync("select * from Users").ConfigureAwait(false)).Count(), originalCount);
-
- Assert.False(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); //returns false, user not found
-
- Assert.True(await connection.InsertAsync(new User { Name = "Adam", Age = 10 }).ConfigureAwait(false) > originalCount + 1);
- }
- }
-
- [Fact]
- public async Task InsertCheckKeyAsync()
- {
- using (var connection = GetOpenConnection())
- {
- await connection.DeleteAllAsync().ConfigureAwait(false);
-
- Assert.Null(await connection.GetAsync(3).ConfigureAwait(false));
- var user = new User { Name = "Adamb", Age = 10 };
- var id = await connection.InsertAsync(user).ConfigureAwait(false);
- Assert.Equal(user.Id, id);
- }
- }
-
- [Fact]
- public async Task BuilderSelectClauseAsync()
- {
- using (var connection = GetOpenConnection())
- {
- await connection.DeleteAllAsync().ConfigureAwait(false);
-
- var rand = new Random(8675309);
- var data = new List();
- for (var i = 0; i < 100; i++)
- {
- var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() };
- data.Add(nU);
- nU.Id = await connection.InsertAsync(nU).ConfigureAwait(false);
- }
-
- var builder = new SqlBuilder();
- var justId = builder.AddTemplate("SELECT /**select**/ FROM Users");
- var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users");
-
- builder.Select("Id");
-
- var ids = await connection.QueryAsync(justId.RawSql, justId.Parameters).ConfigureAwait(false);
- var users = await connection.QueryAsync(all.RawSql, all.Parameters).ConfigureAwait(false);
-
- foreach (var u in data)
- {
- if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select");
- if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age))
- throw new Exception("Missing users in select");
- }
- }
- }
-
- [Fact]
- public async Task BuilderTemplateWithoutCompositionAsync()
- {
- var builder = new SqlBuilder();
- var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new { age = 5 });
-
- if (template.RawSql == null) throw new Exception("RawSql null");
- if (template.Parameters == null) throw new Exception("Parameters null");
-
- using (var connection = GetOpenConnection())
- {
- await connection.DeleteAllAsync().ConfigureAwait(false);
-
- await connection.InsertAsync(new User { Age = 5, Name = "Testy McTestington" }).ConfigureAwait(false);
-
- if ((await connection.QueryAsync(template.RawSql, template.Parameters).ConfigureAwait(false)).Single() != 1)
- throw new Exception("Query failed");
- }
- }
-
- [Fact]
- public async Task InsertArrayAsync()
- {
- await InsertHelperAsync(src => src.ToArray()).ConfigureAwait(false);
- }
-
- [Fact]
- public async Task InsertListAsync()
- {
- await InsertHelperAsync(src => src.ToList()).ConfigureAwait(false);
- }
-
- private async Task InsertHelperAsync(Func, T> helper)
- where T : class
- {
- const int numberOfEntities = 10;
-
- var users = new List();
- for (var i = 0; i < numberOfEntities; i++)
- users.Add(new User { Name = "User " + i, Age = i });
-
- using (var connection = GetOpenConnection())
- {
- await connection.DeleteAllAsync().ConfigureAwait(false);
-
- var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false);
- Assert.Equal(total, numberOfEntities);
- users = connection.Query("select * from Users").ToList();
- Assert.Equal(users.Count, numberOfEntities);
- }
- }
-
- [Fact]
- public async Task UpdateArrayAsync()
- {
- await UpdateHelperAsync(src => src.ToArray()).ConfigureAwait(false);
- }
-
- [Fact]
- public async Task UpdateListAsync()
- {
- await UpdateHelperAsync(src => src.ToList()).ConfigureAwait(false);
- }
-
- private async Task UpdateHelperAsync(Func, T> helper)
- where T : class
- {
- const int numberOfEntities = 10;
-
- var users = new List();
- for (var i = 0; i < numberOfEntities; i++)
- users.Add(new User { Name = "User " + i, Age = i });
-
- using (var connection = GetOpenConnection())
- {
- await connection.DeleteAllAsync().ConfigureAwait(false);
-
- var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false);
- Assert.Equal(total, numberOfEntities);
- users = connection.Query("select * from Users").ToList();
- Assert.Equal(users.Count, numberOfEntities);
- foreach (var user in users)
- {
- user.Name += " updated";
- }
- await connection.UpdateAsync(helper(users)).ConfigureAwait(false);
- var name = connection.Query("select * from Users").First().Name;
- Assert.Contains("updated", name);
- }
- }
-
- [Fact]
- public async Task DeleteArrayAsync()
- {
- await DeleteHelperAsync(src => src.ToArray()).ConfigureAwait(false);
- }
-
- [Fact]
- public async Task DeleteListAsync()
- {
- await DeleteHelperAsync(src => src.ToList()).ConfigureAwait(false);
- }
-
- private async Task DeleteHelperAsync(Func, T> helper)
- where T : class
- {
- const int numberOfEntities = 10;
-
- var users = new List