├── sample └── CustomerSamples │ ├── host.json │ ├── insert.sql │ ├── select.sql │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json │ ├── Model │ └── Customer.cs │ ├── select2.sql │ ├── Startup.cs │ ├── CustomerSamples.csproj │ └── CustomerSamples.cs ├── src └── Dapper.Azure.WebJobs.Extensions.SqlServer │ ├── SqlInput.cs │ ├── Bindings │ ├── OpenTypeToSqlInputConverter.cs │ ├── ExecuteSqlAsyncCollector.cs │ └── DapperAttributeToExecuteQueryAsyncConverter.cs │ ├── Dapper.Azure.WebJobs.Extensions.SqlServer.csproj │ ├── Config │ ├── DapperHostBuilderExtensions.cs │ └── DapperExtensionConfigProvider.cs │ ├── DapperAttribute.cs │ ├── Dapper.Azure.WebJobs.Extensions.SqlServer.nuspec │ ├── Dapper │ └── GenericSqlStore.cs │ └── Utility.cs ├── dapper-azure-function-binding.code-workspace ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── dapper-azure-function-binding.sln ├── .circleci └── config.yml ├── CODE_OF_CONDUCT.md ├── .gitignore └── README.md /sample/CustomerSamples/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /sample/CustomerSamples/insert.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO [Customers] ([FirstName], [LastName]) VALUES (@FirstName, @LastName) -------------------------------------------------------------------------------- /sample/CustomerSamples/select.sql: -------------------------------------------------------------------------------- 1 | SELECT CustomerNumber, FirstName, LastName FROM [Customers] WHERE FirstName = @FirstName -------------------------------------------------------------------------------- /sample/CustomerSamples/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions", 4 | "ms-vscode.csharp" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/SqlInput.cs: -------------------------------------------------------------------------------- 1 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer 2 | { 3 | public class SqlInput 4 | { 5 | public dynamic Parameters { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /dapper-azure-function-binding.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "src\\Dapper.Azure.WebJobs.Extensions.SqlServer" 5 | }, 6 | { 7 | "path": "sample\\CustomerSamples" 8 | }, 9 | { 10 | "path": ".circleci" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /sample/CustomerSamples/Model/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace Samples 2 | { 3 | public class Customer 4 | { 5 | public int CustomerNumber { get; set; } 6 | public string FirstName { get; set; } 7 | public string LastName { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/CustomerSamples/select2.sql: -------------------------------------------------------------------------------- 1 | UPDATE [Customers] SET Processed = @Processed where CustomerNumber in (select top 5 CustomerNumber from [Customers] WHERE Processed is null ORDER BY Updated ASC) 2 | SELECT CustomerNumber, FirstName, LastName FROM [Customers] WHERE Processed = @Processed; 3 | -------------------------------------------------------------------------------- /sample/CustomerSamples/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to C# Functions", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:azureFunctions.pickProcess}" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /sample/CustomerSamples/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.projectRuntime": "~2", 3 | "azureFunctions.projectLanguage": "C#", 4 | "azureFunctions.templateFilter": "Verified", 5 | "azureFunctions.deploySubpath": "bin/Release/netcoreapp2.2/publish", 6 | "azureFunctions.preDeployTask": "publish", 7 | "debug.internalConsoleOptions": "neverOpen" 8 | } 9 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Bindings/OpenTypeToSqlInputConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | 3 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer.Bindings 4 | { 5 | internal class OpenTypeToSqlInputConverter : IConverter 6 | { 7 | public SqlInput Convert(T input) 8 | { 9 | return new SqlInput() { Parameters = input }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/CustomerSamples/Startup.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Azure.WebJobs.Extensions.SqlServer.Config; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Azure.WebJobs.Hosting; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | [assembly: WebJobsStartup(typeof(CustomerSamples.Startup))] 7 | namespace CustomerSamples 8 | { 9 | public class Startup : IWebJobsStartup 10 | { 11 | public void Configure(IWebJobsBuilder builder) 12 | { 13 | builder.AddDapperSqlServer(); 14 | builder.AddServiceBus(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Dapper.Azure.WebJobs.Extensions.SqlServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | Dapper.Azure.WebJobs.Extensions.SqlServer.nuspec 6 | $(NuspecProperties);version=$(PackageVersion) 7 | $(NuspecProperties);configuration=$(configuration) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | - Provide a code sample 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Functions(please complete the following information):** 24 | - Function version [e.g. 2] 25 | - .Net version 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Config/DapperHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper.Azure.WebJobs.Extensions.SqlServer.Config; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | 8 | namespace Microsoft.Extensions.Hosting 9 | { 10 | public static class DapperHostBuilderExtensions 11 | { 12 | public static IWebJobsBuilder AddDapperSqlServer(this IWebJobsBuilder builder) 13 | { 14 | if (builder == null) 15 | { 16 | throw new ArgumentNullException(nameof(builder)); 17 | } 18 | 19 | builder.AddExtension(); 20 | 21 | return builder; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/DapperAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs.Description; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Data; 5 | 6 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer 7 | { 8 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)] 9 | [Binding] 10 | public sealed class DapperAttribute : Attribute 11 | { 12 | [Required(ErrorMessage = "Connection string is required, please add your connection in the application settings")] 13 | [ConnectionString] 14 | public string SqlConnection { get; set; } 15 | [Required(ErrorMessage = "Sql is required, please add it as a string or as reference to a .sql file")] 16 | [AutoResolve] 17 | public string Sql { get; set; } 18 | [AutoResolve] 19 | public string Parameters { get; set; } 20 | public int CommandTimeout { get; set; } = 30; 21 | public IsolationLevel IsolationLevel { get; set; } = IsolationLevel.ReadCommitted; 22 | public CommandType CommandType { get; set; } = CommandType.Text; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kristian Gundry Nielsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sample/CustomerSamples/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "clean", 6 | "command": "dotnet clean", 7 | "type": "shell", 8 | "problemMatcher": "$msCompile" 9 | }, 10 | { 11 | "label": "build", 12 | "command": "dotnet build", 13 | "type": "shell", 14 | "dependsOn": "clean", 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | }, 19 | "problemMatcher": "$msCompile" 20 | }, 21 | { 22 | "label": "clean release", 23 | "command": "dotnet clean --configuration Release", 24 | "type": "shell", 25 | "problemMatcher": "$msCompile" 26 | }, 27 | { 28 | "label": "publish", 29 | "command": "dotnet publish --configuration Release", 30 | "type": "shell", 31 | "dependsOn": "clean release", 32 | "problemMatcher": "$msCompile" 33 | }, 34 | { 35 | "type": "func", 36 | "dependsOn": "build", 37 | "options": { 38 | "cwd": "${workspaceFolder}/bin/Debug/netcoreapp2.2" 39 | }, 40 | "command": "host start", 41 | "isBackground": true, 42 | "problemMatcher": "$func-watch" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /sample/CustomerSamples/CustomerSamples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.2 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | PreserveNewest 16 | Never 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | Always 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Dapper.Azure.WebJobs.Extensions.SqlServer.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dapper.Azure.WebJobs.Extensions.SqlServer 5 | $version$ 6 | SQL binding for azure functions 7 | Kristian Gundry Nielsen 8 | Kristian Gundry Nielsen 9 | https://github.com/kristianGNI/Dapper.Azure.WebJobs.Extensions/blob/master/LICENSE 10 | https://github.com/kristianGNI/Dapper.Azure.WebJobs.Extensions 11 | false 12 | Independent open source project by Kristian Gundry Nielsen. SQL binding for azure functions using Dapper, currently only working with MS SQL Server 13 | Copyright (c) 2019 Kristian Gundry Nielsen 14 | azure-function sql dapper azure 15 | Dapper.Azure.WebJobs.Extensions is input and ouput binding for Azure functions 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /dapper-azure-function-binding.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.421 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Azure.WebJobs.Extensions.SqlServer", "src\Dapper.Azure.WebJobs.Extensions.SqlServer\Dapper.Azure.WebJobs.Extensions.SqlServer.csproj", "{264D84CF-0650-4282-A2BA-4DF8B786EAD7}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomerSamples", "sample\CustomerSamples\CustomerSamples.csproj", "{0937F3A1-5F31-4ED9-9770-83F33BECF713}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {264D84CF-0650-4282-A2BA-4DF8B786EAD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {264D84CF-0650-4282-A2BA-4DF8B786EAD7}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {264D84CF-0650-4282-A2BA-4DF8B786EAD7}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {264D84CF-0650-4282-A2BA-4DF8B786EAD7}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {0937F3A1-5F31-4ED9-9770-83F33BECF713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {0937F3A1-5F31-4ED9-9770-83F33BECF713}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {0937F3A1-5F31-4ED9-9770-83F33BECF713}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {0937F3A1-5F31-4ED9-9770-83F33BECF713}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {377B66A6-CB2A-4744-A927-E9506BF16C49} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Bindings/ExecuteSqlAsyncCollector.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Azure.WebJobs.Extensions.SqlServer.Dapper; 2 | using Microsoft.Azure.WebJobs; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer.Bindings 10 | { 11 | internal class ExecuteSqlAsyncCollector : IAsyncCollector 12 | { 13 | private DapperAttribute _dapperAttribute; 14 | private string _path; 15 | public ExecuteSqlAsyncCollector(DapperAttribute attribute, string path) 16 | { 17 | _dapperAttribute = attribute ?? throw new System.ArgumentNullException(nameof(attribute)); 18 | _path = path; 19 | } 20 | public async Task AddAsync(SqlInput input, CancellationToken cancellationToken = default(CancellationToken)) 21 | { 22 | if (input == null) throw new System.ArgumentNullException(nameof(input)); 23 | 24 | string sql = _dapperAttribute.Sql; 25 | if (Utility.IsSqlScript(sql)) 26 | sql = Utility.GetTextFromFile(_path, sql); 27 | var parameters = GetParameters(input.Parameters); 28 | await GenericSqlStore.Execute(parameters , _dapperAttribute.SqlConnection, sql, _dapperAttribute.CommandTimeout, _dapperAttribute.IsolationLevel, _dapperAttribute.CommandType); 29 | } 30 | public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) 31 | { 32 | return Task.CompletedTask; 33 | } 34 | private IEnumerable GetParameters(dynamic dynParameters) 35 | { 36 | if(dynParameters == null) return null; 37 | IEnumerable parameters; 38 | 39 | if (Utility.IsEnumerable(dynParameters)){ 40 | var list = ((IEnumerable)dynParameters).Cast().ToList(); 41 | parameters = from item in list select new DynamicParameters(item); 42 | } 43 | else 44 | parameters = new List(){ new DynamicParameters(dynParameters)}; 45 | return parameters; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Config/DapperExtensionConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Azure.WebJobs.Extensions.SqlServer.Bindings; 2 | using Microsoft.Azure.WebJobs.Description; 3 | using Microsoft.Azure.WebJobs.Host.Bindings; 4 | using Microsoft.Azure.WebJobs.Host.Config; 5 | using Microsoft.Extensions.Options; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Converters; 8 | using Newtonsoft.Json.Linq; 9 | using System.Collections.Generic; 10 | using System.Dynamic; 11 | 12 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer.Config 13 | { 14 | [Extension("Dapper")] 15 | public class DapperExtensionConfigProvider : IExtensionConfigProvider 16 | { 17 | private readonly string _appRoot; 18 | public DapperExtensionConfigProvider(IOptions options) 19 | { 20 | _appRoot = options.Value.AppDirectory; 21 | } 22 | public void Initialize(ExtensionConfigContext context) 23 | { 24 | context.AddConverter(input => { 25 | return ConvertFromJObject(input); 26 | }); 27 | context.AddConverter(input => { 28 | return ConvertFromJArray(input); 29 | }); 30 | 31 | context.AddOpenConverter(typeof(OpenTypeToSqlInputConverter<>)); 32 | 33 | var rule = context.AddBindingRule(); 34 | rule.BindToCollector(attr => new ExecuteSqlAsyncCollector(attr,_appRoot)); 35 | rule.BindToInput(typeof(DapperAttributeToExecuteQueryAsyncConverter<>), _appRoot); 36 | } 37 | public SqlInput ConvertFromJArray(JArray arrary) 38 | { 39 | SqlInput input = new SqlInput(); 40 | List list = new List(); 41 | foreach (var item in arrary) 42 | { 43 | var expando = JsonConvert.DeserializeObject(item.ToString(Newtonsoft.Json.Formatting.None)); 44 | list.Add(expando); 45 | } 46 | input.Parameters = list; 47 | return input; 48 | } 49 | public SqlInput ConvertFromJObject(JObject jObject) 50 | { 51 | SqlInput input = new SqlInput(); 52 | List list = new List(); 53 | var expando = JsonConvert.DeserializeObject(jObject.ToString(Newtonsoft.Json.Formatting.None), new ExpandoObjectConverter()); 54 | list.Add(expando); 55 | input.Parameters = list; 56 | return input; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | # Settings common to each job 4 | anchor_1: &job_defaults 5 | working_directory: ~/build 6 | docker: 7 | - image: mcr.microsoft.com/dotnet/core/sdk:2.2 8 | 9 | jobs: 10 | build: 11 | <<: *job_defaults 12 | steps: 13 | - checkout 14 | - attach_workspace: 15 | at: ~/build 16 | - run: dotnet clean 17 | - run: dotnet restore 18 | - run: dotnet build --configuration Release 19 | 20 | test: 21 | <<: *job_defaults 22 | steps: 23 | - checkout 24 | - attach_workspace: 25 | at: ~/build 26 | - run: 27 | name: test 28 | command: dotnet test 29 | 30 | pack: 31 | <<: *job_defaults 32 | steps: 33 | - checkout 34 | - attach_workspace: 35 | at: ~/build 36 | - run: 37 | name: pack 38 | command: dotnet pack -p:PackageVersion=${CIRCLE_TAG} -p:TargetFrameworks=netcoreapp2.2 --configuration Release -o ../../dist 39 | - persist_to_workspace: 40 | root: . 41 | paths: 42 | - ./dist 43 | - store_artifacts: 44 | path: ./dist/Dapper.Azure.WebJobs.Extensions.SqlServer.${CIRCLE_TAG}.nupkg 45 | destination: Dapper.Azure.WebJobs.Extensions.SqlServer.${CIRCLE_TAG}.nupkg 46 | 47 | publish: 48 | <<: *job_defaults 49 | steps: 50 | - checkout 51 | - attach_workspace: 52 | at: ~/build 53 | - run: 54 | name: push 55 | command: dotnet nuget push -k ${PUBLISH_KEY} -s https://api.nuget.org/v3/index.json ./dist/Dapper.Azure.WebJobs.Extensions.SqlServer.${CIRCLE_TAG}.nupkg 56 | 57 | workflows: 58 | version: 2 59 | main: 60 | jobs: 61 | - build: 62 | filters: 63 | tags: 64 | only: /^([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)(?:-(beta[.][1-9]+[0-9]?|alpha[.][1-9]+[0-9]?|rc[.][1-9]+[0-9]?))?$/ 65 | - test: 66 | filters: 67 | tags: 68 | only: /^([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)(?:-(beta[.][1-9]+[0-9]?|alpha[.][1-9]+[0-9]?|rc[.][1-9]+[0-9]?))?$/ 69 | - pack: 70 | requires: 71 | - build 72 | - test 73 | filters: 74 | branches: 75 | ignore: /.*/ 76 | tags: 77 | only: /^([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)(?:-(beta[.][1-9]+[0-9]?|alpha[.][1-9]+[0-9]?|rc[.][1-9]+[0-9]?))?$/ 78 | - publish: 79 | requires: 80 | - pack 81 | filters: 82 | branches: 83 | ignore: /.*/ 84 | tags: 85 | only: /^([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)[.]([0]|[1-9]+[0-9]?)(?:-(beta[.][1-9]+[0-9]?|alpha[.][1-9]+[0-9]?|rc[.][1-9]+[0-9]?))?$/ 86 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Bindings/DapperAttributeToExecuteQueryAsyncConverter.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Azure.WebJobs.Extensions.SqlServer.Dapper; 2 | using Microsoft.Azure.WebJobs; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Collections.Generic; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer.Bindings 12 | { 13 | internal class DapperAttributeToExecuteQueryAsyncConverter : IAsyncConverter 14 | { 15 | private string _path; 16 | 17 | public DapperAttributeToExecuteQueryAsyncConverter(string path) 18 | { 19 | _path = path; 20 | } 21 | public async Task ConvertAsync(DapperAttribute attr, CancellationToken cancellationToken) 22 | { 23 | if (attr == null) throw new System.ArgumentNullException(nameof(attr)); 24 | 25 | string sql = attr.Sql; 26 | if (Utility.IsSqlScript(sql)) 27 | sql = Utility.GetTextFromFile(_path, attr.Sql); 28 | 29 | DynamicParameters parameters; 30 | if (Utility.IsJson(attr.Parameters)) 31 | parameters = GetParametersFromJSON(attr.Parameters); 32 | else 33 | parameters = GetParametersFromString(attr.Parameters); 34 | var result = await GenericSqlStore.ExecuteQuery(parameters, attr.SqlConnection, sql, attr.CommandTimeout, attr.IsolationLevel, attr.CommandType); 35 | return HandleQueryResult(result); 36 | } 37 | private DynamicParameters GetParametersFromString(string strParameters) 38 | { 39 | if (string.IsNullOrEmpty(strParameters)) return null; 40 | 41 | var values = Utility.StringToDict(strParameters); 42 | DynamicParameters parameters = new DynamicParameters(); 43 | foreach (var item in values) 44 | { 45 | parameters.Add("@" + item.Key, item.Value); 46 | } 47 | return parameters; 48 | } 49 | private DynamicParameters GetParametersFromJSON(string json) 50 | { 51 | if (string.IsNullOrEmpty(json)) return null; 52 | DynamicParameters parameters = new DynamicParameters(); 53 | var objects = JsonConvert.DeserializeObject(json); 54 | parameters.AddDynamicParams(objects); 55 | 56 | return parameters; 57 | } 58 | private static T HandleQueryResult(IEnumerable result) 59 | { 60 | T resultValue = default(T); 61 | if (result != null && result.Count() > 0) 62 | { 63 | string json; 64 | Type type = typeof(T); 65 | if(Utility.IsEnumerable(type)) 66 | json = JsonConvert.SerializeObject(result); 67 | else 68 | json = JsonConvert.SerializeObject(result.FirstOrDefault()); 69 | resultValue = JsonConvert.DeserializeObject(json); 70 | } 71 | return resultValue; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Dapper/GenericSqlStore.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Data; 8 | using System.Data.SqlClient; 9 | using System.Dynamic; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer.Dapper 14 | { 15 | internal class GenericSqlStore 16 | { 17 | public static async Task Execute(IEnumerable parameters, string connectionString, string sql, int? commandTimeout, IsolationLevel isolationLevel, 18 | CommandType commandType) 19 | { 20 | if (string.IsNullOrEmpty(connectionString)) throw new System.ArgumentNullException(nameof(connectionString)); 21 | if (string.IsNullOrEmpty(sql)) throw new System.ArgumentNullException(nameof(sql)); 22 | 23 | using (var connection = new SqlConnection(connectionString)) 24 | { 25 | await connection.OpenAsync().ConfigureAwait(false); 26 | using (var transaction = connection.BeginTransaction(isolationLevel)) 27 | { 28 | try 29 | { 30 | await connection.ExecuteAsync(sql, parameters, 31 | transaction: transaction, commandTimeout: commandTimeout, commandType: commandType).ConfigureAwait(false); 32 | transaction.Commit(); 33 | } 34 | catch (Exception ex) 35 | { 36 | transaction.Rollback(); 37 | throw ex; 38 | } 39 | } 40 | } 41 | } 42 | public static async Task> ExecuteQuery(DynamicParameters parameters, string connectionString, string sql, int? commandTimeout, IsolationLevel isolationLevel, 43 | CommandType commandType) 44 | { 45 | if (string.IsNullOrEmpty(connectionString)) throw new System.ArgumentNullException(nameof(connectionString)); 46 | if (string.IsNullOrEmpty(sql)) throw new System.ArgumentNullException(nameof(sql)); 47 | 48 | using (var connection = new SqlConnection(connectionString)) 49 | { 50 | IEnumerable result; 51 | await connection.OpenAsync().ConfigureAwait(false); 52 | using (var transaction = connection.BeginTransaction(isolationLevel)) 53 | { 54 | try 55 | { 56 | result = await connection.QueryAsync(sql, parameters, 57 | transaction: transaction, commandTimeout: commandTimeout, commandType: commandType).ConfigureAwait(false); 58 | transaction.Commit(); 59 | } 60 | catch(Exception ex){ 61 | transaction.Rollback(); 62 | throw ex; 63 | } 64 | } 65 | return result; 66 | } 67 | } 68 | 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dawes.report@protonmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/Dapper.Azure.WebJobs.Extensions.SqlServer/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Dynamic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text.RegularExpressions; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Converters; 11 | 12 | namespace Dapper.Azure.WebJobs.Extensions.SqlServer 13 | { 14 | internal class Utility 15 | { 16 | public static bool IsJson(string input) 17 | { 18 | if (string.IsNullOrEmpty(input)) 19 | { 20 | return false; 21 | } 22 | 23 | input = input.Trim(); 24 | return (input.StartsWith("{", StringComparison.OrdinalIgnoreCase) && input.EndsWith("}", StringComparison.OrdinalIgnoreCase)) 25 | || (input.StartsWith("[", StringComparison.OrdinalIgnoreCase) && input.EndsWith("]", StringComparison.OrdinalIgnoreCase)); 26 | } 27 | public static bool IsSqlScript(string input) 28 | { 29 | if (string.IsNullOrEmpty(input)) 30 | { 31 | return false; 32 | } 33 | 34 | return input.ToLower().EndsWith(".sql"); 35 | } 36 | public static string[] GetWords(string input) 37 | { 38 | if (string.IsNullOrEmpty(input)) throw new System.ArgumentNullException(nameof(input)); 39 | 40 | MatchCollection matches = Regex.Matches(input, @"\B@\w+"); 41 | 42 | var words = from m in matches.Cast() 43 | where !string.IsNullOrEmpty(m.Value) 44 | select TrimSuffix(m.Value.Trim()); 45 | 46 | return words.ToArray(); 47 | } 48 | public static string TrimSuffix(string word) 49 | { 50 | if (string.IsNullOrEmpty(word)) throw new System.ArgumentNullException(nameof(word)); 51 | 52 | int apostropheLocation = word.IndexOf('\''); 53 | if (apostropheLocation != -1) 54 | { 55 | word = word.Substring(0, apostropheLocation); 56 | } 57 | 58 | return word; 59 | } 60 | public static string GetTextFromFile(string rootDir, string fileName) 61 | { 62 | if (string.IsNullOrEmpty(fileName)) throw new System.ArgumentNullException(nameof(fileName)); 63 | if (string.IsNullOrEmpty(rootDir)) throw new System.ArgumentNullException(nameof(rootDir)); 64 | 65 | string path = Path.Combine(rootDir, fileName); 66 | return System.IO.File.ReadAllText(path); 67 | } 68 | public static Dictionary StringToDict(string input) 69 | { 70 | if (string.IsNullOrEmpty(input)) throw new System.ArgumentNullException(nameof(input)); 71 | return input 72 | .Split(',') 73 | .Select(part => part.Split(':', 2)) 74 | .Where(part => part.Length == 2) 75 | .ToDictionary(sp => sp[0].Trim(), sp => sp[1].Trim()); 76 | } 77 | public static bool IsEnumerable(object obj) 78 | { 79 | return obj is IEnumerable; 80 | } 81 | public static bool IsEnumerable(Type type){ 82 | return typeof(IEnumerable).IsAssignableFrom(type); 83 | } 84 | public static bool IsParameterizeSql(string sql) 85 | { 86 | if (string.IsNullOrEmpty(sql)) return false; 87 | 88 | var sqlParameters = Utility.GetWords(sql); 89 | return sqlParameters.Count() > 0; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc 265 | 266 | -------------------------------------------------------------------------------- /sample/CustomerSamples/CustomerSamples.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Azure.WebJobs.Extensions.SqlServer; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Data; 8 | using System; 9 | using System.Linq; 10 | 11 | namespace Samples 12 | { 13 | public static class CustomerSamples 14 | { 15 | [FunctionName("InsertCustomerSample1")] 16 | [return: Dapper(Sql = "insert.sql", SqlConnection = "SqlConnection")] 17 | public static Customer InsertCustomerSample1([HttpTrigger] Customer customer, ILogger log) 18 | { 19 | return customer; 20 | } 21 | 22 | [FunctionName("InsertCustomerSample2")] 23 | [return: Dapper(Sql = "INSERT INTO [Customers] ([FirstName], [LastName]) VALUES (@FirstName, @LastName)", 24 | SqlConnection = "SqlConnection", 25 | IsolationLevel = IsolationLevel.ReadCommitted)] 26 | public static Customer InsertCustomerSample2([HttpTrigger] Customer customer, ILogger log) 27 | { 28 | return customer; 29 | } 30 | 31 | [FunctionName("InsertCustomerSample3")] 32 | public static IList InsertCustomerSample3([HttpTrigger] HttpRequestMessage req, 33 | [Dapper(Sql = "insert.sql", SqlConnection = "SqlConnection", 34 | CommandTimeout = 60)] out IList customers, 35 | ILogger log) 36 | { 37 | customers = JsonConvert.DeserializeObject>(req.Content.ReadAsStringAsync().Result); 38 | return customers; 39 | } 40 | 41 | [FunctionName("InsertCustomerSample4")] 42 | public static IList InsertCustomerSample4([HttpTrigger] HttpRequestMessage req, 43 | [Dapper(Sql = "SpInsertCustomer2", SqlConnection = "SqlConnection", 44 | CommandTimeout = 60, 45 | CommandType = CommandType.StoredProcedure)] out IList customers, 46 | ILogger log) 47 | { 48 | customers = JsonConvert.DeserializeObject>(req.Content.ReadAsStringAsync().Result); 49 | return customers; 50 | } 51 | 52 | [FunctionName("SelectCustomerSample1")] 53 | public static IList SelectCustomerSample1([HttpTrigger] HttpRequestMessage req, 54 | [Dapper(Sql = "select.sql", SqlConnection = "SqlConnection", 55 | Parameters = "FirstName:{Query.FirstName}", 56 | IsolationLevel = IsolationLevel.ReadCommitted)] IList customers, 57 | ILogger log) 58 | { 59 | return customers; 60 | } 61 | 62 | [FunctionName("SelectCustomerSample2")] 63 | public static Customer SelectCustomerSample2([HttpTrigger] HttpRequestMessage req, 64 | [Dapper(Sql = "SELECT * FROM [dbo].[Customers] WHERE CustomerNumber = @CustomerNumber", 65 | SqlConnection = "SqlConnection", 66 | Parameters = "CustomerNumber:{Query.CustomerNumber}", 67 | CommandTimeout = 60) 68 | ] Customer customer, 69 | ILogger log) 70 | { 71 | return customer; 72 | } 73 | 74 | [FunctionName("SelectCustomerSample3")] 75 | public static IList SelectCustomerSample3([HttpTrigger] HttpRequestMessage req, 76 | [Dapper(Sql = "SELECT * FROM [dbo].[Customers]", 77 | SqlConnection = "SqlConnection")] IList customers, 78 | ILogger log) 79 | { 80 | return customers; 81 | } 82 | 83 | [FunctionName("SelectCustomerSample4")] 84 | public static IList SelectCustomerSample4 ([HttpTrigger] HttpRequestMessage req, 85 | [Dapper(Sql = "SpGetCustomerByFirstname", 86 | SqlConnection = "SqlConnection", 87 | Parameters = "FirstName:{Query.FirstName}", 88 | CommandType = CommandType.StoredProcedure)] IList customers, 89 | ILogger log) 90 | { 91 | return customers; 92 | } 93 | 94 | [FunctionName("SelectCustomerSample5")] 95 | public static void SelectCustomerSample5 ([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, 96 | [Dapper(Sql = "select2.sql", 97 | SqlConnection = "SqlConnection", 98 | Parameters = "Processed:{datetime:yyyy-MM-dd HH:mm:ss}")] List customers, 99 | [ServiceBus("myqueue", Connection = "ConnectionStrings:ServiceBusConnection")] ICollector outputSbQueue, 100 | ILogger log) 101 | { 102 | customers.ForEach(x=> outputSbQueue.Add(x)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dapper.Azure.WebJobs.Extensions 2 | ##### Independent open source project by Kristian Gundry Nielsen 3 | ##### Currently only working with MS SQL Server 4 | Extension for Azure functions input/output bindings to natively use Dapper sql calls. 5 | [Azure Functions and Bindings Reference](https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings) 6 | 7 | Azure functions can be triggered from various sources, most relevantly here Http requests. As part of an Http request trigger, there can be a need to retrieve additional data from a database (additional input), and then also simply a need to update or create data in a database (output data). By doing this declaratively in the bindings, the overhead of using Dapper is hidden into a simple set of statements. 8 | 9 | This extension allows for a native binding to retrieve or set data automatically via Dapper to a SQL source. This effectively allows the Azure function to be a remotely Http triggerable function with a declarative and simpler means of handling data. 10 | 11 | ## Installation 12 | Dapper.Azure.WebJobs.Extensions is available via NuGet: 13 | 14 | ``` 15 | Install-Package Dapper.Azure.WebJobs.Extensions.SqlServer 16 | ``` 17 | or 18 | ``` 19 | dotnet add package Dapper.Azure.WebJobs.Extensions.SqlServer 20 | ``` 21 | 22 | 23 | ## Using the binding 24 | ### C# 25 | 26 | #### Configure the binding 27 | ```csharp 28 | [assembly: WebJobsStartup(typeof(CustomerSamples.Startup))] 29 | namespace CustomerSamples 30 | { 31 | public class Startup : IWebJobsStartup 32 | { 33 | public void Configure(IWebJobsBuilder builder) 34 | { 35 | builder.AddDapperSqlServer(); 36 | builder.AddServiceBus(); 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | #### Input binding sample with ServiceBus 43 | 44 | ##### select2.sql 45 | ```sql 46 | UPDATE [Customers] SET Processed = @Processed where CustomerNumber in 47 | (select top 5 CustomerNumber from [Customers] 48 | WHERE Processed is null ORDER BY Updated ASC); 49 | SELECT CustomerNumber, FirstName, LastName FROM [Customers] WHERE Processed = @Processed; 50 | ``` 51 | 52 | ```csharp 53 | [FunctionName("SelectCustomerSample5")] 54 | public static void SelectCustomerSample5 ([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, 55 | [Dapper(Sql = "select2.sql", 56 | SqlConnection = "SqlConnection", 57 | Parameters = "Processed:{datetime:yyyy-MM-dd HH:mm:ss}")] List customers, 58 | [ServiceBus("myqueue", Connection = "myconnection")] ICollector outputSbQueue, 59 | ILogger log) 60 | { 61 | customers.ForEach(x=> outputSbQueue.Add(x)); 62 | } 63 | ``` 64 | 65 | #### Output binding samples with Http trigger 66 | 67 | ```csharp 68 | [FunctionName("InsertCustomerSample1")] 69 | [return: Dapper(Sql = "insert.sql", SqlConnection = "SqlConnection")] 70 | public static Customer Run([HttpTrigger] Customer customer, ILogger log) 71 | { 72 | return customer; 73 | } 74 | ``` 75 | 76 | ```csharp 77 | [FunctionName("InsertCustomerSample2")] 78 | [return: Dapper(Sql = "INSERT INTO [Customers] ([FirstName], [LastName]) VALUES (@FirstName, @LastName)", 79 | SqlConnection = "SqlConnection", 80 | IsolationLevel = IsolationLevel.ReadCommitted)] 81 | public static Customer Run([HttpTrigger] Customer customer, ILogger log) 82 | { 83 | return customer; 84 | } 85 | ``` 86 | 87 | ```csharp 88 | [FunctionName("InsertCustomerSample3")] 89 | public static IList Run([HttpTrigger] HttpRequestMessage req, 90 | [Dapper(Sql = "insert.sql", SqlConnection = "SqlConnection")] out IList customers, 91 | ILogger log) 92 | { 93 | customers = JsonConvert.DeserializeObject>(req.Content.ReadAsStringAsync().Result); 94 | return customers; 95 | } 96 | ``` 97 | 98 | ```csharp 99 | [FunctionName("InsertCustomerSample4")] 100 | public static IList InsertCustomerSample4([HttpTrigger] HttpRequestMessage req, 101 | [Dapper(Sql = "EXEC SpInsertCustomer @FirstName, @LastName", SqlConnection = "SqlConnection", 102 | CommandTimeout = 60, 103 | CommandType = CommandType.Text)] out IList customers, 104 | ILogger log) 105 | { 106 | customers = JsonConvert.DeserializeObject>(req.Content.ReadAsStringAsync().Result); 107 | return customers; 108 | } 109 | ``` 110 | 111 | #### Input binding samples 112 | 113 | ```csharp 114 | [FunctionName("SelectCustomerSample1")] 115 | public static IList Run([HttpTrigger] HttpRequestMessage req, 116 | [Dapper(Sql = "select.sql", SqlConnection = "SqlConnection", 117 | Parameters = "FirstName:{Query.FirstName}", 118 | IsolationLevel = IsolationLevel.ReadCommitted)] IList customers, 119 | ILogger log) 120 | { 121 | return customers; 122 | } 123 | ``` 124 | 125 | ```csharp 126 | [FunctionName("SelectCustomerSample2")] 127 | public static Customer SelectCustomerSample2([HttpTrigger] HttpRequestMessage req, 128 | [Dapper(Sql = "SELECT * FROM [dbo].[Customers] WHERE CustomerNumber = @CustomerNumber", 129 | SqlConnection = "SqlConnection", 130 | Parameters = "CustomerNumber:{Query.CustomerNumber}", 131 | CommandTimeout = 60) 132 | ] Customer customer, 133 | ILogger log) 134 | { 135 | return customer; 136 | } 137 | ``` 138 | 139 | ```csharp 140 | [FunctionName("SelectCustomerSample3")] 141 | public static IList Run([HttpTrigger] HttpRequestMessage req, 142 | [Dapper(Sql = "SELECT * FROM [dbo].[Customers]", 143 | SqlConnection = "SqlConnection")] IList customers, 144 | ILogger log) 145 | { 146 | return customers; 147 | } 148 | ``` 149 | 150 | ```csharp 151 | [FunctionName("SelectCustomerSample4")] 152 | public static IList SelectCustomerSample4dotnet ([HttpTrigger] HttpRequestMessage req, 153 | [Dapper(Sql = "SpGetCustomerByFirstname", 154 | SqlConnection = "SqlConnection", 155 | Parameters = "FirstName:{Query.FirstName}", 156 | CommandType = CommandType.StoredProcedure)] IList customers, 157 | ILogger log) 158 | { 159 | return customers; 160 | } 161 | ``` 162 | --------------------------------------------------------------------------------