├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── DbReader.sln ├── build └── build.csx ├── global.json ├── omnisharp.json ├── readme.md └── src ├── .idea └── .idea.DbReader │ └── .idea │ └── workspace.xml ├── DbReader.Tests ├── ArgumentParserMethodBuilderTests.cs ├── ArgumentParserTests.cs ├── ArgumentProcessorTests.cs ├── ArgumentsBuilderTests.cs ├── AssemblyInfo.cs ├── CachedArgumentParserMethodBuilderTests.cs ├── ConstructorSelectorTests.cs ├── CustomValueType.cs ├── DataReaderMockExtensions.cs ├── DataRecordExtensionTests.cs ├── DbReader.Tests.csproj ├── DbReaderExtensions.cs ├── Failing.playlist ├── FakeSql.cs ├── FieldSelectorTests.cs ├── InstanceReaderTests.cs ├── InstanceReaderVerificationTests.cs ├── IntegrationTests.cs ├── KeyConventionTests.cs ├── KeyPropertyMapperTests.cs ├── KeyReaderTests.cs ├── ManyToOnePropertySelectorTests.cs ├── Measure.cs ├── MethodSelectorTests.cs ├── OneToManyPropertySelectorTests.cs ├── PrefixResolverTests.cs ├── PropertyMapperTests.cs ├── PropertyReflectionTests.cs ├── Queries │ ├── Customers.sql │ ├── CustomersAndOrders.sql │ ├── EmployeesHierarchy.sql │ └── EmployeesWithOrdersAndTerritories.sql ├── QueryCacheTests.cs ├── RegExParameterParserTests.cs ├── SQL.cs ├── SQLite119 │ ├── SQLite.Interop.dll │ ├── System.Data.SQLite.dll │ ├── libz.1.3.1.dylib │ ├── libz.1.dylib │ └── libz.dylib ├── SampleClass.cs ├── Settings.StyleCop ├── SimplePropertySelectorTests.cs ├── SqlProvider.cs ├── TestBase.cs ├── TestCollectionOrderer.cs ├── TypeExtensionTests.cs ├── UtilityTests.cs ├── ValueConverterTests.cs ├── VerifiableMethodSkeleton.cs ├── VerifiableMethodSkeletonFactory.cs ├── coverlet.runsettings ├── db │ └── northwind.sql ├── packages.config ├── sqlmd.sh └── xunit.runner.json ├── DbReader.Tracking.SampleAssembly ├── DbReader.Tracking.SampleAssembly.csproj └── SampleClasses.cs ├── DbReader.Tracking ├── DbReader.Tracking.csproj ├── DbReader.Tracking.nuspec ├── DbReader.Tracking.targets ├── TrackingAssemblyWeaver.cs └── WeaveAssemby.cs └── DbReader ├── ArgumentProcessor.cs ├── CommandWrappingDataReader.cs ├── CompositionRoot.cs ├── Construction ├── ArgumentParserMethodBuilder.cs ├── Cache.cs ├── CachedArgumentParserMethodBuilder.cs ├── CachedInstanceReaderMethodBuilder.cs ├── CachedKeyReaderMethodBuilder.cs ├── CachedOneToManyMethodBuilder.cs ├── CachedReaderMethodBuilder.cs ├── ConstructorReaderMethodBuilder.cs ├── DynamicMethodSkeleton.cs ├── IArgumentParserMethodBuilder.cs ├── IKeyReaderMethodBuilder.cs ├── IManyToOneMethodBuilder.cs ├── IMethodSkeleton.cs ├── IMethodSkeletonFactory.cs ├── IOneToManyMethodBuilder.cs ├── IParameterMatcher.cs ├── IPrefixResolver.cs ├── IReaderMethodBuilder.cs ├── InstanceReaderMethodBuilder.cs ├── KeyReaderMethodBuilder.cs ├── ManyToOneMethodBuilder.cs ├── MatchedParameter.cs ├── MethodSkeletonFactory.cs ├── OneToManyMethodBuilder.cs ├── ParameterMatcher.cs ├── PrefixResolver.cs ├── PropertyReaderDelegate.cs ├── PropertyReaderDelegateCache.cs ├── PropertyReaderMethodBuilder.cs ├── ReaderMethodBuilder.cs └── StaticCache.cs ├── ContainerExtensions.cs ├── DataReaderExtensions.cs ├── DataRecordExtensions.cs ├── Database ├── ArgumentParser.cs ├── IArgumentParser.cs ├── IParameterParser.cs ├── QueryInfo.cs └── RegExParameterParser.cs ├── DbConnectionExtensions.cs ├── DbReader.csproj ├── DbReaderOptions.cs ├── DynamicArguments ├── ArgumentsBuilder.cs ├── DynamicMemberInfo.cs ├── DynamicTypeActivator.cs ├── DynamicValueExtractor.cs ├── IDynamicType.cs ├── TypeArrayEqualityComparer.cs └── ValueAccessor.cs ├── ErrorMessages.cs ├── Extensions ├── CollectionExtensions.cs ├── ExpressionExtensions.cs ├── GeneratorExtensions.cs ├── PropertyInfoEnumerableExtensions.cs ├── PropertyReflectionExtensions.cs ├── StringExtensions.cs └── TypeReflectionExtensions.cs ├── Guard.cs ├── Interfaces └── IInstanceReaderMethodBuilder.cs ├── InternalsVisibleTo.cs ├── Mapping ├── CachedPropertyMapper.cs ├── IKeyPropertyMapper.cs ├── IPropertyMapper.cs ├── KeyPropertyMapper.cs ├── KeyPropertyMapperValidator.cs ├── MappingInfo.cs ├── PropertyMapper.cs └── PropertyTypeValidator.cs ├── Nuget └── DbReader.nuspec ├── PassDelegate.cs ├── ReadDelegate.cs ├── Readers ├── CachedInstanceReader.cs ├── IInstanceReader.cs ├── IInstanceReaderFactory.cs ├── IKeyReader.cs ├── IReader.cs ├── InstanceReader.cs ├── InstanceReaderFactory.cs └── KeyReader.cs ├── Selectors ├── CachedFieldSelector.cs ├── CachedPropertySelector.cs ├── ColumnInfo.cs ├── ConstructorValidator.cs ├── FieldSelector.cs ├── FirstConstructorSelector.cs ├── IConstructorSelector.cs ├── IFieldSelector.cs ├── IMethodSelector.cs ├── IOrdinalSelector.cs ├── IPropertySelector.cs ├── ManyToOnePropertySelector.cs ├── MethodSelector.cs ├── OneToManyPropertySelector.cs ├── OneToManyPropertyValidator.cs ├── OrdinalSelector.cs ├── ParameterlessConstructorSelector.cs ├── ReadableArgumentPropertiesValidator.cs ├── ReadablePropertySelector.cs └── SimplePropertySelector.cs ├── Settings.StyleCop ├── SqlStatement.cs ├── Tracking ├── ITrackedObject.cs └── TrackedAttribute.cs ├── TypeEvaluator.cs ├── ValueConverter.cs └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.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: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | submodules: recursive 13 | fetch-depth: 0 14 | fetch-tags: true 15 | - name: Install .Net Core 16 | uses: actions/setup-dotnet@v2.0.0 17 | with: 18 | dotnet-version: 8.0.406 19 | - name: Install dotnet-script 20 | run: dotnet tool install dotnet-script --global 21 | 22 | - name: Install dotnet-ilverify 23 | run: dotnet tool install dotnet-ilverify --global --version 8.0.0 24 | 25 | - name: Run build script 26 | run: dotnet-script build/build.csx 27 | env: # Or as an environment variable 28 | GITHUB_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | IS_SECURE_BUILDENVIRONMENT: ${{ secrets.IS_SECURE_BUILDENVIRONMENT }} 30 | NUGET_APIKEY: ${{ secrets.NUGET_APIKEY }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | Artifacts 18 | .loadpath 19 | # Ignore these since we download dotnet-script into the root folder on the build server. 20 | dotnet-script 21 | dotnet-script.zip 22 | # External tool builders 23 | .externalToolBuilders/ 24 | 25 | # Locally stored "Eclipse launch configurations" 26 | *.launch 27 | 28 | # CDT-specific 29 | .cproject 30 | 31 | # PDT-specific 32 | .buildpath 33 | 34 | 35 | ################# 36 | ## Visual Studio 37 | ################# 38 | 39 | ## Ignore Visual Studio temporary files, build results, and 40 | ## files generated by popular Visual Studio add-ons. 41 | 42 | # User-specific files 43 | *.suo 44 | *.user 45 | *.sln.docstates 46 | .vs/ 47 | 48 | # Build results 49 | [Dd]ebug/ 50 | [Rr]elease/ 51 | *_i.c 52 | *_p.c 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.vspscc 67 | .builds 68 | *.dotCover 69 | *.cs.pp 70 | *.nupkg 71 | project.lock.json 72 | LightInject.PreProcessor.dll 73 | db/ 74 | lib/ 75 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 76 | packages/ 77 | scriptcs_packages/ 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opensdf 84 | *.sdf 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | 90 | # ReSharper is a .NET coding add-in 91 | _ReSharper* 92 | 93 | # Installshield output folder 94 | [Ee]xpress 95 | 96 | # DocProject is a documentation generator add-in 97 | DocProject/buildhelp/ 98 | DocProject/Help/*.HxT 99 | DocProject/Help/*.HxC 100 | DocProject/Help/*.hhc 101 | DocProject/Help/*.hhk 102 | DocProject/Help/*.hhp 103 | DocProject/Help/Html2 104 | DocProject/Help/html 105 | 106 | 107 | # Click-Once directory 108 | publish 109 | 110 | # Others 111 | [Bb]in 112 | [Oo]bj 113 | sql 114 | TestResults 115 | *.Cache 116 | ClientBin 117 | stylecop.* 118 | ~$* 119 | *.dbmdl 120 | Generated_Code #added for RIA/Silverlight projects 121 | *.ncrunch* 122 | 123 | # Backup & report files from converting an old project file to a newer 124 | # Visual Studio version. Backup files are not needed, because we have git ;-) 125 | _UpgradeReport_Files/ 126 | Backup*/ 127 | UpgradeLog*.XML 128 | 129 | 130 | ############ 131 | ## Windows 132 | ############ 133 | 134 | # Windows image file caches 135 | Thumbs.db 136 | 137 | # Folder config file 138 | Desktop.ini 139 | 140 | 141 | ############# 142 | ## Python 143 | ############# 144 | 145 | *.py[co] 146 | 147 | # Packages 148 | *.egg 149 | *.egg-info 150 | dist 151 | eggs 152 | parts 153 | bin 154 | var 155 | sdist 156 | develop-eggs 157 | .installed.cfg 158 | 159 | # Installer logs 160 | pip-log.txt 161 | 162 | # Unit test / coverage reports 163 | .coverage 164 | .tox 165 | 166 | #Translations 167 | *.mo 168 | 169 | #Mr Developer 170 | .mr.developer.cfg 171 | 172 | 173 | /NuGet/Build/Net/LightInject.Annotation/LightInject.Annotation.cs 174 | /NuGet/Build/Net45/LightInject/LightInject.cs 175 | 176 | 177 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cecil"] 2 | path = cecil 3 | url = https://github.com/jbevain/cecil.git 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Script Debug", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "program": "${env:HOME}/.dotnet/tools/dotnet-script", 12 | "args": ["${file}"], 13 | "windows": { 14 | "program": "${env:USERPROFILE}/.dotnet/tools/dotnet-script.exe", 15 | }, 16 | "cwd": "${workspaceFolder}", 17 | "stopAtEntry": false, 18 | } 19 | ,] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["Shouldly", "Xunit"], 3 | 4 | "coverage-gutters.xmlname": "../../build/Artifacts/CodeCoverage/coverage.opencover.xml", 5 | "coverage-gutters.coverageReportFileName": "../../build/Artifacts/CodeCoverage/**/index.htm", 6 | "coverage-gutters.showLineCoverage": true, 7 | 8 | "coverage-gutters.showRulerCoverage": true 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "args": [ 8 | "build", 9 | "/property:GenerateFullPaths=true" 10 | ], 11 | "type": "shell", 12 | "group": "build", 13 | "presentation": { 14 | "reveal": "always" 15 | }, 16 | "options": { 17 | "cwd": "${workspaceFolder}" 18 | }, 19 | "problemMatcher": "$msCompile" 20 | }, 21 | { 22 | "label": "rebuild", 23 | "command": "dotnet", 24 | "args": [ 25 | "build", 26 | "--no-incremental", 27 | "/property:GenerateFullPaths=true" 28 | ], 29 | "type": "shell", 30 | "group": "build", 31 | "presentation": { 32 | "reveal": "always", 33 | "clear": true 34 | }, 35 | "options": { 36 | "cwd": "${workspaceFolder}" 37 | }, 38 | "problemMatcher": "$msCompile" 39 | }, 40 | { 41 | "label": "test", 42 | "command": "dotnet", 43 | "type": "process", 44 | "args": [ 45 | "script", 46 | "${workspaceFolder}/build/build.csx", 47 | "test" 48 | ], 49 | "problemMatcher": "$msCompile", 50 | "group": { 51 | "kind": "test", 52 | "isDefault": true 53 | }, 54 | "presentation": { 55 | "echo": true, 56 | "reveal": "always", 57 | "focus": false, 58 | "panel": "shared", 59 | "showReuseMessage": true, 60 | "clear": true 61 | } 62 | }, 63 | { 64 | "label": "test with coverage", 65 | "command": "dotnet", 66 | "type": "process", 67 | "args": [ 68 | "script", 69 | "${workspaceFolder}/build/build.csx", 70 | "testcoverage" 71 | ], 72 | "problemMatcher": "$msCompile", 73 | "group": { 74 | "kind": "test", 75 | "isDefault": true 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /DbReader.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7D7BAB27-2679-4825-BCAC-506DE94B09BC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbReader", "src\DbReader\DbReader.csproj", "{750224E1-F07A-4F85-AF4A-A58896AE800F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbReader.Tests", "src\DbReader.Tests\DbReader.Tests.csproj", "{C7D0AF4F-6CF0-4D86-8F71-167557A205A5}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbReader.Tracking", "src\DbReader.Tracking\DbReader.Tracking.csproj", "{E1B757E5-F9CD-49AB-AE23-364BEB752711}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbReader.Tracking.SampleAssembly", "src\DbReader.Tracking.SampleAssembly\DbReader.Tracking.SampleAssembly.csproj", "{AF516499-8AE8-4C37-B7D4-444D4C8066DF}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {750224E1-F07A-4F85-AF4A-A58896AE800F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {750224E1-F07A-4F85-AF4A-A58896AE800F}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {750224E1-F07A-4F85-AF4A-A58896AE800F}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {750224E1-F07A-4F85-AF4A-A58896AE800F}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {C7D0AF4F-6CF0-4D86-8F71-167557A205A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {C7D0AF4F-6CF0-4D86-8F71-167557A205A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {C7D0AF4F-6CF0-4D86-8F71-167557A205A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {C7D0AF4F-6CF0-4D86-8F71-167557A205A5}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {E1B757E5-F9CD-49AB-AE23-364BEB752711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {E1B757E5-F9CD-49AB-AE23-364BEB752711}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {E1B757E5-F9CD-49AB-AE23-364BEB752711}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {E1B757E5-F9CD-49AB-AE23-364BEB752711}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {AF516499-8AE8-4C37-B7D4-444D4C8066DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {AF516499-8AE8-4C37-B7D4-444D4C8066DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {AF516499-8AE8-4C37-B7D4-444D4C8066DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {AF516499-8AE8-4C37-B7D4-444D4C8066DF}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {750224E1-F07A-4F85-AF4A-A58896AE800F} = {7D7BAB27-2679-4825-BCAC-506DE94B09BC} 44 | {C7D0AF4F-6CF0-4D86-8F71-167557A205A5} = {7D7BAB27-2679-4825-BCAC-506DE94B09BC} 45 | {E1B757E5-F9CD-49AB-AE23-364BEB752711} = {7D7BAB27-2679-4825-BCAC-506DE94B09BC} 46 | {AF516499-8AE8-4C37-B7D4-444D4C8066DF} = {7D7BAB27-2679-4825-BCAC-506DE94B09BC} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /build/build.csx: -------------------------------------------------------------------------------- 1 | #load "nuget:Dotnet.Build, 0.24.0" 2 | #load "nuget:dotnet-steps, 0.0.2" 3 | 4 | Console.WriteLine($"Building with the latest tag {BuildContext.LatestTag}"); 5 | 6 | BuildContext.CodeCoverageThreshold = 87; 7 | 8 | [StepDescription("Runs the tests with test coverage")] 9 | Step testcoverage = () => DotNet.TestWithCodeCoverage(); 10 | 11 | [StepDescription("Runs all the tests for all target frameworks")] 12 | Step test = () => DotNet.Test(); 13 | 14 | [StepDescription("Creates the NuGet packages")] 15 | AsyncStep pack = async () => 16 | { 17 | test(); 18 | testcoverage(); ; 19 | DotNet.Pack(); 20 | await buildTrackingPackage(); 21 | }; 22 | 23 | [DefaultStep] 24 | [StepDescription("Deploys packages if we are on a tag commit in a secure environment.")] 25 | AsyncStep deploy = async () => 26 | { 27 | await pack(); 28 | await Artifacts.Deploy(); 29 | }; 30 | 31 | AsyncStep buildTrackingPackage = async () => 32 | { 33 | var workingDirectory = Path.Combine(BuildContext.SourceFolder, "DbReader.Tracking"); 34 | await Command.ExecuteAsync("dotnet", $"pack /p:NuspecFile=DbReader.Tracking.nuspec /p:IsPackable=true /p:NuspecProperties=version={BuildContext.LatestTag} -o ../../build/Artifacts/NuGet", workingDirectory); 35 | 36 | }; 37 | 38 | 39 | await StepRunner.Execute(Args); 40 | return 0; 41 | 42 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.406", 4 | "rollForward": "latestFeature" 5 | } 6 | } -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "FormattingOptions": { 3 | "EnableEditorConfigSupport": true, 4 | "OrganizeImports": true 5 | }, 6 | "RoslynExtensionsOptions": { 7 | "EnableAnalyzersSupport": true, 8 | "EnableDecompilationSupport": true 9 | }, 10 | "script": { 11 | "enableScriptNuGetReferences": true, 12 | "defaultTargetFramework": "net8.0" 13 | } 14 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/ArgumentProcessorTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System.Data; 4 | using Extensions; 5 | using Moq; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | public class ArgumentProcessorTests 10 | { 11 | static ArgumentProcessorTests() 12 | { 13 | ArgumentProcessor.RegisterProcessDelegate((parameter, argument) => parameter.Value = argument.Value); 14 | } 15 | 16 | [Fact] 17 | public void ShouldProcessArgument() 18 | { 19 | Mock dataParameterMock = new Mock(); 20 | dataParameterMock.SetupAllProperties(); 21 | 22 | ArgumentProcessor.Process(typeof(CustomValueType), dataParameterMock.Object, new CustomValueType(42)); 23 | 24 | dataParameterMock.Object.Value.ShouldBe(42); 25 | } 26 | 27 | [Fact] 28 | public void ShouldRecognizeCustomArgumentTypeAsSimpleType() 29 | { 30 | typeof(CustomValueType).IsSimpleType().ShouldBeTrue(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // using Xunit; 2 | 3 | // [assembly: CollectionBehavior(DisableTestParallelization = true)] -------------------------------------------------------------------------------- /src/DbReader.Tests/CachedArgumentParserMethodBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace DbReader.Tests 4 | { 5 | using System; 6 | using System.Runtime.CompilerServices; 7 | using Construction; 8 | using Moq; 9 | using Shouldly; 10 | using Xunit; 11 | 12 | public class CachedArgumentParserMethodBuilderTests 13 | { 14 | [Fact] 15 | public void CreateMethod_SameTypeAndSql_InvokedOnlyOnce() 16 | { 17 | var methodBuilderMock = CreateMethodBuilderMock(); 18 | var methodBuilder = new CachedArgumentParserMethodBuilder(methodBuilderMock.Object); 19 | 20 | var arg = new { A = 1 }; 21 | 22 | methodBuilder.CreateMethod(FakeSql.Create("10"), arg.GetType(), Array.Empty()); 23 | methodBuilder.CreateMethod(FakeSql.Create("10"), arg.GetType(), Array.Empty()); 24 | 25 | methodBuilderMock.Verify(m => m.CreateMethod(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); 26 | } 27 | 28 | [Fact] 29 | public void CreateMethod_SameTypeAndDifferentSql_InvokedTwice() 30 | { 31 | var methodBuilderMock = CreateMethodBuilderMock(); 32 | var methodBuilder = new CachedArgumentParserMethodBuilder(methodBuilderMock.Object); 33 | 34 | methodBuilder.CreateMethod(FakeSql.Create("1"), new { A = 1 }.GetType(), Array.Empty()); 35 | methodBuilder.CreateMethod(FakeSql.Create("2"), new { A = 1 }.GetType(), Array.Empty()); 36 | 37 | methodBuilderMock.Verify(m => m.CreateMethod(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); 38 | } 39 | 40 | [Fact] 41 | public void CreateMethod_DifferentTypeAndSameSql_InvokedTwice() 42 | { 43 | var methodBuilderMock = CreateMethodBuilderMock(); 44 | var methodBuilder = new CachedArgumentParserMethodBuilder(methodBuilderMock.Object); 45 | 46 | 47 | methodBuilder.CreateMethod(FakeSql.Create(), new { A = 1 }.GetType(), Array.Empty()); 48 | methodBuilder.CreateMethod(FakeSql.Create(), new { B = 1 }.GetType(), Array.Empty()); 49 | 50 | methodBuilderMock.Verify(m => m.CreateMethod(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); 51 | } 52 | 53 | [Fact] 54 | public void CreateMethod_DifferentTypeAndDifferentSql_InvokedTwice() 55 | { 56 | var methodBuilderMock = CreateMethodBuilderMock(); 57 | var methodBuilder = new CachedArgumentParserMethodBuilder(methodBuilderMock.Object); 58 | 59 | methodBuilder.CreateMethod(FakeSql.Create("1"), new { A = 1 }.GetType(), Array.Empty()); 60 | methodBuilder.CreateMethod(FakeSql.Create("2"), new { B = 1 }.GetType(), Array.Empty()); 61 | 62 | methodBuilderMock.Verify(m => m.CreateMethod(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); 63 | } 64 | 65 | private static Mock CreateMethodBuilderMock() 66 | { 67 | var methodBuilderMock = new Mock(); 68 | methodBuilderMock.Setup(m => m.CreateMethod(It.IsAny(), It.IsAny(), It.IsAny())) 69 | .Returns((s, o, func) => null); 70 | return methodBuilderMock; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/ConstructorSelectorTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | 5 | using DbReader.Interfaces; 6 | using Selectors; 7 | using Shouldly; 8 | using Xunit; 9 | using DbReader.LightInject; 10 | 11 | public class ConstructorSelectorTests : ContainerFixture 12 | { 13 | private readonly Selectors.IConstructorSelector parameterlessConstructorSelector; 14 | private readonly Selectors.IConstructorSelector firstConstructorSelector; 15 | 16 | public ConstructorSelectorTests() 17 | { 18 | parameterlessConstructorSelector = ServiceFactory.GetInstance("ParameterlessConstructorSelector"); 19 | firstConstructorSelector = ServiceFactory.GetInstance("firstConstructorSelector"); 20 | } 21 | 22 | [Fact] 23 | public void Execute_PublicParameterLess_ReturnsConstructor() 24 | { 25 | parameterlessConstructorSelector.Execute(typeof(ClassWithPublicParameteressConstructor)).ShouldNotBeNull(); 26 | } 27 | 28 | [Fact] 29 | public void Execute_PrivateParameterLess_ThrowsException() 30 | { 31 | Should.Throw( 32 | () => parameterlessConstructorSelector.Execute(typeof(ClassWithPrivateParameterlessConstructor))); 33 | } 34 | 35 | [Fact] 36 | public void Execute_TupleClass_ReturnsConstructor() 37 | { 38 | firstConstructorSelector.Execute(typeof(Tuple)).ShouldNotBeNull(); 39 | } 40 | } 41 | 42 | public class ClassWithPublicParameteressConstructor 43 | { 44 | public ClassWithPublicParameteressConstructor() 45 | { 46 | } 47 | } 48 | 49 | public class ClassWithPrivateParameterlessConstructor 50 | { 51 | private ClassWithPrivateParameterlessConstructor() 52 | { 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/CustomValueType.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | public struct CustomValueType 4 | { 5 | public CustomValueType(int value) 6 | : this() 7 | { 8 | Value = value; 9 | } 10 | 11 | public int Value { get; private set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/DataRecordExtensionTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using System.Linq; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | public class DataRecordExtensionTests 9 | { 10 | [Fact] 11 | public void ShouldGetBytesWithSpecifiedLength() 12 | { 13 | var dataRecord = new { Bytes = new byte[] { 42, 84 } }.ToDataRecord(); 14 | 15 | var bytes = dataRecord.GetBytes(0, 2); 16 | 17 | bytes.SequenceEqual(new byte[] { 42, 84 }).ShouldBeTrue(); 18 | } 19 | 20 | [Fact] 21 | public void ShouldGetBytesWithoutSpecifiedLength() 22 | { 23 | byte[] actualBytes = new byte[512]; 24 | Random random = new Random(); 25 | random.NextBytes(actualBytes); 26 | var dataRecord = new {Bytes = actualBytes}.ToDataRecord(); 27 | 28 | var bytes = dataRecord.GetBytes(0); 29 | 30 | bytes.SequenceEqual(actualBytes).ShouldBeTrue(); 31 | } 32 | 33 | [Fact] 34 | public void ShouldGetChars() 35 | { 36 | var dataRecord = new { Bytes = new[] { 'a', 'b' } }.ToDataRecord(); 37 | 38 | var chars = dataRecord.GetChars(0); 39 | 40 | chars.SequenceEqual(new[] { 'a', 'b' }).ShouldBeTrue(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/DbReader.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | runtime; build; native; contentfiles; analyzers; buildtransitive 11 | all 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers 17 | all 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | net8.0 26 | false 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/DbReader.Tests/DbReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System.Data; 4 | using System.Text; 5 | 6 | public static class DbReaderExtensions 7 | { 8 | public static string ToMarkdown(this IDataReader dataReader) 9 | { 10 | StringBuilder sb = new StringBuilder(); 11 | var fieldCount = dataReader.FieldCount; 12 | string[] columnNames = new string[fieldCount]; 13 | 14 | for (int i = 0; i < fieldCount; i++) 15 | { 16 | columnNames[i] = dataReader.GetName(i); 17 | } 18 | 19 | for (int i = 0; i < fieldCount; i++) 20 | { 21 | sb.Append($"| {columnNames[i]} "); 22 | } 23 | sb.Append("|"); 24 | sb.AppendLine(); 25 | 26 | for (int i = 0; i < fieldCount; i++) 27 | { 28 | sb.Append($"| {new string('-', columnNames[i].Length)} "); 29 | } 30 | 31 | sb.Append("|"); 32 | sb.AppendLine(); 33 | 34 | 35 | while (dataReader.Read()) 36 | { 37 | for (int i = 0; i < fieldCount; i++) 38 | { 39 | object value; 40 | if (dataReader.IsDBNull(i)) 41 | { 42 | value = "NULL"; 43 | } 44 | else 45 | { 46 | value = dataReader.GetValue(i); 47 | } 48 | sb.Append($"| {value} "); 49 | } 50 | sb.Append("|"); 51 | sb.AppendLine(); 52 | } 53 | 54 | 55 | return sb.ToString(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/Failing.playlist: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/DbReader.Tests/FakeSql.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | public class FakeSql 7 | { 8 | public static string Create(string prefix = null, [CallerMemberName] string caller = null) 9 | { 10 | // Adding "SQL" forces the string returned not to referenceequals. 11 | return prefix + caller + "Sql"; 12 | } 13 | 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/FieldSelectorTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | 7 | using DbReader.Interfaces; 8 | using Selectors; 9 | using Shouldly; 10 | using Xunit; 11 | 12 | public class FieldSelectorTests : ContainerFixture 13 | { 14 | 15 | public readonly IFieldSelector fieldSelector; 16 | 17 | [Fact] 18 | public void ShouldReturnSameInstanceWithinScope() 19 | { 20 | var first = GetInstance(); 21 | var second = GetInstance(); 22 | first.ShouldBeSameAs(second); 23 | } 24 | 25 | [Fact] 26 | public void Execute_ValidColumnns_ReturnsDictionary() 27 | { 28 | IDataRecord dataRecord = new { SomeColumn = 42 }.ToDataRecord(); 29 | IReadOnlyDictionary result = fieldSelector.Execute(dataRecord); 30 | result["SomeColumn"].Ordinal.ShouldBe(0); 31 | } 32 | 33 | [Fact] 34 | public void Execute_DuplicateFieldNames_ThrowsArgumentOutOfRangeException() 35 | { 36 | IDataRecord dataRecord = new { SomeColumn = 42, somecolumn = 42 }.ToDataRecord(); 37 | Should.Throw(() => fieldSelector.Execute(dataRecord)); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/InstanceReaderVerificationTests.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0 2 | namespace DbReader.Tests 3 | { 4 | using System; 5 | using System.IO; 6 | using Construction; 7 | using LightInject; 8 | using IMethodSkeleton = Construction.IMethodSkeleton; 9 | 10 | 11 | public class InstanceReaderVerificationTests : InstanceReaderTests 12 | { 13 | internal static void Configure(IServiceContainer container) 14 | { 15 | container.Register(new PerContainerLifetime()); 16 | } 17 | 18 | internal override void Configure(IServiceRegistry serviceRegistry) 19 | { 20 | base.Configure(serviceRegistry); 21 | serviceRegistry.Register(new PerContainerLifetime()); 22 | 23 | } 24 | } 25 | } 26 | #endif -------------------------------------------------------------------------------- /src/DbReader.Tests/KeyConventionTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Xunit; 3 | 4 | namespace DbReader.Tests 5 | { 6 | public class KeyConventionTests 7 | { 8 | [Fact] 9 | public void ShouldUseClassIdProperty() 10 | { 11 | var result = DbReaderOptions.KeyConvention(typeof(Customer).GetProperty(nameof(Customer.CustomerId))); 12 | result.ShouldBeTrue(); 13 | } 14 | 15 | [Fact] 16 | public void ShouldUseIdProperty() 17 | { 18 | var result = DbReaderOptions.KeyConvention(typeof(Employee).GetProperty(nameof(Employee.Id))); 19 | result.ShouldBeTrue(); 20 | } 21 | 22 | [Fact] 23 | public void ShouldUseCustomIdIntProperty() 24 | { 25 | DbReaderOptions.KeySelector(ac => ac.CustomId); 26 | var result = DbReaderOptions.KeyConvention(typeof(AnotherCustomer).GetProperty(nameof(AnotherCustomer.CustomId))); 27 | result.ShouldBeTrue(); 28 | } 29 | 30 | [Fact] 31 | public void ShouldUseCustomIdStringProperty() 32 | { 33 | DbReaderOptions.KeySelector(ac => ac.CustomId); 34 | var result = DbReaderOptions.KeyConvention(typeof(AnotherEmployee).GetProperty(nameof(AnotherEmployee.CustomId))); 35 | result.ShouldBeTrue(); 36 | } 37 | 38 | public class AnotherCustomer 39 | { 40 | public string CustomId { get; set; } 41 | } 42 | 43 | public class Customer 44 | { 45 | public int CustomerId { get; set; } 46 | } 47 | 48 | public class Employee 49 | { 50 | public int Id { get; set; } 51 | } 52 | 53 | public class AnotherEmployee 54 | { 55 | public string CustomId { get; set; } 56 | } 57 | } 58 | 59 | 60 | 61 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/KeyPropertyMapperTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using DbReader.Interfaces; 5 | using Mapping; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | public class KeyPropertyMapperTests : ContainerFixture 10 | { 11 | public readonly IKeyPropertyMapper keyPropertyMapper; 12 | 13 | [Fact] 14 | public void Execute_ClassWithIdProperty_ReturnsMapping() 15 | { 16 | var dataRecord = new { Id = 42 }.ToDataRecord(); 17 | var result = keyPropertyMapper.Execute(typeof(ClassWithIdProperty), dataRecord, string.Empty); 18 | result.Length.ShouldBe(1); 19 | result[0].ColumnInfo.Ordinal.ShouldBe(0); 20 | } 21 | 22 | [Fact] 23 | public void Execute_ClassWithTypeNamePrefixedIdProperty_ReturnsMapping() 24 | { 25 | var dataRecord = new { ClassWithTypeNamePrefixedIdPropertyId = 42 }.ToDataRecord(); 26 | var result = keyPropertyMapper.Execute(typeof(ClassWithTypeNamePrefixedIdProperty), dataRecord, string.Empty); 27 | result.Length.ShouldBe(1); 28 | result[0].ColumnInfo.Ordinal.ShouldBe(0); 29 | } 30 | 31 | [Fact] 32 | public void Execute_UnMappedKeyProperty_ThrowsException() 33 | { 34 | var dataRecord = new { InvalidField = 42 }.ToDataRecord(); 35 | var exception = Should.Throw( 36 | () => keyPropertyMapper.Execute(typeof(ClassWithIdProperty), dataRecord, string.Empty)); 37 | exception.Message.ShouldContain("ClassWithIdProperty.Id"); 38 | } 39 | 40 | [Fact] 41 | public void Execute_MissingKeyProperty_ThrowsException() 42 | { 43 | var dataRecord = new { InvalidField = 42 }.ToDataRecord(); 44 | Should.Throw( 45 | () => keyPropertyMapper.Execute(typeof(ClassWithoutKeyProperty), dataRecord, string.Empty)); 46 | } 47 | } 48 | 49 | 50 | public class ClassWithIdProperty 51 | { 52 | public int Id { get; set; } 53 | } 54 | 55 | public class ClassWithTypeNamePrefixedIdProperty 56 | { 57 | public int ClassWithTypeNamePrefixedIdPropertyId { get; set; } 58 | } 59 | 60 | public class ClassWithoutKeyProperty 61 | { 62 | public string Name { get; set; } 63 | } 64 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/KeyReaderTests.cs: -------------------------------------------------------------------------------- 1 | // namespace DbReader.Tests 2 | // { 3 | // using System; 4 | // using System.Collections; 5 | // using System.ComponentModel.DataAnnotations; 6 | // using System.Reflection; 7 | 8 | // using DbReader.Interfaces; 9 | // using Readers; 10 | // using Shouldly; 11 | 12 | // public class KeyReaderTests 13 | // { 14 | // public KeyReaderTests() 15 | // { 16 | // DbReaderOptions.KeySelector(c => c.KeyProperty); 17 | // DbReaderOptions.KeySelector(c => c.FirstKeyProperty, c => c.SecondKeyProperty); 18 | // } 19 | 20 | // public void ShouldReadSingleKey(IKeyReader keyReader) 21 | // { 22 | // var dataRecord = new { KeyProperty = 42 }.ToDataRecord(); 23 | // IStructuralEquatable key = keyReader.Read(typeof(ClassWithSingleKey), dataRecord, string.Empty); 24 | // key.ShouldBe(Tuple.Create(42)); 25 | // } 26 | 27 | // public void ShouldReadCompositeKey(IKeyReader keyReader) 28 | // { 29 | // var dataRecord = new { FirstKeyProperty = 42, SecondKeyProperty = 84 }.ToDataRecord(); 30 | // IStructuralEquatable key = keyReader.Read(typeof(ClassWithCompositeKey), dataRecord, string.Empty); 31 | // key.ShouldBe(Tuple.Create(42, 84)); 32 | // } 33 | // } 34 | 35 | 36 | // public class ClassWithSingleKey 37 | // { 38 | // [Key] 39 | // public int KeyProperty { get; set; } 40 | // } 41 | 42 | // public class ClassWithCompositeKey 43 | // { 44 | // [Key] 45 | // public int FirstKeyProperty { get; set; } 46 | // [Key] 47 | // public int SecondKeyProperty { get; set; } 48 | // } 49 | // } -------------------------------------------------------------------------------- /src/DbReader.Tests/ManyToOnePropertySelectorTests.cs: -------------------------------------------------------------------------------- 1 | // namespace DbReader.Tests 2 | // { 3 | // using DbReader.Interfaces; 4 | // using DbReader.LightInject; 5 | // using DbReader.Tests.LightInject.xUnit2; 6 | // using Shouldly ; 7 | // using Xunit; 8 | // using Xunit.Extensions; 9 | 10 | // public class ManyToOnePropertySelectorTests 11 | // { 12 | // [Theory, Scoped, InjectData] 13 | // public void Execute_NonEnumerableProperty_ReturnsProperty(IPropertySelector manyToOnePropertySelector) 14 | // { 15 | // manyToOnePropertySelector.Execute(typeof(ClassWithComplexProperty)).ShouldNotBeEmpty(); 16 | // } 17 | 18 | // [Theory, InjectData] 19 | // public void Execute_SimpleProperty_ReturnsEmptyList(IPropertySelector manyToOnePropertySelector) 20 | // { 21 | // manyToOnePropertySelector.Execute(typeof(ClassWithPublicProperty)).ShouldBeEmpty(); 22 | // } 23 | 24 | // [Theory, InjectData] 25 | // public void Execute_EnumerableProperty_ReturnsEmptyList(IPropertySelector manyToOnePropertySelector) 26 | // { 27 | // manyToOnePropertySelector.Execute(typeof(ClassWithEnumerableProperty)).ShouldBeEmpty(); 28 | // } 29 | 30 | // [Theory, InjectData] 31 | // public void Execute_StringProperty_ReturnsEmptyList(IPropertySelector manyToOnePropertySelector) 32 | // { 33 | // manyToOnePropertySelector.Execute(typeof(ClassWithStringProperty)).ShouldBeEmpty(); 34 | // } 35 | 36 | // [Theory, InjectData] 37 | // public void Execute_ByteArrayProperty_ReturnsEmptyList(IPropertySelector manyToOnePropertySelector) 38 | // { 39 | // manyToOnePropertySelector.Execute(typeof(ClassWithByteArrayProperty)).ShouldBeEmpty(); 40 | // } 41 | 42 | // } 43 | // } -------------------------------------------------------------------------------- /src/DbReader.Tests/OneToManyPropertySelectorTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using Selectors; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | public class OneToManyPropertySelectorTests : ContainerFixture 12 | { 13 | public IPropertySelector oneToManyPropertySelector; 14 | 15 | 16 | [Fact] 17 | public void ShouldAllowIEnumerable() 18 | { 19 | var properties = oneToManyPropertySelector.Execute(typeof (ClassWithProperty>)); 20 | properties.ShouldNotBeEmpty(); 21 | } 22 | 23 | [Fact] 24 | public void ShouldAllowICollection() 25 | { 26 | var properties = oneToManyPropertySelector.Execute(typeof(ClassWithProperty>)); 27 | properties.ShouldNotBeEmpty(); 28 | } 29 | 30 | [Fact] 31 | public void ShouldAllowCollection() 32 | { 33 | var properties = oneToManyPropertySelector.Execute(typeof(ClassWithProperty>)); 34 | properties.ShouldNotBeEmpty(); 35 | } 36 | 37 | [Fact] 38 | public void ShotNotAllowReadOnlyCollection() 39 | { 40 | var exception = Should.Throw(() => oneToManyPropertySelector.Execute(typeof(ClassWithProperty))); 41 | exception.Message.ShouldStartWith("The navigation property (one-to-many)"); 42 | } 43 | 44 | [Fact] 45 | public void ShouldNotAllowNonGenericCollection() 46 | { 47 | var exception = Should.Throw(() => oneToManyPropertySelector.Execute(typeof(ClassWithProperty))); 48 | exception.Message.ShouldStartWith("The navigation property (one-to-many)"); 49 | } 50 | 51 | [Fact] 52 | public void ShouldNotAllowSimpleTypesAsProjectionType() 53 | { 54 | var exception = Should.Throw(() => oneToManyPropertySelector.Execute(typeof(ClassWithProperty>))); 55 | exception.Message.ShouldContain("Simple types such as string and int"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/PrefixResolverTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using Construction; 4 | using DbReader.Interfaces; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | public class PrefixResolverTests : ContainerFixture 9 | { 10 | 11 | public readonly IPrefixResolver prefixResolver; 12 | 13 | [Fact] 14 | public void GetPrefix_NoPrefix_Null() 15 | { 16 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 17 | var dataRecord = new { OrderId = 42L }.ToDataRecord(); 18 | var prefix = prefixResolver.GetPrefix(property, dataRecord, string.Empty); 19 | prefix.ShouldBeNull(); 20 | } 21 | 22 | [Fact] 23 | public void GetPrefix_FullPrefix_ReturnsPrefix() 24 | { 25 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 26 | var dataRecord = new { Orders_OrderId = 42L }.ToDataRecord(); 27 | 28 | var prefix = prefixResolver.GetPrefix(property, dataRecord, string.Empty); 29 | 30 | prefix.ShouldBe("Orders"); 31 | } 32 | 33 | [Fact] 34 | public void GetPrefix_UpperCasePrefix_ReturnsPrefix() 35 | { 36 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 37 | var dataRecord = new { O_OrderId = 42L }.ToDataRecord(); 38 | 39 | var prefix = prefixResolver.GetPrefix(property, dataRecord, string.Empty); 40 | 41 | prefix.ShouldBe("O"); 42 | } 43 | 44 | [Fact] 45 | public void GetPrefix_ExistingPrefix_AppendsToExistingPrefix() 46 | { 47 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 48 | var dataRecord = new { CustomerWithOrders_Orders_OrderId = 42L }.ToDataRecord(); 49 | 50 | var prefix = prefixResolver.GetPrefix(property, dataRecord, "CustomerWithOrders"); 51 | 52 | prefix.ShouldBe("CustomerWithOrders_Orders"); 53 | } 54 | 55 | [Fact] 56 | public void GetPrefix_UnknownPrefix_ReturnsNull() 57 | { 58 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 59 | var dataRecord = new { UnknownPrefix_OrderId = 42L }.ToDataRecord(); 60 | 61 | var prefix = prefixResolver.GetPrefix(property, dataRecord, string.Empty); 62 | 63 | prefix.ShouldBeNull(); 64 | } 65 | 66 | [Fact] 67 | public void GetPrefix_UnknownField_ReturnsNull() 68 | { 69 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 70 | 71 | var dataRecord = new { UnknownField = 42L }.ToDataRecord(); 72 | 73 | var prefix = prefixResolver.GetPrefix(property, dataRecord, string.Empty); 74 | 75 | prefix.ShouldBeNull(); 76 | } 77 | 78 | [Fact] 79 | public void GetPrefix_UnknownFieldWithPrefix_ReturnsNull() 80 | { 81 | var property = typeof(CustomerWithOrders).GetProperty("Orders"); 82 | 83 | var dataRecord = new { Orders_UnknownField = 42L }.ToDataRecord(); 84 | 85 | var prefix = prefixResolver.GetPrefix(property, dataRecord, "Orders"); 86 | 87 | prefix.ShouldBeNull(); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/PropertyReflectionTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Database; 6 | using Extensions; 7 | using Selectors; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | public class PropertyReflectionTests 12 | { 13 | [Fact] 14 | public void ShouldConsiderPublicInstancePropertyWithSetterAsWriteable() 15 | { 16 | typeof(ClassWithProperty).GetProperty("Property") 17 | .IsWriteable().ShouldBeTrue(); 18 | } 19 | 20 | [Fact] 21 | public void ShouldNotConsiderPublicStaticPropertyWithSetterAsWriteable() 22 | { 23 | typeof(ClassWithStaticProperty).GetProperty("StaticProperty") 24 | .IsWriteable().ShouldBeFalse(); 25 | } 26 | 27 | [Fact] 28 | public void ShouldConsiderPublicInstancePropertyWithGetterAsReadable() 29 | { 30 | typeof(ClassWithProperty).GetProperty("Property") 31 | .IsReadable().ShouldBeTrue(); 32 | } 33 | 34 | [Fact] 35 | public void ShouldNotConsiderPublicStaticPropertyWithGetterAsWriteable() 36 | { 37 | typeof(ClassWithStaticProperty).GetProperty("StaticProperty") 38 | .IsReadable().ShouldBeFalse(); 39 | } 40 | 41 | [Fact] 42 | public void OrderByDeclaration_InOrder_ReturnsPropertiesInOrder() 43 | { 44 | var type = new { A = 1, B = 2 }.GetType(); 45 | var properties = new[] { type.GetProperty("A"), type.GetProperty("B") }; 46 | 47 | var orderedProperties = properties.OrderByDeclaration(); 48 | 49 | orderedProperties.SequenceEqual(properties).ShouldBeTrue(); 50 | } 51 | 52 | [Fact] 53 | public void OrderByDeclaration_OutOfOrder_ReturnsPropertiesInOrder() 54 | { 55 | var type = new { A = 1, B = 2 }.GetType(); 56 | var properties = new[] { type.GetProperty("A"), type.GetProperty("B") }; 57 | 58 | var orderedProperties = properties.Reverse().OrderByDeclaration(); 59 | 60 | orderedProperties.SequenceEqual(properties).ShouldBeTrue(); 61 | } 62 | 63 | [Fact] 64 | public void OrderByDeclaration_SamePropertyTwice_ReturnsPropertiesInOrder() 65 | { 66 | var type = new { A = 1, B = 2 }.GetType(); 67 | var properties = new[] { type.GetProperty("A"), type.GetProperty("A") }; 68 | 69 | var orderedProperties = properties.Reverse().OrderByDeclaration(); 70 | 71 | orderedProperties.SequenceEqual(properties).ShouldBeTrue(); 72 | } 73 | 74 | } 75 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/Queries/Customers.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | CustomerId, 3 | CompanyName, 4 | ContactName, 5 | ContactTitle, 6 | Address, 7 | City, 8 | Region, 9 | PostalCode, 10 | Country, 11 | Phone, 12 | Fax 13 | FROM 14 | Customers 15 | -------------------------------------------------------------------------------- /src/DbReader.Tests/Queries/CustomersAndOrders.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | c.CustomerID as CustomerWithOrdersId, 3 | o.OrderId as O_OrderId 4 | FROM 5 | Customers c 6 | INNER JOIN 7 | Orders o 8 | ON 9 | c.CustomerId =o.CustomerId -------------------------------------------------------------------------------- /src/DbReader.Tests/Queries/EmployeesHierarchy.sql: -------------------------------------------------------------------------------- 1 | WITH RECURSIVE ctx ( 2 | EmployeeId, 3 | FirstName, 4 | LastName, 5 | ReportsTo, 6 | Level 7 | ) 8 | AS ( 9 | SELECT initial.EmployeeId, 10 | initial.FirstName, 11 | initial.LastName, 12 | initial.ReportsTo, 13 | 0 14 | FROM employees initial 15 | WHERE initial.ReportsTo IS NULL 16 | UNION 17 | SELECT emp.Employeeid, 18 | emp.Firstname, 19 | emp.Lastname, 20 | emp.ReportsTo, 21 | ctx.Level + 1 22 | FROM employees emp 23 | INNER JOIN 24 | ctx ON ctx.EmployeeId = emp.ReportsTo 25 | ) 26 | SELECT 27 | EmployeeId, 28 | FirstName, 29 | LastName, 30 | ReportsTo 31 | FROM 32 | ctx 33 | ORDER BY level; 34 | -------------------------------------------------------------------------------- /src/DbReader.Tests/Queries/EmployeesWithOrdersAndTerritories.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | e.EmployeeId, 3 | e.LastName, 4 | e.FirstName, 5 | o.OrderId AS Orders_OrderId, 6 | o.OrderDate as Orders_OrderDate, 7 | NULL as Territories_id, 8 | NULL as Territories_TerritoryDescription 9 | FROM 10 | Employees e 11 | INNER JOIN 12 | Orders o 13 | ON 14 | e.EmployeeId = o.employeeId AND 15 | e.EmployeeId = @EmployeeId 16 | UNION 17 | SELECT 18 | e.EmployeeId, 19 | e.LastName, 20 | e.FirstName, 21 | NULL AS Orders_OrderId, 22 | NULL AS Orders_OrderDate, 23 | t.TerritoryId as Territories_id, 24 | t.TerritoryDescription as Territories_TerritoryDescription 25 | FROM 26 | Employees e 27 | INNER JOIN 28 | EmployeeTerritories et 29 | ON 30 | e.employeeid = et.employeeid AND 31 | e.employeeid = @EmployeeId 32 | INNER JOIN 33 | Territories t 34 | ON 35 | et.TerritoryId = t.TerritoryId -------------------------------------------------------------------------------- /src/DbReader.Tests/RegExParameterParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using Database; 4 | using Shouldly; 5 | using Xunit; 6 | using DbReader.LightInject; 7 | 8 | public class RegExParameterParserTests : ContainerFixture 9 | { 10 | public readonly IParameterParser parameterParser; 11 | 12 | [Fact] 13 | public void ShouldParseParametersWithAtSymbol() 14 | { 15 | string source = "Id = @Parameter"; 16 | var parameters = parameterParser.GetParameters(source); 17 | parameters.ShouldContain(p => p.Name == "Parameter"); 18 | } 19 | 20 | [Fact] 21 | public void ShouldParseParametersWithColonSymbol() 22 | { 23 | string source = "Id = :Parameter"; 24 | var parameters = parameterParser.GetParameters(source); 25 | parameters.ShouldContain(p => p.Name == "Parameter"); 26 | } 27 | 28 | [Fact] 29 | public void ShouldParseMultipleParameters() 30 | { 31 | string source = "Id = @Parameter, AnotherId = @AnotherParameter"; 32 | var parameters = parameterParser.GetParameters(source); 33 | parameters.Length.ShouldBe(2); 34 | parameters.ShouldContain(p => p.Name == "Parameter"); 35 | parameters.ShouldContain(p => p.Name == "AnotherParameter"); 36 | } 37 | 38 | [Fact] 39 | public void ShouldParseDuplicateParametersOnlyOnce() 40 | { 41 | string source = "Id = @Parameter, AnotherId = @Parameter"; 42 | var parameters = parameterParser.GetParameters(source); 43 | parameters.Length.ShouldBe(1); 44 | parameters.ShouldContain(p => p.Name == "Parameter"); 45 | } 46 | 47 | [Theory] 48 | [InlineData("Id = @Parameter, AnotherId IN (@ListParameter)")] 49 | [InlineData("Id = @Parameter, AnotherId IN ( @ListParameter )")] 50 | [InlineData("Id = @Parameter, AnotherId IN(@ListParameter)")] 51 | [InlineData("Id = @Parameter, AnotherId IN( @ListParameter )")] 52 | [InlineData("Id = @Parameter, AnotherId in( @ListParameter )")] 53 | public void ShouldMarkSingleListParameter(string source) 54 | { 55 | var parameters = parameterParser.GetParameters(source); 56 | parameters.Length.ShouldBe(2); 57 | parameters.ShouldContain(p => p.FullName == "@ListParameter" && p.IsListParameter); 58 | } 59 | 60 | [Theory] 61 | [InlineData("Id = @Parameter, AnotherId IN (@Param1, @Param3)")] 62 | public void ShouldNotMarkAsListParameterWhenMultiple(string source) 63 | { 64 | var parameters = parameterParser.GetParameters(source); 65 | parameters.Length.ShouldBe(3); 66 | parameters.ShouldNotContain(p => p.IsListParameter); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/SQL.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | public class SQL 4 | { 5 | private static ISqlProvider sqlProvider = new SqlProvider(); 6 | 7 | public static string Customers => sqlProvider.Customers; 8 | 9 | public static string CustomersAndOrders => sqlProvider.CustomersAndOrders; 10 | 11 | public static string EmployeesHierarchy => sqlProvider.EmployeesHierarchy; 12 | 13 | public static string EmployeesWithOrdersAndTerritories => sqlProvider.EmployeesWithOrdersAndTerritories; 14 | } 15 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/SQLite119/SQLite.Interop.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesharper/DbReader/0e796a16a9c1545771ae0ce4f6a9b73591596c60/src/DbReader.Tests/SQLite119/SQLite.Interop.dll -------------------------------------------------------------------------------- /src/DbReader.Tests/SQLite119/System.Data.SQLite.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesharper/DbReader/0e796a16a9c1545771ae0ce4f6a9b73591596c60/src/DbReader.Tests/SQLite119/System.Data.SQLite.dll -------------------------------------------------------------------------------- /src/DbReader.Tests/SQLite119/libz.1.3.1.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesharper/DbReader/0e796a16a9c1545771ae0ce4f6a9b73591596c60/src/DbReader.Tests/SQLite119/libz.1.3.1.dylib -------------------------------------------------------------------------------- /src/DbReader.Tests/SQLite119/libz.1.dylib: -------------------------------------------------------------------------------- 1 | libz.1.3.1.dylib -------------------------------------------------------------------------------- /src/DbReader.Tests/SQLite119/libz.dylib: -------------------------------------------------------------------------------- 1 | libz.1.dylib -------------------------------------------------------------------------------- /src/DbReader.Tests/SampleClass.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.ComponentModel.DataAnnotations; 7 | using System.Reflection.Emit; 8 | 9 | public class SampleClass 10 | { 11 | public int Int32Property { get; set; } 12 | } 13 | 14 | public class SampleClassCollection : Collection 15 | { 16 | } 17 | 18 | 19 | public class Order 20 | { 21 | [Key] 22 | public long OrderId { get; set; } 23 | 24 | public DateTime? OrderDate { get; set; } 25 | } 26 | 27 | 28 | public class OrderWithCustomer 29 | { 30 | [Key] 31 | public long OrderWithCustomerId { get; set; } 32 | 33 | public Customer Customer { get; set; } 34 | } 35 | 36 | public class CustomerWithOrders 37 | { 38 | public string CustomerWithOrdersId { get; set; } 39 | 40 | public ICollection Orders { get; set; } 41 | } 42 | 43 | public class Customer 44 | { 45 | [Key] 46 | public string CustomerId { get; set; } 47 | 48 | public string CompanyName { get; set; } 49 | 50 | public string ContactName { get; set; } 51 | 52 | public string ContactTitle { get; set; } 53 | 54 | public string Address { get; set; } 55 | 56 | public string City { get; set; } 57 | 58 | public string Region { get; set; } 59 | 60 | public string PostalCode { get; set; } 61 | 62 | public string Country { get; set; } 63 | 64 | public string Phone { get; set; } 65 | 66 | public string Fax { get; set; } 67 | } 68 | 69 | 70 | public class CustomerWithSimpleIdProperty 71 | { 72 | public string Id { get; set; } 73 | 74 | public ICollection Orders { get; set; } 75 | } 76 | 77 | 78 | 79 | 80 | 81 | public class Employee 82 | { 83 | public long EmployeeId { get; set; } 84 | 85 | public string LastName { get; set; } 86 | 87 | public string FirstName { get; set; } 88 | 89 | public long? ReportsTo { get; set; } 90 | 91 | public ICollection Orders { get; set; } 92 | 93 | public ICollection Territories { get; set; } 94 | 95 | public ICollection Employees { get; set; } 96 | } 97 | 98 | public class EmployeeRow 99 | { 100 | [Key] 101 | public long EmployeeId { get; set; } 102 | 103 | public string LastName { get; set; } 104 | 105 | public string FirstName { get; set; } 106 | 107 | public long? ReportsTo { get; set; } 108 | } 109 | 110 | 111 | public class Territory 112 | { 113 | public string Id { get; set; } 114 | public string TerritoryDescription { get; set; } 115 | } 116 | 117 | public class CustomerWithCustomKeySelector : Customer 118 | { 119 | 120 | } 121 | 122 | 123 | public enum SampleEnum 124 | { 125 | Zero, 126 | One, 127 | 128 | Two 129 | } 130 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/SqlProvider.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace DbReader.Tests 5 | { 6 | public interface ISqlProvider 7 | { 8 | string Customers { get; } 9 | 10 | string CustomersAndOrders { get; } 11 | 12 | string EmployeesHierarchy { get; } 13 | 14 | string EmployeesWithOrdersAndTerritories { get; } 15 | 16 | } 17 | 18 | public class SqlProvider : ISqlProvider 19 | { 20 | 21 | public string Customers { get => Load("Customers"); } 22 | 23 | public string CustomersAndOrders { get => Load("CustomersAndOrders"); } 24 | 25 | public string EmployeesHierarchy { get => Load("EmployeesHierarchy"); } 26 | 27 | public string EmployeesWithOrdersAndTerritories { get => Load("EmployeesWithOrdersAndTerritories"); } 28 | 29 | public string Load(string name) 30 | { 31 | return LoadSql(name); 32 | } 33 | 34 | private static string LoadSql(string name) 35 | { 36 | var assembly = typeof(SqlProvider).Assembly; 37 | var test = assembly.GetManifestResourceNames(); 38 | var resourceStream = assembly.GetManifestResourceStream($"DbReader.Tests.Queries.{name}.sql"); 39 | using (var reader = new StreamReader(resourceStream, Encoding.UTF8)) 40 | { 41 | return reader.ReadToEnd(); 42 | } 43 | } 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/TestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using DbReader.LightInject; 4 | 5 | namespace DbReader.Tests 6 | { 7 | public class ContainerFixture : IDisposable 8 | { 9 | public ContainerFixture() 10 | { 11 | var container = CreateContainer(); 12 | container.RegisterFrom(); 13 | Configure(container); 14 | ServiceFactory = container.BeginScope(); 15 | InjectPrivateFields(); 16 | } 17 | 18 | private void InjectPrivateFields() 19 | { 20 | var privateInstanceFields = this.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 21 | foreach (var privateInstanceField in privateInstanceFields) 22 | { 23 | privateInstanceField.SetValue(this, GetInstance(ServiceFactory, privateInstanceField)); 24 | } 25 | } 26 | 27 | internal Scope ServiceFactory { get; } 28 | 29 | public void Dispose() => ServiceFactory.Dispose(); 30 | 31 | public TService GetInstance(string name = "") 32 | => ServiceFactory.GetInstance(name); 33 | 34 | private object GetInstance(IServiceFactory factory, FieldInfo field) 35 | => ServiceFactory.TryGetInstance(field.FieldType) ?? ServiceFactory.GetInstance(field.FieldType, field.Name); 36 | 37 | internal virtual IServiceContainer CreateContainer() => new ServiceContainer(); 38 | 39 | internal virtual void Configure(IServiceRegistry serviceRegistry) {} 40 | } 41 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/TestCollectionOrderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit; 4 | using Xunit.Abstractions; 5 | [assembly: TestCollectionOrderer("DbReader.Tests.TestCollectionOrderer", "DbReader.Tests")] 6 | namespace DbReader.Tests 7 | { 8 | public class TestCollectionOrderer : ITestCollectionOrderer 9 | { 10 | public IEnumerable OrderTestCollections(IEnumerable testCollections) 11 | { 12 | var instanceReaderTestsCollections = testCollections.Where(tc => tc.DisplayName.Contains("InstanceReaderTests")); 13 | 14 | if (instanceReaderTestsCollections.Count() > 0) 15 | { 16 | return instanceReaderTestsCollections.Concat(testCollections.Except(instanceReaderTestsCollections)); 17 | } 18 | 19 | return testCollections; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/TypeExtensionTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Runtime.InteropServices; 7 | using Extensions; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | public class TypeExtensionTests 12 | { 13 | [Fact] 14 | public void GetUnderlyingType_Enum_ReturnsUnderlyingType() 15 | { 16 | typeof(StringComparison).GetUnderlyingType().ShouldBe(typeof(int)); 17 | } 18 | 19 | [Fact] 20 | public void GetUnderlyingType_Nullable_ReturnsUnderlyingType() 21 | { 22 | typeof(int?).GetUnderlyingType().ShouldBe(typeof(int)); 23 | } 24 | 25 | [Fact] 26 | public void GetUnderlyingType_NonNullable_ReturnsType() 27 | { 28 | typeof(int).GetUnderlyingType().ShouldBe(typeof(int)); 29 | } 30 | 31 | [Fact] 32 | public void IsNullable_NullableType_ReturnsTrue() 33 | { 34 | typeof(bool?).IsNullable().ShouldBeTrue(); 35 | } 36 | 37 | [Fact] 38 | public void IsNullable_NonNullableType_ReturnsFalse() 39 | { 40 | typeof(bool).IsNullable().ShouldBeFalse(); 41 | } 42 | 43 | [Fact] 44 | public void IsSimpleType_PrimitiveType_ReturnsTrue() 45 | { 46 | typeof(int).IsSimpleType().ShouldBeTrue(); 47 | } 48 | 49 | [Fact] 50 | public void IsSimpleType_Enum_ReturnsTrue() 51 | { 52 | typeof(StringComparison).IsSimpleType().ShouldBeTrue(); 53 | } 54 | 55 | [Fact] 56 | public void IsSimpleType_NonPrimitive_ReturnsTrue() 57 | { 58 | typeof(string).IsSimpleType().ShouldBeTrue(); 59 | } 60 | 61 | [Fact] 62 | public void IsSimpleType_CustomType_ReturnsTrue() 63 | { 64 | ValueConverter.RegisterReadDelegate((record, i) => new CustomValueType(42)); 65 | typeof(CustomValueType).IsSimpleType().ShouldBeTrue(); 66 | } 67 | 68 | [Fact] 69 | public void IsSimpleType_ComplexType_ReturnsFalse() 70 | { 71 | typeof(TypeExtensionTests).IsSimpleType().ShouldBeFalse(); 72 | } 73 | 74 | [Fact] 75 | public void IsSimpleType_Nullable_ReturnsTrue() 76 | { 77 | typeof(int?).IsSimpleType().ShouldBeTrue(); 78 | } 79 | 80 | [Fact] 81 | public void IsEnumerable_IEnumerable_ReturnsTrue() 82 | { 83 | typeof(IEnumerable).IsEnumerable().ShouldBeTrue(); 84 | } 85 | 86 | [Fact] 87 | public void IsEnumerable_CollectionTypeImplementingIEnumerable_ReturnsTrue() 88 | { 89 | typeof(Collection).IsEnumerable().ShouldBeTrue(); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/UtilityTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Shouldly; 3 | using Xunit; 4 | 5 | namespace DbReader.Tests 6 | { 7 | public class UtilityTests 8 | { 9 | 10 | [Fact] 11 | public void ShouldThrowArgumentNullException() 12 | { 13 | Should.Throw(() => Require.IsNotNull(null, "someParameter")); 14 | } 15 | 16 | [Fact] 17 | public void ShouldThrowInvalidOperationException() 18 | { 19 | Should.Throw(() => Ensure.IsNotNull(null, "some message")); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/ValueConverterTests.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tests 2 | { 3 | using System; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | public class ValueConverterTests 8 | { 9 | static ValueConverterTests() 10 | { 11 | ValueConverter.RegisterReadDelegate((record, i) => new CustomValueType(record.GetInt32(i))); 12 | } 13 | 14 | [Fact] 15 | public void GetExecuteMethod_ReturnsMethodInfo() 16 | { 17 | ValueConverter.GetConvertMethod(typeof(CustomValueType)).ShouldNotBeNull(); 18 | } 19 | 20 | [Fact] 21 | public void CanConvert_KnownType_ReturnsTrue() 22 | { 23 | ValueConverter.CanConvert(typeof(CustomValueType)).ShouldBeTrue(); 24 | } 25 | 26 | [Fact] 27 | public void CanConvert_UnknownType_ReturnsFalse() 28 | { 29 | ValueConverter.CanConvert(typeof(string)).ShouldBeFalse(); 30 | } 31 | 32 | [Fact] 33 | public void Convert_ReturnsConvertedValue() 34 | { 35 | var dataRecord = new { SomeColumn = 42 }.ToDataRecord(); 36 | var convertedValue = ValueConverter.Convert(dataRecord, 0); 37 | convertedValue.Value.ShouldBe(42); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/VerifiableMethodSkeleton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Reflection.Emit; 4 | using DbReader.Construction; 5 | using ILVerifier; 6 | 7 | namespace DbReader.Tests 8 | { 9 | public class VerifiableMethodSkeleton : IMethodSkeleton 10 | { 11 | private readonly string name; 12 | 13 | private TypeBuilder typeBuilder; 14 | private MethodBuilder methodBuilder; 15 | 16 | 17 | public VerifiableMethodSkeleton(string name, Type returnType, Type[] parameterTypes) 18 | { 19 | this.name = name; 20 | var assemblyBuilder = CreateAssemblyBuilder(); 21 | CreateTypeBuilder(assemblyBuilder); 22 | CreateMethodBuilder(returnType, parameterTypes); 23 | } 24 | 25 | private AssemblyBuilder CreateAssemblyBuilder() 26 | => AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("DynamicMethodAssembly"), AssemblyBuilderAccess.Run); 27 | 28 | private void CreateTypeBuilder(AssemblyBuilder assemblyBuilder) 29 | => typeBuilder = assemblyBuilder.DefineDynamicModule("DynamicMethodModule").DefineType(name, TypeAttributes.Public); 30 | 31 | private void CreateMethodBuilder(Type returnType, Type[] parameterTypes) 32 | { 33 | methodBuilder = typeBuilder.DefineMethod( 34 | name, MethodAttributes.Public | MethodAttributes.Static, returnType, parameterTypes); 35 | methodBuilder.InitLocals = true; 36 | } 37 | 38 | public ILGenerator GetGenerator() => methodBuilder.GetILGenerator(); 39 | 40 | public Delegate CreateDelegate(Type delegateType) 41 | { 42 | var dynamicType = typeBuilder.CreateType(); 43 | new Verifier() 44 | .WithAssemblyReferenceFromType() 45 | .WithAssemblyReference(typeof(DbConnectionExtensions).Assembly) 46 | .Verify(dynamicType.Assembly); 47 | MethodInfo methodInfo = dynamicType.GetMethod(name, BindingFlags.Static | BindingFlags.Public); 48 | return Delegate.CreateDelegate(delegateType, methodInfo); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/DbReader.Tests/VerifiableMethodSkeletonFactory.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0 2 | namespace DbReader.Tests 3 | { 4 | using System; 5 | using Construction; 6 | public class VerifiableMethodSkeletonFactory : IMethodSkeletonFactory 7 | { 8 | public IMethodSkeleton GetMethodSkeleton(string name, Type returnType, Type[] parameterTypes) 9 | { 10 | return new VerifiableMethodSkeleton(name, returnType, parameterTypes); 11 | } 12 | 13 | public IMethodSkeleton GetMethodSkeleton(string name, Type returnType, Type[] parameterTypes, Type owner) 14 | { 15 | return new VerifiableMethodSkeleton(name, returnType, parameterTypes); 16 | } 17 | } 18 | } 19 | #endif -------------------------------------------------------------------------------- /src/DbReader.Tests/coverlet.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | lcov,cobertura 7 | [Mono.Cecil]*,[Mono.Cecil.Cil]*,[Mono.Cecil.Metadata]*,[Mono.Cecil.Rocks]* 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/DbReader.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/DbReader.Tests/sqlmd.sh: -------------------------------------------------------------------------------- 1 | ### sqlmd 2 | # Bash function for outputting SQLite results in Markdown-friendly table 3 | 4 | ### Dependency: 5 | # csvlook can be found here: http://csvkit.readthedocs.io/en/540/scripts/csvlook.html 6 | 7 | ### USAGE 8 | # $ sqlmd "SELECT name, age FROM people;" optional_db_name_argument.sqlite 9 | 10 | ### OUTPUT 11 | 12 | # (stderr) Opening database: optional_db_name_argument.sqlite 13 | # 14 | # ```sql 15 | # SELECT name, age FROM people; 16 | # ``` 17 | # 18 | # | name | age | 19 | # |-------|-------| 20 | # | Alice | 42 | 21 | # | Bob | 9 | 22 | # {:.table-sql} 23 | 24 | # That last line is a Kramdown-style CSS class selector 25 | 26 | # Tip: I like piping into OSX's pbcopy for even faster blogging: 27 | 28 | # sqlmd "SELECT * FROM mytable;" mydb.sqlite | pbcopy 29 | 30 | sqlmd(){ 31 | SQLQUERY="$1" 32 | # if two arguments, assume second is the database name 33 | if [ $# -eq 2 ]; then 34 | THEDBNAME="$2" 35 | # (stderr) The name of the database being opened, in green-on-black text 36 | (>&2 printf "\033[1;32m\033[40mOpening database: ${THEDBNAME}\033[m\n\n") 37 | else 38 | THEDBNAME="" 39 | fi 40 | printf '```sql\n' 41 | printf "$SQLQUERY" 42 | printf '\n```\n\n' 43 | 44 | # include headers and print results in CSV format 45 | sqlite3 $THEDBNAME < 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $(MSBuildProjectDirectory)/bin/$(Configuration)/net8.0/DbReader.Tracking.dll 10 | net8.0 11 | enable 12 | enable 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/DbReader.Tracking.SampleAssembly/SampleClasses.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tracking.SampleAssembly; 2 | using DbReader.Tracking; 3 | [Tracked] 4 | public record PositionalRecord(int Id, string Name); 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/DbReader.Tracking/DbReader.Tracking.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/DbReader.Tracking/DbReader.Tracking.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DbReader.Tracking 6 | 7 | 0.0.1 8 | 9 | Bernhard Richter 10 | 11 | A custom MSBuild task package. 12 | 13 | https://github.com/seesharper/DbReader 14 | 15 | MIT 16 | 17 | DbReader Tracking 18 | 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/DbReader.Tracking/DbReader.Tracking.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory)../lib/net8.0/DbReader.Tracking.dll 4 | TrackedAttribute 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/DbReader.Tracking/WeaveAssemby.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tracking; 2 | 3 | public class WeaveAssembly : Microsoft.Build.Utilities.Task 4 | { 5 | public string TargetAssemblyPath { get; set; } = string.Empty; 6 | 7 | public string TrackingAttributeName { get; set; } = string.Empty; 8 | 9 | private readonly TrackingAssemblyWeaver weaver = new(); 10 | 11 | public override bool Execute() 12 | { 13 | Log.LogMessage("Weaving assembly {0}", TargetAssemblyPath); 14 | weaver.Weave(TargetAssemblyPath, TrackingAttributeName); 15 | return true; 16 | } 17 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/Cache.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using LightInject; 4 | 5 | 6 | /// 7 | /// A simple static cache. 8 | /// 9 | /// 10 | /// 11 | public class Cache 12 | { 13 | private static ImmutableHashTree tree = ImmutableHashTree.Empty; 14 | 15 | /// 16 | /// Gets the represented by the given . 17 | /// 18 | /// The key for which to get the value. 19 | /// An if found, otherwise the default value. 20 | public static TValue Get(TKey key) 21 | { 22 | return tree.Search(key); 23 | } 24 | 25 | /// 26 | /// Puts the into the cache. 27 | /// 28 | /// The key to be used to store the value. 29 | /// The value to be put into the cache. 30 | public static void Put(TKey key, TValue value) 31 | { 32 | tree = tree.Add(key, value); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/DbReader/Construction/CachedArgumentParserMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DbReader.Construction 4 | { 5 | using System.Data; 6 | using DbReader.Database; 7 | 8 | /// 9 | /// An decorator that caches 10 | /// the method created at runtime that is used to parse argument from an argument object. 11 | /// 12 | public class CachedArgumentParserMethodBuilder : IArgumentParserMethodBuilder 13 | { 14 | private readonly IArgumentParserMethodBuilder argumentParserMethodBuilder; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The target . 20 | public CachedArgumentParserMethodBuilder(IArgumentParserMethodBuilder argumentParserMethodBuilder) 21 | { 22 | this.argumentParserMethodBuilder = argumentParserMethodBuilder; 23 | } 24 | 25 | /// 26 | public Func, QueryInfo> CreateMethod(string sql, Type argumentsType, IDataParameter[] existingParameters) 27 | { 28 | var key = (sql, argumentsType); 29 | var method = Cache<(string, Type), Func, QueryInfo>>.Get(key); 30 | if (method == null) 31 | { 32 | method = argumentParserMethodBuilder.CreateMethod(sql, argumentsType, existingParameters); 33 | Cache<(string, Type), Func, QueryInfo>>.Put(key, method); 34 | } 35 | return method; 36 | } 37 | } 38 | 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/DbReader/Construction/CachedInstanceReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Readers; 6 | using Interfaces; 7 | 8 | /// 9 | /// An decorator that is used 10 | /// to cache the dynamic method created at runtime to read an instance of type . 11 | /// 12 | /// 13 | public class CachedInstanceReaderMethodBuilder : IInstanceReaderMethodBuilder 14 | { 15 | private readonly Lazy> instanceReaderMethodBuilder; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The target . 21 | public CachedInstanceReaderMethodBuilder(Lazy> instanceReaderMethodBuilder) 22 | { 23 | this.instanceReaderMethodBuilder = instanceReaderMethodBuilder; 24 | } 25 | 26 | /// 27 | /// Creates a method that creates an instance of 28 | /// based on the given . 29 | /// 30 | /// The that contains the data for the instance. 31 | /// The current prefix. 32 | /// A method that creates an instance of 33 | /// based on the given . 34 | public Func CreateMethod(IDataRecord dataRecord, string prefix) 35 | { 36 | return StaticCache>.GetOrAdd(typeof(T), prefix, 37 | () => instanceReaderMethodBuilder.Value.CreateMethod(dataRecord, prefix)); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/CachedKeyReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Concurrent; 6 | using System.Data; 7 | 8 | /// 9 | /// A class that is capable of creating a method that reads the fields from an 10 | /// that maps to the key properties of a given . 11 | /// 12 | public class CachedKeyReaderMethodBuilder : IKeyReaderMethodBuilder 13 | { 14 | private readonly IKeyReaderMethodBuilder keyReaderMethodBuilder; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The that is responsible for dynamically creating a method 20 | /// that is capable of reading key columns. 21 | public CachedKeyReaderMethodBuilder(IKeyReaderMethodBuilder keyReaderMethodBuilder) 22 | { 23 | this.keyReaderMethodBuilder = keyReaderMethodBuilder; 24 | } 25 | 26 | /// 27 | /// Creates a method that reads the key fields from the given . 28 | /// 29 | /// The for which to read the key fields. 30 | /// The target . 31 | /// The field prefix used to identify the key fields. 32 | /// A method that reads the key fields from the given . 33 | public Func CreateMethod(Type type, IDataRecord dataRecord, string prefix) 34 | { 35 | return StaticCache>.GetOrAdd(type, prefix, 36 | () => keyReaderMethodBuilder.CreateMethod(type, dataRecord, prefix)); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/CachedOneToManyMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Readers; 6 | 7 | /// 8 | /// An decorator that 9 | /// caches the dynamically created method used to populate collection 10 | /// properties of a given type. 11 | /// 12 | /// The for which to create the dynamic method. 13 | public class CachedOneToManyMethodBuilder : IOneToManyMethodBuilder 14 | { 15 | private readonly IOneToManyMethodBuilder oneToManyMethodBuilder; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The target . 21 | public CachedOneToManyMethodBuilder(IOneToManyMethodBuilder oneToManyMethodBuilder) 22 | { 23 | this.oneToManyMethodBuilder = oneToManyMethodBuilder; 24 | } 25 | 26 | /// 27 | /// Creates a dynamic method that populates mapped collection properties. 28 | /// 29 | /// The source . 30 | /// The property prefix used to identify the fields in the . 31 | /// A delegate representing a dynamic method that populates mapped collection properties. 32 | public Action CreateMethod(IDataRecord dataRecord, string prefix) 33 | { 34 | return StaticCache>.GetOrAdd(typeof(T), prefix, 35 | () => oneToManyMethodBuilder.CreateMethod(dataRecord, prefix)); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/CachedReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// An decorator that caches the 8 | /// dynamic method created at runtime used to create and pupulate an instance of . 9 | /// 10 | /// 11 | public class CachedReaderMethodBuilder : IReaderMethodBuilder 12 | { 13 | 14 | private readonly Lazy> cache; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The target . 20 | public CachedReaderMethodBuilder(IReaderMethodBuilder readerMethodBuilder) 21 | { 22 | cache = new Lazy>(readerMethodBuilder.CreateMethod); 23 | } 24 | 25 | /// 26 | /// Creates a new method that initializes and populates an instance of from an 27 | /// . 28 | /// 29 | /// A delegate that creates and populates an instance of from an 30 | /// . 31 | public Func CreateMethod() 32 | { 33 | return cache.Value; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/DynamicMethodSkeleton.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | 7 | /// 8 | /// A that uses the class. 9 | /// 10 | public class DynamicMethodSkeleton : IMethodSkeleton 11 | { 12 | private readonly DynamicMethod dynamicMethod; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The name of the dynamic method. 18 | /// The return type of the dynamic method. 19 | /// The parameter types of the dynamic method. 20 | /// A representing the module with which the dynamic method is to be logically associated. 21 | public DynamicMethodSkeleton(string name, Type returnType, Type[] parameterTypes, Module module) 22 | { 23 | #if NET46 24 | dynamicMethod = new DynamicMethod( 25 | name, 26 | returnType, 27 | parameterTypes, 28 | module, 29 | true); 30 | 31 | #else 32 | dynamicMethod = new DynamicMethod( 33 | name, 34 | returnType, 35 | parameterTypes, 36 | module); 37 | #endif 38 | 39 | } 40 | 41 | /// 42 | /// Initializes a new instance of the class. 43 | /// 44 | /// The name of the dynamic method. 45 | /// The return type of the dynamic method. 46 | /// The parameter types of the dynamic method. 47 | /// A with which the dynamic method is logically associated. The dynamic method has access to all members of the type. 48 | public DynamicMethodSkeleton(string name, Type returnType, Type[] parameterTypes, Type owner) 49 | { 50 | #if NET46 51 | dynamicMethod = new DynamicMethod( 52 | name, 53 | returnType, 54 | parameterTypes, 55 | owner, 56 | true); 57 | #else 58 | dynamicMethod = new DynamicMethod( 59 | name, 60 | returnType, 61 | parameterTypes, 62 | owner); 63 | #endif 64 | 65 | } 66 | 67 | /// 68 | /// Gets the for this method. 69 | /// 70 | /// . 71 | public ILGenerator GetGenerator() 72 | { 73 | return dynamicMethod.GetILGenerator(); 74 | } 75 | 76 | /// 77 | /// Completes the dynamic method and creates a delegate that can be used to execute it. 78 | /// 79 | /// A delegate type whose signature matches that of the dynamic method. 80 | /// A delegate of the specified type, which can be used to execute the dynamic method. 81 | public Delegate CreateDelegate(Type delegateType) 82 | { 83 | return dynamicMethod.CreateDelegate(delegateType); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IArgumentParserMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Database; 6 | 7 | /// 8 | /// Represents a class that based on a given sql and the type of the arguments object, 9 | /// can create a method that maps an argument object instance into a list of instances. 10 | /// 11 | public interface IArgumentParserMethodBuilder 12 | { 13 | /// 14 | /// Creates a method at runtime that maps an argument object instance into a list of data parameters. 15 | /// 16 | /// The sql statement for which to create the method. 17 | /// The arguments type for which to create the method. 18 | /// A list of already existing parameters. 19 | /// A method that maps an argument object instance into a list of instances. 20 | Func, QueryInfo> CreateMethod(string sql, Type argumentsType, IDataParameter[] existingParameters); 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IKeyReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Data; 6 | 7 | /// 8 | /// Represents a class that is capable of creating a method that reads the fields from an 9 | /// that maps to the key properties of a given . 10 | /// 11 | public interface IKeyReaderMethodBuilder 12 | { 13 | /// 14 | /// Creates a method that reads the key fields from the given . 15 | /// 16 | /// The for which to read the key fields. 17 | /// The target . 18 | /// The field prefix used to identify the key fields. 19 | /// A method that reads the key fields from the given . 20 | Func CreateMethod(Type type, IDataRecord dataRecord, string prefix); 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IManyToOneMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Readers; 6 | 7 | /// 8 | /// Represents a class that dynamically creates a method used to 9 | /// populate "many-to-one" properties of a given type. 10 | /// 11 | /// The for which to create the dynamic method. 12 | public interface IManyToOneMethodBuilder 13 | { 14 | /// 15 | /// Creates a dynamic method that populates mapped "many-to-one" properties. 16 | /// 17 | /// The source . 18 | /// The property prefix used to identify the fields in the . 19 | /// A delegate representing a dynamic method that populates mapped "many-to-one" properties. 20 | Action CreateMethod(IDataRecord dataRecord, string prefix); 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IMethodSkeleton.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Reflection.Emit; 5 | 6 | /// 7 | /// Represents the skeleton of a dynamic method. 8 | /// 9 | public interface IMethodSkeleton 10 | { 11 | /// 12 | /// Gets the for this method. 13 | /// 14 | /// . 15 | ILGenerator GetGenerator(); 16 | 17 | /// 18 | /// Completes the dynamic method and creates a delegate that can be used to execute it. 19 | /// 20 | /// A delegate type whose signature matches that of the dynamic method. 21 | /// A delegate of the specified type, which can be used to execute the dynamic method. 22 | Delegate CreateDelegate(Type delegateType); 23 | } 24 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IMethodSkeletonFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents a class that is capable of providing an instance. 7 | /// 8 | public interface IMethodSkeletonFactory 9 | { 10 | /// 11 | /// Gets an instance. 12 | /// 13 | /// The name of the method. 14 | /// The return type of the dynamic method. 15 | /// The parameter types of the dynamic method. 16 | /// An instance. 17 | IMethodSkeleton GetMethodSkeleton(string name, Type returnType, Type[] parameterTypes); 18 | 19 | /// 20 | /// Gets an instance. 21 | /// 22 | /// The name of the method. 23 | /// The return type of the dynamic method. 24 | /// The parameter types of the dynamic method. 25 | /// A with which the dynamic method is logically associated. The dynamic method has access to all members of the type. 26 | /// An instance. 27 | IMethodSkeleton GetMethodSkeleton(string name, Type returnType, Type[] parameterTypes, Type owner); 28 | } 29 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IOneToManyMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Readers; 6 | 7 | /// 8 | /// Represents a class that dynamically creates a method used to 9 | /// populate collection properties of a given type. 10 | /// 11 | /// The for which to create the dynamic method. 12 | public interface IOneToManyMethodBuilder 13 | { 14 | /// 15 | /// Creates a dynamic method that populates mapped collection properties. 16 | /// 17 | /// The source . 18 | /// The property prefix used to identify the fields in the . 19 | /// A delegate representing a dynamic method that populates mapped collection properties. 20 | Action CreateMethod(IDataRecord dataRecord, string prefix); 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IParameterMatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace DbReader.Construction 5 | { 6 | /// 7 | /// Represents a class that is capable of matching the properties of a given arguments type to the parameters found in a SQL statement. 8 | /// 9 | public interface IParameterMatcher 10 | { 11 | /// 12 | /// Matches the properties of the to the parameters found in the given . 13 | /// 14 | /// The sql statement containing the parameters. 15 | /// The argument object type. 16 | /// A list of existing data parameters. 17 | /// A list of matching parameters. 18 | MatchedParameter[] Match(string sql, Type argumentsType, IDataParameter[] existingParameters); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/DbReader/Construction/IPrefixResolver.cs: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | The MIT License (MIT) 3 | Copyright (c) 2014 bernhard.richter@gmail.com 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | ****************************************************************************** 20 | DbReader version 1.0.0.1 21 | https://github.com/seesharper/DbReader 22 | http://twitter.com/bernhardrichter 23 | ******************************************************************************/ 24 | namespace DbReader.Construction 25 | { 26 | using System.Data; 27 | using System.Reflection; 28 | 29 | /// 30 | /// Represents a class that is capable of resolving the prefix 31 | /// for a navigation property. 32 | /// 33 | public interface IPrefixResolver 34 | { 35 | /// 36 | /// Returns the prefix for the given . 37 | /// 38 | /// The property for which to get the prefix. 39 | /// The that represents the available fields/columns. 40 | /// The current prefix that the resolved prefix will be appended to. 41 | /// A value that represents the property prefix. 42 | string GetPrefix(PropertyInfo navigationProperty, IDataRecord dataRecord, string currentPrefix); 43 | } 44 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/IReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Represents a class that is capable of creating a delegate that creates and populates an instance of from an 8 | /// . 9 | /// 10 | /// The type of object to be created. 11 | public interface IReaderMethodBuilder 12 | { 13 | /// 14 | /// Creates a new method that initializes and populates an instance of from an 15 | /// . 16 | /// 17 | /// A delegate that creates and populates an instance of from an 18 | /// . 19 | Func CreateMethod(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/InstanceReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Readers; 6 | using Interfaces; 7 | using Selectors; 8 | 9 | /// 10 | /// A class that creates an instance of 11 | /// based on a given . 12 | /// 13 | /// 14 | public class InstanceReaderMethodBuilder : IInstanceReaderMethodBuilder 15 | { 16 | private readonly IReaderMethodBuilder propertyReaderMethodBuilder; 17 | private readonly IOrdinalSelector ordinalSelector; 18 | private readonly IManyToOneMethodBuilder manyToOneMethodBuilder; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The that is responsible for creating a dynamic method 24 | /// that populates an instance of from an . 25 | /// The that is responsible for providing the ordinals used to map a type from a . 26 | /// The that is responsible for creating a dynamic method 27 | /// that populates "many-to-one" properties of a given type. 28 | public InstanceReaderMethodBuilder(IReaderMethodBuilder propertyReaderMethodBuilder, IOrdinalSelector ordinalSelector, IManyToOneMethodBuilder manyToOneMethodBuilder) 29 | { 30 | this.propertyReaderMethodBuilder = propertyReaderMethodBuilder; 31 | this.ordinalSelector = ordinalSelector; 32 | this.manyToOneMethodBuilder = manyToOneMethodBuilder; 33 | } 34 | 35 | /// 36 | /// Creates a method that creates an instance of 37 | /// based on the given . 38 | /// 39 | /// The that contains the data for the instance. 40 | /// The current prefix. 41 | /// A method that creates an instance of 42 | /// based on the given . 43 | public Func CreateMethod(IDataRecord dataRecord, string prefix) 44 | { 45 | int[] ordinals = ordinalSelector.Execute(typeof(T), dataRecord, prefix); 46 | Func propertyReaderMethod = propertyReaderMethodBuilder.CreateMethod(); 47 | 48 | Action manyToOneMethod = manyToOneMethodBuilder.CreateMethod(dataRecord, prefix); 49 | 50 | if (manyToOneMethod != null) 51 | { 52 | return (record, instanceReaderFactory) => 53 | { 54 | var instance = propertyReaderMethod(record, ordinals); 55 | manyToOneMethod(instance, record, instanceReaderFactory); 56 | return instance; 57 | }; 58 | } 59 | 60 | return (record, instanceReaderFactory) => propertyReaderMethod(record, ordinals); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/KeyReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Data; 6 | using System.Linq; 7 | using Extensions; 8 | using Interfaces; 9 | using Mapping; 10 | 11 | /// 12 | /// A class that is capable of creating a method that reads the fields from an 13 | /// that maps to the key properties of a given . 14 | /// 15 | public class KeyReaderMethodBuilder : IKeyReaderMethodBuilder 16 | { 17 | private readonly Func> constructorReaderMethodBuilderFactory; 18 | 19 | private readonly IKeyPropertyMapper keyPropertyMapper; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The function used to create an 25 | /// that is responsible for building a method that reads key fields from a given . 26 | /// The that is responsible for mapping key properties to key fields. 27 | public KeyReaderMethodBuilder( 28 | Func> constructorReaderMethodBuilderFactory, 29 | IKeyPropertyMapper keyPropertyMapper) 30 | { 31 | this.constructorReaderMethodBuilderFactory = constructorReaderMethodBuilderFactory; 32 | this.keyPropertyMapper = keyPropertyMapper; 33 | } 34 | 35 | /// 36 | /// Creates a method that reads the key fields from the given . 37 | /// 38 | /// The type for which to create the key reader method. 39 | /// The target . 40 | /// The field prefix used to identify the key fields. 41 | /// A method that reads the key fields from the given . 42 | public Func CreateMethod(Type type, IDataRecord dataRecord, string prefix) 43 | { 44 | MappingInfo[] keyProperties = keyPropertyMapper.Execute(type, dataRecord, prefix); 45 | Type[] keyTypes = keyProperties.Select(pm => pm.Property.PropertyType).ToArray(); 46 | int[] ordinals = keyProperties.Select(pm => pm.ColumnInfo.Ordinal).ToArray(); 47 | Type tupleType = keyTypes.ToTupleType(); 48 | IReaderMethodBuilder methodBuilder = constructorReaderMethodBuilderFactory(tupleType); 49 | var method = methodBuilder.CreateMethod(); 50 | return record => method(record, ordinals); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/MatchedParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using DbReader.Database; 3 | 4 | namespace DbReader.Construction 5 | { 6 | /// 7 | /// Represents a match between an argument object property and parameter found in a given sql statement. 8 | /// 9 | public class MatchedParameter 10 | { 11 | 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The containing information about a parsed data parameter. 16 | /// The property that matches the . 17 | public MatchedParameter(DataParameterInfo dataParameter, PropertyInfo property) 18 | { 19 | DataParameter = dataParameter; 20 | Property = property; 21 | } 22 | 23 | /// 24 | /// Gets the containing information about a parsed data parameter. 25 | /// 26 | public DataParameterInfo DataParameter { get; } 27 | 28 | /// 29 | /// Gets the property that matches the data parameter. 30 | /// 31 | public PropertyInfo Property { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/DbReader/Construction/MethodSkeletonFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// A class that is capable of providing an instance. 8 | /// 9 | public class DynamicMethodSkeletonFactory : IMethodSkeletonFactory 10 | { 11 | /// 12 | /// Gets an instance. 13 | /// 14 | /// The name of the method. 15 | /// The return type of the dynamic method. 16 | /// The parameter types of the dynamic method. 17 | /// An instance. 18 | public IMethodSkeleton GetMethodSkeleton(string name, Type returnType, Type[] parameterTypes) 19 | { 20 | return new DynamicMethodSkeleton(name, returnType, parameterTypes, typeof(IMethodSkeletonFactory).GetTypeInfo().Module); 21 | } 22 | 23 | /// 24 | /// Gets an instance. 25 | /// 26 | /// The name of the method. 27 | /// The return type of the dynamic method. 28 | /// The parameter types of the dynamic method. 29 | /// A with which the dynamic method is logically associated. The dynamic method has access to all members of the type. 30 | /// An instance. 31 | public IMethodSkeleton GetMethodSkeleton(string name, Type returnType, Type[] parameterTypes, Type owner) 32 | { 33 | return new DynamicMethodSkeleton(name, returnType, parameterTypes, owner); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/PropertyReaderDelegate.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Represents the cached value of a property reader delegate. 8 | /// 9 | /// 10 | public class PropertyReaderDelegate 11 | { 12 | /// 13 | /// The propery reader delegate. 14 | /// 15 | public Func ReadMethod; 16 | 17 | /// 18 | /// The ordinals used to invoke the delegate. 19 | /// 20 | public int[] Ordinals; 21 | } 22 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/PropertyReaderDelegateCache.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | /// 4 | /// Caches a 5 | /// 6 | /// The type of object cached. 7 | public sealed class PropertyReaderDelegateCache : Cache> 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/DbReader/Construction/StaticCache.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Construction 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | 6 | /// 7 | /// A static CachedValues used to CachedValues things related 8 | /// to creating dynamic methods. 9 | /// 10 | /// 11 | public static class StaticCache 12 | { 13 | private static readonly ConcurrentDictionary, TValue> CachedValues = 14 | new ConcurrentDictionary, TValue>(); 15 | 16 | /// 17 | /// Gets or adds a value of based on the 18 | /// given and . 19 | /// 20 | /// The for which a dynamic method is currently being constructed. 21 | /// The prefix for which a dynamic method is currently being constructed. 22 | /// A factory delegate used to provide a value of 23 | /// if the value does not exist in the CachedValues. 24 | /// A value of based on the 25 | /// given and . 26 | public static TValue GetOrAdd(Type type, string prefix, Func delegateFactory) 27 | { 28 | var key = Tuple.Create(type, prefix, SqlStatement.Current); 29 | return CachedValues.GetOrAdd(key, _ => delegateFactory()); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/DbReader/ContainerExtensions.cs: -------------------------------------------------------------------------------- 1 | //namespace DbReader 2 | //{ 3 | // using System; 4 | // using System.Linq; 5 | // using System.Reflection; 6 | // using LightInject; 7 | 8 | // public static class ContainerExtensions 9 | // { 10 | // internal static void Validate(this ServiceContainer serviceContainer, Action warnAction = null) 11 | // { 12 | // if (warnAction == null) 13 | // { 14 | // warnAction = s => { }; 15 | // } 16 | 17 | 18 | // //// Try to resolve all services 19 | // //foreach (var serviceRegistration in serviceContainer.AvailableServices) 20 | // //{ 21 | // // serviceContainer.GetInstance(serviceRegistration.ServiceType, serviceRegistration.ServiceName); 22 | // //} 23 | 24 | 25 | // var serviceMap = 26 | // serviceContainer.AvailableServices.ToDictionary(sr => Tuple.Create(sr.ServiceType, sr.ServiceName)); 27 | 28 | // foreach (var serviceRegistration in serviceContainer.AvailableServices) 29 | // { 30 | // if (serviceRegistration.ImplementingType != null) 31 | // { 32 | // var constructor = 33 | // serviceRegistration.ImplementingType.GetTypeInfo().DeclaredConstructors.FirstOrDefault(); 34 | // var parameters = constructor.GetParameters(); 35 | // foreach (var parameter in parameters) 36 | // { 37 | // ServiceRegistration dependency; 38 | // if (serviceMap.TryGetValue(Tuple.Create(parameter.ParameterType, string.Empty), out dependency)) 39 | // { 40 | // // Validate captive dependency 41 | // if (serviceRegistration.Lifetime is PerContainerLifetime) 42 | // { 43 | // if (dependency.Lifetime == null || dependency.Lifetime is PerScopeLifetime || dependency.Lifetime is PerRequestLifeTime) 44 | // { 45 | // warnAction( 46 | // $"The dependency {dependency} is being injected into {serviceRegistration} that has a longer lifetime"); 47 | // } 48 | // } 49 | 50 | // if (serviceRegistration.Lifetime is PerScopeLifetime) 51 | // { 52 | // if (dependency.Lifetime == null || dependency.Lifetime is PerRequestLifeTime) 53 | // { 54 | // warnAction( 55 | // $"The dependency {dependency} is being injected into {serviceRegistration} that has a longer lifetime"); 56 | // } 57 | // } 58 | // }; 59 | // } 60 | // } 61 | // } 62 | 63 | // } 64 | // } 65 | //} -------------------------------------------------------------------------------- /src/DbReader/DataRecordExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Extends the interface. 8 | /// 9 | public static class DataRecordExtensions 10 | { 11 | /// 12 | /// Gets a byte array from the given . 13 | /// 14 | /// The target . 15 | /// The ordinal of the column that contains the byte array. 16 | /// The number of bytes to read. 17 | /// An array of bytes read from the data record. 18 | public static byte[] GetBytes(this IDataRecord dataRecord, int ordinal, int length) 19 | { 20 | byte[] buffer = new byte[length]; 21 | dataRecord.GetBytes(ordinal, 0, buffer, 0, length); 22 | return buffer; 23 | } 24 | 25 | /// 26 | /// Gets a byte array from the given . 27 | /// 28 | /// The target . 29 | /// The ordinal of the column that contains the byte array. 30 | /// An array of bytes read from the data record. 31 | public static byte[] GetBytes(this IDataRecord dataRecord, int ordinal) 32 | { 33 | long length = dataRecord.GetBytes(ordinal, 0, null, 0, int.MaxValue); 34 | var buffer = new byte[length]; 35 | dataRecord.GetBytes(ordinal, 0, buffer, 0, (int)length); 36 | return buffer; 37 | } 38 | 39 | /// 40 | /// Gets a char array from the given . 41 | /// 42 | /// The target . 43 | /// The ordinal of the column that contains the char array. 44 | /// An array of chars read from the data record. 45 | public static char[] GetChars(this IDataRecord dataRecord, int ordinal) 46 | { 47 | long length = dataRecord.GetChars(ordinal, 0, null, 0, int.MaxValue); 48 | var buffer = new char[length]; 49 | dataRecord.GetChars(ordinal, 0, buffer, 0, (int)length); 50 | return buffer; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/DbReader/Database/ArgumentParser.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Database 2 | { 3 | using System; 4 | using System.Data; 5 | using Construction; 6 | 7 | /// 8 | /// A class that parses an SQL statement and 9 | /// maps each parameter to the properties of an arguments object. 10 | /// 11 | public class ArgumentParser : IArgumentParser 12 | { 13 | private readonly IArgumentParserMethodBuilder argumentParserMethodBuilder; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The 19 | /// that is responsible for creating a method at runtime that maps an argument object instance into a list of data parameters. 20 | public ArgumentParser(IArgumentParserMethodBuilder argumentParserMethodBuilder) 21 | { 22 | this.argumentParserMethodBuilder = argumentParserMethodBuilder; 23 | } 24 | 25 | /// 26 | public QueryInfo Parse(string sql, object arguments, Func parameterFactory, IDataParameter[] existingParameters) 27 | { 28 | if (arguments == null) 29 | { 30 | return new QueryInfo(sql, new IDataParameter[] { }); 31 | } 32 | var argumentParseMethod = argumentParserMethodBuilder.CreateMethod(sql, arguments.GetType(), existingParameters); 33 | return argumentParseMethod(sql, arguments, parameterFactory); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/DbReader/Database/IArgumentParser.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Database 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Represents a class that parses an SQL statement and 8 | /// maps each parameter to the properties of an arguments object. 9 | /// 10 | public interface IArgumentParser 11 | { 12 | /// 13 | /// Parses the given and maps each 14 | /// parameter to the corresponding property of the object. 15 | /// 16 | /// The sql statement containing the parameters to be parsed. 17 | /// An object that represent the argument values for each parameter. 18 | /// A factory delegate used to create an instance. 19 | /// A list of already existing parameters. 20 | /// 21 | QueryInfo Parse(string sql, object arguments, Func parameterFactory, IDataParameter[] existingParameters); 22 | } 23 | } -------------------------------------------------------------------------------- /src/DbReader/Database/IParameterParser.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Database 2 | { 3 | /// 4 | /// Represents a class that is capable of parsing a SQL statement 5 | /// and return a list of parameters. 6 | /// 7 | public interface IParameterParser 8 | { 9 | /// 10 | /// Gets a list that represents the names of the parameters 11 | /// used in the given . 12 | /// 13 | /// The sql statement to be parsed. 14 | /// A list of parameters used in the given . 15 | DataParameterInfo[] GetParameters(string sql); 16 | } 17 | 18 | /// 19 | /// Contains information about a parsed data parameter. 20 | /// 21 | public class DataParameterInfo 22 | { 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The name of the data parameter excluding the prefix. 28 | /// The name of the data parameter including the prefix. 29 | /// Determines if the data parameter is a list parameter that needs to be expanded. 30 | public DataParameterInfo(string name, string fullName, bool isListParameter) 31 | { 32 | Name = name; 33 | FullName = fullName; 34 | IsListParameter = isListParameter; 35 | } 36 | 37 | /// 38 | /// Get the name of the data parameter excluding the prefix. 39 | /// 40 | public string Name { get; } 41 | 42 | /// 43 | /// The name of the data parameter including the prefix. 44 | /// 45 | public string FullName { get; } 46 | 47 | /// 48 | /// Gets a value that indicates whether this is a list parameter that needs to be expanded. 49 | /// 50 | /// 51 | public bool IsListParameter { get; } 52 | } 53 | } -------------------------------------------------------------------------------- /src/DbReader/Database/QueryInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace DbReader.Database 4 | { 5 | 6 | /// 7 | /// Represents information about the query to be executed and its data parameters. 8 | /// 9 | public class QueryInfo 10 | { 11 | 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The query/SQL to be executed. 16 | /// The data parameters to be used when executing the query. 17 | public QueryInfo(string query, IDataParameter[] parameters) 18 | { 19 | Query = query; 20 | Parameters = parameters; 21 | } 22 | 23 | /// 24 | /// Gets the query/SQL to be executed. 25 | /// 26 | public string Query { get; } 27 | 28 | /// 29 | /// Gets the data parameters to be used when executing the query. 30 | /// 31 | public IDataParameter[] Parameters { get; } 32 | } 33 | } -------------------------------------------------------------------------------- /src/DbReader/Database/RegExParameterParser.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Database 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | 9 | /// 10 | /// An that uses regular 11 | /// expressions to parse out references to parameters from 12 | /// a given sql statement. 13 | /// 14 | public class RegExParameterParser : IParameterParser 15 | { 16 | // TODO Cache this. 17 | private readonly Regex parameterMatcher; 18 | 19 | private readonly Regex listParameterMatcher; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The regular expression pattern to be used to identify the 25 | /// parameter references in a sql statement. 26 | /// The regular expression to be used to identify the list 27 | /// parameter references in a sql statement. 28 | public RegExParameterParser(string parameterPattern, string listParameterPattern) 29 | { 30 | this.parameterMatcher = new Regex(parameterPattern, RegexOptions.Compiled); 31 | this.listParameterMatcher = new Regex(listParameterPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); 32 | } 33 | 34 | /// 35 | /// Gets a list that represents the names of the parameters 36 | /// used in the given . 37 | /// 38 | /// The sql statement to be parsed. 39 | /// A list of parameters used in the given . 40 | public DataParameterInfo[] GetParameters(string sql) 41 | { 42 | var result = new List(); 43 | var allParameters = Parse(sql, parameterMatcher); 44 | var listParameters = Parse(sql, listParameterMatcher); 45 | foreach (var parameter in allParameters) 46 | { 47 | result.Add(new DataParameterInfo(parameter.Substring(1), parameter, listParameters.Contains(parameter))); 48 | } 49 | return result.ToArray(); 50 | } 51 | 52 | private string[] Parse(string sql, Regex regex) 53 | { 54 | var result = new HashSet(StringComparer.OrdinalIgnoreCase); 55 | var matches = regex.Matches(sql).Cast(); 56 | foreach (var match in matches) 57 | { 58 | for (int i = 1; i < match.Groups.Count; i++) 59 | { 60 | if (!string.IsNullOrEmpty(match.Groups[i].Value)) 61 | { 62 | result.Add(match.Groups[i].Value.Trim()); 63 | break; 64 | } 65 | } 66 | } 67 | 68 | return result.ToArray(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/DbReader/DbReader.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | Bernhard Richter 6 | https://github.com/seesharper/DbReader 7 | git 8 | https://github.com/seesharper/DbReader 9 | http://opensource.org/licenses/MIT 10 | Bernhard Richter 11 | data-access orm sql micro-orm 12 | A simple and fast database reader for the .Net framework. 13 | true 14 | portable 15 | true 16 | true 17 | true 18 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 19 | latest 20 | 21 | 22 | 23 | 24 | 25 | all 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/DbReader/DynamicArguments/DynamicMemberInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DbReader.DynamicArguments 4 | { 5 | /// 6 | /// Represents metadata about a dynamic member. 7 | /// 8 | public class DynamicMemberInfo 9 | { 10 | /// 11 | /// The name of the member 12 | /// 13 | public readonly string Name; 14 | 15 | /// 16 | /// The type of the member. 17 | /// 18 | public readonly Type Type; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The name of the member. 24 | /// The type of the member 25 | public DynamicMemberInfo(string name, Type type) 26 | { 27 | this.Name = name; 28 | this.Type = type; ; 29 | } 30 | 31 | /// 32 | public bool Equals(DynamicMemberInfo other) 33 | { 34 | return (Name, Type).Equals((other.Name, other.Type)); 35 | } 36 | 37 | /// 38 | public override int GetHashCode() 39 | { 40 | return (Name, Type).GetHashCode(); 41 | } 42 | 43 | /// 44 | public override bool Equals(object obj) 45 | { 46 | return Equals(obj as DynamicMemberInfo); 47 | } 48 | } 49 | 50 | /// 51 | /// Represents the metadata and the value of a dynamic member. 52 | /// 53 | /// 54 | public class DynamicMemberInfo : DynamicMemberInfo 55 | { 56 | /// 57 | /// The value of the dynamic member. 58 | /// 59 | public readonly T Value; 60 | 61 | /// 62 | /// Initializes a new instance of the class. 63 | /// 64 | /// The name of the member. 65 | /// The value of the dynamic member. 66 | /// 67 | public DynamicMemberInfo(string name, T value) : base(name, typeof(T)) 68 | { 69 | this.Value = value; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/DbReader/DynamicArguments/DynamicTypeActivator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection.Emit; 3 | 4 | namespace DbReader.DynamicArguments 5 | { 6 | /// 7 | /// Creates instances. 8 | /// 9 | public class DynamicTypeActivator 10 | { 11 | /// 12 | /// Creates an instance of the given 13 | /// 14 | /// The dynamic type for which to create an instance. 15 | /// The list of members and their values. 16 | /// 17 | public IDynamicType Activate(Type dynamicType, DynamicMemberInfo[] members) 18 | { 19 | var activatorMethod = new DynamicMethod("Activate", typeof(IDynamicType), parameterTypes: new Type[] { typeof(DynamicMemberInfo[]) }, typeof(DynamicTypeActivator), true); 20 | var generator = activatorMethod.GetILGenerator(); 21 | 22 | for (int i = 0; i < members.Length; i++) 23 | { 24 | DynamicMemberInfo member = members[i]; 25 | 26 | generator.Emit(OpCodes.Ldarg_0); 27 | generator.Emit(OpCodes.Ldc_I4, i); 28 | generator.Emit(OpCodes.Ldelem_Ref); 29 | var closedGenericMemberInfoType = typeof(DynamicMemberInfo<>).MakeGenericType(member.Type); 30 | generator.Emit(OpCodes.Castclass, closedGenericMemberInfoType); 31 | var fieldInfo = closedGenericMemberInfoType.GetField("Value"); 32 | generator.Emit(OpCodes.Ldfld, fieldInfo); 33 | } 34 | 35 | var constructorInfo = dynamicType.GetConstructors()[0]; 36 | generator.Emit(OpCodes.Newobj, constructorInfo); 37 | 38 | generator.Emit(OpCodes.Ret); 39 | 40 | 41 | 42 | var activatorDelegate = (Func)activatorMethod.CreateDelegate(typeof(Func)); 43 | 44 | var instance = activatorDelegate(members); 45 | 46 | return instance; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/DbReader/DynamicArguments/IDynamicType.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.DynamicArguments 2 | { 3 | /// 4 | /// Implemented by the types generated by the `ArgumentsBuilder` class to be able to get an value by name. 5 | /// 6 | public interface IDynamicType 7 | { 8 | /// 9 | /// Gets the value from an by name. 10 | /// 11 | /// The name of the member for which to get the value. 12 | /// The type of value to get. 13 | /// The value of the member. 14 | T Get(string memberName); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/DbReader/DynamicArguments/TypeArrayEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DbReader.DynamicArguments 4 | { 5 | internal class DynamicMemberInfoArrayEqualityComparer : IEqualityComparer 6 | { 7 | public bool Equals(DynamicMemberInfo[] x, DynamicMemberInfo[] y) 8 | { 9 | if (x.Length != y.Length) 10 | { 11 | return false; 12 | } 13 | for (int i = 0; i < x.Length; i++) 14 | { 15 | if (!x[i].Equals(y[i])) 16 | { 17 | return false; 18 | } 19 | } 20 | return true; 21 | } 22 | 23 | public int GetHashCode(DynamicMemberInfo[] obj) 24 | { 25 | int result = 17; 26 | for (int i = 0; i < obj.Length; i++) 27 | { 28 | unchecked 29 | { 30 | result = result * 23 + obj[i].GetHashCode(); 31 | } 32 | } 33 | return result; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DbReader/DynamicArguments/ValueAccessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection.Emit; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace DbReader.DynamicArguments 7 | { 8 | /// 9 | /// Enables access to values by the field name. 10 | /// 11 | /// 12 | public static class ValueAccessor 13 | { 14 | private static ConcurrentDictionary delegateCache = new ConcurrentDictionary(); 15 | 16 | /// 17 | /// Gets the field value based on the given . 18 | /// 19 | /// The target object () 20 | /// The name of the field for which to get the value. 21 | /// The type of the field. 22 | /// 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public static TValue GetValue(TTarget target, string fieldName) 25 | { 26 | var accessorDelegate = (Func)delegateCache.GetOrAdd(fieldName, n => CreateAccessorDelegate(n)); 27 | return accessorDelegate(target); 28 | } 29 | 30 | private static Delegate CreateAccessorDelegate(string fieldName) 31 | { 32 | var fieldInfo = typeof(TTarget).GetField("_" + fieldName); 33 | 34 | var valueAccessorMethod = new DynamicMethod("GetValue", typeof(TValue), new[] { typeof(TTarget) }, typeof(ArgumentsBuilder), true); 35 | var generator = valueAccessorMethod.GetILGenerator(); 36 | generator.Emit(OpCodes.Ldarg_0); 37 | generator.Emit(OpCodes.Ldfld, fieldInfo); 38 | generator.Emit(OpCodes.Ret); 39 | 40 | var accessorDelegate = valueAccessorMethod.CreateDelegate(typeof(Func)); 41 | return accessorDelegate; 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/DbReader/ErrorMessages.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | internal class ErrorMessages 4 | { 5 | public const string ConstructorNotFound = "The type ({0}) does not contain a public parameterless constructor."; 6 | public const string DuplicateFieldName = "The data record contains a duplicate field name ({0}). Make sure that every column/field name in the data record is unique."; 7 | public const string IncompatibleTypes = "The property ({0}) is not compatible with the column ({1})returned from the data record. Please make sure that the property is declared as ({2}) or that the column is returned as ({3}). Alternatively register a custom conversion function using the DbReaderOptions class."; 8 | public const string MissingKeyProperties = "The type ({0}) does not contain any properties that is considered a key property."; 9 | public const string UnmappedKeyProperty = "The property ({0}) is considered a key property, but is not available in the result set. Please make sure that the result set contains a field that can be mapped this property."; 10 | public const string MissingArgument = "Unable to resolve an argument value for parameter ({0}). Please make sure that the argument object has a property named '{0}'"; 11 | 12 | public const string DuplicateParameter = 13 | "The parameter {0} is already specified. If this parameter is added in the DbReaderOptions.CommandInitializer method there is no need to define the argument when executing the query." 14 | ; 15 | 16 | public const string InvalidCollectionType = 17 | "The navigation property (one-to-many) {0} must have one of the following types {1}"; 18 | 19 | public const string SimpleProjectType = 20 | "{0} Simple types such as string and int are not allowed as the item type for a collection property."; 21 | 22 | public const string UnknownArgumentType = "The property {0} has a type ({1}) that cannot be passed as an argument. Consider adding support for {1} using DbReaderOptions.WhenPassing()"; 23 | 24 | public const string InvalidListArgument = "The parameter {0} is defined a list parameter, but the property {1} is not IEnumerable"; 25 | 26 | public const string InvalidParameterValue = "The parameter ({0}) did not accept the value `{1}` ({2}). If the value is a custom type, consider adding support for the type using DbReaderOptions.WhenPassing()"; 27 | } 28 | } -------------------------------------------------------------------------------- /src/DbReader/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Extensions 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | 8 | /// 9 | /// Extends the interface. 10 | /// 11 | public static class CollectionExtensions 12 | { 13 | private static readonly MethodInfo OpenGenericTryAddMethod; 14 | 15 | private static readonly ConcurrentDictionary TryAddMethods = 16 | new ConcurrentDictionary(); 17 | 18 | static CollectionExtensions() 19 | { 20 | OpenGenericTryAddMethod = typeof(CollectionExtensions).GetMethod( 21 | "TryAdd", 22 | BindingFlags.Static | BindingFlags.Public); 23 | } 24 | 25 | /// 26 | /// Adds the to the 27 | /// only of the does not already exist. 28 | /// 29 | /// The element type of the collection. 30 | /// The target . 31 | /// The value to be added to the collection. 32 | public static void TryAdd(this ICollection collection, T value) 33 | { 34 | if (value == null) 35 | { 36 | return; 37 | } 38 | if (!collection.Contains(value)) 39 | { 40 | collection.Add(value); 41 | } 42 | } 43 | 44 | /// 45 | /// Gets the that represents calling the method 46 | /// with the given . 47 | /// 48 | /// The element type used to create the method. 49 | /// A closed generic method. 50 | internal static MethodInfo GetTryAddMethod(Type elementType) 51 | { 52 | return TryAddMethods.GetOrAdd(elementType, CreateClosedGenericTryAddMethod); 53 | } 54 | 55 | private static MethodInfo CreateClosedGenericTryAddMethod(Type elementType) 56 | { 57 | return OpenGenericTryAddMethod.MakeGenericMethod(elementType); 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /src/DbReader/Extensions/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq.Expressions; 4 | 5 | namespace DbReader.Extensions 6 | { 7 | /// 8 | /// Extends the class. 9 | /// 10 | public static class ExpressionExtensions 11 | { 12 | /// 13 | /// Flattens the into an . 14 | /// 15 | /// The target . 16 | /// The represented as a list of sub expressions. 17 | public static IEnumerable AsEnumerable(this Expression expression) 18 | { 19 | var flattener = new ExpressionTreeFlattener(); 20 | return flattener.Flatten(expression); 21 | } 22 | 23 | private class ExpressionTreeFlattener : ExpressionVisitor 24 | { 25 | private readonly ICollection nodes = new Collection(); 26 | 27 | public IEnumerable Flatten(Expression expression) 28 | { 29 | Visit(expression); 30 | return nodes; 31 | } 32 | 33 | public override Expression Visit(Expression node) 34 | { 35 | nodes.Add(node); 36 | return base.Visit(node); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/DbReader/Extensions/GeneratorExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Extensions 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Reflection.Emit; 5 | 6 | /// 7 | /// Extends the class. 8 | /// 9 | [ExcludeFromCodeCoverage] 10 | public static class GeneratorExtensions 11 | { 12 | /// 13 | /// Pushes an value onto the stack. 14 | /// 15 | /// The target instance. 16 | /// The value to be pushed onto the stack. 17 | public static void EmitFastInt(this ILGenerator il, int value) 18 | { 19 | switch (value) 20 | { 21 | case -1: 22 | il.Emit(OpCodes.Ldc_I4_M1); 23 | return; 24 | case 0: 25 | il.Emit(OpCodes.Ldc_I4_0); 26 | return; 27 | case 1: 28 | il.Emit(OpCodes.Ldc_I4_1); 29 | return; 30 | case 2: 31 | il.Emit(OpCodes.Ldc_I4_2); 32 | return; 33 | case 3: 34 | il.Emit(OpCodes.Ldc_I4_3); 35 | return; 36 | case 4: 37 | il.Emit(OpCodes.Ldc_I4_4); 38 | return; 39 | case 5: 40 | il.Emit(OpCodes.Ldc_I4_5); 41 | return; 42 | case 6: 43 | il.Emit(OpCodes.Ldc_I4_6); 44 | return; 45 | case 7: 46 | il.Emit(OpCodes.Ldc_I4_7); 47 | return; 48 | case 8: 49 | il.Emit(OpCodes.Ldc_I4_8); 50 | return; 51 | } 52 | 53 | if (value > -129 && value < 128) 54 | { 55 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); 56 | } 57 | else 58 | { 59 | il.Emit(OpCodes.Ldc_I4, value); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/DbReader/Extensions/PropertyInfoEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Extensions 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | /// 8 | /// Adds functionality for ordering properties by their declaration order. 9 | /// 10 | public static class PropertyInfoEnumerableExtensions 11 | { 12 | /// 13 | /// Orders the by their declaration order. 14 | /// 15 | /// The properties for which to be ordered. 16 | /// The ordered by declaration. 17 | public static IEnumerable OrderByDeclaration(this IEnumerable members) 18 | { 19 | return members.OrderBy(p => p, new MetadataTokenComparer()); 20 | } 21 | 22 | private class MetadataTokenComparer : IComparer 23 | { 24 | public int Compare(MemberInfo x, MemberInfo y) 25 | { 26 | 27 | var xToken = x.MetadataToken; 28 | var ytoken = y.MetadataToken; 29 | 30 | if (xToken < ytoken) 31 | { 32 | return -1; 33 | } 34 | if (xToken > ytoken) 35 | { 36 | return 1; 37 | } 38 | return 0; 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/DbReader/Extensions/PropertyReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Extensions 2 | { 3 | using System.Data; 4 | using System.Reflection; 5 | 6 | /// 7 | /// Extends the class. 8 | /// 9 | public static class PropertyReflectionExtensions 10 | { 11 | /// 12 | /// Determines if the is writeable. 13 | /// 14 | /// The target . 15 | /// true, if the is writeable, otherwise, false. 16 | public static bool IsWriteable(this PropertyInfo property) 17 | { 18 | MethodInfo setMethod = property.GetSetMethod(); 19 | return setMethod != null && !setMethod.IsStatic && setMethod.IsPublic; 20 | } 21 | 22 | /// 23 | /// Determines if the is readable. 24 | /// 25 | /// The target . 26 | /// true, if the is readable, otherwise, false. 27 | public static bool IsReadable(this PropertyInfo property) 28 | { 29 | MethodInfo getMethod = property.GetGetMethod(); 30 | return getMethod != null && !getMethod.IsStatic && getMethod.IsPublic; 31 | } 32 | 33 | /// 34 | /// Determines if the is an . 35 | /// 36 | /// The target . 37 | /// true, if the is an , otherwise, false. 38 | public static bool IsDataParameter(this PropertyInfo property) 39 | { 40 | return typeof(IDataParameter).GetTypeInfo().IsAssignableFrom(property.PropertyType); 41 | } 42 | 43 | /// 44 | /// Gets the "full name" of the . 45 | /// 46 | /// The for which to get the full name. 47 | /// The "full name" of the . 48 | public static string GetFullName(this PropertyInfo property) 49 | { 50 | return $"{property.PropertyType} {property.DeclaringType}.{property.Name}"; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/DbReader/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Extensions 2 | { 3 | using System; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | 8 | /// 9 | /// Extends the type. 10 | /// 11 | public static class StringExtensions 12 | { 13 | private static readonly Regex LowerCaseRegex = new Regex("[a-z]"); 14 | 15 | /// 16 | /// Applies the given and returns the formatted string. 17 | /// 18 | /// The target value. 19 | /// An object array that contains zero or more arguments used to format the 20 | /// A copy of the in which the format items have been replaced by the string representation of the . 21 | public static string FormatWith(this string value, params object[] arguments) 22 | { 23 | return string.Format(value, arguments); 24 | } 25 | 26 | /// 27 | /// Gets the upper case letters from the given . 28 | /// 29 | /// The value for which to get the uppercase letters. 30 | /// The upper case letters from the given . 31 | public static string GetUpperCaseLetters(this string value) 32 | { 33 | return LowerCaseRegex.Replace(value, string.Empty); 34 | } 35 | 36 | /// 37 | /// Creates a cache key based upon the given . 38 | /// 39 | /// The strings for which to create a cache key. 40 | /// A cache key based upon the given . 41 | public static string CreateCacheKey(this string[] keys) 42 | { 43 | // Calculate the total length of the combined key 44 | int totalLength = 0; 45 | foreach (var key in keys) 46 | { 47 | totalLength += key.Length; 48 | } 49 | totalLength += keys.Length - 1; // Account for delimiters 50 | 51 | // Use stackalloc to avoid heap allocations for small keys 52 | Span buffer = totalLength <= 256 ? stackalloc char[totalLength] : new char[totalLength]; 53 | 54 | // Combine the keys into the buffer 55 | int position = 0; 56 | for (int i = 0; i < keys.Length; i++) 57 | { 58 | keys[i].AsSpan().CopyTo(buffer.Slice(position)); 59 | position += keys[i].Length; 60 | 61 | if (i < keys.Length - 1) 62 | { 63 | buffer[position] = '|'; // Delimiter 64 | position++; 65 | } 66 | } 67 | 68 | // Convert the Span to a byte array using UTF8 encoding 69 | int byteCount = Encoding.UTF8.GetByteCount(buffer); 70 | Span byteBuffer = byteCount <= 256 ? stackalloc byte[byteCount] : new byte[byteCount]; 71 | Encoding.UTF8.GetBytes(buffer, byteBuffer); 72 | 73 | // Generate a hash of the combined key 74 | using (var sha256 = SHA256.Create()) 75 | { 76 | byte[] hashBytes = sha256.ComputeHash(byteBuffer.ToArray()); 77 | return Convert.ToHexString(hashBytes).ToLowerInvariant(); 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/DbReader/Guard.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | using System; 4 | 5 | internal static class Require 6 | { 7 | public static void IsNotNull(T value, string paramName) where T : class 8 | { 9 | if (value == null) 10 | { 11 | throw new ArgumentNullException(paramName); 12 | } 13 | } 14 | } 15 | 16 | 17 | internal static class Ensure 18 | { 19 | public static T IsNotNull(T value, string message) where T : class 20 | { 21 | if (value == null) 22 | { 23 | throw new InvalidOperationException(message); 24 | } 25 | 26 | return value; 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/DbReader/Interfaces/IInstanceReaderMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Interfaces 2 | { 3 | using System; 4 | using System.Data; 5 | using DbReader.Readers; 6 | 7 | /// 8 | /// Represents a class that creates an instance of 9 | /// based on a given . 10 | /// 11 | /// The type of object to be returned from the method. 12 | public interface IInstanceReaderMethodBuilder 13 | { 14 | /// 15 | /// Creates a method that creates an instance of 16 | /// based on the given . 17 | /// 18 | /// The that contains the data for the instance. 19 | /// The current prefix. 20 | /// A method that creates an instance of 21 | /// based on the given . 22 | Func CreateMethod(IDataRecord dataRecord, string prefix); 23 | } 24 | } -------------------------------------------------------------------------------- /src/DbReader/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("DbReader.Tests")] 4 | [assembly: InternalsVisibleTo("OneToManyDynamicMethod")] 5 | [assembly: InternalsVisibleTo("ManyToOneDynamicMethod")] 6 | [assembly: InternalsVisibleTo("ReaderDynamicMethod")] -------------------------------------------------------------------------------- /src/DbReader/Mapping/CachedPropertyMapper.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Mapping 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Data; 6 | using Construction; 7 | 8 | /// 9 | /// A decorator that caches a list of instances per . 10 | /// 11 | public class CachedPropertyMapper : IPropertyMapper 12 | { 13 | private readonly IPropertyMapper propertyMapper; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// 19 | /// The being decorated. 20 | /// 21 | public CachedPropertyMapper(IPropertyMapper propertyMapper) 22 | { 23 | this.propertyMapper = propertyMapper; 24 | } 25 | 26 | /// 27 | /// Maps the fields from the to the properties of the . 28 | /// 29 | /// The containing the properties to be mapped. 30 | /// The containing the fields to be mapped. 31 | /// The prefix to use when mapping columns to properties. 32 | /// A list of instances that represents the mapping between a field and a property. 33 | public MappingInfo[] Execute(Type type, IDataRecord dataRecord, string prefix) 34 | { 35 | return StaticCache.GetOrAdd(type, prefix, 36 | () => propertyMapper.Execute(type, dataRecord, prefix)); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/DbReader/Mapping/IKeyPropertyMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DbReader.Mapping 4 | { 5 | using System.Data; 6 | 7 | /// 8 | /// Represents a class that is capable of mapping the key fields from a given 9 | /// according to the specified . 10 | /// 11 | public interface IKeyPropertyMapper 12 | { 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | MappingInfo[] Execute(Type type, IDataRecord dataRecord, string prefix); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/DbReader/Mapping/IPropertyMapper.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Mapping 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Represents a class that is capable of mapping the fields from an 8 | /// to the properties of a given . 9 | /// 10 | public interface IPropertyMapper 11 | { 12 | /// 13 | /// Maps the fields from the to the properties of the . 14 | /// 15 | /// The containing the properties to be mapped. 16 | /// The containing the fields to be mapped. 17 | /// The prefix to use when mapping columns to properties. 18 | /// A list of instances that represents the mapping between a field and a property. 19 | MappingInfo[] Execute(Type type, IDataRecord dataRecord, string prefix); 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/Mapping/KeyPropertyMapper.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Mapping 2 | { 3 | using System; 4 | using System.Data; 5 | using System.Linq; 6 | 7 | /// 8 | /// An decorator that maps key fields from a given 9 | /// according to the specified . 10 | /// 11 | public class KeyPropertyMapper : IKeyPropertyMapper 12 | { 13 | private readonly IPropertyMapper propertyMapper; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The that is responsible 19 | /// for mapping properties from a given to the ordinals from an . 20 | public KeyPropertyMapper(IPropertyMapper propertyMapper) 21 | { 22 | this.propertyMapper = propertyMapper; 23 | } 24 | 25 | /// 26 | /// Maps the fields from the to the properties of the . 27 | /// 28 | /// The containing the properties to be mapped. 29 | /// The containing the fields to be mapped. 30 | /// The prefix to use when mapping columns to properties. 31 | /// A list of instances that represents the mapping between a field and a property. 32 | public MappingInfo[] Execute(Type type, IDataRecord dataRecord, string prefix) 33 | { 34 | MappingInfo[] propertyMappings = propertyMapper.Execute(type, dataRecord, prefix); 35 | 36 | MappingInfo[] keyPropertyMappings = 37 | propertyMappings.Where(pm => DbReaderOptions.KeyConvention(pm.Property)).ToArray(); 38 | 39 | return keyPropertyMappings; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/DbReader/Mapping/KeyPropertyMapperValidator.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Mapping 2 | { 3 | using System; 4 | using System.Data; 5 | using Extensions; 6 | 7 | /// 8 | /// A decorator that validates 9 | /// the the result from an that maps 10 | /// key properties to fields. 11 | /// 12 | public class KeyPropertyMapperValidator : IKeyPropertyMapper 13 | { 14 | private readonly IKeyPropertyMapper keyPropertyMapper; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The target . 20 | public KeyPropertyMapperValidator(IKeyPropertyMapper keyPropertyMapper) 21 | { 22 | this.keyPropertyMapper = keyPropertyMapper; 23 | } 24 | 25 | /// 26 | /// Maps the fields from the to the properties of the . 27 | /// 28 | /// The containing the properties to be mapped. 29 | /// The containing the fields to be mapped. 30 | /// The prefix to use when mapping columns to properties. 31 | /// A list of instances that represents the mapping between a field and a property. 32 | public MappingInfo[] Execute(Type type, IDataRecord dataRecord, string prefix) 33 | { 34 | var result = keyPropertyMapper.Execute(type, dataRecord, prefix); 35 | 36 | if (result.Length == 0) 37 | { 38 | throw new InvalidOperationException(ErrorMessages.MissingKeyProperties.FormatWith(type)); 39 | } 40 | 41 | foreach (MappingInfo keyMappingInfo in result) 42 | { 43 | if (keyMappingInfo.ColumnInfo.Ordinal == -1) 44 | { 45 | throw new InvalidOperationException(ErrorMessages.UnmappedKeyProperty.FormatWith(keyMappingInfo.Property.GetFullName())); 46 | } 47 | } 48 | 49 | return result; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/DbReader/Mapping/MappingInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Mapping 2 | { 3 | using System.Reflection; 4 | using Extensions; 5 | using Selectors; 6 | 7 | /// 8 | /// Represents the mapping between a and a 9 | /// 10 | public class MappingInfo 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The target . 16 | /// The target . 17 | public MappingInfo(PropertyInfo property, ColumnInfo columnInfo) 18 | { 19 | Property = property; 20 | ColumnInfo = columnInfo; 21 | } 22 | 23 | /// 24 | /// Gets the that represents the property mapped to a . 25 | /// 26 | public PropertyInfo Property { get; private set; } 27 | 28 | /// 29 | /// Gets the that represents the field mapped to a . 30 | /// 31 | public ColumnInfo ColumnInfo { get; private set; } 32 | 33 | /// 34 | /// Returns a string that represents the current object. 35 | /// 36 | /// 37 | /// A string that represents the current object. 38 | /// 39 | public override string ToString() 40 | { 41 | return "[{0}] Property: {1}, Ordinal: {2}".FormatWith(Property.DeclaringType, Property, ColumnInfo.Ordinal); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/DbReader/Mapping/PropertyTypeValidator.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Mapping 2 | { 3 | using System; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Reflection; 7 | using Extensions; 8 | 9 | /// 10 | /// An decorator that ensures that the mapping 11 | /// between fields and properties are compatible with regards to datatypes. 12 | /// 13 | public class PropertyTypeValidator : IPropertyMapper 14 | { 15 | private readonly IPropertyMapper propertyMapper; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The target . 21 | public PropertyTypeValidator(IPropertyMapper propertyMapper) 22 | { 23 | this.propertyMapper = propertyMapper; 24 | } 25 | 26 | /// 27 | /// Maps the fields from the to the properties of the . 28 | /// 29 | /// The containing the properties to be mapped. 30 | /// The containing the fields to be mapped. 31 | /// The prefix to use when mapping columns to properties. 32 | /// A list of instances that represents the mapping between a field and a property. 33 | public MappingInfo[] Execute(Type type, IDataRecord dataRecord, string prefix) 34 | { 35 | var mappings = propertyMapper.Execute(type, dataRecord, prefix); 36 | foreach (var mappingInfo in mappings.Where(m => m.ColumnInfo.Ordinal != -1)) 37 | { 38 | if (mappingInfo.ColumnInfo.Type == typeof(DBNull)) 39 | { 40 | continue; 41 | } 42 | 43 | var propertyType = mappingInfo.Property.PropertyType; 44 | 45 | if (!propertyType.IsAssignableFrom(mappingInfo.ColumnInfo.Type.GetTypeInfo()) && !ValueConverter.CanConvert(propertyType)) 46 | { 47 | if (propertyType.IsEnum || propertyType.IsNullable()) 48 | { 49 | Execute(propertyType.GetUnderlyingType(), dataRecord, prefix); 50 | } 51 | else 52 | { 53 | throw new InvalidOperationException( 54 | ErrorMessages.IncompatibleTypes.FormatWith( 55 | mappingInfo.Property, 56 | mappingInfo.ColumnInfo, 57 | mappingInfo.ColumnInfo.Type, 58 | mappingInfo.Property.PropertyType)); 59 | } 60 | } 61 | } 62 | return mappings; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/DbReader/Nuget/DbReader.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DbReader 5 | 1.0.0.0 6 | Bernhard Richter 7 | Bernhard Richter 8 | http://opensource.org/licenses/MIT 9 | https://github.com/seesharper/DbReader 10 | false 11 | A simple and fast database reader for the .Net framework. 12 | Bernhard Richter 13 | data-access orm sql micro-orm 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/DbReader/PassDelegate.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Specifies the convert function to be used when passing an argument of type . 8 | /// 9 | /// The type of argument to be converted. 10 | public class PassDelegate 11 | { 12 | /// 13 | /// Specifies the to be used when passing an argument of type . 14 | /// 15 | /// The convert function to be used when passimg the argument. 16 | public void Use(Action convertFunction) 17 | { 18 | ArgumentProcessor.RegisterProcessDelegate(convertFunction); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/ReadDelegate.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Specifies the read delegate to be using for reading a property of . 8 | /// 9 | /// 10 | public class ReadDelegate 11 | { 12 | /// 13 | /// Specifies the to be used to read a value of type 14 | /// 15 | /// The function to be used to read the value. 16 | public DefaultValue Use(Func readFunction) 17 | { 18 | ValueConverter.RegisterReadDelegate(readFunction); 19 | return new DefaultValue(); 20 | } 21 | } 22 | 23 | /// 24 | /// Specifies the default value to be used when the value from the database is . 25 | /// 26 | /// The type of property for which to specify a default value. 27 | public class DefaultValue 28 | { 29 | /// 30 | /// Specifies the to be used when the value from the database is . 31 | /// 32 | /// The value to be used when the value from the database is . 33 | public void WithDefaultValue(TProperty defaultValue) 34 | { 35 | ValueConverter.RegisterDefaultValue(defaultValue); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/IInstanceReader.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Readers 2 | { 3 | using System.Data; 4 | 5 | /// 6 | /// Represents a class that is capable of transforming an into an instance of . 7 | /// 8 | /// The type of object to be read from the . 9 | public interface IInstanceReader 10 | { 11 | /// 12 | /// Reads an instance of from the given . 13 | /// 14 | /// The from which to read an instance of . 15 | /// The current prefix. 16 | /// An instance of . 17 | T Read(IDataRecord dataRecord, string currentPrefix); 18 | } 19 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/IInstanceReaderFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Readers 2 | { 3 | /// 4 | /// Represents a class that is capable of creating an based on the given type. 5 | /// 6 | public interface IInstanceReaderFactory 7 | { 8 | /// 9 | /// Gets an based on the type described as 10 | /// 11 | /// The prefix for which to get an . 12 | /// The type for which to get an . 13 | /// 14 | IInstanceReader GetInstanceReader(string prefix); 15 | } 16 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/IKeyReader.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Readers 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Data; 6 | 7 | /// 8 | /// Represents a class that is capable of reading the key columns for a given . 9 | /// 10 | public interface IKeyReader 11 | { 12 | /// 13 | /// Reads the key columns from the given . 14 | /// 15 | /// The for which to read the key columns. 16 | /// The containing the key columns. 17 | /// The current column prefix. 18 | /// An that represent the key for an instance of the given . 19 | IStructuralEquatable Read(Type type, IDataRecord dataRecord, string prefix); 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/IReader.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Readers 2 | { 3 | using System.Data; 4 | 5 | /// 6 | /// Represents a class that is capable of transforming an into an instance of . 7 | /// 8 | /// The type of object to be returned from the reader. 9 | public interface IReader 10 | { 11 | /// 12 | /// Reads an instance of from the given . 13 | /// 14 | /// The that contains the data for a new instance of . 15 | /// An instance of . 16 | T Read(IDataRecord dataRecord); 17 | } 18 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/InstanceReader.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Readers 2 | { 3 | using System.Data; 4 | using Interfaces; 5 | 6 | /// 7 | /// A class that is capable of transforming an into an instance of . 8 | /// 9 | /// The type of object to be read from the . 10 | public class InstanceReader : IInstanceReader 11 | { 12 | private readonly IInstanceReaderMethodBuilder instanceReaderMethodBuilder; 13 | private readonly IInstanceReaderFactory instanceReaderFactory; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The that is responsible 19 | /// for building a method that is capable of reading an instance of from an . 20 | /// The that is responsible for resolving instances. 21 | public InstanceReader(IInstanceReaderMethodBuilder instanceReaderMethodBuilder, IInstanceReaderFactory instanceReaderFactory) 22 | { 23 | this.instanceReaderMethodBuilder = instanceReaderMethodBuilder; 24 | this.instanceReaderFactory = instanceReaderFactory; 25 | } 26 | 27 | /// 28 | /// Reads an instance of from the given . 29 | /// 30 | /// The from which to read an instance of . 31 | /// The current prefix. 32 | /// An instance of . 33 | public T Read(IDataRecord dataRecord, string currentPrefix) 34 | { 35 | var method = instanceReaderMethodBuilder.CreateMethod(dataRecord, currentPrefix); 36 | return method(dataRecord, instanceReaderFactory); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/InstanceReaderFactory.cs: -------------------------------------------------------------------------------- 1 | using DbReader.LightInject; 2 | 3 | namespace DbReader.Readers 4 | { 5 | 6 | /// 7 | /// A class that is capable of creating an based on the given type. 8 | /// 9 | public class InstanceReaderFactory : IInstanceReaderFactory 10 | { 11 | private readonly IServiceFactory serviceFactory; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The used to resolve instances. 17 | internal InstanceReaderFactory(IServiceFactory serviceFactory) 18 | { 19 | this.serviceFactory = serviceFactory; 20 | } 21 | 22 | /// 23 | public IInstanceReader GetInstanceReader(string prefix) 24 | { 25 | return serviceFactory.GetInstance>(); 26 | 27 | // NOTE: We used to cache the readers on the prefix. Have no idea why anymore; 28 | /* OLD CODE 29 | private readonly ConcurrentDictionary, object> readers = new ConcurrentDictionary, object>(); 30 | return (IInstanceReader)readers.GetOrAdd(Tuple.Create(typeof(T), prefix), t => serviceFactory.GetInstance>()); 31 | */ 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/DbReader/Readers/KeyReader.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Readers 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Data; 6 | using Construction; 7 | using DbReader.Interfaces; 8 | 9 | /// 10 | /// A class that is capable of reading the key columns for a given . 11 | /// 12 | public class KeyReader : IKeyReader 13 | { 14 | private readonly IKeyReaderMethodBuilder keyReaderMethodBuilder; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The that is responsible 20 | /// for creating a method that can read the key columns for a given . 21 | public KeyReader(IKeyReaderMethodBuilder keyReaderMethodBuilder) 22 | { 23 | this.keyReaderMethodBuilder = keyReaderMethodBuilder; 24 | } 25 | 26 | /// 27 | /// Reads the key columns from the given . 28 | /// 29 | /// The for which to read the key columns. 30 | /// The containing the key columns. 31 | /// The current column prefix. 32 | /// An that represent the key for an instance of the given . 33 | public IStructuralEquatable Read(Type type, IDataRecord dataRecord, string prefix) 34 | { 35 | return keyReaderMethodBuilder.CreateMethod(type, dataRecord, prefix)(dataRecord); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/CachedFieldSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System.Collections.Generic; 4 | using System.Data; 5 | 6 | /// 7 | /// An decorator that caches selected field from an . 8 | /// 9 | public class CachedFieldSelector : IFieldSelector 10 | { 11 | private readonly IFieldSelector fieldSelector; 12 | 13 | private IReadOnlyDictionary cachedFields; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The target . 19 | public CachedFieldSelector(IFieldSelector fieldSelector) 20 | { 21 | this.fieldSelector = fieldSelector; 22 | } 23 | 24 | /// 25 | /// Selects the field name and the corresponding ordinal from the given . 26 | /// 27 | /// The for which to select the fieldname and ordinal. 28 | /// An containing the field name and the ordinal. 29 | public IReadOnlyDictionary Execute(IDataRecord dataRecord) 30 | { 31 | if (cachedFields == null) 32 | { 33 | cachedFields = fieldSelector.Execute(dataRecord); 34 | } 35 | return cachedFields; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/CachedPropertySelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Reflection; 6 | 7 | /// 8 | /// An decorator that caches selected properties. 9 | /// 10 | public class CachedPropertySelector : IPropertySelector 11 | { 12 | private readonly IPropertySelector propertySelector; 13 | 14 | private readonly ConcurrentDictionary cache 15 | = new ConcurrentDictionary(); 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The target . 21 | public CachedPropertySelector(IPropertySelector propertySelector) 22 | { 23 | this.propertySelector = propertySelector; 24 | } 25 | 26 | /// 27 | /// Executes the selector and returns a list of properties. 28 | /// 29 | /// The target . 30 | /// An array of instances. 31 | public PropertyInfo[] Execute(Type type) 32 | { 33 | return cache.GetOrAdd(type, propertySelector.Execute); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/ColumnInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using Extensions; 5 | 6 | /// 7 | /// Contains information about a field in a data record. 8 | /// 9 | public class ColumnInfo 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The ordinal of the column. 15 | /// The name of the column. 16 | /// The type of the column as represented in the target data record. 17 | public ColumnInfo(int ordinal,string name, Type type) 18 | { 19 | Ordinal = ordinal; 20 | Name = name; 21 | Type = type; 22 | } 23 | 24 | /// 25 | /// Gets the ordinal of the column. 26 | /// 27 | public int Ordinal { get; } 28 | 29 | /// 30 | /// Gets the type of the column as represented in the target data record. 31 | /// 32 | public Type Type { get; } 33 | 34 | /// 35 | /// Gets the name of the column. 36 | /// 37 | public string Name { get; } 38 | 39 | /// 40 | /// Returns a string that represents the current object. 41 | /// 42 | /// 43 | /// A string that represents the current object. 44 | /// 45 | public override string ToString() 46 | { 47 | if (Ordinal == -1) 48 | { 49 | return "Not Mapped"; 50 | } 51 | return "Name: {0}, Type: {1}, Ordinal: {2}".FormatWith(Name, Type, Ordinal); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/ConstructorValidator.cs: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | The MIT License (MIT) 3 | Copyright (c) 2014 bernhard.richter@gmail.com 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | ****************************************************************************** 20 | DbReader version 1.0.0.1 21 | https://github.com/seesharper/DbReader 22 | http://twitter.com/bernhardrichter 23 | ******************************************************************************/ 24 | namespace DbReader.Selectors 25 | { 26 | using System; 27 | using System.Reflection; 28 | using Extensions; 29 | 30 | /// 31 | /// A decorator that validates 32 | /// the selected . 33 | /// 34 | public class ConstructorValidator : IConstructorSelector 35 | { 36 | private readonly IConstructorSelector constructorSelector; 37 | 38 | /// 39 | /// Initializes a new instance of the class. 40 | /// 41 | /// The target . 42 | public ConstructorValidator(IConstructorSelector constructorSelector) 43 | { 44 | this.constructorSelector = constructorSelector; 45 | } 46 | 47 | /// 48 | /// Gets a from the given 49 | /// 50 | /// The for which to get a . 51 | /// 52 | public ConstructorInfo Execute(Type type) 53 | { 54 | Require.IsNotNull(type, "type"); 55 | var constructor = constructorSelector.Execute(type); 56 | return Ensure.IsNotNull(constructor, ErrorMessages.ConstructorNotFound.FormatWith(type)); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/FieldSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Data; 7 | using Extensions; 8 | 9 | /// 10 | /// A class that returns a dictionary containing the column name and the column ordinal. 11 | /// 12 | public class FieldSelector : IFieldSelector 13 | { 14 | /// 15 | /// Selects the field name and the corresponding ordinal from the given . 16 | /// 17 | /// The for which to select the fieldname and ordinal. 18 | /// An containing the field name and the ordinal. 19 | public IReadOnlyDictionary Execute(IDataRecord dataRecord) 20 | { 21 | int fieldCount = dataRecord.FieldCount; 22 | var result = new Dictionary(fieldCount, StringComparer.OrdinalIgnoreCase); 23 | 24 | for (int i = 0; i < fieldCount; i++) 25 | { 26 | Type fieldType = dataRecord.GetFieldType(i); 27 | string fieldName = dataRecord.GetName(i); 28 | if (result.ContainsKey(fieldName)) 29 | { 30 | throw new ArgumentOutOfRangeException("dataRecord", ErrorMessages.DuplicateFieldName.FormatWith(fieldName)); 31 | } 32 | 33 | result.Add(dataRecord.GetName(i), new ColumnInfo(i, fieldName, fieldType)); 34 | } 35 | 36 | return new ReadOnlyDictionary(result); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/FirstConstructorSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// An that selected the first available public constructor. 8 | /// 9 | public class FirstConstructorSelector : IConstructorSelector 10 | { 11 | /// 12 | /// Gets a from the given 13 | /// 14 | /// The for which to get a . 15 | /// 16 | public ConstructorInfo Execute(Type type) 17 | { 18 | return type.GetConstructors()[0]; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/IConstructorSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// Represents a class that is capable of selecting 8 | /// a constructor from a given . 9 | /// 10 | public interface IConstructorSelector 11 | { 12 | /// 13 | /// Gets a from the given 14 | /// 15 | /// The for which to get a . 16 | /// 17 | ConstructorInfo Execute(Type type); 18 | } 19 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/IFieldSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System.Collections.Generic; 4 | using System.Data; 5 | 6 | /// 7 | /// Represents a class that returns an containing the field name and the ordinal. 8 | /// 9 | public interface IFieldSelector 10 | { 11 | /// 12 | /// Selects the field name and the corresponding ordinal from the given . 13 | /// 14 | /// The for which to select the fieldname and ordinal. 15 | /// An containing the field name and the ordinal. 16 | IReadOnlyDictionary Execute(IDataRecord dataRecord); 17 | } 18 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/IMethodSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Data; 5 | using System.Reflection; 6 | 7 | /// 8 | /// Represents a class that is capable of selecting the 9 | /// appropriate get method. 10 | /// 11 | public interface IMethodSelector 12 | { 13 | /// 14 | /// Selects the get method that will 15 | /// be used to read a value of the given ./>. 16 | /// 17 | /// The type for which to return a . 18 | /// The get method to be used to read the value. 19 | MethodInfo Execute(Type type); 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/IOrdinalSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | /// 7 | /// Represents a class that capable of returning the 8 | /// ordinals used to map a type to a data record. 9 | /// 10 | public interface IOrdinalSelector 11 | { 12 | /// 13 | /// Executes the selector and returns the ordinals required to read the columns from the data record. 14 | /// 15 | /// The for which to return the ordinals. 16 | /// The that represents the available fields/columns. 17 | /// The column prefix to use. 18 | /// A list of ordinals. 19 | int[] Execute(Type type, IDataRecord dataRecord, string prefix); 20 | } 21 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/IPropertySelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// Represents a class that is capable of selecting a set of properties from a given . 8 | /// 9 | public interface IPropertySelector 10 | { 11 | /// 12 | /// Executes the selector and returns a list of properties. 13 | /// 14 | /// The target . 15 | /// An array of instances. 16 | PropertyInfo[] Execute(Type type); 17 | } 18 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/ManyToOnePropertySelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Extensions; 7 | 8 | /// 9 | /// A that selects properties 10 | /// that represents a "Many to One" navigation property. 11 | /// 12 | public class ManyToOnePropertySelector : IPropertySelector 13 | { 14 | /// 15 | /// Executes the selector and returns a list of properties. 16 | /// 17 | /// The target . 18 | /// An array of instances. 19 | public PropertyInfo[] Execute(Type type) 20 | { 21 | return 22 | type.GetProperties() 23 | .Where(p => !TypeReflectionExtensions.IsSimpleType(p.PropertyType) && !TypeReflectionExtensions.IsEnumerable(p.PropertyType) && PropertyReflectionExtensions.IsWriteable(p)) 24 | .ToArray(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/OneToManyPropertySelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Extensions; 7 | 8 | /// 9 | /// A that selects properties 10 | /// that represents a "one-to-many" navigation property. 11 | /// 12 | public class OneToManyPropertySelector : IPropertySelector 13 | { 14 | /// 15 | /// Executes the selector and returns a list of properties. 16 | /// 17 | /// The target . 18 | /// An array of instances. 19 | public PropertyInfo[] Execute(Type type) 20 | { 21 | return type.GetProperties().Where(p => !p.PropertyType.IsSimpleType() && p.PropertyType.IsEnumerable()).ToArray(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/OrdinalSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Data; 5 | using System.Linq; 6 | using Mapping; 7 | 8 | /// 9 | /// A class that capable of returning the 10 | /// ordinals used to map a type to a data record. 11 | /// 12 | public class OrdinalSelector : IOrdinalSelector 13 | { 14 | private readonly IPropertyMapper propertyMapper; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The that is responsible for mapping fields/columns from an to 20 | /// the properties of a . 21 | public OrdinalSelector(IPropertyMapper propertyMapper) 22 | { 23 | this.propertyMapper = propertyMapper; 24 | } 25 | 26 | /// 27 | /// Executes the selector and returns the ordinals required to read the columns from the data record. 28 | /// 29 | /// The for which to return the ordinals. 30 | /// The that represents the available fields/columns. 31 | /// The column prefix to use. 32 | /// A list of ordinals. 33 | public int[] Execute(Type type, IDataRecord dataRecord, string prefix) 34 | { 35 | return propertyMapper.Execute(type, dataRecord, prefix).Select(pm => pm.ColumnInfo.Ordinal).ToArray(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/ParameterlessConstructorSelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Reflection; 5 | using DbReader.Extensions; 6 | 7 | /// 8 | /// An that looks for a parameterless 9 | /// constructor for a given . 10 | /// 11 | public class ParameterlessConstructorSelector : IConstructorSelector 12 | { 13 | /// 14 | /// Gets the parameterless constructor from the given . 15 | /// 16 | /// The for which to get a parameterless constructor. 17 | /// 18 | public ConstructorInfo Execute(Type type) 19 | { 20 | if (type.IsRecordType()) 21 | { 22 | return type.GetConstructors()[0]; 23 | } 24 | return type.GetConstructor(Type.EmptyTypes); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/ReadableArgumentPropertiesValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using DbReader.Extensions; 4 | 5 | namespace DbReader.Selectors 6 | { 7 | /// 8 | /// An decorator that ensures that 9 | /// a given argument type only contains properties that can be passed as arguments. 10 | /// 11 | public class ReadableArgumentPropertiesValidator : IPropertySelector 12 | { 13 | private readonly IPropertySelector readablePropertySelector; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The that is 19 | /// responsible for selecting readable properties. 20 | public ReadableArgumentPropertiesValidator(IPropertySelector readablePropertySelector) 21 | => this.readablePropertySelector = readablePropertySelector; 22 | 23 | /// 24 | /// Executes the selector and returns a list of readable properties that are passable as arguments. 25 | /// 26 | /// The target . 27 | /// An array of instances. 28 | public PropertyInfo[] Execute(Type type) 29 | { 30 | var properties = readablePropertySelector.Execute(type); 31 | 32 | foreach (var property in properties) 33 | { 34 | EnsureIsPassable(property); 35 | } 36 | 37 | return properties; 38 | } 39 | 40 | private static void EnsureIsPassable(PropertyInfo property) 41 | { 42 | if (!IsPassable(property)) 43 | { 44 | throw new InvalidOperationException(ErrorMessages.UnknownArgumentType.FormatWith(property, property.PropertyType)); 45 | } 46 | } 47 | 48 | private static bool IsPassable(PropertyInfo property) 49 | { 50 | return property.PropertyType.IsSimpleType() || property.IsDataParameter() || property.PropertyType.IsEnumerableOfSimpleType(); ; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DbReader/Selectors/ReadablePropertySelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Reflection; 7 | using Extensions; 8 | 9 | /// 10 | /// A that selects readable properties. 11 | /// 12 | public class ReadablePropertySelector : IPropertySelector 13 | { 14 | /// 15 | /// Executes the selector and returns a list of properties. 16 | /// 17 | /// The target . 18 | /// An array of instances. 19 | public PropertyInfo[] Execute(Type type) 20 | { 21 | return type.GetProperties().Where(p => p.IsReadable()).ToArray(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/DbReader/Selectors/SimplePropertySelector.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Selectors 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Extensions; 7 | 8 | /// 9 | /// A that selects writeable properties that is considered 10 | /// a "simple" property. 11 | /// 12 | public class SimplePropertySelector : IPropertySelector 13 | { 14 | /// 15 | /// Executes the selector and returns a list of writeable properties that is considered a "simple" property. 16 | /// 17 | /// The target . 18 | /// An array of instances. 19 | public PropertyInfo[] Execute(Type type) 20 | { 21 | PropertyInfo[] properties = type.GetProperties(); 22 | return properties.Where(p => TypeReflectionExtensions.IsSimpleType(p.PropertyType) && PropertyReflectionExtensions.IsWriteable(p)).ToArray(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/DbReader/Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sql 5 | SQL 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | False 14 | 15 | 16 | 17 | 18 | False 19 | 20 | 21 | 22 | 23 | False 24 | 25 | 26 | 27 | 28 | False 29 | 30 | 31 | 32 | 33 | False 34 | 35 | 36 | 37 | 38 | False 39 | 40 | 41 | 42 | 43 | False 44 | 45 | 46 | 47 | 48 | True 49 | 50 | 51 | 52 | 53 | 54 | 55 | False 56 | 57 | 58 | 59 | 60 | False 61 | 62 | 63 | 64 | 65 | False 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/DbReader/SqlStatement.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents the query currently executing. 7 | /// 8 | public class SqlStatement 9 | { 10 | /// 11 | /// The query currently executing. 12 | /// 13 | [ThreadStatic] 14 | public static string Current; 15 | } 16 | } -------------------------------------------------------------------------------- /src/DbReader/Tracking/ITrackedObject.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader.Tracking; 2 | 3 | using System.Collections.Generic; 4 | 5 | /// 6 | /// This interface is implemented by classes that are tracked for changes. 7 | /// 8 | public interface ITrackedObject 9 | { 10 | /// 11 | /// Gets a list of the properties that have been modified. 12 | /// 13 | /// 14 | HashSet GetModifiedProperties(); 15 | } -------------------------------------------------------------------------------- /src/DbReader/Tracking/TrackedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DbReader.Tracking; 4 | 5 | /// 6 | /// Used to indicate that a class should be tracked. 7 | /// Note that the DbReader.Tracking package is required to use this attribute. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class TrackedAttribute : Attribute 11 | { 12 | } -------------------------------------------------------------------------------- /src/DbReader/TypeEvaluator.cs: -------------------------------------------------------------------------------- 1 | namespace DbReader 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Extensions; 7 | 8 | /// 9 | /// Provides fast and lazy type evaluation. 10 | /// 11 | /// The type to evaluate. 12 | public static class TypeEvaluator 13 | { 14 | // ReSharper disable once StaticMemberInGenericType 15 | private static readonly Lazy EvaluatedValue; 16 | 17 | static TypeEvaluator() 18 | { 19 | EvaluatedValue = new Lazy(EvaluateType); 20 | } 21 | 22 | /// 23 | /// true if the has navigation properties, otherwise false. 24 | /// 25 | public static bool HasNavigationProperties => EvaluatedValue.Value; 26 | 27 | private static bool EvaluateType() 28 | { 29 | PropertyInfo[] properties = typeof(T).GetProperties(); 30 | return properties.Any(p => !p.PropertyType.IsSimpleType()); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/DbReader/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------