diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index c101afcaa..a455a5941 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -19,7 +19,8 @@ - + + diff --git a/Dapper.SqlBuilder/SqlBuilder.cs b/Dapper.SqlBuilder/SqlBuilder.cs index 5726fdc6e..cb45d0582 100644 --- a/Dapper.SqlBuilder/SqlBuilder.cs +++ b/Dapper.SqlBuilder/SqlBuilder.cs @@ -10,7 +10,7 @@ public class SqlBuilder private readonly Dictionary _data = new Dictionary(); private int _seq; - private class Clause + private sealed class Clause { public Clause(string sql, object? parameters, bool isInclusive) { @@ -23,7 +23,7 @@ public Clause(string sql, object? parameters, bool isInclusive) public bool IsInclusive { get; } } - private class Clauses : List + private sealed class Clauses : List { private readonly string _joiner, _prefix, _postfix; @@ -36,11 +36,9 @@ public Clauses(string joiner, string prefix = "", string postfix = "") public string ResolveClauses(DynamicParameters p) { - foreach (var item in this) - { - p.AddDynamicParams(item.Parameters); - } - return this.Any(a => a.IsInclusive) + ForEach(item => p.AddDynamicParams(item.Parameters)); + + return Exists(a => a.IsInclusive) ? _prefix + string.Join(_joiner, this.Where(a => !a.IsInclusive) diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 2aae43d73..705385fd6 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -19,10 +19,12 @@ + + diff --git a/Dapper.sln b/Dapper.sln.old similarity index 98% rename from Dapper.sln rename to Dapper.sln.old index 04fcc008d..e324cc009 100644 --- a/Dapper.sln +++ b/Dapper.sln.old @@ -1,121 +1,121 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.33906.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - appveyor.yml = appveyor.yml - Build.csproj = Build.csproj - build.ps1 = build.ps1 - Dapper.png = Dapper.png - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - global.json = global.json - docs\index.md = docs\index.md - License.txt = License.txt - .github\workflows\main.yml = .github\workflows\main.yml - NonCLA.md = NonCLA.md - nuget.config = nuget.config - Readme.md = Readme.md - version.json = version.json - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csproj", "{FAC24C3F-68F9-4247-A4B9-21D487E99275}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper.StrongName\Dapper.StrongName.csproj", "{549C51A1-222B-4E12-96F1-3AEFF45A7709}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" - ProjectSection(SolutionItems) = preProject - tests\Directory.Build.props = tests\Directory.Build.props - tests\Directory.Build.targets = tests\Directory.Build.targets - tests\docker-compose.yml = tests\docker-compose.yml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "benchmarks\Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.Build.0 = Release|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.Build.0 = Debug|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.ActiveCfg = Release|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.Build.0 = Release|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.Build.0 = Release|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.Build.0 = Release|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.Build.0 = Release|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.Build.0 = Release|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.Build.0 = Release|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {FAC24C3F-68F9-4247-A4B9-21D487E99275} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {549C51A1-222B-4E12-96F1-3AEFF45A7709} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {052C0817-DB26-4925-8929-8C5E42D148D5} = {568BD46C-1C65-4D44-870C-12CD72563262} - {BE401F7B-8611-4A1E-AEAA-5CB700128C16} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {196928F0-7052-4585-90E8-817BD720F5E3} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F} = {9D960D4D-80A2-4DAC-B386-8F4235EC73E6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33906.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + appveyor.yml = appveyor.yml + Build.csproj = Build.csproj + build.ps1 = build.ps1 + Dapper.png = Dapper.png + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + global.json = global.json + docs\index.md = docs\index.md + License.txt = License.txt + .github\workflows\main.yml = .github\workflows\main.yml + NonCLA.md = NonCLA.md + nuget.config = nuget.config + Readme.md = Readme.md + version.json = version.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csproj", "{FAC24C3F-68F9-4247-A4B9-21D487E99275}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper.StrongName\Dapper.StrongName.csproj", "{549C51A1-222B-4E12-96F1-3AEFF45A7709}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" + ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props + tests\Directory.Build.targets = tests\Directory.Build.targets + tests\docker-compose.yml = tests\docker-compose.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "benchmarks\Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.Build.0 = Release|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.Build.0 = Debug|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.ActiveCfg = Release|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.Build.0 = Release|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.Build.0 = Release|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.Build.0 = Release|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.Build.0 = Release|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.Build.0 = Release|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.Build.0 = Release|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FAC24C3F-68F9-4247-A4B9-21D487E99275} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {549C51A1-222B-4E12-96F1-3AEFF45A7709} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {052C0817-DB26-4925-8929-8C5E42D148D5} = {568BD46C-1C65-4D44-870C-12CD72563262} + {BE401F7B-8611-4A1E-AEAA-5CB700128C16} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {196928F0-7052-4585-90E8-817BD720F5E3} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F} = {9D960D4D-80A2-4DAC-B386-8F4235EC73E6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} + EndGlobalSection +EndGlobal diff --git a/Dapper.slnx b/Dapper.slnx new file mode 100644 index 000000000..674239170 --- /dev/null +++ b/Dapper.slnx @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 98d8f11eb..d9518dc45 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -26,10 +26,12 @@ + + diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index 98029741a..c2f691883 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -10,7 +10,7 @@ namespace Dapper /// public sealed class DefaultTypeMap : SqlMapper.ITypeMap { - private readonly List _fields; + private readonly FieldInfo[] _fields; private readonly Type _type; /// @@ -42,7 +42,7 @@ internal static MethodInfo GetPropertySetterOrThrow(PropertyInfo propertyInfo, T BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, propertyInfo.PropertyType, - propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(), + Array.ConvertAll(propertyInfo.GetIndexParameters(), p => p.ParameterType), null)!.GetSetMethod(true); } @@ -54,9 +54,9 @@ internal static List GetSettableProps(Type t) .ToList(); } - internal static List GetSettableFields(Type t) + private static FieldInfo[] GetSettableFields(Type t) { - return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); + return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); } /// @@ -156,20 +156,20 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, // preference order is: // exact match over underscore match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores - var field = _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); + var field = Array.Find(_fields, p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); if (field is null && MatchNamesWithUnderscores) { var effectiveColumnName = columnName.Replace("_", ""); backingFieldName = "<" + effectiveColumnName + ">k__BackingField"; - field = _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); + field = Array.Find(_fields, p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); } if (field is not null) diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index f6708b5ce..1feb4d020 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -62,10 +62,7 @@ public void AddDynamicParams(object? param) if (subDynamic.templates is not null) { templates ??= new List(); - foreach (var t in subDynamic.templates) - { - templates.Add(t); - } + templates.AddRange(subDynamic.templates); } } else @@ -212,14 +209,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } // Now that the parameters are added to the command, let's place our output callbacks - var tmp = outputCallbacks; - if (tmp is not null) - { - foreach (var generator in tmp) - { - generator(); - } - } + outputCallbacks?.ForEach(generator => generator()); } foreach (var param in parameters.Values) diff --git a/Dapper/FeatureSupport.cs b/Dapper/FeatureSupport.cs index 4d7d77e2e..f00c38ed3 100644 --- a/Dapper/FeatureSupport.cs +++ b/Dapper/FeatureSupport.cs @@ -6,7 +6,7 @@ namespace Dapper /// /// Handles variances in features per DBMS /// - internal class FeatureSupport + internal sealed class FeatureSupport { private static readonly FeatureSupport Default = new FeatureSupport(false), diff --git a/Dapper/PublicAPI.Unshipped.txt b/Dapper/PublicAPI.Unshipped.txt index 91b0e1a43..cf9343ccf 100644 --- a/Dapper/PublicAPI.Unshipped.txt +++ b/Dapper/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ -#nullable enable \ No newline at end of file +#nullable enable +static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.get -> bool +static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.set -> void \ No newline at end of file diff --git a/Dapper/SqlMapper.CacheInfo.cs b/Dapper/SqlMapper.CacheInfo.cs index d235beb6f..69edc4eea 100644 --- a/Dapper/SqlMapper.CacheInfo.cs +++ b/Dapper/SqlMapper.CacheInfo.cs @@ -7,7 +7,7 @@ namespace Dapper { public static partial class SqlMapper { - private class CacheInfo + private sealed class CacheInfo { public DeserializerState Deserializer { get; set; } public Func[]? OtherDeserializers { get; set; } diff --git a/Dapper/SqlMapper.DontMap.cs b/Dapper/SqlMapper.DontMap.cs index a97c5811e..765c7b937 100644 --- a/Dapper/SqlMapper.DontMap.cs +++ b/Dapper/SqlMapper.DontMap.cs @@ -5,6 +5,6 @@ public static partial class SqlMapper /// /// Dummy type for excluding from multi-map /// - private class DontMap { /* hiding constructor */ } + private sealed class DontMap { /* hiding constructor */ } } } diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs index 92b434881..710714412 100644 --- a/Dapper/SqlMapper.Link.cs +++ b/Dapper/SqlMapper.Link.cs @@ -12,7 +12,7 @@ public static partial class SqlMapper /// /// The type to cache. /// The value type of the cache. - internal class Link where TKey : class + internal sealed class Link where TKey : class { public static void Clear(ref Link? head) => Interlocked.Exchange(ref head, null); public static bool TryGet(Link? link, TKey key, [NotNullWhen(true)] out TValue? value) diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index cbbc3c687..c19835c11 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -65,7 +65,7 @@ static Settings() public static void SetDefaults() { CommandTimeout = null; - ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = false; + ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = PreferTypeHandlersForEnums = false; AllowedCommandBehaviors = DefaultAllowedCommandBehaviors; FetchSize = InListStringSplitCount = -1; } @@ -129,6 +129,13 @@ public static long FetchSize /// public static bool SupportLegacyParameterTokens { get; set; } = true; + /// + /// When true, Dapper checks for a registered TypeHandler for enum types before + /// falling back to the default behavior of sending enums as their underlying integer type. + /// This enables custom enum serialization (e.g. storing enums as strings), while preserving existing behavior. + /// + public static bool PreferTypeHandlersForEnums { get; set; } + private static long s_FetchSize = -1; } } diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index 2c2f646db..3a34cd292 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -8,7 +8,7 @@ namespace Dapper { public static partial class SqlMapper { - private class TypeDeserializerCache + private sealed class TypeDeserializerCache { private TypeDeserializerCache(Type type) { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index d23e949a5..2fa0e72b7 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -29,7 +29,7 @@ namespace Dapper /// public static partial class SqlMapper { - private class PropertyInfoByNameComparer : IComparer + private sealed class PropertyInfoByNameComparer : IComparer { public int Compare(PropertyInfo? x, PropertyInfo? y) => string.CompareOrdinal(x?.Name, y?.Name); } @@ -465,6 +465,10 @@ public static void SetDbType(IDataParameter parameter, object value) if (nullUnderlyingType is not null) type = nullUnderlyingType; if (type.IsEnum && !typeMap.ContainsKey(type)) { + if (Settings.PreferTypeHandlersForEnums && typeHandlers.TryGetValue(type, out handler)) + { + return DbType.Object; + } type = Enum.GetUnderlyingType(type); } if (typeMap.TryGetValue(type, out var mapEntry)) @@ -2666,7 +2670,11 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true { il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [custom] - if (!prop.PropertyType.IsValueType) + if (prop.PropertyType.IsValueType) + { + il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [boxed-custom] + } + else { // throw if null var notNull = il.DefineLabel(); @@ -2678,7 +2686,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true } il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] - il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] + il.EmitCall(OpCodes.Callvirt, typeof(ICustomQueryParameter).GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] continue; } #pragma warning disable 618 @@ -2751,7 +2759,12 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true if ((nullType ?? propType).IsEnum) { - if (nullType is not null) + if (handler is not null) + { + // TypeHandler registered - box as the enum type, handler does conversion + checkForNull = nullType is not null; + } + else if (nullType is not null) { // Nullable; we want to box as the underlying type; that's just *hard*; for // simplicity, box as Nullable and call SanitizeParameterValue @@ -3097,7 +3110,16 @@ private static Func GetSimpleValueDeserializer(Type type, #pragma warning restore 618 if (effectiveType.IsEnum) - { // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum + { + if (Settings.PreferTypeHandlersForEnums && typeHandlers.TryGetValue(type, out var enumHandler)) + { + return r => + { + var val = r.GetValue(index); + return val is DBNull ? null! : enumHandler.Parse(type, val)!; + }; + } + // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum return r => { var val = r.GetValue(index); @@ -3160,6 +3182,10 @@ private static T Parse(object? value) type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) { + if (Settings.PreferTypeHandlersForEnums && typeHandlers.TryGetValue(type, out ITypeHandler? enumHandler)) + { + return (T)enumHandler.Parse(type, value)!; + } if (value is float || value is double || value is decimal) { value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture); @@ -3530,9 +3556,9 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, il.Emit(OpCodes.Ldloc, returnValueLocal); // [target] } - var members = (specializedConstructor is not null - ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) - : names.Select(n => typeMap.GetMember(n))).ToList(); + var members = Array.ConvertAll(names, specializedConstructor is not null + ? n => typeMap.GetConstructorParameter(specializedConstructor, n) + : n => typeMap.GetMember(n)); // stack is now [target] bool first = true; @@ -3738,22 +3764,31 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind if (unboxType.IsEnum) { - Type numericType = Enum.GetUnderlyingType(unboxType); - if (colType == typeof(string)) + if (Settings.PreferTypeHandlersForEnums && typeHandlers.ContainsKey(unboxType)) { - stringEnumLocal ??= il.DeclareLocal(typeof(string)); - il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] - il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] - il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] - il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null);// stack is now [...][enum-type] - il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] - il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] - il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] - il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] +#pragma warning disable 618 + il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse))!, null); // stack is now [...][typed-value] +#pragma warning restore 618 } else { - FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); + Type numericType = Enum.GetUnderlyingType(unboxType); + if (colType == typeof(string)) + { + stringEnumLocal ??= il.DeclareLocal(typeof(string)); + il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] + il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] + il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] + il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null);// stack is now [...][enum-type] + il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] + il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] + il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] + il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] + } + else + { + FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); + } } if (nullUnderlyingType is not null) diff --git a/Directory.Build.props b/Directory.Build.props index cb8af4d2d..227e4eed5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ git https://github.com/DapperLib/Dapper false - $(NOWARN);IDE0056;IDE0057;IDE0079 + $(NOWARN);IDE0056;IDE0057;IDE0079;DX1001 true embedded en-US diff --git a/Directory.Packages.props b/Directory.Packages.props index 8787a7767..252f417b9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,50 +3,56 @@ + - + - - + + - + - - + + - - - - + + + + - - - - - + + + + + + - - + + - + - - + + - - + + - + + - + - + - \ No newline at end of file + + + + diff --git a/appveyor.yml b/appveyor.yml index ae9d477bf..5ec88faaf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,11 @@ skip_commits: files: - '**/*.md' +install: +- ps: | + Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -UseBasicParsing -OutFile "$env:temp\dotnet-install.ps1" + & $env:temp\dotnet-install.ps1 -Architecture x64 -Version '10.0.102' -InstallDir "$env:ProgramFiles\dotnet" + environment: Appveyor: true # Postgres diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index fa18bd9c5..51c28957a 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -3,7 +3,7 @@ Dapper.Tests.Performance Dapper Core Performance Suite Exe - net462;net8.0 + net472;net10.0 false $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 @@ -14,8 +14,7 @@ - - + @@ -27,22 +26,24 @@ + + - + - - + + - - - + + + - + @@ -50,20 +51,19 @@ - + $(DefineConstants);NET4X - + - - + - + diff --git a/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs index f452a95cb..f4c1c8192 100644 --- a/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs +++ b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs @@ -322,7 +322,6 @@ public virtual DbCommand CreateUpdateCommand(object o, object key) var settings = (IDictionary)expando; var sbKeys = new StringBuilder(); const string stub = "UPDATE {0} SET {1} WHERE {2} = @{3}"; - var args = new List(); var result = CreateCommand(stub, null); int counter = 0; foreach (var item in settings) diff --git a/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs index add3df772..2ffa3887c 100644 --- a/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs +++ b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs @@ -1020,10 +1020,9 @@ public string LastCommand { get { - var sb = new StringBuilder(); if (_lastSql == null) return ""; - sb.Append(_lastSql); + var sb = new StringBuilder(_lastSql); if (_lastArgs != null) { sb.Append("\r\n\r\n"); @@ -1038,14 +1037,14 @@ public string LastCommand public static IMapper Mapper { get; set; } - internal class PocoColumn + internal sealed class PocoColumn { public string ColumnName; public PropertyInfo PropertyInfo; public bool ResultColumn; } - internal class PocoData + internal sealed class PocoData { public static PocoData ForType(Type t) { @@ -1272,7 +1271,7 @@ public Func GetFactory(string key, bool ForceDateTimesToUtc, // ShareableConnection represents either a shared connection used by a transaction, // or a one-off connection if not in a transaction. // Non-shared connections are disposed - private class ShareableConnection : IDisposable + private sealed class ShareableConnection : IDisposable { public ShareableConnection(Database db) { diff --git a/global.json b/global.json index f7b5e40c2..1e3e94a0b 100644 --- a/global.json +++ b/global.json @@ -3,4 +3,4 @@ "version": "10.0.102", "rollForward": "latestMajor" } -} \ No newline at end of file +} diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index e02bb4ba3..93b498618 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -12,11 +12,15 @@ $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB - + + + + + + - @@ -24,6 +28,7 @@ + @@ -32,7 +37,7 @@ - + diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 9323be671..077e93647 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -9,7 +9,7 @@ using Microsoft.CSharp.RuntimeBinder; using Xunit; -#if NETCOREAPP3_1 || NET462 || NET472 +#if NET472 namespace System.Runtime.CompilerServices { [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs index 650624c20..5eb455c65 100644 --- a/tests/Dapper.Tests/ParameterTests.cs +++ b/tests/Dapper.Tests/ParameterTests.cs @@ -61,6 +61,21 @@ public void AddParameter(IDbCommand command, string name) } } + public readonly struct DbCustomParamStruct : SqlMapper.ICustomQueryParameter + { + private readonly IDbDataParameter _sqlParameter; + + public DbCustomParamStruct(IDbDataParameter sqlParameter) + { + _sqlParameter = sqlParameter; + } + + public void AddParameter(IDbCommand command, string name) + { + command.Parameters.Add(_sqlParameter); + } + } + private static IEnumerable CreateSqlDataRecordList(IDbCommand command, IEnumerable numbers) { #pragma warning disable CS0618 // Type or member is obsolete @@ -885,8 +900,23 @@ public void TestCustomParameterReuse() Assert.Equal(123, result2.Foo); Assert.Equal("abc", result2.Bar); } - - + + [Fact] + public void TestCustomParameterValueType() + { + // Value type (struct) ICustomQueryParameter previously caused a segfault + // because the IL emitted Callvirt on an unboxed struct (see #2189) + var args = new { + foo = new DbCustomParamStruct(Provider.CreateRawParameter("foo", 123)), + bar = "abc" + }; + var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + int foo = result.Foo; + string bar = result.Bar; + Assert.Equal(123, foo); + Assert.Equal("abc", bar); + } + [Fact] public void TestDynamicParamNullSupport() { diff --git a/tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs b/tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs new file mode 100644 index 000000000..958f0d546 --- /dev/null +++ b/tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Data; +using System.Linq; +using Xunit; + +namespace Dapper.Tests +{ + [Collection(NonParallelDefinition.Name)] + public sealed class SystemSqlClientPreferTypeHandlersForEnumsTests : PreferTypeHandlersForEnumsTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] + public sealed class MicrosoftSqlClientPreferTypeHandlersForEnumsTests : PreferTypeHandlersForEnumsTests { } +#endif + + public abstract class PreferTypeHandlersForEnumsTests : TestBase where TProvider : DatabaseProvider + { + private enum Color + { + Red = 1, + Green = 2, + Blue = 3 + } + + private class ColorResult + { + public Color Value { get; set; } + } + + private class NullableColorResult + { + public Color? Value { get; set; } + } + + /// + /// A TypeHandler that stores enum values as their name strings + /// and parses them back from strings. + /// + private class StringEnumHandler : SqlMapper.TypeHandler where TEnum : struct, Enum + { + public static readonly StringEnumHandler Instance = new(); + public int ParseCallCount; + public int SetValueCallCount; + + public override TEnum Parse(object? value) + { + ParseCallCount++; + return (TEnum)Enum.Parse(typeof(TEnum), (string)value!); + } + + public override void SetValue(IDbDataParameter parameter, TEnum value) + { + SetValueCallCount++; + parameter.DbType = DbType.AnsiString; + parameter.Value = value.ToString(); + } + } + + [Fact] + public void EnumTypeHandler_WriteAndRead_UsesHandlerWhenEnabled() + { + var handler = new StringEnumHandler(); + var oldSetting = SqlMapper.Settings.PreferTypeHandlersForEnums; + try + { + SqlMapper.ResetTypeHandlers(); + SqlMapper.AddTypeHandler(typeof(Color), handler); + SqlMapper.Settings.PreferTypeHandlersForEnums = true; + SqlMapper.PurgeQueryCache(); + + // Round-trip: write as string, read back via handler + var result = connection.Query( + "SELECT @Value AS Value", new { Value = Color.Green }).Single(); + + Assert.Equal(Color.Green, result.Value); + Assert.True(handler.SetValueCallCount > 0, "SetValue should have been called"); + Assert.True(handler.ParseCallCount > 0, "Parse should have been called"); + } + finally + { + SqlMapper.Settings.PreferTypeHandlersForEnums = oldSetting; + SqlMapper.ResetTypeHandlers(); + SqlMapper.PurgeQueryCache(); + } + } + + [Fact] + public void EnumTypeHandler_NullableWithNull_ReturnsNull() + { + var handler = new StringEnumHandler(); + var oldSetting = SqlMapper.Settings.PreferTypeHandlersForEnums; + try + { + SqlMapper.ResetTypeHandlers(); + SqlMapper.AddTypeHandler(typeof(Color), handler); + SqlMapper.Settings.PreferTypeHandlersForEnums = true; + SqlMapper.PurgeQueryCache(); + + Color? input = null; + var result = connection.Query( + "SELECT @Value AS Value", new { Value = input }).Single(); + + Assert.Null(result.Value); + } + finally + { + SqlMapper.Settings.PreferTypeHandlersForEnums = oldSetting; + SqlMapper.ResetTypeHandlers(); + SqlMapper.PurgeQueryCache(); + } + } + } +}