├── .config └── dotnet-tools.json ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── dependabot-reviewer.yml │ └── docsfx.yml ├── .gitignore ├── .husky ├── commit-msg ├── csxScripts │ └── commit-lint.csx ├── pre-commit └── task-runner.json ├── .versionize ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Icon.png ├── LICENSE ├── README.md ├── SECURITY.md ├── SQLHelper.Example ├── Program.cs └── SQLHelper.Example.csproj ├── SQLHelper.SpeedTests ├── Log.txt ├── Modules │ ├── ConfigurationModule.cs │ └── Logging.cs ├── Program.cs ├── SQLHelper.SpeedTests.csproj └── Tests │ ├── AddQuery.cs │ ├── AltImplementations │ ├── HelperClasses │ │ ├── BaseClasses │ │ │ └── ParameterBase.cs │ │ ├── Batch.cs │ │ ├── Command.cs │ │ ├── Connection.cs │ │ ├── Interfaces │ │ │ ├── IBatch.cs │ │ │ ├── ICommand.cs │ │ │ ├── IConnection.cs │ │ │ └── IParameter.cs │ │ ├── Parameter.cs │ │ ├── SelectFinder.cs │ │ └── StringParameter.cs │ └── SQLHelper.cs │ ├── MassInsert.cs │ ├── RegexOrContains.cs │ ├── StringReplace.cs │ └── ZStringVsStringBuilderPools.cs ├── SQLHelper.sln ├── TestApp ├── EventListener │ └── SqlClientListener.cs ├── Modules │ ├── ConfigurationModule.cs │ └── LoggingModule.cs ├── Program.cs └── TestApp.csproj ├── docfx_project ├── .gitignore ├── api │ ├── .gitignore │ └── index.md ├── articles │ ├── intro.md │ └── toc.yml ├── docfx.json ├── images │ └── icon.png ├── index.md ├── templates │ └── mytemplate │ │ └── public │ │ └── main.css └── toc.yml ├── setup.bat ├── src └── SQLHelper.DB │ ├── CanisterModules │ └── SQLHelperModule.cs │ ├── ExtensionMethods │ ├── DbCommandExtensions.cs │ └── IDataRecordExtensions.cs │ ├── HelperClasses │ ├── BaseClasses │ │ └── ParameterBase.cs │ ├── Batch.cs │ ├── Command.cs │ ├── Connection.cs │ ├── Interfaces │ │ ├── IBatch.cs │ │ ├── ICommand.cs │ │ ├── IConnection.cs │ │ └── IParameter.cs │ ├── Parameter.cs │ ├── SelectFinder.cs │ ├── SimpleSelectFinder.cs │ └── StringParameter.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Registration │ └── Register.cs │ ├── SQLHelper.DB.csproj │ └── SQLHelper.cs └── test └── SQLHelper.Tests ├── BaseClasses └── TestingDirectoryFixture.cs ├── DataClasses └── TestTableClass.cs ├── ExtensionMethods └── DbCommandExtensionTests.cs ├── GlobalSuppressions.cs ├── HelperClasses ├── BatchTests.cs ├── CommandTests.cs ├── ConnectionTests.cs ├── ParameterTests.cs ├── SourceTests.cs └── StringParameterTests.cs ├── Properties └── AssemblyInfo.cs ├── SQLHelper.Tests.csproj ├── SQLHelperAsyncTests.cs ├── SQLHelperTests.cs └── appsettings.json /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "husky": { 6 | "version": "0.7.2", 7 | "commands": [ 8 | "husky" 9 | ], 10 | "rollForward": false 11 | }, 12 | "versionize": { 13 | "version": "2.3.1", 14 | "commands": [ 15 | "versionize" 16 | ], 17 | "rollForward": false 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: JaCraig 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Example code** 14 | Please provide example code that produces the bug either in a gist or pull request adding a test case and link here. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Version Information** 23 | - OS: [e.g. iOS] 24 | - .Net Version: [e.g. .Net 6] 25 | - Release Version of Library [e.g. 2.0.1] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.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/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/src/SQLHelper.DB" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | commit-message: 13 | prefix: "fix" 14 | groups: 15 | dependencies: 16 | patterns: 17 | - "*" 18 | 19 | - package-ecosystem: "nuget" # See documentation for possible values 20 | directory: "/test/SQLHelper.Tests/" # Location of package manifests 21 | schedule: 22 | interval: "daily" 23 | commit-message: 24 | prefix: "chore" 25 | groups: 26 | dependencies: 27 | patterns: 28 | - "*" 29 | 30 | - package-ecosystem: "nuget" # See documentation for possible values 31 | directory: "/TestApp/" # Location of package manifests 32 | schedule: 33 | interval: "daily" 34 | commit-message: 35 | prefix: "chore" 36 | groups: 37 | dependencies: 38 | patterns: 39 | - "*" 40 | 41 | - package-ecosystem: "nuget" # See documentation for possible values 42 | directory: "/SQLHelper.SpeedTests/" # Location of package manifests 43 | schedule: 44 | interval: "daily" 45 | commit-message: 46 | prefix: "chore" 47 | groups: 48 | dependencies: 49 | patterns: 50 | - "*" 51 | 52 | - package-ecosystem: "nuget" # See documentation for possible values 53 | directory: "/SQLHelper.Example/" # Location of package manifests 54 | schedule: 55 | interval: "daily" 56 | commit-message: 57 | prefix: "chore" 58 | groups: 59 | dependencies: 60 | patterns: 61 | - "*" 62 | 63 | - package-ecosystem: "github-actions" 64 | directory: "/" 65 | schedule: 66 | interval: "daily" 67 | commit-message: 68 | prefix: "chore" 69 | groups: 70 | dependencies: 71 | patterns: 72 | - "*" 73 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | branches: [ "master" ] 19 | schedule: 20 | - cron: '37 18 * * 3' 21 | 22 | permissions: 23 | actions: read 24 | contents: read 25 | security-events: write 26 | 27 | jobs: 28 | analyze: 29 | uses: JaCraig/Centralized-Workflows/.github/workflows/codeql.yml@main -------------------------------------------------------------------------------- /.github/workflows/dependabot-reviewer.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot Reviewer 2 | 3 | on: pull_request_target 4 | 5 | permissions: 6 | pull-requests: write 7 | contents: write 8 | 9 | jobs: 10 | review-dependabot-pr: 11 | if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} 12 | uses: JaCraig/Centralized-Workflows/.github/workflows/dependabot-reviewer.yml@main 13 | secrets: 14 | token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/docsfx.yml: -------------------------------------------------------------------------------- 1 | name: Document Site Publish 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | publish-docs: 12 | uses: JaCraig/Centralized-Workflows/.github/workflows/docsfx.yml@main -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | ## husky task runner examples ------------------- 5 | ## Note : for local installation use 'dotnet' prefix. e.g. 'dotnet husky' 6 | 7 | ## run all tasks 8 | #husky run 9 | 10 | ### run all tasks with group: 'group-name' 11 | #husky run --group group-name 12 | 13 | ## run task with name: 'task-name' 14 | #husky run --name task-name 15 | 16 | ## pass hook arguments to task 17 | #husky run --args "$1" "$2" 18 | 19 | ## or put your custom commands ------------------- 20 | #echo 'Husky.Net is awesome!' 21 | 22 | dotnet husky run --name "commit-message-linter" --args "$1" 23 | -------------------------------------------------------------------------------- /.husky/csxScripts/commit-lint.csx: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | private var pattern = @"^(?=.{1,90}$)(?:build|feat|ci|chore|docs|fix|perf|refactor|revert|style|test)(?:\(.+\))*(?::).{4,}(?:#\d+)*(?>. So in order to get the results from the queries: 33 | 34 | var FirstQueryResults = Results[0]; 35 | var SecondQueryResults = Results[1]; 36 | var ThirdQueryResults = Results[2]; 37 | 38 | It is also possible to convert the results from the dynamic type to a class type that you specify: 39 | 40 | var TestTableClasses = FirstQueryResults.Select(x => (TestTableClass)x).ToList(); 41 | 42 | The type will be converted automatically for you with no special type conversion required. SQLHelper also has an ExecuteScalar function: 43 | 44 | var Result = Instance.ExecuteScalar(); 45 | 46 | This will either return the first value of the first set of results OR it will return the number of rows that were effected depending on whether or not the query was a select or not. 47 | 48 | ## Installation 49 | 50 | The library is available via Nuget with the package name "SQLHelper.DB". To install it run the following command in the Package Manager Console: 51 | 52 | Install-Package SQLHelper.DB 53 | 54 | ## Build Process 55 | 56 | In order to build the library you will require the following as a minimum: 57 | 58 | 1. Visual Studio 2022 59 | 60 | Other than that, just clone the project and you should be able to load the solution and build without too much effort. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | -------- | ------------------ | 7 | | latest | :white_check_mark: | 8 | | < latest | :x: | 9 | -------------------------------------------------------------------------------- /SQLHelper.Example/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.SqlClient; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace SQLHelper.Example 5 | { 6 | /// 7 | /// This is an example program that shows how to use the SQLHelper.DB library to execute a batch of queries. 8 | /// 9 | internal class Program 10 | { 11 | /// 12 | /// Defines the entry point of the application. 13 | /// 14 | /// The arguments. 15 | private static async Task Main(string[] args) 16 | { 17 | // Start by creating a new ServiceCollection and adding the Canister modules to it (this will also add the SQLHelper module) 18 | var Services = new ServiceCollection().AddCanisterModules()?.BuildServiceProvider(); 19 | 20 | // Get the SQLHelper instance from the ServiceCollection 21 | var Helper = Services.GetService(); 22 | 23 | // Execute a batch of queries and return the results (this will return a list of lists of rows. The first list contains the results of each query. The inner lists contain the rows.) 24 | var Results = await Helper.CreateBatch(SqlClientFactory.Instance) 25 | .AddQuery(System.Data.CommandType.Text, "SELECT * FROM [dbo].[TestTable]") 26 | .AddQuery(System.Data.CommandType.Text, "SELECT * FROM [dbo].[TestTable]") 27 | .ExecuteAsync() 28 | .ConfigureAwait(false); 29 | 30 | // Go through each result 31 | foreach (var Result in Results) 32 | { 33 | // Go through each row in the result 34 | foreach (var Row in Result) 35 | { 36 | // Write the row to the console 37 | Console.WriteLine(Row.ToString()); 38 | } 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /SQLHelper.Example/SQLHelper.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Log.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaCraig/SQLHelper/2b857f71520fcc1b80f42c0bc5a2c9ebbe23bfd7/SQLHelper.SpeedTests/Log.txt -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Modules/ConfigurationModule.cs: -------------------------------------------------------------------------------- 1 | using Canister.Interfaces; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System.Collections.Generic; 5 | 6 | namespace SQLHelperDB.SpeedTests.Modules 7 | { 8 | public class ConfigurationModule : IModule 9 | { 10 | public int Order => 1; 11 | 12 | protected string ConnectionString => "Data Source=localhost;Initial Catalog=SpeedTestDatabase;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True"; 13 | 14 | public void Load(IServiceCollection? bootstrapper) 15 | { 16 | if (bootstrapper is null) 17 | return; 18 | var dict = new Dictionary 19 | { 20 | { "ConnectionStrings:Default", ConnectionString }, 21 | }; 22 | var Configuration = new ConfigurationBuilder() 23 | .AddInMemoryCollection(dict) 24 | .Build(); 25 | bootstrapper.AddSingleton(_ => Configuration); 26 | bootstrapper.AddSingleton(_ => Configuration); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Modules/Logging.cs: -------------------------------------------------------------------------------- 1 | using Canister.Interfaces; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Serilog; 4 | 5 | namespace SQLHelperDB.SpeedTests.Modules 6 | { 7 | /// 8 | /// Logging module 9 | /// 10 | /// 11 | public class LoggingModule : IModule 12 | { 13 | /// 14 | /// Order to run this in 15 | /// 16 | public int Order => 1; 17 | 18 | /// 19 | /// Loads the module using the bootstrapper 20 | /// 21 | /// The bootstrapper. 22 | public void Load(IServiceCollection bootstrapper) 23 | { 24 | if (bootstrapper is null) 25 | return; 26 | Log.Logger = new LoggerConfiguration() 27 | .WriteTo 28 | .File("./Log.txt") 29 | .MinimumLevel 30 | .Error() 31 | .CreateLogger(); 32 | bootstrapper.AddSingleton(_ => Log.Logger); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace SQLHelperDB.SpeedTests 4 | { 5 | internal static class Program 6 | { 7 | private static void Main(string[] args) => new BenchmarkSwitcher(typeof(Program).Assembly).Run(args); 8 | } 9 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/SQLHelper.SpeedTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AddQuery.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.ObjectPool; 5 | using System.Data; 6 | using System.Text; 7 | 8 | namespace SQLHelperDB.SpeedTests.Tests 9 | { 10 | [MemoryDiagnoser] 11 | public class AddQuery 12 | { 13 | private SQLHelper? Helper { get; set; } 14 | 15 | private string QueryText { get; } = "IF NOT EXISTS (SELECT TOP 1 ID_ FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 6701 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 1341) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (6701,1341) END;"; 16 | 17 | [Benchmark(Baseline = true)] 18 | public void Run() => Helper.AddQuery(CommandType.Text, QueryText, 0, 1, 2); 19 | 20 | [GlobalSetup] 21 | public void Setup() 22 | { 23 | ServiceProvider Services = new ServiceCollection().AddCanisterModules(x => x.AddAssembly(typeof(Program).Assembly) 24 | .RegisterSQLHelper()).BuildServiceProvider(); 25 | Helper = new SQLHelper(Services.GetService>(), Services.GetService(), null); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/BaseClasses/ParameterBase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using BigBook; 18 | using BigBook.Comparison; 19 | using Microsoft.Extensions.DependencyInjection; 20 | using ObjectCartographer; 21 | using SQLHelperDBTests.HelperClasses.Interfaces; 22 | using System; 23 | using System.Collections.Generic; 24 | using System.Data; 25 | using System.Data.Common; 26 | 27 | namespace SQLHelperDBTests.HelperClasses.BaseClasses 28 | { 29 | /// 30 | /// Parameter base class 31 | /// 32 | /// Data type of the parameter 33 | public abstract class ParameterBase : IParameter 34 | { 35 | /// 36 | /// Constructor 37 | /// 38 | /// ID of the parameter 39 | /// Value of the parameter 40 | /// Direction of the parameter 41 | /// 42 | /// What the database expects as the parameter starting string ("@" for SQL Server, ":" for 43 | /// Oracle, etc.) 44 | /// 45 | protected ParameterBase(string id, TDataType value, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 46 | : this(id, value is null ? typeof(TDataType).To(DbType.Int32) : value.GetType().To(DbType.Int32), value, direction, parameterStarter) 47 | { 48 | } 49 | 50 | /// 51 | /// Initializes a new instance of the class. 52 | /// 53 | /// The parameter. 54 | protected ParameterBase(ParameterBase parameter) 55 | : this(parameter.ID, parameter.DatabaseType, parameter.Value, parameter.Direction, parameter.ParameterStarter) 56 | { 57 | } 58 | 59 | /// 60 | /// Constructor 61 | /// 62 | /// ID of the parameter 63 | /// Database type 64 | /// Value of the parameter 65 | /// Direction of the parameter 66 | /// 67 | /// What the database expects as the parameter starting string ("@" for SQL Server, ":" for 68 | /// Oracle, etc.) 69 | /// 70 | protected ParameterBase(string id, SqlDbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 71 | : this(id, type.To(DbType.Int32), value, direction, parameterStarter) 72 | { 73 | } 74 | 75 | /// 76 | /// Constructor 77 | /// 78 | /// ID of the parameter 79 | /// Database type 80 | /// Value of the parameter 81 | /// Direction of the parameter 82 | /// 83 | /// What the database expects as the parameter starting string ("@" for SQL Server, ":" for 84 | /// Oracle, etc.) 85 | /// 86 | protected ParameterBase(string id, DbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 87 | { 88 | ID = id; 89 | Value = (TDataType)value!; 90 | DatabaseType = type; 91 | Direction = direction; 92 | BatchID = id; 93 | ParameterStarter = parameterStarter; 94 | } 95 | 96 | /// 97 | /// Database type 98 | /// 99 | public DbType DatabaseType { get; set; } 100 | 101 | /// 102 | /// Direction of the parameter 103 | /// 104 | public ParameterDirection Direction { get; set; } 105 | 106 | /// 107 | /// The Name that the parameter goes by 108 | /// 109 | public string ID { get; set; } 110 | 111 | /// 112 | /// Gets the internal value. 113 | /// 114 | /// The internal value. 115 | public object? InternalValue => Value; 116 | 117 | /// 118 | /// Starting string of the parameter 119 | /// 120 | public string ParameterStarter { get; set; } 121 | 122 | /// 123 | /// Parameter value 124 | /// 125 | public TDataType Value { get; set; } 126 | 127 | /// 128 | /// Batch ID 129 | /// 130 | protected string BatchID { get; set; } 131 | 132 | /// 133 | /// != operator 134 | /// 135 | /// First item 136 | /// Second item 137 | /// returns true if they are not equal, false otherwise 138 | public static bool operator !=(ParameterBase first, ParameterBase second) 139 | { 140 | return !(first == second); 141 | } 142 | 143 | /// 144 | /// The == operator 145 | /// 146 | /// First item 147 | /// Second item 148 | /// true if the first and second item are the same, false otherwise 149 | public static bool operator ==(ParameterBase first, ParameterBase second) 150 | { 151 | return ReferenceEquals(first, second) 152 | || (!(first is null) 153 | && !(second is null) 154 | && first.GetHashCode() == second.GetHashCode()); 155 | } 156 | 157 | /// 158 | /// Adds this parameter to the SQLHelper 159 | /// 160 | /// SQLHelper 161 | public abstract void AddParameter(DbCommand helper); 162 | 163 | /// 164 | /// Finds itself in the string command and adds the value 165 | /// 166 | /// Command to add to 167 | /// The resulting string 168 | public string AddParameter(string command) 169 | { 170 | if (string.IsNullOrEmpty(command)) 171 | return string.Empty; 172 | var StringValue = Value?.ToString() ?? "NULL"; 173 | return command.Replace(ParameterStarter + ID, typeof(TDataType) == typeof(string) ? "'" + StringValue + "'" : StringValue, StringComparison.OrdinalIgnoreCase); 174 | } 175 | 176 | /// 177 | /// Creates a copy of the parameter 178 | /// 179 | /// Suffix to add to the parameter (for batching purposes) 180 | /// A copy of the parameter 181 | public abstract IParameter CreateCopy(string suffix); 182 | 183 | /// 184 | /// Determines if the objects are equal 185 | /// 186 | /// Object to compare to 187 | /// True if they are equal, false otherwise 188 | public override bool Equals(object? obj) 189 | { 190 | return (obj is ParameterBase OtherParameter) 191 | && OtherParameter.DatabaseType == DatabaseType 192 | && OtherParameter.Direction == Direction 193 | && OtherParameter.ID == ID 194 | && (new ServiceCollection().AddCanisterModules().BuildServiceProvider().GetService>().Equals(OtherParameter.Value, Value)); 195 | } 196 | 197 | /// 198 | /// Returns a hash code for this instance. 199 | /// 200 | /// 201 | /// A hash code for this instance, suitable for use in hashing algorithms and data 202 | /// structures like a hash table. 203 | /// 204 | public override int GetHashCode() 205 | { 206 | var hashCode = 2030399226; 207 | hashCode = (hashCode * -1521134295) + DatabaseType.GetHashCode(); 208 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ID); 209 | return (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Value); 210 | } 211 | 212 | /// 213 | /// Returns the string version of the parameter 214 | /// 215 | /// The string representation of the parameter 216 | public override string ToString() => ParameterStarter + ID; 217 | } 218 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/Connection.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Microsoft.Extensions.Configuration; 18 | using SQLHelperDBTests.HelperClasses.Interfaces; 19 | using System; 20 | using System.Data.Common; 21 | using System.Data.SqlClient; 22 | using System.Text.RegularExpressions; 23 | 24 | namespace SQLHelperDBTests.HelperClasses 25 | { 26 | /// 27 | /// Data source class 28 | /// 29 | /// 30 | public class Connection : IConnection 31 | { 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | /// The configuration. 36 | /// The factory. 37 | /// The name. 38 | public Connection(IConfiguration configuration, DbProviderFactory factory, string name) 39 | : this(configuration, factory, string.Empty, name) 40 | { 41 | } 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// The configuration. 47 | /// The factory. 48 | /// The connection. 49 | /// The name. 50 | /// The parameter prefix. 51 | /// The retries. 52 | /// configuration 53 | public Connection(IConfiguration configuration, DbProviderFactory factory, string connection, string name, string parameterPrefix = "@", int retries = 0) 54 | { 55 | Retries = retries; 56 | Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 57 | Name = string.IsNullOrEmpty(name) ? "Default" : name; 58 | Factory = factory ?? Microsoft.Data.SqlClient.SqlClientFactory.Instance; 59 | SourceType = Factory.GetType().FullName ?? string.Empty; 60 | var TempConfig = configuration.GetConnectionString(Name); 61 | ConnectionString = !string.IsNullOrEmpty(connection) ? connection : (TempConfig ?? Name); 62 | if (string.IsNullOrEmpty(parameterPrefix)) 63 | { 64 | if (Factory is Microsoft.Data.SqlClient.SqlClientFactory || Factory is SqlClientFactory) 65 | { 66 | DatabaseName = DatabaseNameRegex.Match(ConnectionString).Groups[1].Value; 67 | ParameterPrefix = "@"; 68 | } 69 | else if (SourceType.Contains("Oracle", StringComparison.OrdinalIgnoreCase)) 70 | { 71 | ParameterPrefix = ":"; 72 | } 73 | } 74 | else 75 | { 76 | ParameterPrefix = parameterPrefix; 77 | if (Factory is Microsoft.Data.SqlClient.SqlClientFactory || Factory is SqlClientFactory) 78 | { 79 | DatabaseName = DatabaseNameRegex.Match(ConnectionString).Groups[1].Value; 80 | } 81 | } 82 | if (ConnectionTimeoutRegex.IsMatch(ConnectionString)) 83 | { 84 | var TimeoutValue = ConnectionTimeoutRegex.Match(ConnectionString).Groups[2].Value; 85 | CommandTimeout = int.TryParse(TimeoutValue, out var TempCommandTimeout) ? TempCommandTimeout : 30; 86 | } 87 | CommandTimeout = CommandTimeout <= 0 ? 30 : CommandTimeout; 88 | } 89 | 90 | /// 91 | /// Gets the command timeout. 92 | /// 93 | /// The command timeout. 94 | public int CommandTimeout { get; } 95 | 96 | /// 97 | /// Gets the configuration information. 98 | /// 99 | /// Gets the configuration information. 100 | public IConfiguration Configuration { get; } 101 | 102 | /// 103 | /// Connection string 104 | /// 105 | public string ConnectionString { get; protected set; } 106 | 107 | /// 108 | /// Gets the database. 109 | /// 110 | /// The database. 111 | public string? DatabaseName { get; protected set; } 112 | 113 | /// 114 | /// Gets the factory that the system uses to actually do the connection. 115 | /// 116 | /// The factory that the system needs to actually do the connection. 117 | public DbProviderFactory Factory { get; protected set; } 118 | 119 | /// 120 | /// Name of the source 121 | /// 122 | /// The name. 123 | public string Name { get; protected set; } 124 | 125 | /// 126 | /// Parameter prefix that the source uses 127 | /// 128 | /// The parameter prefix. 129 | public string ParameterPrefix { get; protected set; } 130 | 131 | /// 132 | /// Gets the number of retries if unable to connect. 133 | /// 134 | /// The number of retries if unable to connect. 135 | public int Retries { get; protected set; } 136 | 137 | /// 138 | /// Source type, based on ADO.Net provider name or identifier used by CUL 139 | /// 140 | /// The type of the source. 141 | public string SourceType { get; protected set; } 142 | 143 | /// 144 | /// Gets the connection timeout regex. 145 | /// 146 | /// The connection timeout regex. 147 | private static Regex ConnectionTimeoutRegex { get; } = new Regex("Connect(ion)? Timeout=([^;]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); 148 | 149 | /// 150 | /// Gets the database. 151 | /// 152 | /// The database. 153 | private static Regex DatabaseNameRegex { get; } = new Regex("Initial Catalog=([^;]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); 154 | } 155 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/Interfaces/IBatch.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Data; 20 | using System.Threading.Tasks; 21 | 22 | namespace SQLHelperDBTests.HelperClasses.Interfaces 23 | { 24 | /// 25 | /// A batch of commands interface 26 | /// 27 | public interface IBatch 28 | { 29 | /// 30 | /// Number of commands being batched 31 | /// 32 | int CommandCount { get; } 33 | 34 | /// 35 | /// Adds a command to be batched 36 | /// 37 | /// The type of the callback data. 38 | /// Callback action 39 | /// Object used in the callback action 40 | /// 41 | /// Determines if this command is a "header" and should be carried across batches. 42 | /// 43 | /// Command (SQL or stored procedure) to run 44 | /// Command type 45 | /// Parameters to add 46 | /// This 47 | IBatch AddQuery(Action, TCallbackData> callBack, TCallbackData callbackObject, bool header, string command, CommandType commandType, params object[]? parameters); 48 | 49 | /// 50 | /// Adds a batch's commands to the current batch 51 | /// 52 | /// Batch to add 53 | /// This 54 | IBatch AddQuery(IBatch batch); 55 | 56 | /// 57 | /// Clears this instance. 58 | /// 59 | /// This. 60 | IBatch Clear(); 61 | 62 | /// 63 | /// Executes the commands and returns the results 64 | /// 65 | /// The results of the batched commands 66 | List> Execute(); 67 | 68 | /// 69 | /// Executes the commands and returns the results (async) 70 | /// 71 | /// The results of the batched commands 72 | Task>> ExecuteAsync(); 73 | 74 | /// 75 | /// Removes duplicate commands from the batch 76 | /// 77 | /// This 78 | IBatch RemoveDuplicateCommands(); 79 | 80 | /// 81 | /// Sets the connection. 82 | /// 83 | /// The database connection. 84 | void SetConnection(IConnection databaseConnection); 85 | } 86 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/Interfaces/ICommand.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Collections.Generic; 18 | using System.Data; 19 | 20 | namespace SQLHelperDBTests.HelperClasses.Interfaces 21 | { 22 | /// 23 | /// Command interface 24 | /// 25 | public interface ICommand 26 | { 27 | /// 28 | /// Command type 29 | /// 30 | CommandType CommandType { get; } 31 | 32 | /// 33 | /// Gets a value indicating whether this is finalizable. 34 | /// 35 | /// true if finalizable; otherwise, false. 36 | bool Finalizable { get; } 37 | 38 | /// 39 | /// Parameters associated with the command 40 | /// 41 | IParameter[] Parameters { get; } 42 | 43 | /// 44 | /// Actual SQL command 45 | /// 46 | string SQLCommand { get; } 47 | 48 | /// 49 | /// Gets a value indicating whether [transaction needed]. 50 | /// 51 | /// true if [transaction needed]; otherwise, false. 52 | bool TransactionNeeded { get; } 53 | 54 | /// 55 | /// Called after the command is run 56 | /// 57 | /// Result of the command 58 | void Finalize(List result); 59 | } 60 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/Interfaces/IConnection.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Microsoft.Extensions.Configuration; 18 | using System.Data.Common; 19 | 20 | namespace SQLHelperDBTests.HelperClasses.Interfaces 21 | { 22 | /// 23 | /// Data source interface 24 | /// 25 | public interface IConnection 26 | { 27 | /// 28 | /// Gets the configuration information. 29 | /// 30 | /// Gets the configuration information. 31 | IConfiguration Configuration { get; } 32 | 33 | /// 34 | /// Connection string 35 | /// 36 | /// The connection. 37 | string ConnectionString { get; } 38 | 39 | /// 40 | /// Gets the database. 41 | /// 42 | /// The database. 43 | string? DatabaseName { get; } 44 | 45 | /// 46 | /// Gets the factory that the system uses to actually do the connection. 47 | /// 48 | /// The factory that the system needs to actually do the connection. 49 | DbProviderFactory Factory { get; } 50 | 51 | /// 52 | /// Name of the source 53 | /// 54 | /// The name. 55 | string Name { get; } 56 | 57 | /// 58 | /// Parameter prefix that the source uses 59 | /// 60 | /// The parameter prefix. 61 | string ParameterPrefix { get; } 62 | 63 | /// 64 | /// Gets the number of retries if unable to connect. 65 | /// 66 | /// The number of retries if unable to connect. 67 | int Retries { get; } 68 | 69 | /// 70 | /// Source type, based on ADO.Net provider name 71 | /// 72 | /// The type of the source. 73 | string SourceType { get; } 74 | } 75 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/Interfaces/IParameter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Data; 18 | using System.Data.Common; 19 | 20 | namespace SQLHelperDBTests.HelperClasses.Interfaces 21 | { 22 | /// 23 | /// Parameter interface 24 | /// 25 | /// Value type 26 | public interface IParameter : IParameter 27 | { 28 | /// 29 | /// The value that the parameter is associated with 30 | /// 31 | T Value { get; set; } 32 | } 33 | 34 | /// 35 | /// Parameter interface 36 | /// 37 | public interface IParameter 38 | { 39 | /// 40 | /// Database type 41 | /// 42 | DbType DatabaseType { get; set; } 43 | 44 | /// 45 | /// Direction of the parameter 46 | /// 47 | ParameterDirection Direction { get; set; } 48 | 49 | /// 50 | /// The name that the parameter goes by 51 | /// 52 | string ID { get; set; } 53 | 54 | /// 55 | /// Gets the internal value. 56 | /// 57 | /// The internal value. 58 | object? InternalValue { get; } 59 | 60 | /// 61 | /// Gets the parameter starter. 62 | /// 63 | /// The parameter starter. 64 | string ParameterStarter { get; } 65 | 66 | /// 67 | /// Adds this parameter to the SQLHelper 68 | /// 69 | /// SQLHelper 70 | void AddParameter(DbCommand helper); 71 | 72 | /// 73 | /// Finds itself in the string command and adds the value 74 | /// 75 | /// Command to add to 76 | /// The resulting string 77 | string AddParameter(string command); 78 | 79 | /// 80 | /// Creates a copy of the parameter 81 | /// 82 | /// Suffix to add to the parameter (for batching purposes) 83 | /// A copy of the parameter 84 | IParameter CreateCopy(string suffix); 85 | } 86 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/Parameter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using SQLHelperDB.ExtensionMethods; 18 | using SQLHelperDBTests.HelperClasses.BaseClasses; 19 | using SQLHelperDBTests.HelperClasses.Interfaces; 20 | using System.Data; 21 | using System.Data.Common; 22 | 23 | namespace SQLHelperDBTests.HelperClasses 24 | { 25 | /// 26 | /// Holds parameter information 27 | /// 28 | /// Data type of the parameter 29 | public class Parameter : ParameterBase 30 | { 31 | /// 32 | /// Constructor 33 | /// 34 | /// ID of the parameter 35 | /// Value of the parameter 36 | /// Direction of the parameter 37 | /// Parameter starter 38 | public Parameter(string id, TDataType value, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 39 | : base(id, value, direction, parameterStarter) 40 | { 41 | } 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// ID of the parameter 47 | /// Database type 48 | /// Value of the parameter 49 | /// Direction of the parameter 50 | /// Parameter starter 51 | public Parameter(string id, SqlDbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 52 | : base(id, type, value, direction, parameterStarter) 53 | { 54 | } 55 | 56 | /// 57 | /// Constructor 58 | /// 59 | /// ID of the parameter 60 | /// Database type 61 | /// Value of the parameter 62 | /// Direction of the parameter 63 | /// Parameter starter 64 | public Parameter(string id, DbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 65 | : base(id, type, value, direction, parameterStarter) 66 | { 67 | } 68 | 69 | /// 70 | /// Adds this parameter to the SQLHelper 71 | /// 72 | /// SQLHelper 73 | public override void AddParameter(DbCommand helper) => helper.AddParameter(ID, DatabaseType, Value, Direction); 74 | 75 | /// 76 | /// Creates a copy of the parameter 77 | /// 78 | /// Suffix to add to the parameter (for batching purposes) 79 | /// A copy of the parameter 80 | public override IParameter CreateCopy(string suffix) => new Parameter(ID + suffix, DatabaseType, Value, Direction, ParameterStarter); 81 | } 82 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/SelectFinder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Antlr4.Runtime.Misc; 18 | using SQLParser.Parsers.TSql; 19 | 20 | namespace SQLHelperDBTests.HelperClasses 21 | { 22 | /// 23 | /// Finds selects within SQL code. 24 | /// 25 | /// 26 | public class SelectFinder : TSqlParserBaseListener 27 | { 28 | /// 29 | /// Gets or sets a value indicating whether a select [statement found]. 30 | /// 31 | /// true if [statement found]; otherwise, false. 32 | public bool StatementFound { get; set; } 33 | 34 | /// 35 | /// Enter a parse tree produced by . 36 | /// The default implementation does nothing. 37 | /// 38 | /// The parse tree. 39 | public override void EnterDml_clause([NotNull] TSqlParser.Dml_clauseContext context) 40 | { 41 | var SelectStatement = context?.select_statement_standalone()?.select_statement(); 42 | if (!(SelectStatement is null)) 43 | { 44 | StatementFound |= !(SelectStatement.query_expression().query_specification() is null); 45 | } 46 | base.EnterDml_clause(context); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/HelperClasses/StringParameter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using SQLHelperDB.ExtensionMethods; 18 | using SQLHelperDBTests.HelperClasses.BaseClasses; 19 | using SQLHelperDBTests.HelperClasses.Interfaces; 20 | using System.Data; 21 | using System.Data.Common; 22 | 23 | namespace SQLHelperDBTests.HelperClasses 24 | { 25 | /// 26 | /// Holds parameter information 27 | /// 28 | public class StringParameter : ParameterBase 29 | { 30 | /// 31 | /// Constructor 32 | /// 33 | /// ID of the parameter 34 | /// Value of the parameter 35 | /// Direction of the parameter 36 | /// Parameter starter 37 | public StringParameter(string id, string value, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 38 | : base(id, DbType.String, value, direction, parameterStarter) 39 | { 40 | } 41 | 42 | /// 43 | /// Adds this parameter to the SQLHelper 44 | /// 45 | /// SQLHelper 46 | public override void AddParameter(DbCommand helper) => helper.AddParameter(ID, Value, Direction); 47 | 48 | /// 49 | /// Creates a copy of the parameter 50 | /// 51 | /// Suffix to add to the parameter (for batching purposes) 52 | /// A copy of the parameter 53 | public override IParameter CreateCopy(string suffix) => new StringParameter(ID + suffix, Value, Direction, ParameterStarter); 54 | } 55 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/AltImplementations/SQLHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using BigBook; 18 | using Microsoft.Extensions.Configuration; 19 | using ObjectCartographer; 20 | using SQLHelperDBTests.HelperClasses; 21 | using SQLHelperDBTests.HelperClasses.Interfaces; 22 | using System; 23 | using System.Collections.Concurrent; 24 | using System.Collections.Generic; 25 | using System.Data; 26 | using System.Data.Common; 27 | using System.Linq; 28 | using System.Threading.Tasks; 29 | 30 | namespace SQLHelperDBTests 31 | { 32 | /// 33 | /// SQL helper class 34 | /// 35 | public class SQLHelper 36 | { 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// The configuration object. 41 | /// The factory. 42 | /// The database. 43 | public SQLHelper(IConfiguration configuration, DbProviderFactory? factory = null, string database = "Default") 44 | : this(Connections.ContainsKey(database) ? Connections[database] : new Connection(configuration, factory ?? Microsoft.Data.SqlClient.SqlClientFactory.Instance, database)) 45 | { 46 | } 47 | 48 | /// 49 | /// Initializes a new instance of the class. 50 | /// 51 | /// The connection to use. 52 | public SQLHelper(IConnection connection) 53 | { 54 | SetConnection(connection); 55 | } 56 | 57 | /// 58 | /// Gets the number of commands currently in the batch. 59 | /// 60 | /// The number of commands currently in the batch 61 | public int Count => Batch.CommandCount; 62 | 63 | /// 64 | /// Gets or sets the source. 65 | /// 66 | /// The source. 67 | public IConnection DatabaseConnection { get; private set; } 68 | 69 | /// 70 | /// Gets the batch. 71 | /// 72 | /// The batch. 73 | protected IBatch Batch { get; private set; } 74 | 75 | /// 76 | /// Gets the connections. 77 | /// 78 | /// The connections. 79 | private static ConcurrentDictionary Connections { get; } = new ConcurrentDictionary(); 80 | 81 | /// 82 | /// Adds a query that gets carried across in internal batches. 83 | /// 84 | /// Type of the command. 85 | /// The command. 86 | /// The parameters. 87 | /// This 88 | public SQLHelper AddHeader(CommandType commandType, string command, params object[]? parameters) => AddHeader(DefaultAction, null!, commandType, command, parameters); 89 | 90 | /// 91 | /// Adds a query that gets carried across in internal batches. 92 | /// 93 | /// The type of the callback data. 94 | /// The callback. 95 | /// The callback object. 96 | /// Type of the command. 97 | /// The command. 98 | /// The parameters. 99 | /// This 100 | public SQLHelper AddHeader(Action, TCallbackData> callback, TCallbackData callbackObject, 101 | CommandType commandType, string command, params object[]? parameters) 102 | { 103 | Batch.AddQuery(callback, callbackObject, true, command, commandType, parameters); 104 | return this; 105 | } 106 | 107 | /// 108 | /// Adds a command. 109 | /// 110 | /// Type of the command. 111 | /// The command. 112 | /// The parameters. 113 | /// This 114 | public SQLHelper AddQuery(CommandType commandType, string command, params object[]? parameters) => AddQuery(DefaultAction, null!, commandType, command, parameters); 115 | 116 | /// 117 | /// Adds a command which will call the callback function with the object specified when it 118 | /// 119 | /// The type of the callback data. 120 | /// The callback. 121 | /// The callback object. 122 | /// Type of the command. 123 | /// The command. 124 | /// The parameters. 125 | /// This 126 | public SQLHelper AddQuery(Action, TCallbackData> callback, TCallbackData callbackObject, 127 | CommandType commandType, string command, params object[]? parameters) 128 | { 129 | Batch.AddQuery(callback, callbackObject, false, command, commandType, parameters); 130 | return this; 131 | } 132 | 133 | /// 134 | /// Adds an SQLHelper's commands to this instance 135 | /// 136 | /// The helper to copy the commands from 137 | /// This 138 | public SQLHelper AddQuery(SQLHelper helper) 139 | { 140 | if (!(helper is null)) 141 | Batch.AddQuery(helper.Batch); 142 | return this; 143 | } 144 | 145 | /// 146 | /// Clears the system and creates a new batch. 147 | /// 148 | /// This 149 | public SQLHelper CreateBatch() 150 | { 151 | Batch.Clear(); 152 | return this; 153 | } 154 | 155 | /// 156 | /// Creates the batch using the connection specified. 157 | /// 158 | /// The connection. 159 | /// This 160 | public SQLHelper CreateBatch(IConnection connection) 161 | { 162 | Batch.Clear(); 163 | SetConnection(connection); 164 | return this; 165 | } 166 | 167 | /// 168 | /// Creates the batch using the connection info specified. 169 | /// 170 | /// The configuration. 171 | /// The factory. 172 | /// The database. 173 | /// This. 174 | public SQLHelper CreateBatch(IConfiguration configuration, DbProviderFactory? factory = null, string database = "Default") => CreateBatch(Connections.ContainsKey(database) ? Connections[database] : new Connection(configuration, factory ?? Microsoft.Data.SqlClient.SqlClientFactory.Instance, database)); 175 | 176 | /// 177 | /// Executes this instance. 178 | /// 179 | /// The results of the batched queries. 180 | public List> Execute() => Batch.Execute(); 181 | 182 | /// 183 | /// Executes the queries asynchronously. 184 | /// 185 | /// The result of the queries 186 | public Task>> ExecuteAsync() => Batch.ExecuteAsync(); 187 | 188 | /// 189 | /// Executes the batched commands and returns the first value, ignoring the rest. 190 | /// 191 | /// The type of the data to return. 192 | /// The default value. 193 | /// The first value of the batch 194 | public TData ExecuteScalar(TData defaultValue = default) => AsyncHelper.RunSync(() => ExecuteScalarAsync(defaultValue)); 195 | 196 | /// 197 | /// Executes the batched commands and returns the first value, ignoring the rest (async). 198 | /// 199 | /// The type of the data to return. 200 | /// The default value. 201 | /// The first value of the batch 202 | public async Task ExecuteScalarAsync(TData defaultValue = default) 203 | { 204 | var BatchResults = await Batch.ExecuteAsync().ConfigureAwait(false); 205 | if (BatchResults.Count == 0 || BatchResults[0].Count == 0) 206 | return defaultValue; 207 | if (!(BatchResults[0][0] is IDictionary Value)) 208 | return ((object)BatchResults[0][0]).To(defaultValue); 209 | return Value[Value.Keys.First()].To(defaultValue); 210 | } 211 | 212 | /// 213 | /// Removes duplicate queries from the batch. 214 | /// 215 | /// This 216 | public SQLHelper RemoveDuplicateCommands() 217 | { 218 | Batch.RemoveDuplicateCommands(); 219 | return this; 220 | } 221 | 222 | /// 223 | /// Returns a that represents this instance. 224 | /// 225 | /// A that represents this instance. 226 | public override string ToString() => Batch.ToString() ?? string.Empty; 227 | 228 | /// 229 | /// The default action 230 | /// 231 | /// Ignored 232 | /// Ignored 233 | /// Ignored 234 | private static void DefaultAction(ICommand ___, List __, object _) 235 | { } 236 | 237 | /// 238 | /// Sets the connection. 239 | /// 240 | /// The connection. 241 | private void SetConnection(IConnection connection) 242 | { 243 | DatabaseConnection = connection ?? throw new ArgumentNullException(nameof(connection)); 244 | if (!Connections.ContainsKey(connection.Name)) 245 | Connections.AddOrUpdate(connection.Name, connection, (_, value) => value); 246 | Batch ??= new Batch(DatabaseConnection); 247 | Batch.SetConnection(DatabaseConnection); 248 | } 249 | } 250 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/MassInsert.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.ObjectPool; 5 | using SQLHelperDB.ExtensionMethods; 6 | using System; 7 | using System.Data; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SQLHelperDB.SpeedTests.Tests 12 | { 13 | /// 14 | /// Mass insert test. 15 | /// 16 | [MemoryDiagnoser] 17 | public class MassInsert 18 | { 19 | /// 20 | /// Gets or sets the count. 21 | /// 22 | /// The count. 23 | [Params(1, 10, 100)] 24 | public int Count { get; set; } 25 | 26 | /// 27 | /// Gets or sets the helper. 28 | /// 29 | /// The helper. 30 | private SQLHelper? Helper { get; set; } 31 | 32 | /// 33 | /// Gets or sets the helper2. 34 | /// 35 | /// The helper2. 36 | private SQLHelperDBTests.SQLHelper? Helper2 { get; set; } 37 | 38 | /// 39 | /// Cleanups this instance. 40 | /// 41 | [GlobalCleanup] 42 | public void Cleanup() 43 | { 44 | using System.Data.Common.DbConnection TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection(); 45 | TempConnection.ConnectionString = "Data Source=localhost;Initial Catalog=master;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True"; 46 | using System.Data.Common.DbCommand TempCommand = TempConnection.CreateCommand(); 47 | try 48 | { 49 | TempCommand.CommandText = "ALTER DATABASE SpeedTestDatabase SET OFFLINE WITH ROLLBACK IMMEDIATE\r\nALTER DATABASE SpeedTestDatabase SET ONLINE\r\nDROP DATABASE SpeedTestDatabase"; 50 | _ = TempCommand.Open(3); 51 | _ = TempCommand.ExecuteNonQuery(); 52 | } 53 | finally { _ = TempCommand.Close(); } 54 | } 55 | 56 | /// 57 | /// Runs the changes. 58 | /// 59 | [Benchmark] 60 | public async Task RunChanges() 61 | { 62 | _ = Helper2.CreateBatch(); 63 | for (var X = 0; X < Count; ++X) 64 | { 65 | _ = Helper2.AddQuery(CommandType.Text, 66 | "INSERT INTO [SpeedTestDatabase].[dbo].[TestTable](StringValue1,StringValue2,BigIntValue,BitValue,DecimalValue,FloatValue,DateTimeValue,GUIDValue,TimeSpanValue) VALUES(@0,@1,@2,@3,@4,@5,@6,@7,@8)", 67 | "A", 68 | "B", 69 | 10, 70 | true, 71 | 75.12m, 72 | 4.53f, 73 | new DateTime(2010, 1, 1), 74 | Guid.NewGuid(), 75 | new TimeSpan(1, 0, 0)); 76 | } 77 | _ = await Helper2.ExecuteScalarAsync().ConfigureAwait(false); 78 | } 79 | 80 | /// 81 | /// Runs the current. 82 | /// 83 | [Benchmark(Baseline = true)] 84 | public async Task RunCurrent() 85 | { 86 | _ = Helper.CreateBatch(); 87 | for (var X = 0; X < Count; ++X) 88 | { 89 | _ = Helper.AddQuery(CommandType.Text, 90 | "INSERT INTO [SpeedTestDatabase].[dbo].[TestTable](StringValue1,StringValue2,BigIntValue,BitValue,DecimalValue,FloatValue,DateTimeValue,GUIDValue,TimeSpanValue) VALUES(@0,@1,@2,@3,@4,@5,@6,@7,@8)", 91 | "A", 92 | "B", 93 | 10, 94 | true, 95 | 75.12m, 96 | 4.53f, 97 | new DateTime(2010, 1, 1), 98 | Guid.NewGuid(), 99 | new TimeSpan(1, 0, 0)); 100 | } 101 | _ = await Helper.ExecuteScalarAsync().ConfigureAwait(false); 102 | } 103 | 104 | /// 105 | /// Setups this instance. 106 | /// 107 | [GlobalSetup] 108 | public void Setup() 109 | { 110 | ServiceProvider Services = new ServiceCollection().AddCanisterModules(x => x.AddAssembly(typeof(Program).Assembly) 111 | .RegisterSQLHelper()).BuildServiceProvider(); 112 | Helper = new SQLHelper(Services.GetService>(), Services.GetService(), null); 113 | Helper2 = new SQLHelperDBTests.SQLHelper(Services.GetService(), Microsoft.Data.SqlClient.SqlClientFactory.Instance); 114 | 115 | using (System.Data.Common.DbConnection TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection()) 116 | { 117 | TempConnection.ConnectionString = "Data Source=localhost;Initial Catalog=master;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True"; 118 | using System.Data.Common.DbCommand TempCommand = TempConnection.CreateCommand(); 119 | try 120 | { 121 | TempCommand.CommandText = "Create Database SpeedTestDatabase"; 122 | _ = TempCommand.Open(3); 123 | _ = TempCommand.ExecuteNonQuery(); 124 | } 125 | catch { } 126 | finally { _ = TempCommand.Close(); } 127 | } 128 | 129 | using (System.Data.Common.DbConnection TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection()) 130 | { 131 | TempConnection.ConnectionString = "Data Source=localhost;Initial Catalog=SpeedTestDatabase;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True"; 132 | using System.Data.Common.DbCommand TempCommand = TempConnection.CreateCommand(); 133 | try 134 | { 135 | TempCommand.CommandText = "Create Table TestTable(ID INT PRIMARY KEY IDENTITY,StringValue1 NVARCHAR(100),StringValue2 NVARCHAR(MAX),BigIntValue BIGINT,BitValue BIT,DecimalValue DECIMAL(12,6),FloatValue FLOAT,DateTimeValue DATETIME,GUIDValue UNIQUEIDENTIFIER,TimeSpanValue TIME(7))"; 136 | _ = TempCommand.Open(3); 137 | _ = TempCommand.ExecuteNonQuery(); 138 | TempCommand.CommandText = "Create Table TestTableNotNull(ID INT PRIMARY KEY IDENTITY,UShortValue_ SMALLINT NOT NULL)"; 139 | _ = TempCommand.ExecuteNonQuery(); 140 | } 141 | catch { } 142 | finally { _ = TempCommand.Close(); } 143 | } 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/RegexOrContains.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace SQLHelper.SpeedTests.Tests 6 | { 7 | [MemoryDiagnoser] 8 | public class RegexOrContains 9 | { 10 | private static Regex FirstPass { get; } = new Regex("(INSERT)|(UPDATE)|(DELETE)|(CREATE)|(ALTER)|(INTO)|(DROP)", RegexOptions.IgnoreCase | RegexOptions.Compiled); 11 | private static Regex SecondPass { get; } = new Regex("(ALTER DATABASE)|(CREATE DATABASE)", RegexOptions.IgnoreCase | RegexOptions.Compiled); 12 | private string QueryText { get; } = "IF NOT EXISTS (SELECT TOP 1 ID_ FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 6701 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 1341) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (6701,1341) END;"; 13 | 14 | [Benchmark] 15 | public void Contains() 16 | { 17 | _ = (QueryText.Contains("INSERT", StringComparison.Ordinal) 18 | || QueryText.Contains("UPDATE", StringComparison.Ordinal) 19 | || QueryText.Contains("DELETE", StringComparison.Ordinal) 20 | || QueryText.Contains("CREATE", StringComparison.Ordinal) 21 | || QueryText.Contains("ALTER", StringComparison.Ordinal) 22 | || QueryText.Contains("INTO", StringComparison.Ordinal) 23 | || QueryText.Contains("DROP", StringComparison.Ordinal)) 24 | && (!(QueryText.Contains("ALTER DATABASE", StringComparison.Ordinal) 25 | || QueryText.Contains("CREATE DATABASE", StringComparison.Ordinal))); 26 | } 27 | 28 | [Benchmark(Baseline = true)] 29 | public void ContainsIgnoreCase() 30 | { 31 | _ = (QueryText.Contains("INSERT", StringComparison.OrdinalIgnoreCase) 32 | || QueryText.Contains("UPDATE", StringComparison.OrdinalIgnoreCase) 33 | || QueryText.Contains("DELETE", StringComparison.OrdinalIgnoreCase) 34 | || QueryText.Contains("CREATE", StringComparison.OrdinalIgnoreCase) 35 | || QueryText.Contains("ALTER", StringComparison.OrdinalIgnoreCase) 36 | || QueryText.Contains("INTO", StringComparison.OrdinalIgnoreCase) 37 | || QueryText.Contains("DROP", StringComparison.OrdinalIgnoreCase)) 38 | && (!(QueryText.Contains("ALTER DATABASE", StringComparison.OrdinalIgnoreCase) 39 | || QueryText.Contains("CREATE DATABASE", StringComparison.OrdinalIgnoreCase))); 40 | } 41 | 42 | [Benchmark] 43 | public void RegexCall() 44 | { 45 | _ = FirstPass.IsMatch(QueryText) && !SecondPass.IsMatch(QueryText); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/StringReplace.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.ObjectPool; 3 | using System; 4 | using System.Text; 5 | 6 | namespace SQLHelper.SpeedTests.Tests 7 | { 8 | [MemoryDiagnoser] 9 | public class StringReplace 10 | { 11 | [Params(1, 10, 100, 1000, 10000)] 12 | public int Count { get; set; } 13 | 14 | public string Value { get; set; } 15 | private ObjectPool StringBuilderPool { get; set; } 16 | 17 | [GlobalSetup] 18 | public void Setup() 19 | { 20 | var objectPoolProvider = new DefaultObjectPoolProvider(); 21 | StringBuilderPool = objectPoolProvider.CreateStringBuilderPool(); 22 | for (var x = 0; x < char.MaxValue; ++x) 23 | { 24 | Value += (char)x + " "; 25 | } 26 | } 27 | 28 | [Benchmark(Baseline = true)] 29 | public void StringBuilderPools() 30 | { 31 | var Builder = StringBuilderPool.Get(); 32 | Builder.Append(Value); 33 | for (var x = 0; x < Count; ++x) 34 | { 35 | Builder.Replace("A", "C"); 36 | } 37 | _ = Builder.ToString(); 38 | StringBuilderPool.Return(Builder); 39 | } 40 | 41 | [Benchmark] 42 | public void StringConcat() => _ = Value.Replace("A", "C", StringComparison.Ordinal); 43 | } 44 | } -------------------------------------------------------------------------------- /SQLHelper.SpeedTests/Tests/ZStringVsStringBuilderPools.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Cysharp.Text; 3 | using Microsoft.Extensions.ObjectPool; 4 | using System.Text; 5 | 6 | namespace SQLHelper.SpeedTests.Tests 7 | { 8 | [MemoryDiagnoser] 9 | public class ZStringVsStringBuilderPools 10 | { 11 | [Params(1, 10, 100, 1000, 10000)] 12 | public int Count { get; set; } 13 | 14 | private ObjectPool StringBuilderPool { get; set; } 15 | 16 | [GlobalSetup] 17 | public void Setup() 18 | { 19 | var objectPoolProvider = new DefaultObjectPoolProvider(); 20 | StringBuilderPool = objectPoolProvider.CreateStringBuilderPool(); 21 | } 22 | 23 | [Benchmark(Baseline = true)] 24 | public void StringBuilderPools() 25 | { 26 | var Builder = StringBuilderPool.Get(); 27 | for (var x = 0; x < Count; ++x) 28 | { 29 | Builder.Append("Testing this"); 30 | Builder.AppendFormat(" out {0}", 12); 31 | Builder.AppendLine("Blah"); 32 | } 33 | _ = Builder.ToString(); 34 | StringBuilderPool.Return(Builder); 35 | } 36 | 37 | [Benchmark] 38 | public void StringConcat() 39 | { 40 | var Builder = string.Empty; 41 | for (var x = 0; x < Count; ++x) 42 | { 43 | Builder += "Testing this" + string.Format(" out {0}", 12) + "Blah"; 44 | } 45 | } 46 | 47 | [Benchmark] 48 | public void ZStringUse() 49 | { 50 | using var Builder = ZString.CreateUtf8StringBuilder(); 51 | for (var x = 0; x < Count; ++x) 52 | { 53 | Builder.Append("Testing this"); 54 | Builder.AppendFormat(" out {0}", 12); 55 | Builder.AppendLine("Blah"); 56 | } 57 | _ = Builder.ToString(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /SQLHelper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33815.320 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E92158B2-82F1-441D-B06A-54EE621AC498}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{414A3619-5FF0-4C66-8199-5C1E0A467D40}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9E153F81-41BF-4B3F-9488-15923085B8B2}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLHelper.Tests", "test\SQLHelper.Tests\SQLHelper.Tests.csproj", "{E0A10594-6D81-4890-A7D6-2D04F5D8567F}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLHelper.DB", "src\SQLHelper.DB\SQLHelper.DB.csproj", "{337AE3D9-0691-486A-BBE5-BCAA96F68548}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLHelper.SpeedTests", "SQLHelper.SpeedTests\SQLHelper.SpeedTests.csproj", "{54157F1F-9C1B-49ED-881D-FCCB38248A7F}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "TestApp\TestApp.csproj", "{6463FFD0-3528-4DCD-8333-909775AEC7B7}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLHelper.Example", "SQLHelper.Example\SQLHelper.Example.csproj", "{C957DF62-B9CC-4ADD-8289-71BC3C95370F}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {E0A10594-6D81-4890-A7D6-2D04F5D8567F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {E0A10594-6D81-4890-A7D6-2D04F5D8567F}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {E0A10594-6D81-4890-A7D6-2D04F5D8567F}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {E0A10594-6D81-4890-A7D6-2D04F5D8567F}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {337AE3D9-0691-486A-BBE5-BCAA96F68548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {337AE3D9-0691-486A-BBE5-BCAA96F68548}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {337AE3D9-0691-486A-BBE5-BCAA96F68548}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {337AE3D9-0691-486A-BBE5-BCAA96F68548}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {54157F1F-9C1B-49ED-881D-FCCB38248A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {54157F1F-9C1B-49ED-881D-FCCB38248A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {54157F1F-9C1B-49ED-881D-FCCB38248A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {54157F1F-9C1B-49ED-881D-FCCB38248A7F}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {6463FFD0-3528-4DCD-8333-909775AEC7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {6463FFD0-3528-4DCD-8333-909775AEC7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {6463FFD0-3528-4DCD-8333-909775AEC7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {6463FFD0-3528-4DCD-8333-909775AEC7B7}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {C957DF62-B9CC-4ADD-8289-71BC3C95370F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {C957DF62-B9CC-4ADD-8289-71BC3C95370F}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {C957DF62-B9CC-4ADD-8289-71BC3C95370F}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {C957DF62-B9CC-4ADD-8289-71BC3C95370F}.Release|Any CPU.Build.0 = Release|Any CPU 51 | EndGlobalSection 52 | GlobalSection(SolutionProperties) = preSolution 53 | HideSolutionNode = FALSE 54 | EndGlobalSection 55 | GlobalSection(NestedProjects) = preSolution 56 | {E0A10594-6D81-4890-A7D6-2D04F5D8567F} = {9E153F81-41BF-4B3F-9488-15923085B8B2} 57 | {337AE3D9-0691-486A-BBE5-BCAA96F68548} = {E92158B2-82F1-441D-B06A-54EE621AC498} 58 | {54157F1F-9C1B-49ED-881D-FCCB38248A7F} = {9E153F81-41BF-4B3F-9488-15923085B8B2} 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {89A38DC9-C76A-4343-8547-4B6EE09301D1} 62 | EndGlobalSection 63 | GlobalSection(Performance) = preSolution 64 | HasPerformanceSessions = true 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /TestApp/EventListener/SqlClientListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Tracing; 3 | 4 | namespace TestApp.EventListeners 5 | { 6 | public class SqlClientListener : EventListener 7 | { 8 | protected override void OnEventSourceCreated(EventSource eventSource) 9 | { 10 | // Only enable events from SqlClientEventSource. 11 | if (eventSource.Name.Equals("Microsoft.Data.SqlClient.EventSource")) 12 | { 13 | // Use EventKeyWord 2 to capture basic application flow events. See the above table 14 | // for all available keywords. 15 | EnableEvents(eventSource, EventLevel.Informational, (EventKeywords)2); 16 | } 17 | } 18 | 19 | // This callback runs whenever an event is written by SqlClientEventSource. Event data is 20 | // accessed through the EventWrittenEventArgs parameter. 21 | protected override void OnEventWritten(EventWrittenEventArgs eventData) 22 | { 23 | // Print event data. 24 | Console.WriteLine(eventData.Payload[0]); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /TestApp/Modules/ConfigurationModule.cs: -------------------------------------------------------------------------------- 1 | using Canister.Interfaces; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System.Collections.Generic; 5 | 6 | namespace TestApp.Modules 7 | { 8 | public class ConfigurationModule : IModule 9 | { 10 | public int Order => 1; 11 | 12 | protected string ConnectionString => "Data Source=localhost;Initial Catalog=SereneCMS;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True"; 13 | 14 | public void Load(IServiceCollection bootstrapper) 15 | { 16 | if (bootstrapper is null) 17 | return; 18 | var dict = new Dictionary 19 | { 20 | { "ConnectionStrings:Default", ConnectionString }, 21 | }; 22 | var Configuration = new ConfigurationBuilder() 23 | .AddInMemoryCollection(dict) 24 | .Build(); 25 | bootstrapper.AddSingleton(_ => Configuration); 26 | bootstrapper.AddSingleton(_ => Configuration); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /TestApp/Modules/LoggingModule.cs: -------------------------------------------------------------------------------- 1 | using Canister.Interfaces; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Serilog; 4 | 5 | namespace TestApp.Modules 6 | { 7 | public class LoggingModule : IModule 8 | { 9 | /// 10 | /// Order to run this in 11 | /// 12 | public int Order => 1; 13 | 14 | /// 15 | /// Loads the module using the bootstrapper 16 | /// 17 | /// The bootstrapper. 18 | public void Load(IServiceCollection bootstrapper) 19 | { 20 | if (bootstrapper is null) 21 | return; 22 | Log.Logger = new LoggerConfiguration() 23 | .WriteTo 24 | .File("./Log.txt") 25 | .MinimumLevel 26 | .Debug() 27 | .CreateLogger(); 28 | bootstrapper.AddSingleton(_ => Log.Logger); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBook; 2 | using Microsoft.Data.SqlClient; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using SQLHelperDB; 5 | using System; 6 | using System.Data; 7 | using System.Threading.Tasks; 8 | using TestApp.EventListeners; 9 | 10 | namespace TestApp 11 | { 12 | internal class Program 13 | { 14 | private static async Task Main(string[] args) 15 | { 16 | using (var Listener = new SqlClientListener()) 17 | { 18 | var Services = new ServiceCollection(); 19 | _ = Services.AddCanisterModules(x => x.AddAssembly(typeof(Program).Assembly).RegisterSQLHelper()); 20 | _ = Services.AddLogging(); 21 | IServiceProvider ServiceProvider = new DefaultServiceProviderFactory().CreateServiceProvider(Services); 22 | SQLHelper Helper = ServiceProvider.GetRequiredService(); 23 | 24 | System.Collections.Generic.List> Results = await Helper.CreateBatch(SqlClientFactory.Instance) 25 | .AddQuery(CommandType.Text, @";WITH MyDuplicate AS (SELECT 26 | Sch.[name] AS SchemaName, 27 | Obj.[name] AS TableName, 28 | Idx.[name] AS IndexName, 29 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 1) AS Col1, 30 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 2) AS Col2, 31 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 3) AS Col3, 32 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 4) AS Col4, 33 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 5) AS Col5, 34 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 6) AS Col6, 35 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 7) AS Col7, 36 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 8) AS Col8, 37 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 9) AS Col9, 38 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 10) AS Col10, 39 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 11) AS Col11, 40 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 12) AS Col12, 41 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 13) AS Col13, 42 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 14) AS Col14, 43 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 15) AS Col15, 44 | INDEX_COL(Sch.[name] + '.' + Obj.[name], Idx.index_id, 16) AS Col16 45 | FROM sys.indexes Idx 46 | INNER JOIN sys.objects Obj ON Idx.[object_id] = Obj.[object_id] INNER JOIN sys.schemas Sch ON Sch.[schema_id] = Obj.[schema_id] WHERE index_id > 0) 47 | SELECT MD1.SchemaName, MD1.TableName, MD1.IndexName, 48 | MD2.IndexName AS OverLappingIndex, 49 | MD1.Col1, MD1.Col2, MD1.Col3, MD1.Col4, 50 | MD1.Col5, MD1.Col6, MD1.Col7, MD1.Col8, 51 | MD1.Col9, MD1.Col10, MD1.Col11, MD1.Col12, 52 | MD1.Col13, MD1.Col14, MD1.Col15, MD1.Col16 53 | FROM MyDuplicate MD1 54 | INNER JOIN MyDuplicate MD2 ON MD1.tablename = MD2.tablename 55 | AND MD1.indexname <> MD2.indexname 56 | AND MD1.Col1 = MD2.Col1 57 | AND (MD1.Col2 IS NULL OR MD2.Col2 IS NULL OR MD1.Col2 = MD2.Col2) 58 | AND (MD1.Col3 IS NULL OR MD2.Col3 IS NULL OR MD1.Col3 = MD2.Col3) 59 | AND (MD1.Col4 IS NULL OR MD2.Col4 IS NULL OR MD1.Col4 = MD2.Col4) 60 | AND (MD1.Col5 IS NULL OR MD2.Col5 IS NULL OR MD1.Col5 = MD2.Col5) 61 | AND (MD1.Col6 IS NULL OR MD2.Col6 IS NULL OR MD1.Col6 = MD2.Col6) 62 | AND (MD1.Col7 IS NULL OR MD2.Col7 IS NULL OR MD1.Col7 = MD2.Col7) 63 | AND (MD1.Col8 IS NULL OR MD2.Col8 IS NULL OR MD1.Col8 = MD2.Col8) 64 | AND (MD1.Col9 IS NULL OR MD2.Col9 IS NULL OR MD1.Col9 = MD2.Col9) 65 | AND (MD1.Col10 IS NULL OR MD2.Col10 IS NULL OR MD1.Col10 = MD2.Col10) 66 | AND (MD1.Col11 IS NULL OR MD2.Col11 IS NULL OR MD1.Col11 = MD2.Col11) 67 | AND (MD1.Col12 IS NULL OR MD2.Col12 IS NULL OR MD1.Col12 = MD2.Col12) 68 | AND (MD1.Col13 IS NULL OR MD2.Col13 IS NULL OR MD1.Col13 = MD2.Col13) 69 | AND (MD1.Col14 IS NULL OR MD2.Col14 IS NULL OR MD1.Col14 = MD2.Col14) 70 | AND (MD1.Col15 IS NULL OR MD2.Col15 IS NULL OR MD1.Col15 = MD2.Col15) 71 | AND (MD1.Col16 IS NULL OR MD2.Col16 IS NULL OR MD1.Col16 = MD2.Col16) 72 | ORDER BY MD1.SchemaName,MD1.TableName,MD1.IndexName") 73 | .AddQuery(CommandType.Text, @"SELECT TOP 25 74 | dm_mid.database_id AS DatabaseID, 75 | dm_migs.avg_user_impact*(dm_migs.user_seeks+dm_migs.user_scans) Avg_Estimated_Impact, 76 | dm_migs.last_user_seek AS Last_User_Seek, 77 | OBJECT_NAME(dm_mid.OBJECT_ID,dm_mid.database_id) AS [TableName], 78 | 'CREATE INDEX [IX_' + OBJECT_NAME(dm_mid.OBJECT_ID,dm_mid.database_id) + '_' 79 | + REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns,''),', ','_'),'[',''),']','') 80 | + CASE 81 | WHEN dm_mid.equality_columns IS NOT NULL 82 | AND dm_mid.inequality_columns IS NOT NULL THEN '_' 83 | ELSE '' 84 | END 85 | + REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.inequality_columns,''),', ','_'),'[',''),']','') 86 | + ']' 87 | + ' ON ' + dm_mid.statement 88 | + ' (' + ISNULL (dm_mid.equality_columns,'') 89 | + CASE WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns 90 | IS NOT NULL THEN ',' ELSE 91 | '' END 92 | + ISNULL (dm_mid.inequality_columns, '') 93 | + ')' 94 | + ISNULL (' INCLUDE (' + dm_mid.included_columns + ')', '') AS Create_Statement 95 | FROM sys.dm_db_missing_index_groups dm_mig 96 | INNER JOIN sys.dm_db_missing_index_group_stats dm_migs 97 | ON dm_migs.group_handle = dm_mig.index_group_handle 98 | INNER JOIN sys.dm_db_missing_index_details dm_mid 99 | ON dm_mig.index_handle = dm_mid.index_handle 100 | WHERE dm_mid.database_ID = DB_ID() 101 | ORDER BY Avg_Estimated_Impact DESC") 102 | .AddQuery(CommandType.Text, @"SELECT TOP 25 SUBSTRING(qt.TEXT, (qs.statement_start_offset/2)+1, 103 | ((CASE qs.statement_end_offset 104 | WHEN -1 THEN DATALENGTH(qt.TEXT) 105 | ELSE qs.statement_end_offset 106 | END - qs.statement_start_offset)/2)+1) as [query_text], 107 | qs.execution_count, 108 | qs.total_logical_reads, qs.last_logical_reads, 109 | qs.total_logical_writes, qs.last_logical_writes, 110 | qs.total_worker_time, 111 | qs.last_worker_time, 112 | qs.total_elapsed_time/1000000 total_elapsed_time_in_S, 113 | qs.last_elapsed_time/1000000 last_elapsed_time_in_S, 114 | qs.last_execution_time 115 | FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) st 116 | CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt 117 | CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp 118 | where DB_NAME(st.dbid)=DB_NAME() 119 | ORDER BY qs.total_worker_time DESC") 120 | .AddQuery(CommandType.Text, @"SELECT TOP 25 121 | o.name AS ObjectName 122 | , i.name AS IndexName 123 | , i.index_id AS IndexID 124 | , dm_ius.user_seeks AS UserSeek 125 | , dm_ius.user_scans AS UserScans 126 | , dm_ius.user_lookups AS UserLookups 127 | , dm_ius.user_updates AS UserUpdates 128 | , p.TableRows 129 | , 'DROP INDEX ' + QUOTENAME(i.name) 130 | + ' ON ' + QUOTENAME(s.name) + '.' 131 | + QUOTENAME(OBJECT_NAME(dm_ius.OBJECT_ID)) AS 'drop statement' 132 | FROM sys.dm_db_index_usage_stats dm_ius 133 | INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id 134 | AND dm_ius.OBJECT_ID = i.OBJECT_ID 135 | INNER JOIN sys.objects o ON dm_ius.OBJECT_ID = o.OBJECT_ID 136 | INNER JOIN sys.schemas s ON o.schema_id = s.schema_id 137 | INNER JOIN(SELECT SUM(p.rows) TableRows, p.index_id, p.OBJECT_ID 138 | FROM sys.partitions p GROUP BY p.index_id, p.OBJECT_ID) p 139 | ON p.index_id = dm_ius.index_id AND dm_ius.OBJECT_ID = p.OBJECT_ID 140 | WHERE OBJECTPROPERTY(dm_ius.OBJECT_ID, 'IsUserTable') = 1 141 | AND dm_ius.database_id = DB_ID() 142 | AND i.type_desc = 'nonclustered' 143 | AND i.is_primary_key = 0 144 | AND i.is_unique_constraint = 0 145 | AND(dm_ius.user_scans + dm_ius.user_lookups) > dm_ius.user_seeks 146 | ORDER BY(dm_ius.user_scans + dm_ius.user_lookups) DESC") 147 | .ExecuteAsync().ConfigureAwait(false); 148 | 149 | Console.WriteLine("These are the most expensive queries by total CPU time found."); 150 | Console.WriteLine(Results[3].ToString(x => x.ToString())); 151 | } 152 | 153 | using var listener = new SqlClientListener(); 154 | const string connectionString = "Data Source=localhost;Initial Catalog=SereneCMS;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True"; 155 | 156 | // Open a connection to the AdventureWorks database. 157 | await using var connection = new SqlConnection(connectionString); 158 | connection.Open(); 159 | 160 | const string sql = @"SELECT TOP 25 161 | o.name AS ObjectName 162 | , i.name AS IndexName 163 | , i.index_id AS IndexID 164 | , dm_ius.user_seeks AS UserSeek 165 | , dm_ius.user_scans AS UserScans 166 | , dm_ius.user_lookups AS UserLookups 167 | , dm_ius.user_updates AS UserUpdates 168 | , p.TableRows 169 | , 'DROP INDEX ' + QUOTENAME(i.name) 170 | + ' ON ' + QUOTENAME(s.name) + '.' 171 | + QUOTENAME(OBJECT_NAME(dm_ius.OBJECT_ID)) AS 'drop statement' 172 | FROM sys.dm_db_index_usage_stats dm_ius 173 | INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id 174 | AND dm_ius.OBJECT_ID = i.OBJECT_ID 175 | INNER JOIN sys.objects o ON dm_ius.OBJECT_ID = o.OBJECT_ID 176 | INNER JOIN sys.schemas s ON o.schema_id = s.schema_id 177 | INNER JOIN(SELECT SUM(p.rows) TableRows, p.index_id, p.OBJECT_ID 178 | FROM sys.partitions p GROUP BY p.index_id, p.OBJECT_ID) p 179 | ON p.index_id = dm_ius.index_id AND dm_ius.OBJECT_ID = p.OBJECT_ID 180 | WHERE OBJECTPROPERTY(dm_ius.OBJECT_ID, 'IsUserTable') = 1 181 | AND dm_ius.database_id = DB_ID() 182 | AND i.type_desc = 'nonclustered' 183 | AND i.is_primary_key = 0 184 | AND i.is_unique_constraint = 0 185 | AND(dm_ius.user_scans + dm_ius.user_lookups) > dm_ius.user_seeks 186 | ORDER BY(dm_ius.user_scans + dm_ius.user_lookups) DESC"; 187 | var command = new SqlCommand(sql, connection); 188 | 189 | // Perform a data operation on the server. 190 | SqlDataReader reader = command.ExecuteReader(); 191 | try 192 | { 193 | while (reader.Read()) 194 | { 195 | // Read the data. 196 | } 197 | } 198 | catch (Exception ex) { Console.WriteLine(ex.ToString()); } 199 | reader.Close(); 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /TestApp/TestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docfx_project/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | 10 | -------------------------------------------------------------------------------- /docfx_project/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /docfx_project/api/index.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | Welcome to the API browser. -------------------------------------------------------------------------------- /docfx_project/articles/intro.md: -------------------------------------------------------------------------------- 1 | # Code 2 | [!code-csharp[](../../SQLHelper.Example/Program.cs)] 3 | -------------------------------------------------------------------------------- /docfx_project/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Example 2 | href: intro.md 3 | -------------------------------------------------------------------------------- /docfx_project/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": ["src/SQLHelper.DB/SQLHelper.DB.csproj"], 7 | "src": "../" 8 | } 9 | ], 10 | "dest": "api", 11 | "properties": { 12 | "TargetFramework": "net8.0" 13 | }, 14 | "includePrivateMembers": false, 15 | "disableGitFeatures": false, 16 | "disableDefaultFilter": false, 17 | "noRestore": false, 18 | "namespaceLayout": "flattened", 19 | "memberLayout": "samePage", 20 | "allowCompilationErrors": false 21 | } 22 | ], 23 | "build": { 24 | "globalMetadata": { 25 | "_appTitle": "SQLHelper API Reference", 26 | "_appName": "SQLHelper.DB", 27 | "_appLogoPath": "images/icon.png", 28 | "_appFooter": "Open in Github", 29 | "_copyrightFooter": "© James Craig. All rights reserved.", 30 | "_enableSearch": true, 31 | "_disableSideFilter": false, 32 | "_enableNewTab": true, 33 | "_disableContribution": false, 34 | "_disableBreadcrumb": false 35 | }, 36 | "content": [ 37 | { 38 | "files": [ 39 | "api/**.yml", 40 | "api/index.md" 41 | ] 42 | }, 43 | { 44 | "files": [ 45 | "articles/**.md", 46 | "articles/**/toc.yml", 47 | "toc.yml", 48 | "*.md" 49 | ] 50 | } 51 | ], 52 | "resource": [ 53 | { 54 | "files": [ 55 | "images/**" 56 | ] 57 | } 58 | ], 59 | "output": "_site", 60 | "globalMetadataFiles": [], 61 | "fileMetadataFiles": [], 62 | "template": [ 63 | "default", 64 | "modern", 65 | "./templates/mytemplate" 66 | ], 67 | "postProcessors": [], 68 | "keepFileLink": false, 69 | "disableGitFeatures": false 70 | } 71 | } -------------------------------------------------------------------------------- /docfx_project/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaCraig/SQLHelper/2b857f71520fcc1b80f42c0bc5a2c9ebbe23bfd7/docfx_project/images/icon.png -------------------------------------------------------------------------------- /docfx_project/index.md: -------------------------------------------------------------------------------- 1 | [!INCLUDE [README.md](../README.md)] -------------------------------------------------------------------------------- /docfx_project/templates/mytemplate/public/main.css: -------------------------------------------------------------------------------- 1 | img#logo { 2 | height: 35px; 3 | padding-right: 10px; 4 | } -------------------------------------------------------------------------------- /docfx_project/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Example 2 | href: articles/ 3 | - name: Api Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | -------------------------------------------------------------------------------- /setup.bat: -------------------------------------------------------------------------------- 1 | dotnet new tool-manifest 2 | dotnet tool install Husky 3 | dotnet tool install Versionize 4 | dotnet tool update -g docfx 5 | docfx init --quiet 6 | dotnet husky install -------------------------------------------------------------------------------- /src/SQLHelper.DB/CanisterModules/SQLHelperModule.cs: -------------------------------------------------------------------------------- 1 | using Canister.Interfaces; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace SQLHelperDB.CanisterModules 5 | { 6 | /// 7 | /// SQLHelper Canister module. 8 | /// 9 | /// 10 | public class SQLHelperModule : IModule 11 | { 12 | /// 13 | /// Order to run this in 14 | /// 15 | public int Order { get; } = 1; 16 | 17 | /// 18 | /// Loads the module using the bootstrapper 19 | /// 20 | /// The bootstrapper. 21 | public void Load(IServiceCollection? bootstrapper) => bootstrapper?.RegisterSQLHelper(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/ExtensionMethods/IDataRecordExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using BigBook; 18 | using ObjectCartographer; 19 | using System; 20 | using System.Data; 21 | using System.Diagnostics.CodeAnalysis; 22 | 23 | namespace SQLHelperDB.ExtensionMethods 24 | { 25 | /// 26 | /// Extension methods for IDataRecord objects 27 | /// 28 | public static class IDataRecordExtensions 29 | { 30 | /// 31 | /// Returns a parameter's value 32 | /// 33 | /// The type of the ata type. 34 | /// Reader object 35 | /// Parameter name 36 | /// Default value for the parameter 37 | /// 38 | /// if the parameter exists (and isn't null or empty), it returns the parameter's value. 39 | /// Otherwise the default value is returned. 40 | /// 41 | [return: NotNullIfNotNull(nameof(defaultValue))] 42 | public static TDataType? GetParameter(this IDataRecord reader, string id, TDataType? defaultValue = default) 43 | { 44 | if (reader is null) 45 | return defaultValue; 46 | for (var X = 0; X < reader.FieldCount; ++X) 47 | { 48 | if (reader.GetName(X) == id) 49 | return reader.GetParameter(X, defaultValue); 50 | } 51 | return defaultValue; 52 | } 53 | 54 | /// 55 | /// Returns a parameter's value 56 | /// 57 | /// The type of the data type. 58 | /// Reader object 59 | /// Position in the reader row 60 | /// Default value for the parameter 61 | /// 62 | /// if the parameter exists (and isn't null or empty), it returns the parameter's value. 63 | /// Otherwise the default value is returned. 64 | /// 65 | [return: NotNullIfNotNull(nameof(defaultValue))] 66 | public static TDataType? GetParameter(this IDataRecord reader, int position, TDataType? defaultValue = default) 67 | { 68 | if (reader is null) 69 | return defaultValue; 70 | var Value = reader[position]; 71 | return (Value is null || DBNull.Value == Value) ? defaultValue : Value.To(defaultValue); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/BaseClasses/ParameterBase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using BigBook.Comparison; 18 | using ObjectCartographer; 19 | using SQLHelperDB.HelperClasses.Interfaces; 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Data; 23 | using System.Data.Common; 24 | 25 | namespace SQLHelperDB.HelperClasses.BaseClasses 26 | { 27 | /// 28 | /// Parameter base class 29 | /// 30 | /// Data type of the parameter 31 | /// Constructor 32 | /// ID of the parameter 33 | /// Database type 34 | /// Value of the parameter 35 | /// Direction of the parameter 36 | /// 37 | /// What the database expects as the parameter starting string ("@" for SQL Server, ":" for 38 | /// Oracle, etc.) 39 | /// 40 | public abstract class ParameterBase(string id, DbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") : IParameter 41 | { 42 | /// 43 | /// Constructor 44 | /// 45 | /// ID of the parameter 46 | /// Value of the parameter 47 | /// Direction of the parameter 48 | /// 49 | /// What the database expects as the parameter starting string ("@" for SQL Server, ":" for 50 | /// Oracle, etc.) 51 | /// 52 | protected ParameterBase(string id, TDataType value, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 53 | : this(id, value is null ? typeof(TDataType).To() : value.GetType().To(), value, direction, parameterStarter) 54 | { 55 | } 56 | 57 | /// 58 | /// Initializes a new instance of the class. 59 | /// 60 | /// The parameter. 61 | protected ParameterBase(ParameterBase parameter) 62 | : this(parameter.ID, parameter.DatabaseType, parameter.Value, parameter.Direction, parameter.ParameterStarter) 63 | { 64 | } 65 | 66 | /// 67 | /// Constructor 68 | /// 69 | /// ID of the parameter 70 | /// Database type 71 | /// Value of the parameter 72 | /// Direction of the parameter 73 | /// 74 | /// What the database expects as the parameter starting string ("@" for SQL Server, ":" for 75 | /// Oracle, etc.) 76 | /// 77 | protected ParameterBase(string id, SqlDbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 78 | : this(id, type.To(), value, direction, parameterStarter) 79 | { 80 | } 81 | 82 | /// 83 | /// Database type 84 | /// 85 | public DbType DatabaseType { get; set; } = type; 86 | 87 | /// 88 | /// Direction of the parameter 89 | /// 90 | public ParameterDirection Direction { get; set; } = direction; 91 | 92 | /// 93 | /// The Name that the parameter goes by 94 | /// 95 | public string ID { get; set; } = id; 96 | 97 | /// 98 | /// Gets the internal value. 99 | /// 100 | /// The internal value. 101 | public object? InternalValue => Value; 102 | 103 | /// 104 | /// Starting string of the parameter 105 | /// 106 | public string ParameterStarter { get; set; } = parameterStarter; 107 | 108 | /// 109 | /// Parameter value 110 | /// 111 | public TDataType Value { get; set; } = (TDataType)value!; 112 | 113 | /// 114 | /// Batch ID 115 | /// 116 | protected string BatchID { get; set; } = id; 117 | 118 | /// 119 | /// != operator 120 | /// 121 | /// First item 122 | /// Second item 123 | /// returns true if they are not equal, false otherwise 124 | public static bool operator !=(ParameterBase first, ParameterBase second) => !(first == second); 125 | 126 | /// 127 | /// The == operator 128 | /// 129 | /// First item 130 | /// Second item 131 | /// true if the first and second item are the same, false otherwise 132 | public static bool operator ==(ParameterBase first, ParameterBase second) 133 | { 134 | return ReferenceEquals(first, second) 135 | || (first is not null 136 | && second is not null 137 | && first.GetHashCode() == second.GetHashCode()); 138 | } 139 | 140 | /// 141 | /// Adds this parameter to the SQLHelper 142 | /// 143 | /// SQLHelper 144 | public abstract void AddParameter(DbCommand helper); 145 | 146 | /// 147 | /// Finds itself in the string command and adds the value 148 | /// 149 | /// Command to add to 150 | /// The resulting string 151 | public string AddParameter(string command) 152 | { 153 | if (string.IsNullOrEmpty(command)) 154 | return string.Empty; 155 | var StringValue = Value is null ? "NULL" : Value.ToString(); 156 | return command.Replace(ParameterStarter + ID, typeof(TDataType) == typeof(string) ? "'" + StringValue + "'" : StringValue, StringComparison.OrdinalIgnoreCase); 157 | } 158 | 159 | /// 160 | /// Creates a copy of the parameter 161 | /// 162 | /// Suffix to add to the parameter (for batching purposes) 163 | /// A copy of the parameter 164 | public abstract IParameter CreateCopy(string suffix); 165 | 166 | /// 167 | /// Determines if the objects are equal 168 | /// 169 | /// Object to compare to 170 | /// True if they are equal, false otherwise 171 | public override bool Equals(object? obj) 172 | { 173 | return (obj is ParameterBase OtherParameter) 174 | && OtherParameter.DatabaseType == DatabaseType 175 | && OtherParameter.Direction == Direction 176 | && OtherParameter.ID == ID 177 | && GenericEqualityComparer.Comparer.Equals(OtherParameter.Value, Value); 178 | } 179 | 180 | /// 181 | /// Returns a hash code for this instance. 182 | /// 183 | /// 184 | /// A hash code for this instance, suitable for use in hashing algorithms and data 185 | /// structures like a hash table. 186 | /// 187 | public override int GetHashCode() 188 | { 189 | var HashCode = 2030399226; 190 | HashCode = (HashCode * -1521134295) + DatabaseType.GetHashCode(); 191 | HashCode = (HashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ID); 192 | return (HashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Value!); 193 | } 194 | 195 | /// 196 | /// Returns the string version of the parameter 197 | /// 198 | /// The string representation of the parameter 199 | public override string ToString() => ParameterStarter + ID; 200 | } 201 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/Connection.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Microsoft.Extensions.Configuration; 18 | using SQLHelperDB.HelperClasses.Interfaces; 19 | using System; 20 | using System.Data.Common; 21 | using System.Text.RegularExpressions; 22 | 23 | namespace SQLHelperDB.HelperClasses 24 | { 25 | /// 26 | /// Data source class 27 | /// 28 | /// 29 | public partial class Connection : IConnection 30 | { 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | /// The configuration. 35 | /// The factory. 36 | /// The name. 37 | public Connection(IConfiguration configuration, DbProviderFactory factory, string name) 38 | : this(configuration, factory, string.Empty, name) 39 | { 40 | } 41 | 42 | /// 43 | /// Constructor 44 | /// 45 | /// The configuration. 46 | /// The factory. 47 | /// The connection. 48 | /// The name. 49 | /// The parameter prefix. 50 | /// The retries. 51 | /// configuration 52 | public Connection(IConfiguration configuration, DbProviderFactory factory, string connection, string name, string parameterPrefix = "@", int retries = 0) 53 | { 54 | ArgumentNullException.ThrowIfNull(configuration, nameof(configuration)); 55 | Retries = retries; 56 | Name = string.IsNullOrEmpty(name) ? "Default" : name; 57 | Factory = factory ?? Microsoft.Data.SqlClient.SqlClientFactory.Instance; 58 | ConnectionString = !string.IsNullOrEmpty(connection) ? connection : (configuration.GetConnectionString(Name) ?? Name); 59 | Match DatabaseRegexResult = DatabaseNameRegex.Match(ConnectionString); 60 | if (DatabaseRegexResult.Success) 61 | DatabaseName = DatabaseRegexResult.Groups["name"].Value; 62 | ParameterPrefix = !string.IsNullOrEmpty(parameterPrefix) ? parameterPrefix : GetParameterPrefix(Factory); 63 | CommandTimeout = GetCommandTimeout(ConnectionString); 64 | } 65 | 66 | /// 67 | /// Gets the command timeout. 68 | /// 69 | /// The command timeout. 70 | public int CommandTimeout { get; } 71 | 72 | /// 73 | /// Connection string 74 | /// 75 | public string ConnectionString { get; protected set; } 76 | 77 | /// 78 | /// Gets the database. 79 | /// 80 | /// The database. 81 | public string? DatabaseName { get; protected set; } 82 | 83 | /// 84 | /// Gets the factory that the system uses to actually do the connection. 85 | /// 86 | /// The factory that the system needs to actually do the connection. 87 | public DbProviderFactory Factory { get; protected set; } 88 | 89 | /// 90 | /// Name of the source 91 | /// 92 | /// The name. 93 | public string Name { get; protected set; } 94 | 95 | /// 96 | /// Parameter prefix that the source uses 97 | /// 98 | /// The parameter prefix. 99 | public string ParameterPrefix { get; protected set; } 100 | 101 | /// 102 | /// Gets the number of retries if unable to connect. 103 | /// 104 | /// The number of retries if unable to connect. 105 | public int Retries { get; protected set; } 106 | 107 | /// 108 | /// Gets the connection timeout regex. 109 | /// 110 | /// The connection timeout regex. 111 | private static Regex ConnectionTimeoutRegex { get; } = CreateConnectionTimeoutRegex(); 112 | 113 | /// 114 | /// Gets the database. 115 | /// 116 | /// The database. 117 | private static Regex DatabaseNameRegex { get; } = CreateDatabaseNameRegex(); 118 | 119 | /// 120 | /// Gets the connection timeout regex. 121 | /// 122 | /// The connection timeout regex. 123 | [GeneratedRegex("Connect(ion)? Timeout=([^;]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] 124 | private static partial Regex CreateConnectionTimeoutRegex(); 125 | 126 | /// 127 | /// Gets the database name regex. 128 | /// 129 | /// The database name regex. 130 | [GeneratedRegex("(Initial Catalog|Database)=(?[^;]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] 131 | private static partial Regex CreateDatabaseNameRegex(); 132 | 133 | /// 134 | /// Gets the command timeout. 135 | /// 136 | /// The connection string. 137 | /// The command timeout. 138 | private static int GetCommandTimeout(string connectionString) 139 | { 140 | Match TimeoutMatch = ConnectionTimeoutRegex.Match(connectionString); 141 | return TimeoutMatch.Success 142 | && int.TryParse(TimeoutMatch.Groups[2].Value, out var TempCommandTimeout) 143 | && TempCommandTimeout > 0 144 | ? TempCommandTimeout 145 | : 30; 146 | } 147 | 148 | /// 149 | /// Gets the parameter prefix. 150 | /// 151 | /// The factory. 152 | /// The parameter prefix. 153 | private static string GetParameterPrefix(DbProviderFactory factory) 154 | { 155 | var SourceType = factory.GetType().FullName ?? string.Empty; 156 | return SourceType.Contains("Oracle", StringComparison.OrdinalIgnoreCase) ? ":" : "@"; 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/Interfaces/IBatch.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Data; 20 | using System.Threading.Tasks; 21 | 22 | namespace SQLHelperDB.HelperClasses.Interfaces 23 | { 24 | /// 25 | /// A batch of commands interface 26 | /// 27 | public interface IBatch 28 | { 29 | /// 30 | /// Number of commands being batched 31 | /// 32 | int CommandCount { get; } 33 | 34 | /// 35 | /// Adds a command to be batched 36 | /// 37 | /// The type of the callback data. 38 | /// Callback action 39 | /// Object used in the callback action 40 | /// 41 | /// Determines if this command is a "header" and should be carried across batches. 42 | /// 43 | /// Command (SQL or stored procedure) to run 44 | /// Command type 45 | /// Parameters to add 46 | /// This 47 | IBatch AddQuery(Action, TCallbackData> callBack, TCallbackData callbackObject, bool header, string command, CommandType commandType, params object[]? parameters); 48 | 49 | /// 50 | /// Adds a batch's commands to the current batch 51 | /// 52 | /// Batch to add 53 | /// This 54 | IBatch AddQuery(IBatch batch); 55 | 56 | /// 57 | /// Clears this instance. 58 | /// 59 | /// This. 60 | IBatch Clear(); 61 | 62 | /// 63 | /// Executes the commands and returns the results (async) 64 | /// 65 | /// The results of the batched commands 66 | Task>> ExecuteAsync(); 67 | 68 | /// 69 | /// Removes duplicate commands from the batch 70 | /// 71 | /// This 72 | IBatch RemoveDuplicateCommands(); 73 | 74 | /// 75 | /// Sets the connection. 76 | /// 77 | /// The database connection. 78 | void SetConnection(IConnection databaseConnection); 79 | } 80 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/Interfaces/ICommand.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Collections.Generic; 18 | using System.Data; 19 | 20 | namespace SQLHelperDB.HelperClasses.Interfaces 21 | { 22 | /// 23 | /// Command interface 24 | /// 25 | public interface ICommand 26 | { 27 | /// 28 | /// Command type 29 | /// 30 | CommandType CommandType { get; } 31 | 32 | /// 33 | /// Gets a value indicating whether this is finalizable. 34 | /// 35 | /// true if finalizable; otherwise, false. 36 | bool Finalizable { get; } 37 | 38 | /// 39 | /// Parameters associated with the command 40 | /// 41 | IParameter[] Parameters { get; } 42 | 43 | /// 44 | /// Actual SQL command 45 | /// 46 | string SQLCommand { get; } 47 | 48 | /// 49 | /// Gets a value indicating whether [transaction needed]. 50 | /// 51 | /// true if [transaction needed]; otherwise, false. 52 | bool TransactionNeeded { get; } 53 | 54 | /// 55 | /// Called after the command is run 56 | /// 57 | /// Result of the command 58 | void Finalize(List result); 59 | } 60 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/Interfaces/IConnection.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Data.Common; 18 | 19 | namespace SQLHelperDB.HelperClasses.Interfaces 20 | { 21 | /// 22 | /// Data source interface 23 | /// 24 | public interface IConnection 25 | { 26 | /// 27 | /// Connection string 28 | /// 29 | /// The connection. 30 | string ConnectionString { get; } 31 | 32 | /// 33 | /// Gets the database. 34 | /// 35 | /// The database. 36 | string? DatabaseName { get; } 37 | 38 | /// 39 | /// Gets the factory that the system uses to actually do the connection. 40 | /// 41 | /// The factory that the system needs to actually do the connection. 42 | DbProviderFactory Factory { get; } 43 | 44 | /// 45 | /// Name of the source 46 | /// 47 | /// The name. 48 | string Name { get; } 49 | 50 | /// 51 | /// Parameter prefix that the source uses 52 | /// 53 | /// The parameter prefix. 54 | string ParameterPrefix { get; } 55 | 56 | /// 57 | /// Gets the number of retries if unable to connect. 58 | /// 59 | /// The number of retries if unable to connect. 60 | int Retries { get; } 61 | } 62 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/Interfaces/IParameter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Data; 18 | using System.Data.Common; 19 | 20 | namespace SQLHelperDB.HelperClasses.Interfaces 21 | { 22 | /// 23 | /// Parameter interface 24 | /// 25 | /// Value type 26 | public interface IParameter : IParameter 27 | { 28 | /// 29 | /// The value that the parameter is associated with 30 | /// 31 | T Value { get; set; } 32 | } 33 | 34 | /// 35 | /// Parameter interface 36 | /// 37 | public interface IParameter 38 | { 39 | /// 40 | /// Database type 41 | /// 42 | DbType DatabaseType { get; set; } 43 | 44 | /// 45 | /// Direction of the parameter 46 | /// 47 | ParameterDirection Direction { get; set; } 48 | 49 | /// 50 | /// The name that the parameter goes by 51 | /// 52 | string ID { get; set; } 53 | 54 | /// 55 | /// Gets the internal value. 56 | /// 57 | /// The internal value. 58 | object? InternalValue { get; } 59 | 60 | /// 61 | /// Gets the parameter starter. 62 | /// 63 | /// The parameter starter. 64 | string ParameterStarter { get; } 65 | 66 | /// 67 | /// Adds this parameter to the SQLHelper 68 | /// 69 | /// SQLHelper 70 | void AddParameter(DbCommand helper); 71 | 72 | /// 73 | /// Finds itself in the string command and adds the value 74 | /// 75 | /// Command to add to 76 | /// The resulting string 77 | string AddParameter(string command); 78 | 79 | /// 80 | /// Creates a copy of the parameter 81 | /// 82 | /// Suffix to add to the parameter (for batching purposes) 83 | /// A copy of the parameter 84 | IParameter CreateCopy(string suffix); 85 | } 86 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/Parameter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using SQLHelperDB.ExtensionMethods; 18 | using SQLHelperDB.HelperClasses.BaseClasses; 19 | using SQLHelperDB.HelperClasses.Interfaces; 20 | using System.Data; 21 | using System.Data.Common; 22 | 23 | namespace SQLHelperDB.HelperClasses 24 | { 25 | /// 26 | /// Holds parameter information 27 | /// 28 | /// Data type of the parameter 29 | public class Parameter : ParameterBase 30 | { 31 | /// 32 | /// Constructor 33 | /// 34 | /// ID of the parameter 35 | /// Value of the parameter 36 | /// Direction of the parameter 37 | /// Parameter starter 38 | public Parameter(string id, TDataType value, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 39 | : base(id, value, direction, parameterStarter) 40 | { 41 | } 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// ID of the parameter 47 | /// Database type 48 | /// Value of the parameter 49 | /// Direction of the parameter 50 | /// Parameter starter 51 | public Parameter(string id, SqlDbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 52 | : base(id, type, value, direction, parameterStarter) 53 | { 54 | } 55 | 56 | /// 57 | /// Constructor 58 | /// 59 | /// ID of the parameter 60 | /// Database type 61 | /// Value of the parameter 62 | /// Direction of the parameter 63 | /// Parameter starter 64 | public Parameter(string id, DbType type, object? value = null, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") 65 | : base(id, type, value, direction, parameterStarter) 66 | { 67 | } 68 | 69 | /// 70 | /// Adds this parameter to the SQLHelper 71 | /// 72 | /// SQLHelper 73 | public override void AddParameter(DbCommand helper) => helper.AddParameter(ID, DatabaseType, Value, Direction); 74 | 75 | /// 76 | /// Creates a copy of the parameter 77 | /// 78 | /// Suffix to add to the parameter (for batching purposes) 79 | /// A copy of the parameter 80 | public override IParameter CreateCopy(string suffix) => new Parameter(ID + suffix, DatabaseType, Value, Direction, ParameterStarter); 81 | } 82 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/SelectFinder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Antlr4.Runtime.Misc; 18 | using SQLParser.Parsers.TSql; 19 | 20 | namespace SQLHelperDB.HelperClasses 21 | { 22 | /// 23 | /// Finds selects within SQL code. 24 | /// 25 | /// 26 | public class SelectFinder : TSqlParserBaseListener 27 | { 28 | /// 29 | /// Gets or sets a value indicating whether a select [statement found]. 30 | /// 31 | /// true if [statement found]; otherwise, false. 32 | public bool StatementFound { get; set; } 33 | 34 | /// 35 | /// Enter a parse tree produced by . 36 | /// The default implementation does nothing. 37 | /// 38 | /// The parse tree. 39 | public override void EnterDml_clause([NotNull] TSqlParser.Dml_clauseContext context) 40 | { 41 | TSqlParser.Select_statementContext? SelectStatement = context?.select_statement_standalone()?.select_statement(); 42 | if (SelectStatement is not null) 43 | { 44 | StatementFound |= SelectStatement.query_expression().query_specification() is not null; 45 | } 46 | base.EnterDml_clause(context); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/HelperClasses/StringParameter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using SQLHelperDB.ExtensionMethods; 18 | using SQLHelperDB.HelperClasses.BaseClasses; 19 | using SQLHelperDB.HelperClasses.Interfaces; 20 | using System.Data; 21 | using System.Data.Common; 22 | 23 | namespace SQLHelperDB.HelperClasses 24 | { 25 | /// 26 | /// Holds parameter information 27 | /// 28 | /// Constructor 29 | /// ID of the parameter 30 | /// Value of the parameter 31 | /// Direction of the parameter 32 | /// Parameter starter 33 | public class StringParameter(string id, string value, ParameterDirection direction = ParameterDirection.Input, string parameterStarter = "@") : ParameterBase(id, DbType.String, value, direction, parameterStarter) 34 | { 35 | /// 36 | /// Adds this parameter to the SQLHelper 37 | /// 38 | /// SQLHelper 39 | public override void AddParameter(DbCommand helper) => helper.AddParameter(ID, Value, Direction); 40 | 41 | /// 42 | /// Creates a copy of the parameter 43 | /// 44 | /// Suffix to add to the parameter (for batching purposes) 45 | /// A copy of the parameter 46 | public override IParameter CreateCopy(string suffix) => new StringParameter(ID + suffix, Value, Direction, ParameterStarter); 47 | } 48 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following set of attributes. 5 | // Change these attribute values to modify the information associated with an assembly. 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("SQLHelper")] 9 | [assembly: AssemblyTrademark("")] 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible to COM components. If 12 | // you need to access a type in this assembly from COM, set the ComVisible attribute to true on that type. 13 | [assembly: ComVisible(false)] 14 | 15 | // The following GUID is for the ID of the typelib if this project is exposed to COM 16 | [assembly: Guid("337ae3d9-0691-486a-bbe5-bcaa96f68548")] -------------------------------------------------------------------------------- /src/SQLHelper.DB/Registration/Register.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using BigBook.Registration; 18 | using Canister.Interfaces; 19 | using SQLHelperDB; 20 | 21 | namespace Microsoft.Extensions.DependencyInjection 22 | { 23 | /// 24 | /// Registration extension methods 25 | /// 26 | public static class RegistrationExtensions 27 | { 28 | /// 29 | /// Registers the library with the bootstrapper. 30 | /// 31 | /// The bootstrapper. 32 | /// The bootstrapper 33 | public static ICanisterConfiguration? RegisterSQLHelper(this ICanisterConfiguration? bootstrapper) 34 | { 35 | return bootstrapper?.AddAssembly(typeof(RegistrationExtensions).Assembly) 36 | .RegisterBigBookOfDataTypes(); 37 | } 38 | 39 | /// 40 | /// Registers the SQLHelper library with the service collection. 41 | /// 42 | /// The service collection. 43 | /// The service collection with SQLHelper registered. 44 | public static IServiceCollection? RegisterSQLHelper(this IServiceCollection? services) 45 | { 46 | if (services.Exists()) 47 | return services; 48 | return services?.AddTransient() 49 | ?.RegisterBigBookOfDataTypes(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/SQLHelper.DB/SQLHelper.DB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(APPVEYOR_BUILD_NUMBER) 5 | 6 | $(BUILD_NUMBER) 7 | 8 | 0 9 | 10 | SQLHelper is a simple class to help with databases. 11 | SQLHelper DB 12 | James Craig 13 | net8.0 14 | true 15 | SQLHelper.DB 16 | SQLHelper.DB 17 | SQL;Databases 18 | https://github.com/JaCraig/SQLHelper 19 | Apache-2.0 20 | false 21 | false 22 | false 23 | True 24 | 5.0.$(BuildNumber) 25 | enable 26 | true 27 | true 28 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 29 | true 30 | snupkg 31 | SQLHelper.DB 32 | Copyright © James Craig 2016 33 | Icon.png 34 | README.md 35 | https://github.com/JaCraig/SQLHelper 36 | git 37 | true 38 | 39 | 40 | true 41 | 42 | 43 | 44 | True 45 | \ 46 | 47 | 48 | True 49 | \ 50 | 51 | 52 | 53 | 54 | 55 | all 56 | runtime; build; native; contentfiles; analyzers; buildtransitive 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/SQLHelper.DB/SQLHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 James Craig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using BigBook; 18 | using Microsoft.Extensions.Configuration; 19 | using Microsoft.Extensions.Logging; 20 | using Microsoft.Extensions.ObjectPool; 21 | using ObjectCartographer; 22 | using SQLHelperDB.HelperClasses; 23 | using SQLHelperDB.HelperClasses.Interfaces; 24 | using System; 25 | using System.Collections.Concurrent; 26 | using System.Collections.Generic; 27 | using System.Data; 28 | using System.Data.Common; 29 | using System.Diagnostics.CodeAnalysis; 30 | using System.Linq; 31 | using System.Text; 32 | using System.Threading.Tasks; 33 | 34 | namespace SQLHelperDB 35 | { 36 | /// 37 | /// SQL helper class 38 | /// 39 | public class SQLHelper 40 | { 41 | /// 42 | /// Initializes a new instance of the class. 43 | /// 44 | /// The string builder pool. 45 | /// The configuration. 46 | /// The logger. 47 | public SQLHelper(ObjectPool stringBuilderPool, IConfiguration configuration, ILogger? logger = null) 48 | { 49 | StringBuilderPool = stringBuilderPool; 50 | Configuration = configuration; 51 | SetConnection(new Connection(configuration, Microsoft.Data.SqlClient.SqlClientFactory.Instance, "Default")); 52 | Logger = logger; 53 | } 54 | 55 | /// 56 | /// Gets the number of commands currently in the batch. 57 | /// 58 | /// The number of commands currently in the batch 59 | public int Count => Batch.CommandCount; 60 | 61 | /// 62 | /// Gets or sets the source. 63 | /// 64 | /// The source. 65 | public IConnection? DatabaseConnection { get; private set; } 66 | 67 | /// 68 | /// Gets the batch. 69 | /// 70 | /// The batch. 71 | protected IBatch Batch { get; private set; } 72 | 73 | /// 74 | /// Gets the configuration. 75 | /// 76 | /// The configuration. 77 | protected IConfiguration Configuration { get; } 78 | 79 | /// 80 | /// Gets the string builder pool. 81 | /// 82 | /// The string builder pool. 83 | protected ObjectPool StringBuilderPool { get; } 84 | 85 | /// 86 | /// Gets the connections. 87 | /// 88 | /// The connections. 89 | private static ConcurrentDictionary Connections { get; } = []; 90 | 91 | /// 92 | /// Gets the logger. 93 | /// 94 | /// The logger. 95 | private ILogger? Logger { get; } 96 | 97 | /// 98 | /// Adds a query that gets carried across in internal batches. 99 | /// 100 | /// Type of the command. 101 | /// The command. 102 | /// The parameters. 103 | /// This 104 | public SQLHelper AddHeader(CommandType commandType, string command, params object[]? parameters) => AddHeader(DefaultAction, null!, commandType, command, parameters); 105 | 106 | /// 107 | /// Adds a query that gets carried across in internal batches. 108 | /// 109 | /// The type of the callback data. 110 | /// The callback. 111 | /// The callback object. 112 | /// Type of the command. 113 | /// The command. 114 | /// The parameters. 115 | /// This 116 | public SQLHelper AddHeader(Action, TCallbackData> callback, TCallbackData callbackObject, 117 | CommandType commandType, string command, params object[]? parameters) 118 | { 119 | _ = Batch.AddQuery(callback, callbackObject, true, command, commandType, parameters); 120 | return this; 121 | } 122 | 123 | /// 124 | /// Adds a command. 125 | /// 126 | /// Type of the command. 127 | /// The command. 128 | /// The parameters. 129 | /// This 130 | public SQLHelper AddQuery(CommandType commandType, string command, params object[]? parameters) => AddQuery(DefaultAction, null!, commandType, command, parameters); 131 | 132 | /// 133 | /// Adds a command which will call the callback function with the object specified when it 134 | /// 135 | /// The type of the callback data. 136 | /// The callback. 137 | /// The callback object. 138 | /// Type of the command. 139 | /// The command. 140 | /// The parameters. 141 | /// This 142 | public SQLHelper AddQuery(Action, TCallbackData> callback, TCallbackData callbackObject, 143 | CommandType commandType, string command, params object[]? parameters) 144 | { 145 | _ = Batch.AddQuery(callback, callbackObject, false, command, commandType, parameters); 146 | return this; 147 | } 148 | 149 | /// 150 | /// Adds an SQLHelper's commands to this instance 151 | /// 152 | /// The helper to copy the commands from 153 | /// This 154 | public SQLHelper AddQuery(SQLHelper helper) 155 | { 156 | if (helper is not null) 157 | _ = Batch.AddQuery(helper.Batch); 158 | return this; 159 | } 160 | 161 | /// 162 | /// Creates a copy of this instance. 163 | /// 164 | /// A new SQLHelper based on this instance. 165 | public SQLHelper Copy() => new(StringBuilderPool, Configuration, Logger); 166 | 167 | /// 168 | /// Clears the system and creates a new batch. 169 | /// 170 | /// This 171 | public SQLHelper CreateBatch() 172 | { 173 | _ = Batch.Clear(); 174 | return this; 175 | } 176 | 177 | /// 178 | /// Creates the batch using the connection specified. 179 | /// 180 | /// The connection. 181 | /// This 182 | public SQLHelper CreateBatch(IConnection connection) 183 | { 184 | _ = Batch.Clear(); 185 | SetConnection(connection); 186 | return this; 187 | } 188 | 189 | /// 190 | /// Creates the batch using the connection info specified. 191 | /// 192 | /// The factory. 193 | /// The database. 194 | /// This. 195 | public SQLHelper CreateBatch(DbProviderFactory? factory = null, string database = "Default") => CreateBatch(Connections.TryGetValue(database, out IConnection? Value) ? Value : new Connection(Configuration, factory ?? Microsoft.Data.SqlClient.SqlClientFactory.Instance, database)); 196 | 197 | /// 198 | /// Executes the queries asynchronously. 199 | /// 200 | /// The result of the queries 201 | public Task>> ExecuteAsync() => Batch.ExecuteAsync(); 202 | 203 | /// 204 | /// Executes the batched commands and returns the first value, ignoring the rest (async). 205 | /// 206 | /// The type of the data to return. 207 | /// The default value. 208 | /// The first value of the batch 209 | [return: NotNullIfNotNull(nameof(defaultValue))] 210 | public async Task ExecuteScalarAsync(TData? defaultValue = default) 211 | { 212 | List> BatchResults = await Batch.ExecuteAsync().ConfigureAwait(false); 213 | if (BatchResults.Count == 0 || BatchResults[0].Count == 0) 214 | return defaultValue; 215 | if (BatchResults[0][0] is not IDictionary Value) 216 | return ((object)BatchResults[0][0]).To(defaultValue); 217 | return Value[Value.Keys.First()].To(defaultValue); 218 | } 219 | 220 | /// 221 | /// Removes duplicate queries from the batch. 222 | /// 223 | /// This 224 | public SQLHelper RemoveDuplicateCommands() 225 | { 226 | _ = Batch.RemoveDuplicateCommands(); 227 | return this; 228 | } 229 | 230 | /// 231 | /// Returns a that represents this instance. 232 | /// 233 | /// A that represents this instance. 234 | public override string ToString() => Batch.ToString() ?? ""; 235 | 236 | /// 237 | /// The default action 238 | /// 239 | /// Ignored 240 | /// Ignored 241 | /// Ignored 242 | private static void DefaultAction(ICommand ___, List __, object _) 243 | { } 244 | 245 | /// 246 | /// Sets the connection. 247 | /// 248 | /// The connection. 249 | [MemberNotNull(nameof(Batch))] 250 | private void SetConnection(IConnection connection) 251 | { 252 | DatabaseConnection = connection ?? throw new ArgumentNullException(nameof(connection)); 253 | if (!Connections.ContainsKey(connection.Name)) 254 | _ = Connections.AddOrUpdate(connection.Name, connection, (_, value) => value); 255 | Batch ??= new Batch(DatabaseConnection, StringBuilderPool, Logger); 256 | Batch.SetConnection(DatabaseConnection); 257 | } 258 | } 259 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/BaseClasses/TestingDirectoryFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using SQLHelperDB.ExtensionMethods; 4 | using System; 5 | using Xunit; 6 | 7 | namespace SQLHelperDB.Tests.BaseClasses 8 | { 9 | [Collection("DirectoryCollection")] 10 | public class TestingDirectoryFixture : IDisposable 11 | { 12 | public TestingDirectoryFixture() 13 | { 14 | Configuration = new ConfigurationBuilder() 15 | .AddJsonFile("appsettings.json").AddEnvironmentVariables() 16 | .Build(); 17 | using (var TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection()) 18 | { 19 | TempConnection.ConnectionString = Configuration.GetConnectionString("Master"); 20 | using var TempCommand = TempConnection.CreateCommand(); 21 | try 22 | { 23 | TempCommand.CommandText = "Create Database TestDatabase"; 24 | TempCommand.Open(3); 25 | TempCommand.ExecuteNonQuery(); 26 | } 27 | catch { } 28 | finally { TempCommand.Close(); } 29 | } 30 | using (var TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection()) 31 | { 32 | TempConnection.ConnectionString = Configuration.GetConnectionString("Default"); 33 | using var TempCommand = TempConnection.CreateCommand(); 34 | try 35 | { 36 | TempCommand.CommandText = "Create Table TestTable(ID INT PRIMARY KEY IDENTITY,StringValue1 NVARCHAR(100),StringValue2 NVARCHAR(MAX),BigIntValue BIGINT,BitValue BIT,DecimalValue DECIMAL(12,6),FloatValue FLOAT,DateTimeValue DATETIME,GUIDValue UNIQUEIDENTIFIER,TimeSpanValue TIME(7))"; 37 | TempCommand.Open(3); 38 | TempCommand.ExecuteNonQuery(); 39 | TempCommand.CommandText = "Create Table TestTableNotNull(ID INT PRIMARY KEY IDENTITY,UShortValue_ SMALLINT NOT NULL)"; 40 | TempCommand.ExecuteNonQuery(); 41 | TempCommand.CommandText = @"Create PROCEDURE TestSP 42 | @Value nvarchar(100) 43 | AS 44 | BEGIN 45 | SELECT @Value as [Value] 46 | END"; 47 | TempCommand.ExecuteNonQuery(); 48 | } 49 | catch { } 50 | finally { TempCommand.Close(); } 51 | } 52 | } 53 | 54 | /// 55 | /// The service provider lock 56 | /// 57 | private static readonly object ServiceProviderLock = new object(); 58 | 59 | /// 60 | /// The service provider 61 | /// 62 | private static IServiceProvider ServiceProvider; 63 | 64 | protected IConfiguration Configuration { get; } 65 | 66 | public void Dispose() 67 | { 68 | using var TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection(); 69 | TempConnection.ConnectionString = Configuration.GetConnectionString("Master"); 70 | using var TempCommand = TempConnection.CreateCommand(); 71 | try 72 | { 73 | TempCommand.CommandText = "ALTER DATABASE TestDatabase SET OFFLINE WITH ROLLBACK IMMEDIATE\r\nALTER DATABASE TestDatabase SET ONLINE\r\nDROP DATABASE TestDatabase"; 74 | TempCommand.Open(3); 75 | TempCommand.ExecuteNonQuery(); 76 | } 77 | finally { TempCommand.Close(); } 78 | 79 | GC.SuppressFinalize(this); 80 | } 81 | 82 | /// 83 | /// Gets the service provider. 84 | /// 85 | /// 86 | protected static IServiceProvider GetServiceProvider() 87 | { 88 | if (ServiceProvider is not null) 89 | return ServiceProvider; 90 | lock (ServiceProviderLock) 91 | { 92 | if (ServiceProvider is not null) 93 | return ServiceProvider; 94 | ServiceProvider = new ServiceCollection().AddCanisterModules()?.BuildServiceProvider(); 95 | } 96 | return ServiceProvider; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/DataClasses/TestTableClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SQLHelperDB.Tests.DataClasses 4 | { 5 | public class TestTableClass 6 | { 7 | public long BigIntValue { get; set; } 8 | public bool BitValue { get; set; } 9 | public DateTime DateTimeValue { get; set; } 10 | public decimal DecimalValue { get; set; } 11 | public float FloatValue { get; set; } 12 | public Guid GUIDValue { get; set; } 13 | public string StringValue1 { get; set; } 14 | 15 | public string StringValue2 { get; set; } 16 | 17 | public TimeSpan TimeSpanValue { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/ExtensionMethods/DbCommandExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using SQLHelperDB.ExtensionMethods; 2 | using System; 3 | using System.Data; 4 | using Xunit; 5 | 6 | namespace SQLHelperDB.Tests.ExtensionMethods 7 | { 8 | public class DbCommandExtensionTests 9 | { 10 | public static readonly TheoryData ParameterTypes = new TheoryData() 11 | { 12 | { (sbyte)123 }, 13 | { (byte)123 }, 14 | { 123 }, 15 | { (uint)123 }, 16 | { (short)123 }, 17 | { (ushort)123 }, 18 | { (long)123 }, 19 | { (ulong)123 }, 20 | { "asdf" }, 21 | { 'a' }, 22 | { 0.43 }, 23 | { (decimal)0.53 }, 24 | { (float)0.42 }, 25 | { (sbyte?)123 }, 26 | { (byte?)123 }, 27 | { (int?)123 }, 28 | { (uint?)123 }, 29 | { (short?)123 }, 30 | { (ushort?)123 }, 31 | { (long?)123 }, 32 | { (ulong?)123 }, 33 | { (char?)'a' }, 34 | { (double?)0.43 }, 35 | { (decimal?)0.53 }, 36 | { (float?)0.42 }, 37 | { (DateTime?)new DateTime(2000, 1, 1) }, 38 | { (TimeSpan?)new TimeSpan(1, 1, 1) }, 39 | { (bool?)true }, 40 | { new byte[] { 1, 2, 3, 4 } }, 41 | { (DateTimeOffset?)new DateTimeOffset(new DateTime(2000, 1, 1), new TimeSpan(1, 1, 0)) }, 42 | { (Guid?)Guid.NewGuid() } 43 | }; 44 | 45 | [Theory] 46 | [MemberData(nameof(ParameterTypes))] 47 | public void AddParameter(object value) 48 | { 49 | using var TempConnection = Microsoft.Data.SqlClient.SqlClientFactory.Instance.CreateConnection(); 50 | using var TempCommand = TempConnection.CreateCommand(); 51 | TempCommand.AddParameter("0", value); 52 | Assert.Equal(TempCommand.Parameters[0].Value, value); 53 | Assert.Equal(TempCommand.Parameters[0].IsNullable, value is Nullable); 54 | Assert.Equal(ParameterDirection.Input, TempCommand.Parameters[0].Direction); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "RECS0022:A catch clause that catches System.Exception and has an empty body", Justification = "", Scope = "member", Target = "~M:SQLHelperDB.Tests.BaseClasses.TestingDirectoryFixture.#ctor")] 8 | -------------------------------------------------------------------------------- /test/SQLHelper.Tests/HelperClasses/BatchTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.ObjectPool; 4 | using SQLHelperDB.HelperClasses; 5 | using SQLHelperDB.Tests.BaseClasses; 6 | using System.Data; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace SQLHelperDB.Tests.HelperClasses 11 | { 12 | public class BatchTests : TestingDirectoryFixture 13 | { 14 | [Fact] 15 | public void AddQuery() 16 | { 17 | var Configuration = new ConfigurationBuilder() 18 | .AddInMemoryCollection() 19 | .Build(); 20 | var Instance = new Batch(new Connection(Configuration, 21 | Microsoft.Data.SqlClient.SqlClientFactory.Instance, 22 | Configuration.GetConnectionString("Default"), 23 | "DATABASE NAME"), 24 | GetServiceProvider().GetService>(), 25 | null 26 | ); 27 | Instance.AddQuery((___, __, _) => { }, 10, false, "SELECT * FROM TestUsers", CommandType.Text); 28 | Assert.NotNull(Instance); 29 | Assert.Equal("SELECT * FROM TestUsers", Instance.ToString()); 30 | } 31 | 32 | [Fact] 33 | public void AddQuerys() 34 | { 35 | var Configuration = new ConfigurationBuilder() 36 | .AddInMemoryCollection() 37 | .Build(); 38 | var Instance = new Batch(new Connection(Configuration, 39 | Microsoft.Data.SqlClient.SqlClientFactory.Instance, 40 | Configuration.GetConnectionString("Default"), 41 | "DATABASE NAME"), 42 | GetServiceProvider().GetService>(), 43 | null 44 | ); 45 | Instance.AddQuery((___, __, _) => { }, 10, false, "SELECT * FROM TestUsers", CommandType.Text) 46 | .AddQuery((___, __, _) => { }, 10, false, "SELECT * FROM TestGroups", CommandType.Text); 47 | Assert.NotNull(Instance); 48 | Assert.Equal("SELECT * FROM TestUsers\r\nSELECT * FROM TestGroups", Instance.ToString()); 49 | } 50 | 51 | [Fact] 52 | public void AddQuerysWithParameters() 53 | { 54 | var Configuration = new ConfigurationBuilder() 55 | .AddInMemoryCollection() 56 | .Build(); 57 | var Instance = new Batch(new Connection(Configuration, 58 | Microsoft.Data.SqlClient.SqlClientFactory.Instance, 59 | Configuration.GetConnectionString("Default"), 60 | "DATABASE NAME"), 61 | GetServiceProvider().GetService>(), 62 | null 63 | ); 64 | Instance.AddQuery((___, __, _) => { }, 10, false, "SELECT * FROM TestUsers WHERE UserID=@0", CommandType.Text, 1) 65 | .AddQuery((___, __, _) => { }, 10, false, "SELECT * FROM TestGroups WHERE GroupID=@0", CommandType.Text, 10); 66 | Assert.NotNull(Instance); 67 | Assert.Equal("SELECT * FROM TestUsers WHERE UserID=1\r\nSELECT * FROM TestGroups WHERE GroupID=10", Instance.ToString()); 68 | } 69 | 70 | [Fact] 71 | public void AddQueryWithParameters() 72 | { 73 | var Configuration = new ConfigurationBuilder() 74 | .AddInMemoryCollection() 75 | .Build(); 76 | var Instance = new Batch(new Connection(Configuration, 77 | Microsoft.Data.SqlClient.SqlClientFactory.Instance, 78 | Configuration.GetConnectionString("Default"), 79 | "DATABASE NAME"), 80 | GetServiceProvider().GetService>(), 81 | null 82 | ); 83 | Instance.AddQuery((___, __, _) => { }, 10, false, "SELECT * FROM TestUsers WHERE UserID=@0", CommandType.Text, 1); 84 | Assert.NotNull(Instance); 85 | Assert.Equal("SELECT * FROM TestUsers WHERE UserID=1", Instance.ToString()); 86 | } 87 | 88 | [Fact] 89 | public void Creation() 90 | { 91 | var Configuration = new ConfigurationBuilder() 92 | .AddInMemoryCollection() 93 | .Build(); 94 | var Instance = new Batch(new Connection(Configuration, 95 | Microsoft.Data.SqlClient.SqlClientFactory.Instance, 96 | Configuration.GetConnectionString("Default"), 97 | "DATABASE NAME"), 98 | GetServiceProvider().GetService>(), 99 | null 100 | ); 101 | Assert.NotNull(Instance); 102 | Assert.Equal("", Instance.ToString()); 103 | Assert.Equal(0, Instance.CommandCount); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/HelperClasses/CommandTests.cs: -------------------------------------------------------------------------------- 1 | using SQLHelperDB.HelperClasses; 2 | using SQLHelperDB.HelperClasses.Interfaces; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace SQLHelperDB.Tests.HelperClasses 9 | { 10 | public class CommandTests 11 | { 12 | [Fact] 13 | public void CanFinalizeAlterTable() 14 | { 15 | var Builder = new StringBuilder(); 16 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "ALTER TABLE [dbo].[SelectOption_] ADD FOREIGN KEY ([User_Creator_ID_]) REFERENCES [dbo].[User_]([ID_]);", CommandType.Text, "@", System.Array.Empty()); 17 | Assert.False(TestItem.Finalizable); 18 | Assert.True(TestItem.TransactionNeeded); 19 | } 20 | 21 | [Fact] 22 | public void CanFinalizeDeclare() 23 | { 24 | var Builder = new StringBuilder(); 25 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "DECLARE @SelectOption_ID_Temp AS BIGINT;", CommandType.Text, "@", System.Array.Empty()); 26 | Assert.False(TestItem.Finalizable); 27 | Assert.False(TestItem.TransactionNeeded); 28 | TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "DECLARE @SelectOption_ID_Temp AS BIGINT;", CommandType.Text, System.Array.Empty()); 29 | Assert.False(TestItem.Finalizable); 30 | Assert.False(TestItem.TransactionNeeded); 31 | } 32 | 33 | [Fact] 34 | public void CanFinalizeDeleteTestNoParameters() 35 | { 36 | var Builder = new StringBuilder(); 37 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "DELETE FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = @ManyToManyPropertiesWithCascade_ID_ AND NOT (([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = @AllReferencesAndID_ID_0) OR ([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = @AllReferencesAndID_ID_1));", CommandType.Text, "@", System.Array.Empty()); 38 | Assert.False(TestItem.Finalizable); 39 | Assert.True(TestItem.TransactionNeeded); 40 | } 41 | 42 | [Fact] 43 | public void CanFinalizeIfTest() 44 | { 45 | var Builder = new StringBuilder(); 46 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "IF NOT EXISTS (SELECT * FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 4 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 4) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (4,4) END;", CommandType.Text, "@", new object[] { 1, 2, 3 }); 47 | Assert.False(TestItem.Finalizable); 48 | Assert.True(TestItem.TransactionNeeded); 49 | TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "IF NOT EXISTS (SELECT * FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 4 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 4) BEGIN SELECT * FROM Users END;", CommandType.Text, "@", new object[] { 1, 2, 3 }); 50 | Assert.True(TestItem.Finalizable); 51 | Assert.False(TestItem.TransactionNeeded); 52 | } 53 | 54 | [Fact] 55 | public void CanFinalizeIfTestNoParameters() 56 | { 57 | var Builder = new StringBuilder(); 58 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "IF NOT EXISTS (SELECT * FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 4 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 4) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (4,4) END;", CommandType.Text, "@", null); 59 | Assert.False(TestItem.Finalizable); 60 | Assert.True(TestItem.TransactionNeeded); 61 | } 62 | 63 | [Fact] 64 | public void CanFinalizeMultiLine() 65 | { 66 | var Builder = new StringBuilder(); 67 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, @"SELECT 68 | * 69 | FROM Table", CommandType.Text, "@", System.Array.Empty()); 70 | Assert.True(TestItem.Finalizable); 71 | Assert.False(TestItem.TransactionNeeded); 72 | } 73 | 74 | [Fact] 75 | public void CanFinalizeMultipleTest() 76 | { 77 | var Builder = new StringBuilder(); 78 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, @"DELETE FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 4 AND NOT (([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 4)); 79 | IF NOT EXISTS (SELECT * FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 4 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 4) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (4,4) END; 80 | DELETE FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 5 AND NOT (([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 5)); 81 | IF NOT EXISTS (SELECT * FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 5 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 5) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (5,5) END; 82 | DELETE FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 6 AND NOT (([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 6)); 83 | IF NOT EXISTS (SELECT * FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = 6 AND [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = 6) BEGIN INSERT INTO [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade]([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_],[dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_]) VALUES (6,6) END;", CommandType.Text, "@", new object[] { 1, 2, 3 }); 84 | Assert.False(TestItem.Finalizable); 85 | } 86 | 87 | //DELETE FROM [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade] WHERE [dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[ManyToManyPropertiesWithCascade_ID_] = @ManyToManyPropertiesWithCascade_ID_ AND NOT (([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = @AllReferencesAndID_ID_0) OR ([dbo].[AllReferencesAndID_ManyToManyPropertiesWithCascade].[AllReferencesAndID_ID_] = @AllReferencesAndID_ID_1)); 88 | [Fact] 89 | public void Creation() 90 | { 91 | var TestItem = new Command((___, __, _) => { }, 10, false, "SELECT * FROM TestUsers", CommandType.Text, "@", new object[] { 1, 2, 3 }); 92 | Assert.NotNull(TestItem.CallBack); 93 | Assert.Equal(CommandType.Text, TestItem.CommandType); 94 | Assert.True(TestItem.Finalizable); 95 | Assert.Equal(10, TestItem.CallbackData); 96 | Assert.Equal(3, TestItem.Parameters.Length); 97 | 98 | for (var x = 0; x < 3; ++x) 99 | { 100 | Assert.Equal(DbType.Int32, TestItem.Parameters[x].DatabaseType); 101 | Assert.Equal(ParameterDirection.Input, TestItem.Parameters[x].Direction); 102 | Assert.Equal(x.ToString(), TestItem.Parameters[x].ID); 103 | Assert.Equal(x + 1, TestItem.Parameters[x].InternalValue); 104 | } 105 | Assert.Equal("SELECT * FROM TestUsers", TestItem.SQLCommand); 106 | } 107 | 108 | [Fact] 109 | public void FinalizeTest() 110 | { 111 | var Builder = new StringBuilder(); 112 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "SELECT * FROM TestUsers", CommandType.Text, "@", new object[] { 1, 2, 3 }); 113 | TestItem.Finalize(new List()); 114 | Assert.Equal("10", Builder.ToString()); 115 | } 116 | 117 | [Fact] 118 | public void TransactionNeededAlterDatabase() 119 | { 120 | var Builder = new StringBuilder(); 121 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "ALTER DATABASE TestDatabase SET OFFLINE WITH ROLLBACK IMMEDIATE\r\nALTER DATABASE TestDatabase SET ONLINE\r\nDROP DATABASE TestDatabase", CommandType.Text, "@", System.Array.Empty()); 122 | Assert.False(TestItem.TransactionNeeded); 123 | } 124 | 125 | [Fact] 126 | public void TransactionNeededCreateDatabase() 127 | { 128 | var Builder = new StringBuilder(); 129 | var TestItem = new Command((__, _, z) => Builder.Append(z), 10, false, "Create Database TestDatabase", CommandType.Text, "@", System.Array.Empty()); 130 | Assert.False(TestItem.TransactionNeeded); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/HelperClasses/ConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Xunit; 3 | 4 | namespace SQLHelper.Tests.HelperClasses 5 | { 6 | public class ConnectionTests 7 | { 8 | [Fact] 9 | public void Initialization() 10 | { 11 | var Configuration = new ConfigurationBuilder() 12 | .AddJsonFile("appsettings.json").AddEnvironmentVariables() 13 | .Build(); 14 | var TestConnection = new SQLHelperDB.HelperClasses.Connection(Configuration, Microsoft.Data.SqlClient.SqlClientFactory.Instance, Configuration.GetConnectionString("Default")); 15 | Assert.Equal(30, TestConnection.CommandTimeout); 16 | Assert.Equal(Configuration.GetConnectionString("Default"), TestConnection.ConnectionString); 17 | Assert.Equal("TestDatabase", TestConnection.DatabaseName); 18 | Assert.Same(Microsoft.Data.SqlClient.SqlClientFactory.Instance, TestConnection.Factory); 19 | Assert.Equal("@", TestConnection.ParameterPrefix); 20 | Assert.Equal(0, TestConnection.Retries); 21 | } 22 | 23 | [Fact] 24 | public void InitializationWithTimeout() 25 | { 26 | var Configuration = new ConfigurationBuilder() 27 | .AddJsonFile("appsettings.json").AddEnvironmentVariables() 28 | .Build(); 29 | var TestConnection = new SQLHelperDB.HelperClasses.Connection(Configuration, Microsoft.Data.SqlClient.SqlClientFactory.Instance, Configuration.GetConnectionString("DefaultWithTimeout")); 30 | Assert.Equal(100, TestConnection.CommandTimeout); 31 | Assert.Equal(Configuration.GetConnectionString("DefaultWithTimeout"), TestConnection.ConnectionString); 32 | Assert.Equal("TestDatabase", TestConnection.DatabaseName); 33 | Assert.Same(Microsoft.Data.SqlClient.SqlClientFactory.Instance, TestConnection.Factory); 34 | Assert.Equal("@", TestConnection.ParameterPrefix); 35 | Assert.Equal(0, TestConnection.Retries); 36 | } 37 | 38 | [Fact] 39 | public void InitializationWithTimeout2() 40 | { 41 | var Configuration = new ConfigurationBuilder() 42 | .AddJsonFile("appsettings.json").AddEnvironmentVariables() 43 | .Build(); 44 | var TestConnection = new SQLHelperDB.HelperClasses.Connection(Configuration, Microsoft.Data.SqlClient.SqlClientFactory.Instance, Configuration.GetConnectionString("DefaultWithTimeout2")); 45 | Assert.Equal(100, TestConnection.CommandTimeout); 46 | Assert.Equal(Configuration.GetConnectionString("DefaultWithTimeout2"), TestConnection.ConnectionString); 47 | Assert.Equal("TestDatabase", TestConnection.DatabaseName); 48 | Assert.Same(Microsoft.Data.SqlClient.SqlClientFactory.Instance, TestConnection.Factory); 49 | Assert.Equal("@", TestConnection.ParameterPrefix); 50 | Assert.Equal(0, TestConnection.Retries); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/HelperClasses/ParameterTests.cs: -------------------------------------------------------------------------------- 1 | using SQLHelperDB.HelperClasses; 2 | using SQLHelperDB.Tests.BaseClasses; 3 | using System.Data; 4 | using Xunit; 5 | 6 | namespace SQLHelperDB.Tests.HelperClasses 7 | { 8 | public class ParameterTests : TestingDirectoryFixture 9 | { 10 | [Fact] 11 | public void Create() 12 | { 13 | var TestItem = new Parameter("0", 10); 14 | Assert.Equal(DbType.Int32, TestItem.DatabaseType); 15 | Assert.Equal(ParameterDirection.Input, TestItem.Direction); 16 | Assert.Equal("0", TestItem.ID); 17 | Assert.Equal(10, TestItem.InternalValue); 18 | Assert.Equal("@", TestItem.ParameterStarter); 19 | Assert.Equal(10, TestItem.Value); 20 | } 21 | 22 | [Fact] 23 | public void CreateCopy() 24 | { 25 | var TestItem = new Parameter("0", 10).CreateCopy("ABC"); 26 | Assert.Equal(DbType.Int32, TestItem.DatabaseType); 27 | Assert.Equal(ParameterDirection.Input, TestItem.Direction); 28 | Assert.Equal("0ABC", TestItem.ID); 29 | Assert.Equal(10, TestItem.InternalValue); 30 | } 31 | 32 | [Fact] 33 | public void EqualsTest() 34 | { 35 | var TestItem1 = new Parameter("0", 10); 36 | var TestItem2 = new Parameter("0", 10); 37 | var TestItem3 = new Parameter("1", 10); 38 | Assert.True(TestItem1.Equals(TestItem2)); 39 | Assert.False(TestItem1.Equals(TestItem3)); 40 | Assert.False(TestItem2.Equals(TestItem3)); 41 | Assert.True(TestItem1 == TestItem2); 42 | Assert.False(TestItem1 == TestItem3); 43 | Assert.False(TestItem2 == TestItem3); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/HelperClasses/SourceTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using SQLHelperDB.HelperClasses; 3 | using SQLHelperDB.Tests.BaseClasses; 4 | using Xunit; 5 | 6 | namespace SQLHelperDB.Tests.HelperClasses 7 | { 8 | public class SourceTests : TestingDirectoryFixture 9 | { 10 | [Fact] 11 | public void Create() 12 | { 13 | var Configuration = new ConfigurationBuilder() 14 | .AddJsonFile("appsettings.json").AddEnvironmentVariables() 15 | .Build(); 16 | var TestItem = new Connection(Configuration, Microsoft.Data.SqlClient.SqlClientFactory.Instance, Configuration.GetConnectionString("Default"), "DATABASE NAME"); 17 | Assert.Equal(Configuration.GetConnectionString("Default"), TestItem.ConnectionString); 18 | Assert.Equal("TestDatabase", TestItem.DatabaseName); 19 | Assert.Equal(Microsoft.Data.SqlClient.SqlClientFactory.Instance, TestItem.Factory); 20 | Assert.Equal("DATABASE NAME", TestItem.Name); 21 | Assert.Equal("@", TestItem.ParameterPrefix); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/HelperClasses/StringParameterTests.cs: -------------------------------------------------------------------------------- 1 | using SQLHelperDB.HelperClasses; 2 | using SQLHelperDB.Tests.BaseClasses; 3 | using System.Data; 4 | using Xunit; 5 | 6 | namespace SQLHelperDB.Tests.HelperClasses 7 | { 8 | public class StringParameterTests : TestingDirectoryFixture 9 | { 10 | [Fact] 11 | public void Create() 12 | { 13 | var TestItem = new StringParameter("0", "This is the value"); 14 | Assert.Equal(DbType.String, TestItem.DatabaseType); 15 | Assert.Equal(ParameterDirection.Input, TestItem.Direction); 16 | Assert.Equal("0", TestItem.ID); 17 | Assert.Equal("This is the value", TestItem.InternalValue); 18 | Assert.Equal("@", TestItem.ParameterStarter); 19 | Assert.Equal("This is the value", TestItem.Value); 20 | } 21 | 22 | [Fact] 23 | public void CreateCopy() 24 | { 25 | var TestItem = new StringParameter("0", "This is the value").CreateCopy("ABC"); 26 | Assert.Equal(DbType.String, TestItem.DatabaseType); 27 | Assert.Equal(ParameterDirection.Input, TestItem.Direction); 28 | Assert.Equal("0ABC", TestItem.ID); 29 | Assert.Equal("This is the value", TestItem.InternalValue); 30 | } 31 | 32 | [Fact] 33 | public void EqualsTest() 34 | { 35 | var TestItem1 = new StringParameter("0", "This is the value"); 36 | var TestItem2 = new StringParameter("0", "This is the value"); 37 | var TestItem3 = new StringParameter("1", "This is the value"); 38 | Assert.True(TestItem1.Equals(TestItem2)); 39 | Assert.False(TestItem1.Equals(TestItem3)); 40 | Assert.False(TestItem2.Equals(TestItem3)); 41 | Assert.True(TestItem1 == TestItem2); 42 | Assert.False(TestItem1 == TestItem3); 43 | Assert.False(TestItem2 == TestItem3); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /test/SQLHelper.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using Xunit; 4 | 5 | // General Information about an assembly is controlled through the following set of attributes. 6 | // Change these attribute values to modify the information associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("SQLHelperDB.Tests")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible to COM components. If you 13 | // need to access a type in this assembly from COM, set the ComVisible attribute to true on that type. 14 | [assembly: ComVisible(false)] 15 | 16 | // The following GUID is for the ID of the typelib if this project is exposed to COM 17 | [assembly: Guid("e0a10594-6d81-4890-a7d6-2d04f5d8567f")] 18 | [assembly: CollectionBehavior(DisableTestParallelization = true)] -------------------------------------------------------------------------------- /test/SQLHelper.Tests/SQLHelper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Tests for SQLHelper 5 | SQLHelper.Tests 6 | James Craig 7 | net8.0 8 | portable 9 | SQLHelper.Tests 10 | SQLHelper.Tests 11 | true 12 | SQL 13 | https://github.com/JaCraig/SQLHelper/ 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | false 16 | false 17 | false 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | all 44 | runtime; build; native; contentfiles; analyzers 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/SQLHelper.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "ConnectionStrings": { 11 | "Master": "Data Source=localhost;Initial Catalog=master;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True", 12 | "Default": "Data Source=localhost;Initial Catalog=TestDatabase;Integrated Security=SSPI;Pooling=false;TrustServerCertificate=True", 13 | "DefaultWithTimeout": "Data Source=localhost;Initial Catalog=TestDatabase;Integrated Security=SSPI;Pooling=false;connection timeout=100;TrustServerCertificate=True;", 14 | "DefaultWithTimeout2": "Data Source=localhost;Initial Catalog=TestDatabase;Integrated Security=SSPI;Pooling=false;connect timeout=100;TrustServerCertificate=True;" 15 | } 16 | } --------------------------------------------------------------------------------