├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── renovate.json ├── settings.yml └── workflows │ ├── ci.yml │ └── labeled.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── MicroOrm.Dapper.Repositories.sln ├── README.md ├── dapper-plus-sponsor.png ├── entity-framework-extensions-sponsor.png ├── icon.png ├── src ├── Attributes │ ├── IdentityAttribute.cs │ ├── IgnoreUpdateAttribute.cs │ ├── Joins │ │ ├── CrossJoinAttribute.cs │ │ ├── InnerJoinAttribute.cs │ │ ├── JoinAttributeBase.cs │ │ ├── LeftJoinAttribute.cs │ │ └── RightJoinAttribute.cs │ ├── LogicalDelete │ │ ├── DeletedAttribute.cs │ │ └── StatusAttribute.cs │ └── UpdatedAtAttribute.cs ├── Config │ └── MicroOrmConfig.cs ├── DapperRepository.BulkInsert.cs ├── DapperRepository.BulkUpdate.cs ├── DapperRepository.Delete.cs ├── DapperRepository.ExecuteJoinQuery.cs ├── DapperRepository.Insert.cs ├── DapperRepository.Update.cs ├── DapperRepository.cs ├── DbContext │ ├── DapperDbContext.cs │ └── IDapperDbContext.cs ├── Extensions │ ├── CollectionExtensions.cs │ ├── OracleDynamicParametersExtensions.cs │ └── TypeExtensions.cs ├── IDapperRepository.cs ├── IReadOnlyDapperRepository.cs ├── MicroOrm.Dapper.Repositories.csproj ├── ReadOnlyDapperRepository.Count.Join.cs ├── ReadOnlyDapperRepository.Count.cs ├── ReadOnlyDapperRepository.Find.Join.cs ├── ReadOnlyDapperRepository.Find.cs ├── ReadOnlyDapperRepository.FindAll.Join.cs ├── ReadOnlyDapperRepository.FindAll.cs ├── ReadOnlyDapperRepository.FindAllBetween.cs ├── ReadOnlyDapperRepository.FindById.Join.cs ├── ReadOnlyDapperRepository.FindById.cs ├── ReadOnlyDapperRepository.SetGroupBy.cs ├── ReadOnlyDapperRepository.SetLimit.cs ├── ReadOnlyDapperRepository.SetOrderBy.cs ├── ReadOnlyDapperRepository.SetSelect.cs ├── ReadOnlyDapperRepository.cs └── SqlGenerator │ ├── ExpressionHelper.cs │ ├── Filters │ ├── FilterData.cs │ ├── GroupInfo.cs │ ├── LimitInfo.cs │ ├── OrderInfo.cs │ └── SelectInfo.cs │ ├── ISqlGenerator.cs │ ├── QueryExpressions │ ├── QueryBinaryExpression.cs │ ├── QueryExpression.cs │ ├── QueryExpressionType.cs │ └── QueryParameterExpression.cs │ ├── SqlGenerator.AppendJoin.cs │ ├── SqlGenerator.AppendWherePredicateQuery.cs │ ├── SqlGenerator.GetBulkInsert.cs │ ├── SqlGenerator.GetBulkUpdate.cs │ ├── SqlGenerator.GetCount.cs │ ├── SqlGenerator.GetDelete.cs │ ├── SqlGenerator.GetInsert.cs │ ├── SqlGenerator.GetQueryProperties.cs │ ├── SqlGenerator.GetSelect.cs │ ├── SqlGenerator.GetTableName.cs │ ├── SqlGenerator.GetUpdate.cs │ ├── SqlGenerator.InitConfig.cs │ ├── SqlGenerator.InitLogicalDeleted.cs │ ├── SqlGenerator.InitProperties.cs │ ├── SqlGenerator.cs │ ├── SqlJoinPropertyMetadata.cs │ ├── SqlPropertyMetadata.cs │ ├── SqlProvider.cs │ └── SqlQuery.cs └── tests ├── .editorconfig ├── .env ├── Repositories.Base ├── BaseDbContextTests.cs ├── BaseRepositoriesTests.cs ├── DotEnv.cs ├── InitData.cs ├── RandomGenerator.cs ├── Repositories.Base.csproj └── TestDbContext.cs ├── Repositories.MSSQL.Tests ├── DatabaseFixture.cs ├── MicrosoftDatabaseFixture.cs ├── MicrosoftDbContextTests.cs ├── MicrosoftRepositoriesTests.cs ├── Repositories.MSSQL.Tests.csproj ├── RepositoriesTests.cs ├── SystemDatabaseFixture.cs ├── SystemDbContextTests.cs └── SystemRepositoriesTests.cs ├── Repositories.MySql.Tests ├── DatabaseFixture.cs ├── MySqlClientDatabaseFixture.cs ├── MySqlClientDbContextTests.cs ├── MySqlClientRepositoriesTests.cs ├── MySqlConnectorDatabaseFixture.cs ├── MySqlConnectorDbContextTests.cs ├── MySqlConnectorRepositoriesTests.cs ├── Repositories.MySql.Tests.csproj └── RepositoriesTests.cs ├── Repositories.Oracle.Tests ├── DatabaseFixture.cs ├── DbContextTests.cs ├── Repositories.Oracle.Tests.csproj └── RepositoriesTests.cs ├── Repositories.PostgreSQL.Tests ├── DatabaseFixture.cs ├── DbContextTests.cs ├── Repositories.PostgreSQL.Tests.csproj └── RepositoriesTests.cs ├── Repositories.SQLite.Tests ├── DatabaseFixture.cs ├── MicrosoftDatabaseFixture.cs ├── MicrosoftDbContextTests.cs ├── MicrosoftRepositoriesTests.cs ├── Repositories.SQLite.Tests.csproj ├── RepositoriesTests.cs ├── SystemDatabaseFixture.cs ├── SystemDbContextTests.cs └── SystemRepositoriesTests.cs ├── SqlGenerator.Tests ├── ExpressionHelperTests.cs ├── GetKeysParamTests.cs ├── MsSqlGeneratorTests.cs ├── MySqlGeneratorTests.cs ├── OracleSqlGeneratorTests.cs ├── PostgresSqlGeneratorTests.cs ├── SQLiteGeneratorTests.cs └── SqlGenerator.Tests.csproj ├── TestClasses ├── Address.cs ├── AddressKeyAsIdentity.cs ├── BaseEntity.cs ├── Car.cs ├── City.cs ├── ComplicatedObj.cs ├── Phone.cs ├── Report.cs ├── TestClasses.csproj └── User.cs └── docker-compose.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | max_line_length = 100 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.cs] 13 | indent_size = 4 14 | max_line_length = 160 15 | 16 | ## Dotnet code style settings: 17 | 18 | # Sort using and Import directives with System.* appearing first 19 | dotnet_sort_system_directives_first = true 20 | # Avoid "this." and "Me." if not necessary 21 | dotnet_style_qualification_for_field = false:suggestion 22 | dotnet_style_qualification_for_property = false:suggestion 23 | dotnet_style_qualification_for_method = false:suggestion 24 | dotnet_style_qualification_for_event = false:suggestion 25 | 26 | # Use language keywords instead of framework type names for type references 27 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 28 | dotnet_style_predefined_type_for_member_access = true:suggestion 29 | 30 | # Suggest more modern language features when available 31 | dotnet_style_object_initializer = true:suggestion 32 | dotnet_style_collection_initializer = true:suggestion 33 | dotnet_style_coalesce_expression = true:suggestion 34 | dotnet_style_null_propagation = true:suggestion 35 | dotnet_style_explicit_tuple_names = true:suggestion 36 | 37 | # CSharp code style settings: 38 | 39 | # Prefer "var" everywhere 40 | csharp_style_var_for_built_in_types = false:none 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | csharp_style_var_elsewhere = true:suggestion 43 | 44 | # Prefer method-like constructs to have a block body 45 | csharp_style_expression_bodied_methods = false:none 46 | csharp_style_expression_bodied_constructors = false:none 47 | csharp_style_expression_bodied_operators = false:none 48 | 49 | # Prefer property-like constructs to have an expression-body 50 | csharp_style_expression_bodied_properties = true:none 51 | csharp_style_expression_bodied_indexers = true:none 52 | csharp_style_expression_bodied_accessors = true:none 53 | 54 | # Suggest more modern language features when available 55 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 56 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 57 | csharp_style_inlined_variable_declaration = true:suggestion 58 | csharp_style_throw_expression = true:suggestion 59 | csharp_style_conditional_delegate_call = true:suggestion 60 | 61 | # Newline settings 62 | csharp_new_line_before_else = true 63 | csharp_new_line_before_catch = true 64 | csharp_new_line_before_finally = true 65 | 66 | ## Naming 67 | 68 | ### private fields should be _camelCase 69 | 70 | dotnet_naming_style.underscore_prefix.capitalization = camel_case 71 | dotnet_naming_style.underscore_prefix.required_prefix = _ 72 | 73 | dotnet_naming_rule.private_fields_with_underscore.symbols = private_fields 74 | dotnet_naming_rule.private_fields_with_underscore.style = underscore_prefix 75 | dotnet_naming_rule.private_fields_with_underscore.severity = suggestion 76 | 77 | dotnet_naming_symbols.private_fields.applicable_kinds = field 78 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 79 | 80 | ## Code Quality Analysis 81 | dotnet_diagnostic.CA2007.severity = error # Use ConfigureAwait 82 | configure_await_analysis_mode = library 83 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @phnx47 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: phnx47 2 | ko_fi: phnx47 3 | buy_me_a_coffee: phnx47 4 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [":dependencyDashboard", "group:monorepos"], 4 | "labels": ["dependencies"], 5 | "assignees": ["@phnx47"], 6 | "packageRules": [ 7 | { 8 | "automerge": true, 9 | "groupName": "coverlet packages", 10 | "matchSourceUrls": ["https://github.com/coverlet-coverage/coverlet"] 11 | }, 12 | { 13 | "automerge": true, 14 | "extends": ["monorepo:vstest", "monorepo:xunit-dotnet"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | has_issues: true 3 | has_projects: false 4 | has_wiki: false 5 | has_downloads: true 6 | default_branch: main 7 | allow_squash_merge: true 8 | allow_merge_commit: true 9 | allow_rebase_merge: true 10 | allow_auto_merge: true 11 | delete_branch_on_merge: true 12 | enable_automated_security_fixes: false 13 | enable_vulnerability_alerts: true 14 | 15 | labels: 16 | - name: dependencies 17 | color: 0052CC 18 | description: 19 | 20 | - name: bug 21 | color: D73A4A 22 | description: 23 | 24 | - name: documentation 25 | color: 0075CA 26 | description: 27 | 28 | - name: duplicate 29 | color: CFD3D7 30 | description: 31 | 32 | - name: enhancement 33 | color: A2EEEF 34 | description: 35 | 36 | - name: good first issue 37 | color: 7057FF 38 | description: 39 | 40 | - name: help wanted 41 | color: 008672 42 | description: 43 | 44 | - name: invalid 45 | color: E4E669 46 | description: 47 | 48 | - name: question 49 | color: D876E3 50 | description: 51 | 52 | - name: wontfix 53 | color: FFFFFF 54 | description: 55 | 56 | - name: vulnerability 57 | color: D1260F 58 | description: 59 | 60 | - name: sync 61 | color: 6E81A3 62 | description: 63 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | tags: 8 | - "v*" 9 | pull_request: 10 | branches: 11 | - "main" 12 | workflow_dispatch: 13 | 14 | env: 15 | TEST_DB_PASS: "Password12!" 16 | 17 | jobs: 18 | build: 19 | name: Build, Test & Pack 20 | runs-on: ubuntu-24.04 21 | services: 22 | mysql: 23 | image: mysql:8.0-debian 24 | env: 25 | MYSQL_ROOT_PASSWORD: ${{ env.TEST_DB_PASS }} 26 | ports: 27 | - "3306:3306" 28 | mssql: 29 | image: mcr.microsoft.com/mssql/server:2025-latest 30 | env: 31 | ACCEPT_EULA: "Y" 32 | SA_PASSWORD: ${{ env.TEST_DB_PASS }} 33 | ports: 34 | - "1433:1433" 35 | oracle: 36 | image: gvenzl/oracle-xe:21-slim-faststart 37 | env: 38 | ORACLE_PASSWORD: ${{ env.TEST_DB_PASS }} 39 | ports: 40 | - "1521:1521" 41 | postgres: 42 | image: postgres:17.5-alpine 43 | ports: 44 | - "5432:5432" 45 | env: 46 | POSTGRES_PASSWORD: ${{ env.TEST_DB_PASS }} 47 | 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v4 51 | with: 52 | fetch-depth: 0 53 | 54 | - name: Build 55 | run: dotnet build -c Release 56 | 57 | - name: Run tests 58 | run: dotnet test --no-build -c Release 59 | 60 | - name: Publish to Codecov 61 | uses: codecov/codecov-action@v5 62 | with: 63 | fail_ci_if_error: true 64 | token: ${{ secrets.CODECOV_TOKEN }} 65 | 66 | - name: Set Dev version 67 | if: github.ref == 'refs/heads/main' 68 | run: echo "VERSION=$(git describe --long --tags | sed 's/^v//;0,/-/s//./')" >> $GITHUB_ENV 69 | 70 | - name: Set Release version 71 | if: startsWith(github.ref, 'refs/tags/v') 72 | run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV 73 | 74 | - name: Pack NuGet artifacts 75 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') 76 | run: dotnet pack --no-build -c Release -p:PackageVersion="${{ env.VERSION }}" -o packages 77 | 78 | - name: Upload artifacts 79 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: packages 83 | path: packages/*nupkg 84 | 85 | github: 86 | name: Deploy to GitHub 87 | needs: [build] 88 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') 89 | runs-on: ubuntu-24.04 90 | steps: 91 | - name: Download artifacts 92 | uses: actions/download-artifact@v4 93 | with: 94 | name: packages 95 | - name: Push to GitHub 96 | run: | 97 | dotnet nuget push "*.nupkg" \ 98 | --skip-duplicate \ 99 | -k ${{ secrets.GITHUB_TOKEN }} \ 100 | -s https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json 101 | 102 | release: 103 | name: Create GitHub release 104 | needs: [build] 105 | if: startsWith(github.ref, 'refs/tags/v') 106 | runs-on: ubuntu-24.04 107 | steps: 108 | - name: Checkout 109 | uses: actions/checkout@v4 110 | - name: Download artifacts 111 | uses: actions/download-artifact@v4 112 | with: 113 | name: packages 114 | path: packages 115 | - name: Create GitHub Release 116 | run: gh release create ${{ github.ref_name }} packages/*nupkg 117 | env: 118 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | 120 | nuget: 121 | name: Deploy to NuGet 122 | needs: [release] 123 | if: startsWith(github.ref, 'refs/tags/v') 124 | runs-on: ubuntu-24.04 125 | steps: 126 | - name: Download artifacts 127 | uses: actions/download-artifact@v4 128 | with: 129 | name: packages 130 | - name: Push to NuGet 131 | run: | 132 | dotnet nuget push "*.nupkg" \ 133 | -k ${{ secrets.NUGET_KEY }} \ 134 | -s https://api.nuget.org/v3/index.json 135 | -------------------------------------------------------------------------------- /.github/workflows/labeled.yml: -------------------------------------------------------------------------------- 1 | name: Labeled 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | branches: 7 | - "main" 8 | 9 | permissions: 10 | pull-requests: write 11 | contents: write 12 | 13 | jobs: 14 | automerge: 15 | name: Enable auto-merge 16 | runs-on: ubuntu-24.04 17 | if: github.actor == 'phnx47-bot' && contains(github.event.pull_request.labels.*.name, 'sync') 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Run command 23 | run: gh pr merge -s --auto ${{ github.event.pull_request.number }} 24 | env: 25 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.nupkg 2 | *.snupkg 3 | *.user 4 | *.opencover.xml 5 | *.orig 6 | .idea 7 | bin 8 | obj 9 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12.0 4 | false 5 | CS1591;NETSDK1138 6 | 7 | 8 | true 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Serge K 4 | Copyright (c) 2014 Diego García 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /MicroOrm.Dapper.Repositories.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E409731D-9AB4-4389-B611-53A3AF8324A4}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{94F86B38-C3FB-4FBB-802E-7786C457050D}" 9 | ProjectSection(SolutionItems) = preProject 10 | tests\.env = tests\.env 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroOrm.Dapper.Repositories", "src\MicroOrm.Dapper.Repositories.csproj", "{A8258060-EDF7-4713-9428-088ADD6FE503}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClasses", "tests\TestClasses\TestClasses.csproj", "{2691E152-7033-4C25-A8D5-4941004BD611}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlGenerator.Tests", "tests\SqlGenerator.Tests\SqlGenerator.Tests.csproj", "{0D5ADDAF-B82B-479E-9000-295162C4679B}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repositories.Base", "tests\Repositories.Base\Repositories.Base.csproj", "{C927DED2-5B55-4FE8-9F62-C2D8BE4922B6}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repositories.MSSQL.Tests", "tests\Repositories.MSSQL.Tests\Repositories.MSSQL.Tests.csproj", "{5049285B-1366-41C2-AD7C-4F70B11BFD5C}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repositories.MySql.Tests", "tests\Repositories.MySql.Tests\Repositories.MySql.Tests.csproj", "{E0118ECB-F93A-4297-9D3F-0E2AC7C709A4}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repositories.Oracle.Tests", "tests\Repositories.Oracle.Tests\Repositories.Oracle.Tests.csproj", "{9F38EB03-41AD-4CAD-9903-12E0E890FF41}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repositories.PostgreSQL.Tests", "tests\Repositories.PostgreSQL.Tests\Repositories.PostgreSQL.Tests.csproj", "{BFBD1CAD-3831-4815-A95C-6A43F8E9FAEE}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repositories.SQLite.Tests", "tests\Repositories.SQLite.Tests\Repositories.SQLite.Tests.csproj", "{5AD638E4-639A-425E-857E-60EB21CBCBC0}" 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {A8258060-EDF7-4713-9428-088ADD6FE503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {A8258060-EDF7-4713-9428-088ADD6FE503}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {A8258060-EDF7-4713-9428-088ADD6FE503}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {A8258060-EDF7-4713-9428-088ADD6FE503}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {2691E152-7033-4C25-A8D5-4941004BD611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {2691E152-7033-4C25-A8D5-4941004BD611}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {2691E152-7033-4C25-A8D5-4941004BD611}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {2691E152-7033-4C25-A8D5-4941004BD611}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {0D5ADDAF-B82B-479E-9000-295162C4679B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {0D5ADDAF-B82B-479E-9000-295162C4679B}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {0D5ADDAF-B82B-479E-9000-295162C4679B}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {0D5ADDAF-B82B-479E-9000-295162C4679B}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {C927DED2-5B55-4FE8-9F62-C2D8BE4922B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {C927DED2-5B55-4FE8-9F62-C2D8BE4922B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {C927DED2-5B55-4FE8-9F62-C2D8BE4922B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {C927DED2-5B55-4FE8-9F62-C2D8BE4922B6}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {5049285B-1366-41C2-AD7C-4F70B11BFD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {5049285B-1366-41C2-AD7C-4F70B11BFD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {5049285B-1366-41C2-AD7C-4F70B11BFD5C}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {5049285B-1366-41C2-AD7C-4F70B11BFD5C}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {E0118ECB-F93A-4297-9D3F-0E2AC7C709A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {E0118ECB-F93A-4297-9D3F-0E2AC7C709A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {E0118ECB-F93A-4297-9D3F-0E2AC7C709A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {E0118ECB-F93A-4297-9D3F-0E2AC7C709A4}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {9F38EB03-41AD-4CAD-9903-12E0E890FF41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {9F38EB03-41AD-4CAD-9903-12E0E890FF41}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {9F38EB03-41AD-4CAD-9903-12E0E890FF41}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {9F38EB03-41AD-4CAD-9903-12E0E890FF41}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {BFBD1CAD-3831-4815-A95C-6A43F8E9FAEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {BFBD1CAD-3831-4815-A95C-6A43F8E9FAEE}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {BFBD1CAD-3831-4815-A95C-6A43F8E9FAEE}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {BFBD1CAD-3831-4815-A95C-6A43F8E9FAEE}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {5AD638E4-639A-425E-857E-60EB21CBCBC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {5AD638E4-639A-425E-857E-60EB21CBCBC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {5AD638E4-639A-425E-857E-60EB21CBCBC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {5AD638E4-639A-425E-857E-60EB21CBCBC0}.Release|Any CPU.Build.0 = Release|Any CPU 73 | EndGlobalSection 74 | GlobalSection(SolutionProperties) = preSolution 75 | HideSolutionNode = FALSE 76 | EndGlobalSection 77 | GlobalSection(NestedProjects) = preSolution 78 | {A8258060-EDF7-4713-9428-088ADD6FE503} = {E409731D-9AB4-4389-B611-53A3AF8324A4} 79 | {2691E152-7033-4C25-A8D5-4941004BD611} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 80 | {0D5ADDAF-B82B-479E-9000-295162C4679B} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 81 | {C927DED2-5B55-4FE8-9F62-C2D8BE4922B6} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 82 | {5049285B-1366-41C2-AD7C-4F70B11BFD5C} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 83 | {E0118ECB-F93A-4297-9D3F-0E2AC7C709A4} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 84 | {9F38EB03-41AD-4CAD-9903-12E0E890FF41} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 85 | {BFBD1CAD-3831-4815-A95C-6A43F8E9FAEE} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 86 | {5AD638E4-639A-425E-857E-60EB21CBCBC0} = {94F86B38-C3FB-4FBB-802E-7786C457050D} 87 | EndGlobalSection 88 | GlobalSection(ExtensibilityGlobals) = postSolution 89 | SolutionGuid = {4D543221-E30F-4763-AAC8-A744A0F8D88E} 90 | EndGlobalSection 91 | EndGlobal 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroOrm.Dapper.Repositories 2 | 3 | [![ci](https://img.shields.io/github/actions/workflow/status/phnx47/dapper-repositories/ci.yml?branch=main&label=ci&logo=github%20actions&logoColor=white&style=flat-square)](https://github.com/phnx47/dapper-repositories/actions/workflows/ci.yml) 4 | [![nuget](https://img.shields.io/nuget/v/MicroOrm.Dapper.Repositories?logo=nuget&style=flat-square)](https://www.nuget.org/packages/MicroOrm.Dapper.Repositories) 5 | [![nuget](https://img.shields.io/nuget/dt/MicroOrm.Dapper.Repositories?logo=nuget&style=flat-square)](https://www.nuget.org/packages/MicroOrm.Dapper.Repositories) 6 | [![codecov](https://img.shields.io/codecov/c/github/phnx47/dapper-repositories?logo=codecov&style=flat-square&token=wR4U6i4vhk)](https://codecov.io/gh/phnx47/dapper-repositories) 7 | [![license](https://img.shields.io/github/license/phnx47/dapper-repositories?style=flat-square)](https://github.com/phnx47/dapper-repositories/blob/main/LICENSE) 8 | 9 | 10 | ## Description 11 | 12 | Lightweight library that extends [Dapper](https://github.com/DapperLib/Dapper) by generating SQL for CRUD operations based on your POCO classes. It simplifies data access by using metadata attributes to build queries automatically, including support for joins, logical deletes, and pagination. 13 | 14 | Read more at [Learn Dapper](https://www.learndapper.com/extensions/microorm-dapper-repositories). 15 | 16 | ## Sponsors 17 | 18 | [Dapper Plus](https://dapper-plus.net/) and [Entity Framework Extensions](https://entityframework-extensions.net/) are major sponsors and are proud to contribute to the development of MicroOrm.Dapper.Repositories 19 | 20 | [![Dapper Plus](https://raw.githubusercontent.com/phnx47/dapper-repositories/main/dapper-plus-sponsor.png)](https://dapper-plus.net/bulk-insert) 21 | 22 | [![Entity Framework Extensions](https://raw.githubusercontent.com/phnx47/dapper-repositories/main/entity-framework-extensions-sponsor.png)](https://entityframework-extensions.net/bulk-insert) 23 | 24 | ## License 25 | 26 | All contents of this package are licensed under the [MIT license](https://opensource.org/licenses/MIT). 27 | -------------------------------------------------------------------------------- /dapper-plus-sponsor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phnx47/dapper-repositories/58feffd27c8bb6bb7463ec90de9d41d1d2e4379a/dapper-plus-sponsor.png -------------------------------------------------------------------------------- /entity-framework-extensions-sponsor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phnx47/dapper-repositories/58feffd27c8bb6bb7463ec90de9d41d1d2e4379a/entity-framework-extensions-sponsor.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phnx47/dapper-repositories/58feffd27c8bb6bb7463ec90de9d41d1d2e4379a/icon.png -------------------------------------------------------------------------------- /src/Attributes/IdentityAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Attributes; 4 | 5 | 6 | /// 7 | /// Identity key 8 | /// 9 | public sealed class IdentityAttribute : Attribute 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Attributes/IgnoreUpdateAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Attributes; 4 | 5 | 6 | /// 7 | /// Ignore property attribute 8 | /// 9 | public sealed class IgnoreUpdateAttribute : Attribute 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Attributes/Joins/CrossJoinAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.Attributes.Joins; 2 | 3 | 4 | /// 5 | /// Generate CROSS JOIN 6 | /// 7 | public sealed class CrossJoinAttribute : JoinAttributeBase 8 | { 9 | 10 | /// 11 | /// Constructor 12 | /// 13 | public CrossJoinAttribute() 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// Constructor 20 | /// 21 | /// Name of external table 22 | public CrossJoinAttribute(string tableName) 23 | : base(tableName, string.Empty, string.Empty, string.Empty, string.Empty, "CROSS JOIN") 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Attributes/Joins/InnerJoinAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.Attributes.Joins; 2 | 3 | 4 | /// 5 | /// Generate INNER JOIN 6 | /// 7 | public sealed class InnerJoinAttribute : JoinAttributeBase 8 | { 9 | 10 | /// 11 | /// Constructor 12 | /// 13 | public InnerJoinAttribute() 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// Constructor 20 | /// 21 | /// Name of external table 22 | /// ForeignKey of this table 23 | /// Key of external table 24 | public InnerJoinAttribute(string tableName, string key, string externalKey) 25 | : base(tableName, key, externalKey, string.Empty, string.Empty, "INNER JOIN") 26 | { 27 | } 28 | 29 | 30 | /// 31 | /// Constructor 32 | /// 33 | /// Name of external table 34 | /// ForeignKey of this table 35 | /// Key of external table 36 | /// Name of external table schema 37 | public InnerJoinAttribute(string tableName, string key, string externalKey, string tableSchema) 38 | : base(tableName, key, externalKey, tableSchema, string.Empty, "INNER JOIN") 39 | { 40 | } 41 | 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// Name of external table 47 | /// ForeignKey of this table 48 | /// Key of external table 49 | /// Name of external table schema 50 | /// External table alias 51 | public InnerJoinAttribute(string tableName, string key, string externalKey, string tableSchema, string tableAbbreviation) 52 | : base(tableName, key, externalKey, tableSchema, tableAbbreviation, "INNER JOIN") 53 | { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Attributes/Joins/JoinAttributeBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MicroOrm.Dapper.Repositories.Config; 3 | 4 | namespace MicroOrm.Dapper.Repositories.Attributes.Joins; 5 | 6 | 7 | /// 8 | /// Base JOIN for LEFT/INNER/RIGHT 9 | /// 10 | public abstract class JoinAttributeBase : Attribute 11 | { 12 | 13 | /// 14 | /// Constructor 15 | /// 16 | protected JoinAttributeBase() 17 | { 18 | } 19 | 20 | 21 | /// 22 | /// Constructor 23 | /// 24 | protected JoinAttributeBase(string tableName, string key, string externalKey, string tableSchema, string tableAlias, 25 | string attrString = "JOIN") 26 | { 27 | TableName = MicroOrmConfig.TablePrefix + tableName; 28 | Key = key; 29 | ExternalKey = externalKey; 30 | TableSchema = tableSchema; 31 | TableAlias = tableAlias; 32 | JoinAttribute = attrString; 33 | } 34 | 35 | /// 36 | /// Join attribute string 37 | /// 38 | private string? JoinAttribute { get; } 39 | 40 | /// 41 | /// Name of external table 42 | /// 43 | public string? TableName { get; set; } 44 | 45 | /// 46 | /// Name of external table schema 47 | /// 48 | public string? TableSchema { get; set; } 49 | 50 | /// 51 | /// ForeignKey of this table 52 | /// 53 | public string? Key { get; set; } 54 | 55 | /// 56 | /// Key of external table 57 | /// 58 | public string? ExternalKey { get; set; } 59 | 60 | /// 61 | /// Table abbreviation override 62 | /// 63 | public string? TableAlias { get; set; } 64 | 65 | /// 66 | /// Convert to string 67 | /// 68 | /// 69 | public override string? ToString() 70 | { 71 | return JoinAttribute; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Attributes/Joins/LeftJoinAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.Attributes.Joins; 2 | 3 | 4 | /// 5 | /// Generate LEFT JOIN 6 | /// 7 | public sealed class LeftJoinAttribute : JoinAttributeBase 8 | { 9 | 10 | /// 11 | /// Constructor 12 | /// 13 | public LeftJoinAttribute() 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// Constructor 20 | /// 21 | /// Name of external table 22 | /// ForeignKey of this table 23 | /// Key of external table 24 | public LeftJoinAttribute(string tableName, string key, string externalKey) 25 | : base(tableName, key, externalKey, string.Empty, string.Empty, "LEFT JOIN") 26 | { 27 | } 28 | 29 | 30 | /// 31 | /// Constructor 32 | /// 33 | /// Name of external table 34 | /// ForeignKey of this table 35 | /// Key of external table 36 | /// Name of external table schema 37 | public LeftJoinAttribute(string tableName, string key, string externalKey, string tableSchema) 38 | : base(tableName, key, externalKey, tableSchema, string.Empty, "LEFT JOIN") 39 | { 40 | } 41 | 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// Name of external table 47 | /// ForeignKey of this table 48 | /// Key of external table 49 | /// Name of external table schema 50 | /// External table alias 51 | public LeftJoinAttribute(string tableName, string key, string externalKey, string tableSchema, string tableAbbreviation) 52 | : base(tableName, key, externalKey, tableSchema, tableAbbreviation, "LEFT JOIN") 53 | { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Attributes/Joins/RightJoinAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.Attributes.Joins; 2 | 3 | 4 | /// 5 | /// Generate RIGHT JOIN 6 | /// 7 | public sealed class RightJoinAttribute : JoinAttributeBase 8 | { 9 | 10 | /// 11 | /// Constructor 12 | /// 13 | public RightJoinAttribute() 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// Constructor 20 | /// 21 | /// Name of external table 22 | /// ForeignKey of this table 23 | /// Key of external table 24 | public RightJoinAttribute(string tableName, string key, string externalKey) 25 | : base(tableName, key, externalKey, string.Empty, string.Empty, "RIGHT JOIN") 26 | { 27 | } 28 | 29 | 30 | /// 31 | /// Constructor 32 | /// 33 | /// Name of external table 34 | /// ForeignKey of this table 35 | /// Key of external table 36 | /// Name of external table schema 37 | public RightJoinAttribute(string tableName, string key, string externalKey, string tableSchema) 38 | : base(tableName, key, externalKey, tableSchema, string.Empty, "RIGHT JOIN") 39 | { 40 | } 41 | 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// Name of external table 47 | /// ForeignKey of this table 48 | /// Key of external table 49 | /// Name of external table schema 50 | /// External table alias 51 | public RightJoinAttribute(string tableName, string key, string externalKey, string tableSchema, string tableAbbreviation) 52 | : base(tableName, key, externalKey, tableSchema, tableAbbreviation, "RIGHT JOIN") 53 | { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Attributes/LogicalDelete/DeletedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Attributes.LogicalDelete; 4 | 5 | 6 | /// 7 | /// Use with "Status" for logical delete 8 | /// 9 | public sealed class DeletedAttribute : Attribute 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Attributes/LogicalDelete/StatusAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Attributes.LogicalDelete; 4 | 5 | 6 | /// 7 | /// Use with "Deleted" for logical delete 8 | /// 9 | public sealed class StatusAttribute : Attribute 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Attributes/UpdatedAtAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Attributes; 4 | 5 | 6 | /// 7 | /// UpdatedAt. Warning!!! Changes the property during SQL generation 8 | /// 9 | public sealed class UpdatedAtAttribute : Attribute 10 | { 11 | /// 12 | /// [UpdatedAt] Attribute 13 | /// 14 | public UpdatedAtAttribute() 15 | { 16 | TimeKind = DateTimeKind.Utc; 17 | OffSet = 0; 18 | } 19 | 20 | /// 21 | /// The timezone offset 22 | /// 23 | public int OffSet { get; set; } 24 | 25 | /// 26 | /// Specified time kind, default UTC. 27 | /// 28 | public DateTimeKind TimeKind { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /src/Config/MicroOrmConfig.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.SqlGenerator; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Config; 4 | 5 | /// 6 | /// This class is used to support dependency injection 7 | /// 8 | public static class MicroOrmConfig 9 | { 10 | /// 11 | /// Type Sql provider 12 | /// 13 | public static SqlProvider SqlProvider { get; set; } 14 | 15 | /// 16 | /// Use quotation marks for TableName and ColumnName 17 | /// 18 | public static bool UseQuotationMarks { get; set; } 19 | 20 | /// 21 | /// Prefix for tables 22 | /// 23 | public static string? TablePrefix { get; set; } 24 | 25 | /// 26 | /// Allow Key attribute as Identity if Identity is not set 27 | /// 28 | public static bool AllowKeyAsIdentity { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /src/DapperRepository.BulkInsert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Dapper; 8 | using MicroOrm.Dapper.Repositories.SqlGenerator; 9 | 10 | namespace MicroOrm.Dapper.Repositories; 11 | 12 | /// 13 | /// Base Repository 14 | /// 15 | public partial class DapperRepository 16 | where TEntity : class 17 | { 18 | 19 | public virtual int BulkInsert(IEnumerable instances) 20 | { 21 | return BulkInsert(instances, null); 22 | } 23 | 24 | 25 | public virtual Task BulkInsertAsync(IEnumerable instances) 26 | { 27 | return BulkInsertAsync(instances, null, CancellationToken.None); 28 | } 29 | 30 | 31 | public virtual Task BulkInsertAsync(IEnumerable instances, CancellationToken cancellationToken) 32 | { 33 | return BulkInsertAsync(instances, null, cancellationToken); 34 | } 35 | 36 | 37 | public virtual int BulkInsert(IEnumerable instances, IDbTransaction? transaction) 38 | { 39 | int totalInstances = instances.Count(); 40 | if(totalInstances == 0) 41 | return 0; 42 | 43 | if (SqlGenerator.Provider == SqlProvider.MSSQL) 44 | { 45 | var count = 0; 46 | var properties = 47 | (SqlGenerator.IsIdentity 48 | ? SqlGenerator.SqlProperties.Where(p => !p.PropertyName.Equals(SqlGenerator.IdentitySqlProperty.PropertyName, StringComparison.OrdinalIgnoreCase)) 49 | : SqlGenerator.SqlProperties).ToList(); 50 | 51 | var exceededTimes = (int)Math.Ceiling(totalInstances * properties.Count / 2099d); 52 | if (exceededTimes > 1) 53 | { 54 | var maxAllowedInstancesPerBatch = totalInstances / exceededTimes; 55 | if (maxAllowedInstancesPerBatch > 1000) maxAllowedInstancesPerBatch = 1000; 56 | 57 | var maxIterationCount = (int)Math.Ceiling((double)totalInstances / (double)maxAllowedInstancesPerBatch); 58 | 59 | for (var i = 0; i <= maxIterationCount; i++) 60 | { 61 | var skips = i * maxAllowedInstancesPerBatch; 62 | 63 | if (skips >= totalInstances) 64 | break; 65 | 66 | var items = instances.Skip(skips).Take(maxAllowedInstancesPerBatch); 67 | var msSqlQueryResult = SqlGenerator.GetBulkInsert(items); 68 | count += Connection.Execute(msSqlQueryResult.GetSql(), msSqlQueryResult.Param, transaction); 69 | } 70 | 71 | return count; 72 | } 73 | } 74 | 75 | var queryResult = SqlGenerator.GetBulkInsert(instances); 76 | return Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction); 77 | } 78 | 79 | 80 | public virtual Task BulkInsertAsync(IEnumerable instances, IDbTransaction? transaction) 81 | { 82 | return BulkInsertAsync(instances, transaction, CancellationToken.None); 83 | } 84 | 85 | 86 | public virtual async Task BulkInsertAsync(IEnumerable instances, IDbTransaction? transaction, CancellationToken cancellationToken) 87 | { 88 | if (SqlGenerator.Provider == SqlProvider.MSSQL) 89 | { 90 | var count = 0; 91 | var totalInstances = instances.Count(); 92 | 93 | var properties = 94 | (SqlGenerator.IsIdentity 95 | ? SqlGenerator.SqlProperties.Where(p => !p.PropertyName.Equals(SqlGenerator.IdentitySqlProperty.PropertyName, StringComparison.OrdinalIgnoreCase)) 96 | : SqlGenerator.SqlProperties).ToList(); 97 | 98 | var exceededTimes = (int)Math.Ceiling(totalInstances * properties.Count / 2099d); 99 | if (exceededTimes > 1) 100 | { 101 | var maxAllowedInstancesPerBatch = totalInstances / exceededTimes; 102 | if (maxAllowedInstancesPerBatch > 1000) maxAllowedInstancesPerBatch = 1000; 103 | 104 | var maxIterationCount = (int)Math.Ceiling((double)totalInstances / (double)maxAllowedInstancesPerBatch); 105 | 106 | for (var i = 0; i <= maxIterationCount; i++) 107 | { 108 | var skips = i * maxAllowedInstancesPerBatch; 109 | 110 | if (skips >= totalInstances) 111 | break; 112 | 113 | var items = instances.Skip(i * maxAllowedInstancesPerBatch).Take(maxAllowedInstancesPerBatch); 114 | var msSqlQueryResult = SqlGenerator.GetBulkInsert(items); 115 | count += await Connection.ExecuteAsync(new CommandDefinition(msSqlQueryResult.GetSql(), msSqlQueryResult.Param, transaction, 116 | cancellationToken: cancellationToken)).ConfigureAwait(false); 117 | } 118 | 119 | return count; 120 | } 121 | } 122 | 123 | var queryResult = SqlGenerator.GetBulkInsert(instances); 124 | return await Connection.ExecuteAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)) 125 | .ConfigureAwait(false); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/DapperRepository.BulkUpdate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Dapper; 8 | using MicroOrm.Dapper.Repositories.SqlGenerator; 9 | 10 | namespace MicroOrm.Dapper.Repositories; 11 | 12 | /// 13 | /// Base Repository 14 | /// 15 | public partial class DapperRepository 16 | where TEntity : class 17 | { 18 | 19 | public virtual bool BulkUpdate(IEnumerable instances) 20 | { 21 | return BulkUpdate(instances, null); 22 | } 23 | 24 | 25 | public virtual bool BulkUpdate(IEnumerable instances, IDbTransaction? transaction) 26 | { 27 | int totalInstances = instances.Count(); 28 | if(totalInstances == 0) 29 | return true; 30 | 31 | if (SqlGenerator.Provider == SqlProvider.MSSQL) 32 | { 33 | int count = 0; 34 | var properties = SqlGenerator.SqlProperties.ToList(); 35 | 36 | int exceededTimes = (int)Math.Ceiling(totalInstances * properties.Count / 2099d); 37 | if (exceededTimes > 1) 38 | { 39 | int maxAllowedInstancesPerBatch = totalInstances / exceededTimes; 40 | if (maxAllowedInstancesPerBatch > 1000) maxAllowedInstancesPerBatch = 1000; 41 | var maxIterationCount = (int)Math.Ceiling((double)totalInstances / (double)maxAllowedInstancesPerBatch); 42 | for (int i = 0; i <= maxIterationCount; i++) 43 | { 44 | var skips = i * maxAllowedInstancesPerBatch; 45 | 46 | if (skips >= totalInstances) 47 | break; 48 | 49 | var items = instances.Skip(skips).Take(maxAllowedInstancesPerBatch); 50 | var msSqlQueryResult = SqlGenerator.GetBulkUpdate(items); 51 | count += Connection.Execute(msSqlQueryResult.GetSql(), msSqlQueryResult.Param, transaction); 52 | } 53 | 54 | return count > 0; 55 | } 56 | } 57 | 58 | var queryResult = SqlGenerator.GetBulkUpdate(instances); 59 | var result = Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction) > 0; 60 | return result; 61 | } 62 | 63 | 64 | public virtual Task BulkUpdateAsync(IEnumerable instances) 65 | { 66 | return BulkUpdateAsync(instances, null, CancellationToken.None); 67 | } 68 | 69 | 70 | public virtual Task BulkUpdateAsync(IEnumerable instances, CancellationToken cancellationToken) 71 | { 72 | return BulkUpdateAsync(instances, null, cancellationToken); 73 | } 74 | 75 | 76 | public virtual Task BulkUpdateAsync(IEnumerable instances, IDbTransaction? transaction) 77 | { 78 | return BulkUpdateAsync(instances, transaction, CancellationToken.None); 79 | } 80 | 81 | 82 | public virtual async Task BulkUpdateAsync(IEnumerable instances, IDbTransaction? transaction, CancellationToken cancellationToken) 83 | { 84 | if (SqlGenerator.Provider == SqlProvider.MSSQL) 85 | { 86 | int count = 0; 87 | int totalInstances = instances.Count(); 88 | 89 | var properties = SqlGenerator.SqlProperties.ToList(); 90 | 91 | int exceededTimes = (int)Math.Ceiling(totalInstances * properties.Count / 2099d); 92 | if (exceededTimes > 1) 93 | { 94 | int maxAllowedInstancesPerBatch = totalInstances / exceededTimes; 95 | if (maxAllowedInstancesPerBatch > 1000) maxAllowedInstancesPerBatch = 1000; 96 | 97 | var maxIterationCount = (int)Math.Ceiling((double)totalInstances / (double)maxAllowedInstancesPerBatch); 98 | 99 | for (var i = 0; i <= maxIterationCount; i++) 100 | { 101 | var skips = i * maxAllowedInstancesPerBatch; 102 | 103 | if (skips >= totalInstances) 104 | break; 105 | 106 | var items = instances.Skip(skips).Take(maxAllowedInstancesPerBatch); 107 | var msSqlQueryResult = SqlGenerator.GetBulkUpdate(items); 108 | count += await Connection.ExecuteAsync(new CommandDefinition(msSqlQueryResult.GetSql(), msSqlQueryResult.Param, transaction, 109 | cancellationToken: cancellationToken)).ConfigureAwait(false); 110 | } 111 | 112 | return count > 0; 113 | } 114 | } 115 | 116 | var queryResult = SqlGenerator.GetBulkUpdate(instances); 117 | var result = await Connection.ExecuteAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)).ConfigureAwait(false) > 0; 118 | return result; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/DapperRepository.Delete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Dapper; 7 | 8 | namespace MicroOrm.Dapper.Repositories; 9 | 10 | /// 11 | /// Base Repository 12 | /// 13 | public partial class DapperRepository 14 | where TEntity : class 15 | { 16 | 17 | public virtual bool Delete(TEntity instance) 18 | { 19 | return Delete(instance, null, null); 20 | } 21 | 22 | 23 | public virtual bool Delete(TEntity instance, TimeSpan? timeout) 24 | { 25 | return Delete(instance, null, timeout); 26 | } 27 | 28 | 29 | public virtual bool Delete(TEntity instance, IDbTransaction? transaction, TimeSpan? timeout) 30 | { 31 | var queryResult = SqlGenerator.GetDelete(instance); 32 | int? commandTimeout = null; 33 | if (timeout.HasValue) 34 | commandTimeout = timeout.Value.Seconds; 35 | var deleted = Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction, commandTimeout) > 0; 36 | return deleted; 37 | } 38 | 39 | 40 | public virtual bool Delete(Expression>? predicate) 41 | { 42 | return Delete(predicate, null, null); 43 | } 44 | 45 | 46 | public virtual bool Delete(Expression>? predicate, TimeSpan? timeout) 47 | { 48 | return Delete(predicate, null, timeout); 49 | } 50 | 51 | 52 | public virtual bool Delete(Expression>? predicate, IDbTransaction? transaction, TimeSpan? timeout) 53 | { 54 | var queryResult = SqlGenerator.GetDelete(predicate); 55 | int? commandTimeout = null; 56 | if (timeout.HasValue) 57 | commandTimeout = timeout.Value.Seconds; 58 | var deleted = Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction, commandTimeout) > 0; 59 | return deleted; 60 | } 61 | 62 | 63 | public virtual Task DeleteAsync(TEntity instance, CancellationToken cancellationToken = default) 64 | { 65 | return DeleteAsync(instance, null, null, cancellationToken); 66 | } 67 | 68 | 69 | public virtual Task DeleteAsync(TEntity instance, TimeSpan? timeout, CancellationToken cancellationToken = default) 70 | { 71 | return DeleteAsync(instance, null, timeout, cancellationToken); 72 | } 73 | 74 | 75 | 76 | public virtual async Task DeleteAsync(TEntity instance, IDbTransaction? transaction, TimeSpan? timeout, CancellationToken cancellationToken = default) 77 | { 78 | var queryResult = SqlGenerator.GetDelete(instance); 79 | int? commandTimeout = null; 80 | if (timeout.HasValue) 81 | commandTimeout = timeout.Value.Seconds; 82 | var deleted = await Connection.ExecuteAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, commandTimeout, 83 | cancellationToken: cancellationToken)).ConfigureAwait(false) > 0; 84 | return deleted; 85 | } 86 | 87 | 88 | public virtual Task DeleteAsync(Expression>? predicate, CancellationToken cancellationToken = default) 89 | { 90 | return DeleteAsync(predicate, null, null, cancellationToken); 91 | } 92 | 93 | 94 | public virtual Task DeleteAsync(Expression>? predicate, TimeSpan? timeout, CancellationToken cancellationToken = default) 95 | { 96 | return DeleteAsync(predicate, null, timeout, cancellationToken); 97 | } 98 | 99 | 100 | 101 | public virtual async Task DeleteAsync(Expression>? predicate, IDbTransaction? transaction, TimeSpan? timeout, 102 | CancellationToken cancellationToken = default) 103 | { 104 | var queryResult = SqlGenerator.GetDelete(predicate); 105 | int? commandTimeout = null; 106 | if (timeout.HasValue) 107 | commandTimeout = timeout.Value.Seconds; 108 | var deleted = await Connection.ExecuteAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, commandTimeout, 109 | cancellationToken: cancellationToken)).ConfigureAwait(false) > 0; 110 | return deleted; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/DapperRepository.Insert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Dapper; 7 | 8 | namespace MicroOrm.Dapper.Repositories; 9 | 10 | /// 11 | /// Base Repository 12 | /// 13 | public partial class DapperRepository 14 | where TEntity : class 15 | { 16 | 17 | public virtual bool Insert(TEntity instance) 18 | { 19 | return Insert(instance, null); 20 | } 21 | 22 | 23 | public virtual Task InsertAsync(TEntity instance) 24 | { 25 | return InsertAsync(instance, null, CancellationToken.None); 26 | } 27 | 28 | 29 | public virtual Task InsertAsync(TEntity instance, CancellationToken cancellationToken) 30 | { 31 | return InsertAsync(instance, null, cancellationToken); 32 | } 33 | 34 | 35 | public virtual bool Insert(TEntity instance, IDbTransaction? transaction) 36 | { 37 | var queryResult = SqlGenerator.GetInsert(instance); 38 | if (SqlGenerator.IsIdentity) 39 | { 40 | if (SqlGenerator.Provider == Repositories.SqlGenerator.SqlProvider.Oracle) 41 | { 42 | Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction); 43 | int newId = ((DynamicParameters)(queryResult.Param!)).Get(":newId"); 44 | return SetValue(newId, instance); 45 | } 46 | else 47 | { 48 | var newId = Connection.Query(queryResult.GetSql(), queryResult.Param, transaction).FirstOrDefault(); 49 | return SetValue(newId, instance); 50 | } 51 | } 52 | 53 | return Connection.Execute(queryResult.GetSql(), instance, transaction) > 0; 54 | } 55 | 56 | 57 | public virtual Task InsertAsync(TEntity instance, IDbTransaction? transaction) 58 | { 59 | return InsertAsync(instance, transaction, CancellationToken.None); 60 | } 61 | 62 | 63 | public virtual async Task InsertAsync(TEntity instance, IDbTransaction? transaction, CancellationToken cancellationToken) 64 | { 65 | var queryResult = SqlGenerator.GetInsert(instance); 66 | if (SqlGenerator.IsIdentity) 67 | { 68 | if (SqlGenerator.Provider == Repositories.SqlGenerator.SqlProvider.Oracle) 69 | { 70 | await Connection.ExecuteAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)).ConfigureAwait(false); 71 | int newId = ((DynamicParameters)(queryResult.Param!)).Get(":newId"); 72 | return SetValue(newId, instance); 73 | } 74 | else 75 | { 76 | var newId = (await Connection.QueryAsync(queryResult.GetSql(), queryResult.Param, transaction).ConfigureAwait(false)).FirstOrDefault(); 77 | return SetValue(newId, instance); 78 | } 79 | } 80 | 81 | return await Connection.ExecuteAsync(new CommandDefinition(queryResult.GetSql(), instance, transaction, cancellationToken: cancellationToken)).ConfigureAwait(false) > 0; 82 | } 83 | 84 | private bool SetValue(long newId, TEntity instance) 85 | { 86 | var added = newId > 0; 87 | if (added && SqlGenerator.IsIdentity) 88 | { 89 | var newParsedId = Convert.ChangeType(newId, SqlGenerator.IdentitySqlProperty.PropertyInfo.PropertyType); 90 | SqlGenerator.IdentitySqlProperty.PropertyInfo.SetValue(instance, newParsedId); 91 | } 92 | 93 | return added; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/DapperRepository.Update.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Dapper; 7 | 8 | namespace MicroOrm.Dapper.Repositories; 9 | 10 | /// 11 | /// Base Repository 12 | /// 13 | public partial class DapperRepository 14 | where TEntity : class 15 | { 16 | 17 | public virtual bool Update(TEntity instance, params Expression>[] includes) 18 | { 19 | return Update(instance, null, includes); 20 | } 21 | 22 | 23 | public virtual bool Update(TEntity instance, IDbTransaction? transaction, params Expression>[] includes) 24 | { 25 | var sqlQuery = SqlGenerator.GetUpdate(instance, includes); 26 | var updated = Connection.Execute(sqlQuery.GetSql(), sqlQuery.Param, transaction) > 0; 27 | return updated; 28 | } 29 | 30 | 31 | public virtual Task UpdateAsync(TEntity instance, params Expression>[] includes) 32 | { 33 | return UpdateAsync(instance, null, CancellationToken.None, includes); 34 | } 35 | 36 | 37 | public virtual Task UpdateAsync(TEntity instance, CancellationToken cancellationToken, params Expression>[] includes) 38 | { 39 | return UpdateAsync(instance, null, cancellationToken, includes); 40 | } 41 | 42 | 43 | public virtual Task UpdateAsync(TEntity instance, IDbTransaction? transaction, params Expression>[] includes) 44 | { 45 | return UpdateAsync(instance, transaction, CancellationToken.None, includes); 46 | } 47 | 48 | 49 | public virtual async Task UpdateAsync(TEntity instance, IDbTransaction? transaction, CancellationToken cancellationToken, 50 | params Expression>[] includes) 51 | { 52 | var sqlQuery = SqlGenerator.GetUpdate(instance, includes); 53 | var updated = await Connection.ExecuteAsync(new CommandDefinition(sqlQuery.GetSql(), sqlQuery.Param, transaction, cancellationToken: cancellationToken)).ConfigureAwait(false) > 0; 54 | return updated; 55 | } 56 | 57 | 58 | public virtual bool Update(Expression>? predicate, TEntity instance, params Expression>[] includes) 59 | { 60 | return Update(predicate, instance, null, includes); 61 | } 62 | 63 | 64 | public virtual Task UpdateAsync(Expression>? predicate, TEntity instance, params Expression>[] includes) 65 | { 66 | return UpdateAsync(predicate, instance, CancellationToken.None, includes); 67 | } 68 | 69 | 70 | public virtual bool Update(Expression>? predicate, TEntity instance, IDbTransaction? transaction, params Expression>[] includes) 71 | { 72 | var sqlQuery = SqlGenerator.GetUpdate(predicate, instance, includes); 73 | var updated = Connection.Execute(sqlQuery.GetSql(), sqlQuery.Param, transaction) > 0; 74 | return updated; 75 | } 76 | 77 | 78 | public virtual Task UpdateAsync(Expression>? predicate, TEntity instance, CancellationToken cancellationToken, params Expression>[] includes) 79 | { 80 | return UpdateAsync(predicate, instance, null, cancellationToken, includes); 81 | } 82 | 83 | 84 | public virtual Task UpdateAsync(Expression>? predicate, TEntity instance, IDbTransaction? transaction, params Expression>[] includes) 85 | { 86 | return UpdateAsync(predicate, instance, transaction, CancellationToken.None, includes); 87 | } 88 | 89 | 90 | public virtual async Task UpdateAsync(Expression>? predicate, TEntity instance, IDbTransaction? transaction, 91 | CancellationToken cancellationToken, 92 | params Expression>[] includes) 93 | { 94 | var sqlQuery = SqlGenerator.GetUpdate(predicate, instance, includes); 95 | var updated = await Connection.ExecuteAsync(new CommandDefinition(sqlQuery.GetSql(), sqlQuery.Param, transaction, cancellationToken: cancellationToken)).ConfigureAwait(false) > 0; 96 | return updated; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/DapperRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using MicroOrm.Dapper.Repositories.SqlGenerator; 3 | 4 | namespace MicroOrm.Dapper.Repositories; 5 | 6 | /// 7 | /// Base Repository 8 | /// 9 | public partial class DapperRepository : ReadOnlyDapperRepository, IDapperRepository 10 | where TEntity : class 11 | { 12 | /// 13 | /// Constructor 14 | /// 15 | public DapperRepository(IDbConnection connection) 16 | : base(connection) 17 | { 18 | } 19 | 20 | /// 21 | /// Constructor 22 | /// 23 | public DapperRepository(IDbConnection connection, ISqlGenerator sqlGenerator) 24 | : base(connection, sqlGenerator) 25 | { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/DbContext/DapperDbContext.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.Common; 3 | using System.Threading.Tasks; 4 | 5 | namespace MicroOrm.Dapper.Repositories.DbContext; 6 | 7 | public class DapperDbContext : IDapperDbContext 8 | { 9 | /// 10 | /// Internal DB connection instance 11 | /// 12 | protected readonly IDbConnection InnerConnection; 13 | 14 | /// 15 | /// Create new DB context with connection 16 | /// 17 | protected DapperDbContext(IDbConnection connection) 18 | { 19 | InnerConnection = connection; 20 | } 21 | 22 | public virtual IDbConnection Connection 23 | { 24 | get 25 | { 26 | OpenConnection(); 27 | return InnerConnection; 28 | } 29 | } 30 | 31 | public void OpenConnection() 32 | { 33 | if (InnerConnection.State != ConnectionState.Open && InnerConnection.State != ConnectionState.Connecting) 34 | InnerConnection.Open(); 35 | } 36 | 37 | public async Task OpenConnectionAsync() 38 | { 39 | if (InnerConnection.State != ConnectionState.Open && InnerConnection.State != ConnectionState.Connecting) 40 | { 41 | if (InnerConnection is DbConnection dbConnection) 42 | await dbConnection.OpenAsync().ConfigureAwait(false); 43 | else 44 | InnerConnection.Open(); 45 | } 46 | } 47 | 48 | public virtual IDbTransaction BeginTransaction() 49 | { 50 | return Connection.BeginTransaction(); 51 | } 52 | 53 | public virtual async Task BeginTransactionAsync() 54 | { 55 | if (InnerConnection is DbConnection dbConnection) 56 | { 57 | await OpenConnectionAsync().ConfigureAwait(false); 58 | #if NETCOREAPP3_0_OR_GREATER 59 | return await dbConnection.BeginTransactionAsync().ConfigureAwait(false); 60 | #else 61 | return dbConnection.BeginTransaction(); 62 | #endif 63 | } 64 | 65 | return BeginTransaction(); 66 | } 67 | 68 | /// 69 | /// Close DB connection 70 | /// 71 | public void Dispose() 72 | { 73 | if (InnerConnection.State != ConnectionState.Closed) 74 | InnerConnection.Close(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/DbContext/IDapperDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Threading.Tasks; 4 | 5 | namespace MicroOrm.Dapper.Repositories.DbContext; 6 | 7 | /// 8 | /// Wrapper for database connection management 9 | /// 10 | public interface IDapperDbContext : IDisposable 11 | { 12 | /// 13 | /// Get DB Connection, open connection if necessary 14 | /// 15 | IDbConnection Connection { get; } 16 | 17 | /// 18 | /// Open DB connection if not already open 19 | /// 20 | void OpenConnection(); 21 | 22 | /// 23 | /// Open DB connection asynchronously if not already open 24 | /// 25 | Task OpenConnectionAsync(); 26 | 27 | /// 28 | /// Begin transaction, open connection if necessary 29 | /// 30 | IDbTransaction BeginTransaction(); 31 | 32 | /// 33 | /// Begin transaction asynchronously, open connection if necessary 34 | /// 35 | Task BeginTransactionAsync(); 36 | } 37 | -------------------------------------------------------------------------------- /src/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MicroOrm.Dapper.Repositories.Extensions; 4 | 5 | internal static class CollectionExtensions 6 | { 7 | /// 8 | /// AddRange ICollection 9 | /// 10 | /// 11 | /// 12 | public static void AddRange(this ICollection collection, IEnumerable addCollection) 13 | { 14 | if (collection == null || addCollection == null) 15 | return; 16 | 17 | foreach (var item in addCollection) 18 | { 19 | collection.Add(item); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Extensions/OracleDynamicParametersExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Dapper; 3 | 4 | namespace MicroOrm.Dapper.Repositories.Extensions; 5 | 6 | /// 7 | /// Currently it only add output parameter with type DbType.Int32. 8 | /// It should actually check the prima key type then mapping it to different DbType such as DbType.Int64, etc. 9 | /// If GUID support is added, it should also be added here. 10 | /// 11 | internal static class OracleDynamicParametersExtensions 12 | { 13 | public static void AddOracleOutputParameterForId(this DynamicParameters param) 14 | { 15 | param.Add(name: "newId", dbType: DbType.Int32, direction: ParameterDirection.Output); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Reflection; 7 | using MicroOrm.Dapper.Repositories.Attributes; 8 | using MicroOrm.Dapper.Repositories.SqlGenerator; 9 | 10 | namespace MicroOrm.Dapper.Repositories.Extensions; 11 | 12 | internal static class TypeExtensions 13 | { 14 | private static readonly ConcurrentDictionary _reflectionPropertyCache = new ConcurrentDictionary(); 15 | private static readonly ConcurrentDictionary _reflectionPrimitivePropertyCache = new ConcurrentDictionary(); 16 | private static readonly ConcurrentDictionary _metaDataPropertyCache = new ConcurrentDictionary(); 17 | 18 | public static PropertyInfo[] FindClassProperties(this Type objectType) 19 | { 20 | if (_reflectionPropertyCache.TryGetValue(objectType, out var cachedEntry)) 21 | return cachedEntry; 22 | 23 | var propertyInfos = objectType.GetProperties() 24 | .OrderByDescending(x => x.GetCustomAttribute() != null) 25 | .ThenByDescending(x => x.GetCustomAttribute() != null) 26 | .ThenBy(p => p.GetCustomAttributes() 27 | .Select(a => a.Order) 28 | .DefaultIfEmpty(int.MaxValue) 29 | .FirstOrDefault()).ToArray(); 30 | 31 | _reflectionPropertyCache.TryAdd(objectType, propertyInfos); 32 | 33 | return propertyInfos; 34 | } 35 | 36 | public static PropertyInfo[] FindClassPrimitiveProperties(this Type objectType) 37 | { 38 | if (_reflectionPrimitivePropertyCache.TryGetValue(objectType, out var cachedEntry)) 39 | return cachedEntry; 40 | 41 | var props = objectType.GetProperties(); 42 | var propertyInfos = props 43 | .OrderByDescending(x => x.GetCustomAttribute() != null) 44 | .ThenByDescending(x => x.GetCustomAttribute() != null) 45 | .ThenBy(p => p.GetCustomAttributes() 46 | .Select(a => a.Order) 47 | .DefaultIfEmpty(int.MaxValue) 48 | .FirstOrDefault()).Where(ExpressionHelper.GetPrimitivePropertiesPredicate()).ToArray(); 49 | 50 | _reflectionPrimitivePropertyCache.TryAdd(objectType, propertyInfos); 51 | 52 | return propertyInfos; 53 | } 54 | 55 | public static SqlPropertyMetadata[] FindClassMetaDataProperties(this Type objectType) 56 | { 57 | if (_metaDataPropertyCache.TryGetValue(objectType, out var cachedEntry)) 58 | return cachedEntry; 59 | 60 | var props = objectType.GetProperties(); 61 | var propertyInfos = props 62 | .OrderByDescending(x => x.GetCustomAttribute() != null) 63 | .ThenByDescending(x => x.GetCustomAttribute() != null) 64 | .ThenBy(p => p.GetCustomAttributes() 65 | .Select(a => a.Order) 66 | .DefaultIfEmpty(int.MaxValue) 67 | .FirstOrDefault()).Where(ExpressionHelper.GetPrimitivePropertiesPredicate()) 68 | .Where(p => !p.GetCustomAttributes().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); 69 | 70 | _metaDataPropertyCache.TryAdd(objectType, propertyInfos); 71 | 72 | return propertyInfos; 73 | } 74 | 75 | public static Type UnwrapNullableType(this Type type) => Nullable.GetUnderlyingType(type) ?? type; 76 | } 77 | -------------------------------------------------------------------------------- /src/MicroOrm.Dapper.Repositories.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | CRUD for Dapper 4 | 2015 © Serge K 5 | Serge K and Contributors 6 | net462;netstandard2.0;net8.0 7 | enable 8 | true 9 | true 10 | MicroOrm.Dapper.Repositories 11 | dapper;micro-orm;repositories;crud 12 | icon.png 13 | README.md 14 | MIT 15 | git 16 | https://github.com/phnx47/dapper-repositories 17 | true 18 | true 19 | snupkg 20 | true 21 | nupkgs 22 | true 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.Count.Join.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq.Expressions; 4 | using System.Threading.Tasks; 5 | using Dapper; 6 | 7 | namespace MicroOrm.Dapper.Repositories; 8 | 9 | 10 | /// 11 | /// Base Repository 12 | /// 13 | public partial class ReadOnlyDapperRepository 14 | where TEntity : class 15 | { 16 | 17 | public virtual int Count(params Expression>[] includes) 18 | { 19 | return Count(null, transaction: null); 20 | } 21 | 22 | 23 | public virtual int Count(IDbTransaction? transaction, params Expression>[] includes) 24 | { 25 | return Count(null, transaction, includes: includes); 26 | } 27 | 28 | 29 | public virtual int Count(Expression>? predicate, params Expression>[] includes) 30 | { 31 | return Count(predicate, transaction: null, includes: includes); 32 | } 33 | 34 | 35 | public virtual int Count(Expression>? predicate, IDbTransaction? transaction, params Expression>[] includes) 36 | { 37 | var queryResult = SqlGenerator.GetCount(predicate, includes); 38 | return Connection.QueryFirstOrDefault(queryResult.GetSql(), queryResult.Param, transaction); 39 | } 40 | 41 | 42 | public virtual int Count(Expression> distinctField, params Expression>[] includes) 43 | { 44 | return Count(distinctField, null, includes: includes); 45 | } 46 | 47 | 48 | public virtual int Count(Expression> distinctField, IDbTransaction? transaction, params Expression>[] includes) 49 | { 50 | return Count(null, distinctField, transaction); 51 | } 52 | 53 | 54 | public virtual int Count(Expression>? predicate, Expression> distinctField, params Expression>[] includes) 55 | { 56 | return Count(predicate, distinctField, null, includes); 57 | } 58 | 59 | 60 | public virtual int Count(Expression>? predicate, Expression> distinctField, IDbTransaction? transaction, 61 | params Expression>[] includes) 62 | { 63 | var queryResult = SqlGenerator.GetCount(predicate, distinctField, includes); 64 | return Connection.QueryFirstOrDefault(queryResult.GetSql(), queryResult.Param, transaction); 65 | } 66 | 67 | 68 | public virtual Task CountAsync(params Expression>[] includes) 69 | { 70 | return CountAsync(transaction: null, includes: includes); 71 | } 72 | 73 | 74 | public virtual Task CountAsync(IDbTransaction? transaction, params Expression>[] includes) 75 | { 76 | return CountAsync(null, transaction, includes: includes); 77 | } 78 | 79 | 80 | public virtual Task CountAsync(Expression>? predicate, params Expression>[] includes) 81 | { 82 | return CountAsync(predicate, transaction: null, includes: includes); 83 | } 84 | 85 | 86 | public virtual Task CountAsync(Expression>? predicate, IDbTransaction? transaction, params Expression>[] includes) 87 | { 88 | var queryResult = SqlGenerator.GetCount(predicate, includes); 89 | return Connection.QueryFirstOrDefaultAsync(queryResult.GetSql(), queryResult.Param, transaction); 90 | } 91 | 92 | 93 | public virtual Task CountAsync(Expression> distinctField, params Expression>[] includes) 94 | { 95 | return CountAsync(distinctField, null, includes: includes); 96 | } 97 | 98 | 99 | public virtual Task CountAsync(Expression> distinctField, IDbTransaction? transaction, params Expression>[] includes) 100 | { 101 | return CountAsync(null, distinctField, transaction, includes: includes); 102 | } 103 | 104 | 105 | public virtual Task CountAsync(Expression>? predicate, Expression> distinctField, 106 | params Expression>[] includes) 107 | { 108 | return CountAsync(predicate, distinctField, null, includes: includes); 109 | } 110 | 111 | 112 | public virtual Task CountAsync(Expression>? predicate, Expression> distinctField, IDbTransaction? transaction, 113 | params Expression>[] includes) 114 | { 115 | var queryResult = SqlGenerator.GetCount(predicate, distinctField, includes); 116 | return Connection.QueryFirstOrDefaultAsync(queryResult.GetSql(), queryResult.Param, transaction); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.Count.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Dapper; 7 | 8 | namespace MicroOrm.Dapper.Repositories; 9 | 10 | 11 | /// 12 | /// Base Repository 13 | /// 14 | public partial class ReadOnlyDapperRepository 15 | where TEntity : class 16 | { 17 | 18 | public virtual int Count() 19 | { 20 | return Count(null, transaction: null); 21 | } 22 | 23 | 24 | public virtual Task CountAsync() 25 | { 26 | return CountAsync(transaction: null, cancellationToken: default); 27 | } 28 | 29 | 30 | public virtual Task CountAsync(CancellationToken cancellationToken) 31 | { 32 | return CountAsync(transaction: null, cancellationToken: cancellationToken); 33 | } 34 | 35 | 36 | public virtual int Count(IDbTransaction transaction) 37 | { 38 | return Count(null, transaction); 39 | } 40 | 41 | 42 | public virtual Task CountAsync(IDbTransaction transaction) 43 | { 44 | return CountAsync(null, transaction, cancellationToken: default); 45 | } 46 | 47 | 48 | public virtual Task CountAsync(IDbTransaction? transaction, CancellationToken cancellationToken) 49 | { 50 | return CountAsync(null, transaction, cancellationToken: cancellationToken); 51 | } 52 | 53 | 54 | public virtual int Count(Expression> predicate) 55 | { 56 | return Count(predicate, transaction: null); 57 | } 58 | 59 | 60 | public virtual Task CountAsync(Expression> predicate) 61 | { 62 | return CountAsync(predicate, transaction: null, cancellationToken: default); 63 | } 64 | 65 | 66 | public virtual int Count(Expression>? predicate, IDbTransaction? transaction) 67 | { 68 | var queryResult = SqlGenerator.GetCount(predicate); 69 | return Connection.QueryFirstOrDefault(queryResult.GetSql(), queryResult.Param, transaction); 70 | } 71 | 72 | 73 | public virtual Task CountAsync(Expression>? predicate, CancellationToken cancellationToken) 74 | { 75 | return CountAsync(predicate, transaction: null, cancellationToken: cancellationToken); 76 | } 77 | 78 | 79 | public virtual Task CountAsync(Expression>? predicate, IDbTransaction? transaction) 80 | { 81 | return CountAsync(predicate, transaction, cancellationToken: default); 82 | } 83 | 84 | 85 | public virtual Task CountAsync(Expression>? predicate, IDbTransaction? transaction, CancellationToken cancellationToken) 86 | { 87 | var queryResult = SqlGenerator.GetCount(predicate); 88 | return Connection.QueryFirstOrDefaultAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)); 89 | } 90 | 91 | 92 | public virtual int Count(Expression> distinctField) 93 | { 94 | return Count(distinctField, transaction: null); 95 | } 96 | 97 | 98 | public virtual Task CountAsync(Expression> distinctField) 99 | { 100 | return CountAsync(distinctField, transaction: null, cancellationToken: default); 101 | } 102 | 103 | 104 | public virtual Task CountAsync(Expression> distinctField, CancellationToken cancellationToken) 105 | { 106 | return CountAsync(distinctField, transaction: null, cancellationToken: cancellationToken); 107 | } 108 | 109 | 110 | public virtual int Count(Expression> distinctField, IDbTransaction? transaction) 111 | { 112 | return Count(null, distinctField, transaction); 113 | } 114 | 115 | 116 | public virtual Task CountAsync(Expression> distinctField, IDbTransaction? transaction) 117 | { 118 | return CountAsync(null, distinctField, transaction, cancellationToken: default); 119 | } 120 | 121 | 122 | public virtual Task CountAsync(Expression> distinctField, IDbTransaction? transaction, CancellationToken cancellationToken) 123 | { 124 | return CountAsync(null, distinctField, transaction, cancellationToken: cancellationToken); 125 | } 126 | 127 | 128 | public virtual int Count(Expression>? predicate, Expression> distinctField) 129 | { 130 | return Count(predicate, distinctField, transaction: null); 131 | } 132 | 133 | 134 | public virtual Task CountAsync(Expression>? predicate, Expression> distinctField) 135 | { 136 | return CountAsync(predicate, distinctField, transaction: null, cancellationToken: default); 137 | } 138 | 139 | 140 | public virtual Task CountAsync(Expression>? predicate, Expression> distinctField, CancellationToken cancellationToken) 141 | { 142 | return CountAsync(predicate, distinctField, transaction: null, cancellationToken: cancellationToken); 143 | } 144 | 145 | 146 | public virtual int Count(Expression>? predicate, Expression> distinctField, IDbTransaction? transaction) 147 | { 148 | var queryResult = SqlGenerator.GetCount(predicate, distinctField); 149 | return Connection.QueryFirstOrDefault(queryResult.GetSql(), queryResult.Param, transaction); 150 | } 151 | 152 | 153 | public virtual Task CountAsync(Expression>? predicate, Expression> distinctField, IDbTransaction? transaction) 154 | { 155 | return CountAsync(predicate, distinctField, transaction, cancellationToken: default); 156 | } 157 | 158 | 159 | public virtual Task CountAsync(Expression>? predicate, Expression> distinctField, IDbTransaction? transaction, CancellationToken cancellationToken) 160 | { 161 | var queryResult = SqlGenerator.GetCount(predicate, distinctField); 162 | return Connection.QueryFirstOrDefaultAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.Find.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Dapper; 7 | 8 | 9 | namespace MicroOrm.Dapper.Repositories; 10 | 11 | /// 12 | /// Base Repository 13 | /// 14 | public partial class ReadOnlyDapperRepository 15 | where TEntity : class 16 | { 17 | 18 | public virtual TEntity? Find() 19 | { 20 | return Find(null, null); 21 | } 22 | 23 | 24 | public virtual Task FindAsync() 25 | { 26 | return FindAsync(null, null, cancellationToken: default); 27 | } 28 | 29 | 30 | public virtual Task FindAsync(CancellationToken cancellationToken) 31 | { 32 | return FindAsync(null, null, cancellationToken: cancellationToken); 33 | } 34 | 35 | 36 | public virtual TEntity? Find(IDbTransaction transaction) 37 | { 38 | return Find(null, transaction); 39 | } 40 | 41 | 42 | public virtual Task FindAsync(IDbTransaction transaction) 43 | { 44 | return FindAsync(null, transaction, cancellationToken: default); 45 | } 46 | 47 | 48 | public virtual Task FindAsync(IDbTransaction? transaction, CancellationToken cancellationToken) 49 | { 50 | return FindAsync(null, transaction, cancellationToken: cancellationToken); 51 | } 52 | 53 | 54 | public virtual TEntity? Find(Expression> predicate) 55 | { 56 | return Find(predicate, null); 57 | } 58 | 59 | 60 | public virtual Task FindAsync(Expression> predicate) 61 | { 62 | return FindAsync(predicate, null, cancellationToken: default); 63 | } 64 | 65 | 66 | public virtual Task FindAsync(Expression>? predicate, CancellationToken cancellationToken) 67 | { 68 | return FindAsync(predicate, null, cancellationToken: cancellationToken); 69 | } 70 | 71 | 72 | public virtual TEntity? Find(Expression>? predicate, IDbTransaction? transaction) 73 | { 74 | var queryResult = SqlGenerator.GetSelectFirst(predicate, FilterData); 75 | return Connection.QueryFirstOrDefault(queryResult.GetSql(), queryResult.Param, transaction); 76 | } 77 | 78 | 79 | public virtual Task FindAsync(Expression>? predicate, IDbTransaction? transaction) 80 | { 81 | return FindAsync(predicate, transaction, cancellationToken: default); 82 | } 83 | 84 | 85 | public virtual Task FindAsync(Expression>? predicate, IDbTransaction? transaction, CancellationToken cancellationToken) 86 | { 87 | var queryResult = SqlGenerator.GetSelectFirst(predicate, FilterData); 88 | return Connection.QueryFirstOrDefaultAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.FindAll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq.Expressions; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Dapper; 8 | 9 | namespace MicroOrm.Dapper.Repositories; 10 | 11 | /// 12 | /// Base Repository 13 | /// 14 | public partial class ReadOnlyDapperRepository 15 | where TEntity : class 16 | { 17 | 18 | public virtual IEnumerable FindAll() 19 | { 20 | return FindAll(null, transaction: null); 21 | } 22 | 23 | 24 | public virtual Task> FindAllAsync() 25 | { 26 | return FindAllAsync(null, transaction: null, cancellationToken: default); 27 | } 28 | 29 | 30 | public virtual Task> FindAllAsync(CancellationToken cancellationToken) 31 | { 32 | return FindAllAsync(null, transaction: null, cancellationToken: cancellationToken); 33 | } 34 | 35 | 36 | public virtual IEnumerable FindAll(IDbTransaction transaction) 37 | { 38 | return FindAll(null, transaction); 39 | } 40 | 41 | 42 | public virtual Task> FindAllAsync(IDbTransaction transaction) 43 | { 44 | return FindAllAsync(null, transaction, cancellationToken: default); 45 | } 46 | 47 | 48 | public virtual Task> FindAllAsync(IDbTransaction? transaction, CancellationToken cancellationToken) 49 | { 50 | return FindAllAsync(null, transaction, cancellationToken: cancellationToken); 51 | } 52 | 53 | 54 | public virtual IEnumerable FindAll(Expression> predicate) 55 | { 56 | return FindAll(predicate, transaction: null); 57 | } 58 | 59 | 60 | public virtual Task> FindAllAsync(Expression> predicate) 61 | { 62 | return FindAllAsync(predicate, transaction: null, cancellationToken: default); 63 | } 64 | 65 | 66 | public virtual Task> FindAllAsync(Expression>? predicate, CancellationToken cancellationToken) 67 | { 68 | return FindAllAsync(predicate, transaction: null, cancellationToken: cancellationToken); 69 | } 70 | 71 | 72 | public virtual IEnumerable FindAll(Expression>? predicate, 73 | Expression> orderBy) 74 | { 75 | return FindAll(predicate, transaction: null); 76 | } 77 | 78 | 79 | public virtual IEnumerable FindAll(Expression>? predicate, IDbTransaction? transaction) 80 | { 81 | var queryResult = SqlGenerator.GetSelectAll(predicate, FilterData); 82 | return Connection.Query(queryResult.GetSql(), queryResult.Param, transaction); 83 | } 84 | 85 | 86 | public virtual Task> FindAllAsync(Expression>? predicate, IDbTransaction? transaction) 87 | { 88 | return FindAllAsync(predicate, transaction, cancellationToken: default); 89 | } 90 | 91 | 92 | public virtual async Task> FindAllAsync(Expression>? predicate, IDbTransaction? transaction, CancellationToken cancellationToken) 93 | { 94 | var queryResult = SqlGenerator.GetSelectAll(predicate, FilterData); 95 | return await Connection.QueryAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)).ConfigureAwait(false); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.FindById.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Dapper; 5 | 6 | namespace MicroOrm.Dapper.Repositories; 7 | 8 | /// 9 | /// Base Repository 10 | /// 11 | public partial class ReadOnlyDapperRepository 12 | where TEntity : class 13 | { 14 | 15 | public virtual TEntity? FindById(object id) 16 | { 17 | return FindById(id, null); 18 | } 19 | 20 | 21 | public virtual TEntity? FindById(object id, IDbTransaction? transaction) 22 | { 23 | var queryResult = SqlGenerator.GetSelectById(id, null); 24 | return Connection.QuerySingleOrDefault(queryResult.GetSql(), queryResult.Param, transaction); 25 | } 26 | 27 | 28 | public virtual Task FindByIdAsync(object id) 29 | { 30 | return FindByIdAsync(id, null, cancellationToken: default); 31 | } 32 | 33 | 34 | public virtual Task FindByIdAsync(object id, CancellationToken cancellationToken) 35 | { 36 | return FindByIdAsync(id, null, cancellationToken: cancellationToken); 37 | } 38 | 39 | 40 | public virtual Task FindByIdAsync(object id, IDbTransaction? transaction) 41 | { 42 | return FindByIdAsync(id, transaction, cancellationToken: default); 43 | } 44 | 45 | 46 | public virtual Task FindByIdAsync(object id, IDbTransaction? transaction, CancellationToken cancellationToken) 47 | { 48 | var queryResult = SqlGenerator.GetSelectById(id, null); 49 | return Connection.QuerySingleOrDefaultAsync(new CommandDefinition(queryResult.GetSql(), queryResult.Param, transaction, cancellationToken: cancellationToken)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.SetGroupBy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 6 | 7 | namespace MicroOrm.Dapper.Repositories; 8 | 9 | /// 10 | /// Base Repository 11 | /// 12 | public partial class ReadOnlyDapperRepository 13 | where TEntity : class 14 | { 15 | 16 | public virtual IReadOnlyDapperRepository SetGroupBy() 17 | { 18 | FilterData.OrderInfo = null; 19 | return this; 20 | } 21 | 22 | 23 | public virtual IReadOnlyDapperRepository SetGroupBy(bool permanent, 24 | Expression> expr) 25 | { 26 | return SetGroupBy(permanent, expr); 27 | } 28 | 29 | 30 | public virtual IReadOnlyDapperRepository SetGroupBy(bool permanent, 31 | Expression> expr) 32 | { 33 | var order = FilterData.GroupInfo ?? new GroupInfo(); 34 | 35 | var type = typeof(T); 36 | switch (expr.Body.NodeType) 37 | { 38 | case ExpressionType.Convert: 39 | { 40 | if (expr.Body is UnaryExpression { Operand: MemberExpression expression }) 41 | { 42 | order.Columns = new List { GetProperty(expression, type) }; 43 | } 44 | 45 | break; 46 | } 47 | case ExpressionType.MemberAccess: 48 | order.Columns = new List { GetProperty(expr.Body, type) }; 49 | break; 50 | default: 51 | { 52 | var cols = (expr.Body as NewExpression)?.Arguments; 53 | var propertyNames = cols?.Select(expression => GetProperty(expression, type)).ToList(); 54 | order.Columns = propertyNames; 55 | break; 56 | } 57 | } 58 | 59 | order.Permanent = permanent; 60 | 61 | FilterData.GroupInfo = order; 62 | 63 | return this; 64 | } 65 | 66 | 67 | public virtual IReadOnlyDapperRepository SetGroupBy(Expression> expr) 68 | { 69 | return SetGroupBy(false, expr); 70 | } 71 | 72 | 73 | public virtual IReadOnlyDapperRepository SetOrderBy(Expression> expr) 74 | { 75 | return SetGroupBy(false, expr); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.SetLimit.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 2 | 3 | namespace MicroOrm.Dapper.Repositories; 4 | 5 | /// 6 | /// Base Repository 7 | /// 8 | public partial class ReadOnlyDapperRepository 9 | where TEntity : class 10 | { 11 | 12 | public virtual IReadOnlyDapperRepository SetLimit() 13 | { 14 | FilterData.LimitInfo = null; 15 | return this; 16 | } 17 | 18 | 19 | public virtual IReadOnlyDapperRepository SetLimit(uint limit, uint? offset, bool permanent) 20 | { 21 | if (limit <= 0) 22 | return this; 23 | 24 | var data = FilterData.LimitInfo ?? new LimitInfo(); 25 | data.Limit = limit; 26 | data.Offset = offset; 27 | data.Permanent = permanent; 28 | FilterData.LimitInfo = data; 29 | 30 | return this; 31 | } 32 | 33 | 34 | public virtual IReadOnlyDapperRepository SetLimit(uint limit) 35 | { 36 | return SetLimit(limit, null, false); 37 | } 38 | 39 | 40 | public virtual IReadOnlyDapperRepository SetLimit(uint limit, uint offset) 41 | { 42 | return SetLimit(limit, offset, false); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.SetOrderBy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 6 | 7 | namespace MicroOrm.Dapper.Repositories; 8 | 9 | /// 10 | /// Base Repository 11 | /// 12 | public partial class ReadOnlyDapperRepository 13 | where TEntity : class 14 | { 15 | 16 | public virtual IReadOnlyDapperRepository SetOrderBy() 17 | { 18 | FilterData.OrderInfo = null; 19 | return this; 20 | } 21 | 22 | 23 | public virtual IReadOnlyDapperRepository SetOrderBy(OrderInfo.SortDirection direction, params string[] cols) 24 | { 25 | var order = FilterData.OrderInfo ?? new OrderInfo(); 26 | 27 | order.Direction = direction; 28 | order.Columns = cols.ToList(); 29 | 30 | FilterData.OrderInfo = order; 31 | 32 | return this; 33 | } 34 | 35 | 36 | public virtual IReadOnlyDapperRepository SetOrderBy(string query) 37 | { 38 | return SetOrderBy(query, false); 39 | } 40 | 41 | 42 | public virtual IReadOnlyDapperRepository SetOrderBy(string query, bool permanent) 43 | { 44 | var order = FilterData.OrderInfo ?? new OrderInfo(); 45 | 46 | order.Direction = null; 47 | order.CustomQuery = query; 48 | order.Permanent = permanent; 49 | FilterData.OrderInfo = order; 50 | 51 | return this; 52 | } 53 | 54 | 55 | public virtual IReadOnlyDapperRepository SetOrderBy(OrderInfo.SortDirection direction, bool permanent, 56 | Expression> expr) 57 | { 58 | return SetOrderBy(direction, permanent, expr); 59 | } 60 | 61 | 62 | public virtual IReadOnlyDapperRepository SetOrderBy(OrderInfo.SortDirection direction, bool permanent, 63 | Expression> expr) 64 | { 65 | var order = FilterData.OrderInfo ?? new OrderInfo(); 66 | order.Direction = direction; 67 | 68 | var type = typeof(T); 69 | switch (expr.Body.NodeType) 70 | { 71 | case ExpressionType.Convert: 72 | { 73 | if (expr.Body is UnaryExpression { Operand: MemberExpression expression }) 74 | { 75 | order.Columns = new List { GetProperty(expression, type) }; 76 | } 77 | 78 | break; 79 | } 80 | case ExpressionType.MemberAccess: 81 | order.Columns = new List { GetProperty(expr.Body, type) }; 82 | break; 83 | default: 84 | { 85 | var cols = (expr.Body as NewExpression)?.Arguments; 86 | if (cols != null) 87 | { 88 | var propertyNames = cols.Select(expression => GetProperty(expression, type)).ToList(); 89 | order.Columns = propertyNames; 90 | } 91 | 92 | break; 93 | } 94 | } 95 | 96 | order.Permanent = permanent; 97 | 98 | FilterData.OrderInfo = order; 99 | 100 | return this; 101 | } 102 | 103 | 104 | public virtual IReadOnlyDapperRepository SetOrderBy(OrderInfo.SortDirection direction, Expression> expr) 105 | { 106 | return SetOrderBy(direction, false, expr); 107 | } 108 | 109 | 110 | public virtual IReadOnlyDapperRepository SetOrderBy(OrderInfo.SortDirection direction, Expression> expr) 111 | { 112 | return SetOrderBy(direction, false, expr); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.SetSelect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 5 | 6 | namespace MicroOrm.Dapper.Repositories; 7 | 8 | /// 9 | /// Base Repository 10 | /// 11 | public partial class ReadOnlyDapperRepository 12 | where TEntity : class 13 | { 14 | 15 | public virtual IReadOnlyDapperRepository SetSelect(Expression> expr, bool permanent) 16 | { 17 | if (FilterData.SelectInfo == null) 18 | { 19 | FilterData.SelectInfo = new SelectInfo(); 20 | } 21 | 22 | FilterData.SelectInfo.Permanent = permanent; 23 | 24 | var type = typeof(T); 25 | if (expr.Body.NodeType == ExpressionType.Lambda) 26 | { 27 | if (expr.Body is UnaryExpression { Operand: MemberExpression expression }) 28 | { 29 | var prop = GetProperty(expression, type); 30 | FilterData.SelectInfo.Columns.Add(prop); 31 | } 32 | } 33 | else 34 | { 35 | var cols = (expr.Body as NewExpression)?.Arguments; 36 | if (cols != null) 37 | { 38 | foreach (var expression in cols) 39 | { 40 | var prop = GetProperty(expression, type); 41 | if (string.IsNullOrEmpty(prop)) 42 | continue; 43 | 44 | FilterData.SelectInfo.Columns.Add(prop); 45 | } 46 | } 47 | } 48 | 49 | return this; 50 | } 51 | 52 | 53 | public virtual IReadOnlyDapperRepository SetSelect(params string[] customSelect) 54 | { 55 | if (FilterData.SelectInfo == null) 56 | { 57 | FilterData.SelectInfo = new SelectInfo(); 58 | } 59 | 60 | FilterData.SelectInfo.Columns = customSelect.ToList(); 61 | 62 | return this; 63 | } 64 | 65 | 66 | public virtual IReadOnlyDapperRepository SetSelect(Expression> expr) 67 | { 68 | return SetSelect(expr, false); 69 | } 70 | 71 | 72 | public virtual IReadOnlyDapperRepository SetSelect(Expression> expr) 73 | { 74 | return SetSelect(expr, false); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ReadOnlyDapperRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using System.Data; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using MicroOrm.Dapper.Repositories.Config; 7 | using MicroOrm.Dapper.Repositories.SqlGenerator; 8 | using MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 9 | 10 | namespace MicroOrm.Dapper.Repositories; 11 | 12 | /// 13 | /// Base ReadOnlyRepository 14 | /// 15 | public partial class ReadOnlyDapperRepository : IReadOnlyDapperRepository 16 | where TEntity : class 17 | { 18 | private IDbConnection? _connection; 19 | private FilterData? _filterData; 20 | 21 | /// 22 | /// Constructor 23 | /// 24 | public ReadOnlyDapperRepository(IDbConnection connection) 25 | { 26 | _connection = connection; 27 | _filterData = new FilterData(); 28 | SqlGenerator = new SqlGenerator(); 29 | } 30 | 31 | /// 32 | /// Constructor 33 | /// 34 | public ReadOnlyDapperRepository(IDbConnection connection, ISqlGenerator sqlGenerator) 35 | { 36 | _connection = connection; 37 | _filterData = new FilterData(); 38 | SqlGenerator = sqlGenerator; 39 | } 40 | 41 | 42 | public IDbConnection Connection => _connection ?? throw new ObjectDisposedException(GetType().FullName); 43 | 44 | 45 | public FilterData FilterData => _filterData ?? throw new ObjectDisposedException(GetType().FullName); 46 | 47 | 48 | public ISqlGenerator SqlGenerator { get; } 49 | 50 | private static string GetProperty(Expression expression, Type type) 51 | { 52 | var field = (MemberExpression)expression; 53 | 54 | var prop = type.GetProperty(field.Member.Name); 55 | var declaringType = type.GetTypeInfo(); 56 | var tableAttribute = declaringType.GetCustomAttribute(); 57 | var tableName = MicroOrmConfig.TablePrefix + (tableAttribute != null ? tableAttribute.Name : declaringType.Name); 58 | 59 | if (prop == null || prop.GetCustomAttribute() != null) 60 | return string.Empty; 61 | 62 | var name = prop.GetCustomAttribute()?.Name ?? prop.Name; 63 | return $"{tableName}.{name}"; 64 | } 65 | 66 | 67 | public void Dispose() 68 | { 69 | _connection?.Dispose(); 70 | _connection = null; 71 | 72 | if (_filterData == null) 73 | return; 74 | 75 | _filterData.Ordered = false; 76 | 77 | if (_filterData.LimitInfo != null) 78 | { 79 | _filterData.LimitInfo = null; 80 | } 81 | 82 | if (_filterData.OrderInfo != null) 83 | { 84 | _filterData.OrderInfo.Columns?.Clear(); 85 | _filterData.OrderInfo.Columns = null; 86 | _filterData.OrderInfo = null; 87 | } 88 | 89 | if (_filterData.SelectInfo != null) 90 | { 91 | _filterData.SelectInfo.Columns.Clear(); 92 | _filterData.SelectInfo = null; 93 | } 94 | 95 | _filterData = null; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/SqlGenerator/Filters/FilterData.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 2 | 3 | /// 4 | /// The filter data class; This should have some more things... 5 | /// 6 | public class FilterData 7 | { 8 | /// 9 | /// The query select settings 10 | /// 11 | public SelectInfo? SelectInfo { get; set; } 12 | 13 | /// 14 | /// The query order settings 15 | /// 16 | public OrderInfo? OrderInfo { get; set; } 17 | 18 | /// 19 | /// The query group settings 20 | /// 21 | public GroupInfo? GroupInfo { get; set; } 22 | 23 | /// 24 | /// The query limits settings 25 | /// 26 | public LimitInfo? LimitInfo { get; set; } 27 | 28 | /// 29 | /// Specify if the query is ordered 30 | /// 31 | public bool Ordered { get; set; } 32 | } 33 | -------------------------------------------------------------------------------- /src/SqlGenerator/Filters/GroupInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 4 | 5 | /// 6 | /// Query Group by info 7 | /// 8 | public class GroupInfo 9 | { 10 | /// 11 | /// Columns to sort 12 | /// 13 | public List? Columns { get; set; } 14 | 15 | /// 16 | /// If true, will be used for all queries 17 | /// 18 | public bool Permanent { get; set; } 19 | 20 | /// 21 | /// You can specify a custom query if you need more "liberty" 22 | /// 23 | public string? CustomQuery { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /src/SqlGenerator/Filters/LimitInfo.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 2 | 3 | /// 4 | /// Limit settings 5 | /// 6 | public class LimitInfo 7 | { 8 | /// 9 | /// The limit; Should be greater than 0. 10 | /// 11 | public uint Limit { get; set; } 12 | 13 | /// 14 | /// The offset (optional); Used for pagination 15 | /// 16 | public uint? Offset { get; set; } 17 | 18 | /// 19 | /// If true, will be used for all queries 20 | /// 21 | public bool Permanent { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /src/SqlGenerator/Filters/OrderInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | // ReSharper disable InconsistentNaming 4 | 5 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 6 | 7 | /// 8 | /// Query order info 9 | /// 10 | public class OrderInfo 11 | { 12 | /// 13 | /// The sorting direction 14 | /// 15 | public SortDirection? Direction { get; set; } 16 | 17 | /// 18 | /// Columns to sort 19 | /// 20 | public List? Columns { get; set; } 21 | 22 | /// 23 | /// You can specify a custom query if you need more "liberty" 24 | /// 25 | public string? CustomQuery { get; set; } 26 | 27 | /// 28 | /// If true, will be used for all queries 29 | /// 30 | public bool Permanent { get; set; } 31 | 32 | /// 33 | /// Possible sorting Direction 34 | /// 35 | public enum SortDirection 36 | { 37 | /// 38 | /// Ascending 39 | /// 40 | ASC, 41 | 42 | /// 43 | /// Descending 44 | /// 45 | DESC 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/SqlGenerator/Filters/SelectInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 4 | 5 | /// 6 | /// Select Info 7 | /// 8 | public class SelectInfo 9 | { 10 | /// 11 | /// The constructor 12 | /// 13 | public SelectInfo() 14 | { 15 | Columns = new List(); 16 | } 17 | 18 | /// 19 | /// Columns 20 | /// 21 | public List Columns { get; set; } 22 | 23 | /// 24 | /// If true, will be used for all queries 25 | /// 26 | public bool Permanent { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /src/SqlGenerator/QueryExpressions/QueryBinaryExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.QueryExpressions; 4 | 5 | 6 | /// 7 | /// `Binary` Query Expression 8 | /// 9 | internal class QueryBinaryExpression : QueryExpression 10 | { 11 | public QueryBinaryExpression(List nodes) 12 | { 13 | Nodes = nodes; 14 | NodeType = QueryExpressionType.Binary; 15 | } 16 | 17 | public List Nodes { get; } 18 | 19 | public override string ToString() 20 | { 21 | return $"[{base.ToString()} ({string.Join(",", Nodes)})]"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SqlGenerator/QueryExpressions/QueryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.QueryExpressions; 2 | 3 | /// 4 | /// Abstract Query Expression 5 | /// 6 | internal abstract class QueryExpression 7 | { 8 | /// 9 | /// Query Expression Node Type 10 | /// 11 | public QueryExpressionType NodeType { get; set; } 12 | 13 | /// 14 | /// Operator OR/AND 15 | /// 16 | public string? LinkingOperator { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return $"[NodeType:{this.NodeType}, LinkingOperator:{LinkingOperator}]"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/SqlGenerator/QueryExpressions/QueryExpressionType.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.QueryExpressions; 2 | 3 | /// 4 | /// Query Expression Node Type 5 | /// 6 | internal enum QueryExpressionType 7 | { 8 | Parameter = 0, 9 | Binary = 1, 10 | } 11 | -------------------------------------------------------------------------------- /src/SqlGenerator/QueryExpressions/QueryParameterExpression.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.SqlGenerator.QueryExpressions; 2 | 3 | 4 | /// 5 | /// Class that models the data structure in coverting the expression tree into SQL and Params. 6 | /// `Parameter` Query Expression 7 | /// 8 | internal class QueryParameterExpression : QueryExpression 9 | { 10 | public QueryParameterExpression() 11 | { 12 | NodeType = QueryExpressionType.Parameter; 13 | } 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The linking operator. 19 | /// Name of the property. 20 | /// The property value. 21 | /// The query operator. 22 | /// Signilize if it is nested property. 23 | internal QueryParameterExpression(string linkingOperator, 24 | string propertyName, object? propertyValue, 25 | string queryOperator, bool nestedProperty) : this() 26 | { 27 | LinkingOperator = linkingOperator; 28 | PropertyName = propertyName; 29 | PropertyValue = propertyValue; 30 | QueryOperator = queryOperator; 31 | NestedProperty = nestedProperty; 32 | } 33 | 34 | public string? PropertyName { get; set; } 35 | public object? PropertyValue { get; set; } 36 | public string? QueryOperator { get; set; } 37 | public bool NestedProperty { get; set; } 38 | 39 | public override string ToString() 40 | { 41 | return 42 | $"[{base.ToString()}, PropertyName:{PropertyName}, PropertyValue:{PropertyValue}, QueryOperator:{QueryOperator}, NestedProperty:{NestedProperty}]"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.GetBulkInsert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MicroOrm.Dapper.Repositories.Attributes; 6 | 7 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 8 | 9 | 10 | public partial class SqlGenerator 11 | where TEntity : class 12 | { 13 | 14 | public virtual SqlQuery GetBulkInsert(IEnumerable entities) 15 | { 16 | var entitiesArray = entities as TEntity[] ?? entities.ToArray(); 17 | if (!entitiesArray.Any()) 18 | throw new ArgumentException("collection is empty"); 19 | 20 | var entityType = entitiesArray[0].GetType(); 21 | 22 | var properties = 23 | (IsIdentity 24 | ? SqlProperties.Where(p => !p.PropertyName.Equals(IdentitySqlProperty.PropertyName, StringComparison.OrdinalIgnoreCase)) 25 | : SqlProperties).ToList(); 26 | 27 | var query = new SqlQuery(); 28 | 29 | var values = new List(); 30 | var parameters = new Dictionary(); 31 | 32 | for (var i = 0; i < entitiesArray.Length; i++) 33 | { 34 | var entity = entitiesArray[i]; 35 | if (HasUpdatedAt && UpdatedAtProperty.GetCustomAttribute() is { } attribute) 36 | { 37 | var offset = attribute.TimeKind == DateTimeKind.Local 38 | ? new DateTimeOffset(DateTime.Now) 39 | : new DateTimeOffset(DateTime.UtcNow); 40 | if (attribute.OffSet != 0) 41 | { 42 | offset = offset.ToOffset(TimeSpan.FromHours(attribute.OffSet)); 43 | } 44 | 45 | UpdatedAtProperty.SetValue(entity, offset.DateTime); 46 | } 47 | 48 | foreach (var property in properties) 49 | parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName)?.GetValue(entity, null)); 50 | 51 | values.Add(string.Format("({0})", string.Join(", ", properties.Select(p => ParameterSymbol + p.PropertyName + i)))); 52 | } 53 | 54 | if (Provider != SqlProvider.Oracle) 55 | { 56 | query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1}) VALUES {2}", TableName, string.Join(", ", properties.Select(p => p.ColumnName)), 57 | string.Join(",", values)); // values 58 | } 59 | else 60 | { 61 | query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1})", TableName, string.Join(", ", properties.Select(p => p.ColumnName))); 62 | var singleInsert = values.Select(v => " SELECT " + v.Substring(1, v.Length - 2) + " FROM DUAL "); 63 | query.SqlBuilder.Append(string.Join("UNION ALL", singleInsert)); 64 | } 65 | 66 | query.SetParam(parameters); 67 | 68 | return query; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.GetBulkUpdate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MicroOrm.Dapper.Repositories.Attributes; 6 | 7 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 8 | 9 | 10 | public partial class SqlGenerator 11 | where TEntity : class 12 | { 13 | 14 | public virtual SqlQuery GetBulkUpdate(IEnumerable entities) 15 | { 16 | var entitiesArray = entities as TEntity[] ?? entities.ToArray(); 17 | if (!entitiesArray.Any()) 18 | throw new ArgumentException("collection is empty"); 19 | 20 | var entityType = entitiesArray[0].GetType(); 21 | 22 | var properties = SqlProperties.Where(p => 23 | !KeySqlProperties.Any(k => k.PropertyName.Equals(p.PropertyName, StringComparison.OrdinalIgnoreCase)) && !p.IgnoreUpdate).ToArray(); 24 | 25 | var query = new SqlQuery(); 26 | 27 | var parameters = new Dictionary(); 28 | 29 | //In Oracle we use MERGE INTO to excute multipe update with argument. 30 | List singleSelectsForOracle = new List(); 31 | 32 | for (var i = 0; i < entitiesArray.Length; i++) 33 | { 34 | var entity = entitiesArray[i]; 35 | if (HasUpdatedAt && UpdatedAtProperty.GetCustomAttribute() is { } attribute) 36 | { 37 | var offset = attribute.TimeKind == DateTimeKind.Local 38 | ? new DateTimeOffset(DateTime.Now) 39 | : new DateTimeOffset(DateTime.UtcNow); 40 | if (attribute.OffSet != 0) 41 | { 42 | offset = offset.ToOffset(TimeSpan.FromHours(attribute.OffSet)); 43 | } 44 | 45 | UpdatedAtProperty.SetValue(entity, offset.DateTime); 46 | } 47 | 48 | if (Provider != SqlProvider.Oracle) 49 | { 50 | if (i > 0) 51 | query.SqlBuilder.Append("; "); 52 | 53 | query.SqlBuilder.Append( 54 | $"UPDATE {TableName} SET {string.Join(", ", properties.Select(p => $"{p.ColumnName} = {ParameterSymbol}{p.PropertyName}{i}"))} WHERE {string.Join(" AND ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{p.ColumnName} = {ParameterSymbol}{p.PropertyName}{i}"))}"); 55 | } 56 | else 57 | { 58 | var singleSelect = 59 | $"SELECT {string.Join(", ", properties.Select(p => $"{ParameterSymbol}{p.PropertyName}{i} AS {p.ColumnName}"))}, {string.Join(" , ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{ParameterSymbol}{p.PropertyName}{i} AS {p.ColumnName}"))} FROM DUAL"; 60 | singleSelectsForOracle.Add(singleSelect); 61 | } 62 | 63 | // ReSharper disable PossibleNullReferenceException 64 | foreach (var property in properties) 65 | parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName)?.GetValue(entity, null)); 66 | 67 | foreach (var property in KeySqlProperties.Where(p => !p.IgnoreUpdate)) 68 | parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName)?.GetValue(entity, null)); 69 | 70 | // ReSharper restore PossibleNullReferenceException 71 | } 72 | 73 | query.SetParam(parameters); 74 | 75 | if (Provider == SqlProvider.Oracle) 76 | { 77 | var unionTName = $"{TableName}_BULKUPDATE"; 78 | var unionSelect = string.Join(" UNION ALL ", singleSelectsForOracle); 79 | var unionOn = $"({string.Join(" AND ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{unionTName}.{p.ColumnName} = {TableName}.{p.ColumnName}"))})"; 80 | var unionSet = $"{string.Join(",", properties.Select(p => $"{p.ColumnName} = {unionTName}.{p.ColumnName} "))}"; 81 | 82 | query.SqlBuilder.Append($"MERGE INTO {TableName} {TableName} USING ({unionSelect}) {unionTName} ON {unionOn} WHEN MATCHED THEN UPDATE SET {unionSet}"); 83 | } 84 | 85 | return query; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.GetCount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 6 | 7 | 8 | public partial class SqlGenerator 9 | where TEntity : class 10 | { 11 | 12 | public virtual SqlQuery GetCount(Expression>? predicate, params Expression>[] includes) 13 | { 14 | var sqlQuery = new SqlQuery(); 15 | 16 | sqlQuery.SqlBuilder 17 | .Append("SELECT COUNT(*)"); 18 | 19 | if (includes.Length > 0) 20 | { 21 | var joinsBuilder = AppendJoinToSelect(sqlQuery, true, includes); 22 | sqlQuery.SqlBuilder 23 | .Append(" FROM ") 24 | .Append(TableName) 25 | .Append(" "); 26 | 27 | sqlQuery.SqlBuilder.Append(joinsBuilder); 28 | } 29 | else 30 | { 31 | sqlQuery.SqlBuilder 32 | .Append(" FROM ") 33 | .Append(TableName) 34 | .Append(" "); 35 | } 36 | 37 | AppendWherePredicateQuery(sqlQuery, predicate, QueryType.Select); 38 | 39 | return sqlQuery; 40 | } 41 | 42 | 43 | public virtual SqlQuery GetCount(Expression>? predicate, Expression> distinctField, 44 | params Expression>[] includes) 45 | { 46 | var propertyName = ExpressionHelper.GetPropertyName(distinctField); 47 | var property = SqlProperties.First(x => x.PropertyName == propertyName); 48 | var sqlQuery = InitBuilderCountWithDistinct(property); 49 | 50 | if (includes.Length > 0) 51 | { 52 | var joinsBuilder = AppendJoinToSelect(sqlQuery, true, includes); 53 | sqlQuery.SqlBuilder 54 | .Append(" FROM ") 55 | .Append(TableName) 56 | .Append(" "); 57 | 58 | sqlQuery.SqlBuilder.Append(joinsBuilder); 59 | } 60 | else 61 | { 62 | sqlQuery.SqlBuilder 63 | .Append(" FROM ") 64 | .Append(TableName) 65 | .Append(" "); 66 | } 67 | 68 | AppendWherePredicateQuery(sqlQuery, predicate, QueryType.Select); 69 | 70 | return sqlQuery; 71 | } 72 | 73 | private SqlQuery InitBuilderCountWithDistinct(SqlPropertyMetadata sqlProperty) 74 | { 75 | var query = new SqlQuery(); 76 | query.SqlBuilder.Append("SELECT COUNT(DISTINCT "); 77 | 78 | query.SqlBuilder 79 | .Append(TableName) 80 | .Append(".") 81 | .Append(sqlProperty.ColumnName) 82 | .Append(")"); 83 | 84 | if (sqlProperty.Alias != null) 85 | query.SqlBuilder 86 | .Append(" AS ") 87 | .Append(sqlProperty.PropertyName); 88 | 89 | return query; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.GetDelete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using MicroOrm.Dapper.Repositories.Attributes; 6 | 7 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 8 | 9 | 10 | public partial class SqlGenerator 11 | where TEntity : class 12 | { 13 | 14 | public virtual SqlQuery GetDelete(TEntity entity) 15 | { 16 | var sqlQuery = new SqlQuery(); 17 | var whereAndSql = 18 | string.Join(" AND ", KeySqlProperties.Select(p => string.Format("{0}.{1} = {2}", TableName, p.ColumnName, ParameterSymbol + p.PropertyName))); 19 | 20 | if (!LogicalDelete) 21 | { 22 | sqlQuery.SqlBuilder 23 | .Append("DELETE FROM ") 24 | .Append(TableName) 25 | .Append(" WHERE ") 26 | .Append(whereAndSql); 27 | } 28 | else 29 | { 30 | sqlQuery.SqlBuilder 31 | .Append("UPDATE ") 32 | .Append(TableName) 33 | .Append(" SET ") 34 | .Append(StatusPropertyName) 35 | .Append(" = ") 36 | .Append(LogicalDeleteValue); 37 | 38 | if (HasUpdatedAt && UpdatedAtProperty.GetCustomAttribute() is { } attribute) 39 | { 40 | var offset = attribute.TimeKind == DateTimeKind.Local 41 | ? new DateTimeOffset(DateTime.Now) 42 | : new DateTimeOffset(DateTime.UtcNow); 43 | if (attribute.OffSet != 0) 44 | { 45 | offset = offset.ToOffset(TimeSpan.FromHours(attribute.OffSet)); 46 | } 47 | 48 | UpdatedAtProperty.SetValue(entity, offset.DateTime); 49 | 50 | sqlQuery.SqlBuilder 51 | .Append(", ") 52 | .Append(UpdatedAtPropertyMetadata.ColumnName) 53 | .Append($" = {ParameterSymbol}") 54 | .Append(UpdatedAtPropertyMetadata.PropertyName); 55 | } 56 | 57 | sqlQuery.SqlBuilder 58 | .Append(" WHERE ") 59 | .Append(whereAndSql); 60 | } 61 | 62 | sqlQuery.SetParam(entity); 63 | return sqlQuery; 64 | } 65 | 66 | 67 | public virtual SqlQuery GetDelete(Expression>? predicate) 68 | { 69 | var sqlQuery = new SqlQuery(); 70 | 71 | if (!LogicalDelete) 72 | { 73 | sqlQuery.SqlBuilder 74 | .Append("DELETE FROM ") 75 | .Append(TableName); 76 | } 77 | else 78 | { 79 | sqlQuery.SqlBuilder 80 | .Append("UPDATE ") 81 | .Append(TableName) 82 | .Append(" SET ") 83 | .Append(StatusPropertyName) 84 | .Append(" = ") 85 | .Append(LogicalDeleteValue); 86 | 87 | if (HasUpdatedAt) 88 | sqlQuery.SqlBuilder 89 | .Append(", ") 90 | .Append(UpdatedAtPropertyMetadata.ColumnName) 91 | .Append($" = {ParameterSymbol}") 92 | .Append(UpdatedAtPropertyMetadata.PropertyName); 93 | } 94 | 95 | sqlQuery.SqlBuilder.Append(" "); 96 | AppendWherePredicateQuery(sqlQuery, predicate, QueryType.Delete); 97 | return sqlQuery; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.GetInsert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using Dapper; 5 | using MicroOrm.Dapper.Repositories.Attributes; 6 | using MicroOrm.Dapper.Repositories.Extensions; 7 | 8 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 9 | 10 | 11 | public partial class SqlGenerator 12 | where TEntity : class 13 | { 14 | 15 | public virtual SqlQuery GetInsert(TEntity entity) 16 | { 17 | var properties = 18 | (IsIdentity 19 | ? SqlProperties.Where(p => !p.PropertyName.Equals(IdentitySqlProperty.PropertyName, StringComparison.OrdinalIgnoreCase)) 20 | : SqlProperties).ToList(); 21 | 22 | if (HasUpdatedAt && UpdatedAtProperty.GetCustomAttribute() is { } attribute) 23 | { 24 | var offset = attribute.TimeKind == DateTimeKind.Local 25 | ? new DateTimeOffset(DateTime.Now) 26 | : new DateTimeOffset(DateTime.UtcNow); 27 | if (attribute.OffSet != 0) 28 | { 29 | offset = offset.ToOffset(TimeSpan.FromHours(attribute.OffSet)); 30 | } 31 | 32 | UpdatedAtProperty.SetValue(entity, offset.DateTime); 33 | } 34 | 35 | var query = new SqlQuery(entity); 36 | 37 | //sorry, dapper doesn't support guid mapping in oracle. 38 | //I can not convert guid to bytearray to build a seperate params, it can not be achieved in this lib. 39 | //see details below to get more informations. 40 | //https://github.com/DapperLib/Dapper/issues/633 41 | //https://github.com/DapperLib/Dapper/issues/637 42 | //https://github.com/vauto/Dapper.Database/pull/1 43 | 44 | if (Provider == SqlProvider.Oracle) 45 | { 46 | var oracleParams = new DynamicParameters(entity); 47 | oracleParams.AddOracleOutputParameterForId(); 48 | query.SetParam(oracleParams); 49 | } 50 | 51 | query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", TableName, string.Join(", ", properties.Select(p => p.ColumnName)), 52 | string.Join(", ", properties.Select(p => ParameterSymbol + p.PropertyName))); // values 53 | 54 | if (IsIdentity) 55 | switch (Provider) 56 | { 57 | case SqlProvider.MSSQL: 58 | query.SqlBuilder.Append(" SELECT SCOPE_IDENTITY() AS " + IdentitySqlProperty.ColumnName); 59 | break; 60 | 61 | case SqlProvider.MySQL: 62 | query.SqlBuilder.Append("; SELECT CONVERT(LAST_INSERT_ID(), SIGNED INTEGER) AS " + IdentitySqlProperty.ColumnName); 63 | break; 64 | 65 | case SqlProvider.SQLite: 66 | query.SqlBuilder.Append("; SELECT LAST_INSERT_ROWID() AS " + IdentitySqlProperty.ColumnName); 67 | break; 68 | 69 | case SqlProvider.PostgreSQL: 70 | query.SqlBuilder.Append(" RETURNING " + IdentitySqlProperty.ColumnName); 71 | break; 72 | 73 | case SqlProvider.Oracle: 74 | query.SqlBuilder.Append(" RETURNING " + IdentitySqlProperty.ColumnName + " INTO :newId"); 75 | break; 76 | 77 | default: 78 | throw new ArgumentOutOfRangeException(); 79 | } 80 | 81 | return query; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.GetTableName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 3 | 4 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 5 | 6 | 7 | public partial class SqlGenerator 8 | where TEntity : class 9 | { 10 | private static string GetTableNameWithSchemaPrefix(string? tableName, string? tableSchema, SqlProvider sqlProvider, string startQuotationMark = "", string endQuotationMark = "") 11 | { 12 | if (string.IsNullOrEmpty(tableSchema)) 13 | return startQuotationMark + tableName + endQuotationMark; 14 | 15 | return sqlProvider == SqlProvider.SQLite 16 | ? startQuotationMark + tableSchema + "." + tableName + endQuotationMark 17 | : startQuotationMark + tableSchema + endQuotationMark + "." + startQuotationMark + tableName + endQuotationMark; 18 | } 19 | 20 | private string GetTableNameWithQuotes(JoinAttributeBase attrJoin, SqlPropertyMetadata[] props, string tableName) 21 | { 22 | switch (Provider) 23 | { 24 | case SqlProvider.MSSQL: 25 | tableName = "[" + tableName + "]"; 26 | attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, Provider, "[", "]"); 27 | attrJoin.Key = "[" + attrJoin.Key + "]"; 28 | attrJoin.ExternalKey = "[" + attrJoin.ExternalKey + "]"; 29 | attrJoin.TableAlias = string.IsNullOrEmpty(attrJoin.TableAlias) ? string.Empty : "[" + attrJoin.TableAlias + "]"; 30 | foreach (var prop in props) 31 | { 32 | prop.ColumnName = "[" + prop.CleanColumnName + "]"; 33 | } 34 | 35 | break; 36 | 37 | case SqlProvider.MySQL: 38 | case SqlProvider.SQLite: 39 | tableName = "`" + tableName + "`"; 40 | attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, Provider, "`", "`"); 41 | attrJoin.Key = "`" + attrJoin.Key + "`"; 42 | attrJoin.ExternalKey = "`" + attrJoin.ExternalKey + "`"; 43 | attrJoin.TableAlias = string.IsNullOrEmpty(attrJoin.TableAlias) ? string.Empty : "`" + attrJoin.TableAlias + "`"; 44 | foreach (var prop in props) 45 | { 46 | prop.ColumnName = "`" + prop.CleanColumnName + "`"; 47 | } 48 | 49 | break; 50 | 51 | case SqlProvider.PostgreSQL: 52 | tableName = "\"" + tableName + "\""; 53 | attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, Provider, "\"", "\""); 54 | attrJoin.Key = "\"" + attrJoin.Key + "\""; 55 | attrJoin.ExternalKey = "\"" + attrJoin.ExternalKey + "\""; 56 | attrJoin.TableAlias = string.IsNullOrEmpty(attrJoin.TableAlias) ? string.Empty : "\"" + attrJoin.TableAlias + "\""; 57 | foreach (var prop in props) 58 | { 59 | prop.ColumnName = "\"" + prop.CleanColumnName + "\""; 60 | } 61 | 62 | break; 63 | 64 | default: 65 | throw new ArgumentOutOfRangeException(nameof(Provider)); 66 | } 67 | 68 | return tableName; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.InitConfig.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System; 3 | using System.Data; 4 | 5 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 6 | 7 | 8 | public partial class SqlGenerator 9 | where TEntity : class 10 | { 11 | /// 12 | /// Init type Sql provider 13 | /// 14 | private void InitConfig() 15 | { 16 | if (UseQuotationMarks == true) 17 | { 18 | switch (Provider) 19 | { 20 | case SqlProvider.MSSQL: 21 | InitMetaData("[", "]"); 22 | break; 23 | 24 | case SqlProvider.MySQL: 25 | case SqlProvider.SQLite: 26 | InitMetaData("`", "`"); 27 | break; 28 | 29 | case SqlProvider.PostgreSQL: 30 | InitMetaData("\"", "\""); 31 | break; 32 | default: 33 | throw new ArgumentOutOfRangeException(nameof(Provider)); 34 | } 35 | } 36 | else 37 | { 38 | TableName = GetTableNameWithSchemaPrefix(TableName, TableSchema, Provider); 39 | foreach (var propertyMetadata in SqlJoinProperties) 40 | propertyMetadata.TableName = GetTableNameWithSchemaPrefix(propertyMetadata.TableName, propertyMetadata.TableSchema, Provider); 41 | } 42 | 43 | // set ParameterSymbol with : and mapping Boolean type to Int 44 | if (Provider == SqlProvider.Oracle) 45 | { 46 | ParameterSymbol = ":"; 47 | SqlMapper.AddTypeMap(typeof(bool), DbType.Int32); 48 | } 49 | } 50 | 51 | private void InitMetaData(string startQuotationMark, string endQuotationMark) 52 | { 53 | TableName = GetTableNameWithSchemaPrefix(TableName, TableSchema, Provider, startQuotationMark, endQuotationMark); 54 | 55 | foreach (var propertyMetadata in SqlProperties) 56 | propertyMetadata.ColumnName = startQuotationMark + propertyMetadata.CleanColumnName + endQuotationMark; 57 | 58 | foreach (var propertyMetadata in KeySqlProperties) 59 | propertyMetadata.ColumnName = startQuotationMark + propertyMetadata.CleanColumnName + endQuotationMark; 60 | 61 | foreach (var propertyMetadata in SqlJoinProperties) 62 | { 63 | propertyMetadata.TableName = GetTableNameWithSchemaPrefix(propertyMetadata.TableName, propertyMetadata.TableSchema, Provider, startQuotationMark, endQuotationMark); 64 | propertyMetadata.ColumnName = startQuotationMark + propertyMetadata.CleanColumnName + endQuotationMark; 65 | propertyMetadata.TableAlias = 66 | string.IsNullOrEmpty(propertyMetadata.TableAlias) ? string.Empty : startQuotationMark + propertyMetadata.TableAlias + endQuotationMark; 67 | } 68 | 69 | if (IdentitySqlProperty != null) 70 | IdentitySqlProperty.ColumnName = startQuotationMark + IdentitySqlProperty.CleanColumnName + endQuotationMark; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.InitLogicalDeleted.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 6 | using MicroOrm.Dapper.Repositories.Attributes.LogicalDelete; 7 | 8 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 9 | 10 | 11 | public partial class SqlGenerator 12 | where TEntity : class 13 | { 14 | private void InitLogicalDeleted() 15 | { 16 | var statusProperty = 17 | SqlProperties.FirstOrDefault(x => x.PropertyInfo.GetCustomAttribute() != null); 18 | 19 | foreach (var property in AllProperties) 20 | { 21 | var joinAttr = property.GetCustomAttribute(); 22 | if (joinAttr?.TableName == null) 23 | continue; 24 | 25 | //var deleted = joinProperty.JoinPropertyInfo.PropertyType.GetCustomAttribute(); 26 | var deleteAttr = property.PropertyType.GetProperties().FirstOrDefault(x => x.GetCustomAttribute() != null); 27 | if (deleteAttr == null) 28 | continue; 29 | 30 | JoinsLogicalDelete ??= new Dictionary(); 31 | JoinsLogicalDelete.Add(joinAttr.TableName, deleteAttr); 32 | } 33 | 34 | 35 | if (statusProperty == null) 36 | return; 37 | StatusPropertyName = statusProperty.ColumnName; 38 | 39 | if (statusProperty.PropertyInfo.PropertyType == typeof(bool)) 40 | { 41 | LogicalDelete = true; 42 | LogicalDeleteValue = Provider == SqlProvider.PostgreSQL ? "true" : 1; 43 | } 44 | else if (statusProperty.PropertyInfo.PropertyType == typeof(bool?)) 45 | { 46 | LogicalDelete = true; 47 | LogicalDeleteValue = Provider == SqlProvider.PostgreSQL ? "true" : 1; 48 | LogicalDeleteValueNullable = true; 49 | } 50 | else if (statusProperty.PropertyInfo.PropertyType.IsEnum) 51 | { 52 | var deleteOption = statusProperty.PropertyInfo.PropertyType.GetFields().FirstOrDefault(f => f.GetCustomAttribute() != null); 53 | 54 | if (deleteOption == null) 55 | return; 56 | 57 | var enumValue = Enum.Parse(statusProperty.PropertyInfo.PropertyType, deleteOption.Name); 58 | LogicalDeleteValue = Convert.ChangeType(enumValue, Enum.GetUnderlyingType(statusProperty.PropertyInfo.PropertyType)); 59 | 60 | LogicalDelete = true; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.InitProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Reflection; 7 | using MicroOrm.Dapper.Repositories.Attributes; 8 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 9 | using MicroOrm.Dapper.Repositories.Config; 10 | using MicroOrm.Dapper.Repositories.Extensions; 11 | 12 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 13 | 14 | 15 | public partial class SqlGenerator 16 | where TEntity : class 17 | { 18 | private void InitProperties() 19 | { 20 | var entityType = typeof(TEntity); 21 | var entityTypeInfo = entityType.GetTypeInfo(); 22 | var tableAttribute = entityTypeInfo.GetCustomAttribute(); 23 | 24 | TableName = MicroOrmConfig.TablePrefix + (tableAttribute != null ? tableAttribute.Name : entityTypeInfo.Name); 25 | 26 | TableSchema = tableAttribute != null ? tableAttribute.Schema : string.Empty; 27 | 28 | AllProperties = entityType.FindClassProperties().Where(q => q.CanWrite).ToArray(); 29 | 30 | var props = entityType.FindClassPrimitiveProperties(); 31 | 32 | var joinProperties = AllProperties.Where(p => p.GetCustomAttributes().Any()).ToArray(); 33 | 34 | SqlJoinProperties = GetJoinPropertyMetadata(joinProperties); 35 | 36 | // Filter the non stored properties 37 | SqlProperties = props.Where(p => !p.GetCustomAttributes().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); 38 | 39 | // Filter key properties 40 | KeySqlProperties = props.Where(p => p.GetCustomAttributes().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); 41 | 42 | // Use identity as key pattern 43 | var identityProperty = props.FirstOrDefault(p => p.GetCustomAttributes().Any()); 44 | if (identityProperty == null && MicroOrmConfig.AllowKeyAsIdentity) 45 | { 46 | identityProperty = props.FirstOrDefault(p => p.GetCustomAttributes().Any()); 47 | } 48 | 49 | IdentitySqlProperty = identityProperty != null ? new SqlPropertyMetadata(identityProperty) : null; 50 | 51 | var dateChangedProperty = props.FirstOrDefault(p => p.GetCustomAttributes().Any()); 52 | if (dateChangedProperty != null && (dateChangedProperty.PropertyType == typeof(DateTime) || dateChangedProperty.PropertyType == typeof(DateTime?))) 53 | { 54 | UpdatedAtProperty = dateChangedProperty; 55 | UpdatedAtPropertyMetadata = new SqlPropertyMetadata(UpdatedAtProperty); 56 | } 57 | } 58 | 59 | /// 60 | /// Get join/nested properties 61 | /// 62 | /// 63 | private static SqlJoinPropertyMetadata[] GetJoinPropertyMetadata(PropertyInfo[] joinPropertiesInfo) 64 | { 65 | // Filter and get only non collection nested properties 66 | var singleJoinTypes = joinPropertiesInfo.Where(p => !p.PropertyType.IsConstructedGenericType).ToArray(); 67 | 68 | var joinPropertyMetadatas = new List(); 69 | 70 | foreach (var propertyInfo in singleJoinTypes) 71 | { 72 | var joinInnerProperties = propertyInfo.PropertyType.GetProperties().Where(q => q.CanWrite) 73 | .Where(ExpressionHelper.GetPrimitivePropertiesPredicate()); 74 | joinPropertyMetadatas.AddRange(joinInnerProperties.Where(p => !p.GetCustomAttributes().Any()) 75 | .Select(p => new SqlJoinPropertyMetadata(propertyInfo, p)).ToArray()); 76 | } 77 | 78 | return joinPropertyMetadatas.ToArray(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Reflection; 5 | using MicroOrm.Dapper.Repositories.Config; 6 | 7 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 8 | 9 | 10 | public partial class SqlGenerator : ISqlGenerator 11 | where TEntity : class 12 | { 13 | /// 14 | /// Constructor 15 | /// 16 | public SqlGenerator() 17 | { 18 | Provider = MicroOrmConfig.SqlProvider; 19 | 20 | UseQuotationMarks ??= Provider != SqlProvider.Oracle && MicroOrmConfig.UseQuotationMarks; 21 | 22 | Initialize(); 23 | } 24 | 25 | private void Initialize() 26 | { 27 | // Order is important 28 | InitProperties(); 29 | InitConfig(); 30 | InitLogicalDeleted(); 31 | } 32 | 33 | /// 34 | /// Constructor with params 35 | /// 36 | public SqlGenerator(SqlProvider provider, bool useQuotationMarks) 37 | { 38 | Provider = provider; 39 | UseQuotationMarks = provider != SqlProvider.Oracle && useQuotationMarks; 40 | Initialize(); 41 | } 42 | 43 | /// 44 | /// Constructor with params 45 | /// 46 | public SqlGenerator(SqlProvider provider) 47 | { 48 | Provider = provider; 49 | UseQuotationMarks = false; 50 | Initialize(); 51 | } 52 | 53 | /// 54 | /// 55 | /// 56 | public SqlProvider Provider { get; } 57 | 58 | /// 59 | /// 60 | /// 61 | public bool? UseQuotationMarks { get; set; } 62 | 63 | 64 | public PropertyInfo[] AllProperties { get; protected set; } = Array.Empty(); 65 | 66 | 67 | [MemberNotNullWhen(true, nameof(UpdatedAtProperty), nameof(UpdatedAtPropertyMetadata))] 68 | public bool HasUpdatedAt => UpdatedAtProperty != null; 69 | 70 | 71 | public PropertyInfo? UpdatedAtProperty { get; protected set; } 72 | 73 | 74 | public SqlPropertyMetadata? UpdatedAtPropertyMetadata { get; protected set; } 75 | 76 | 77 | [MemberNotNullWhen(true, nameof(IdentitySqlProperty))] 78 | public bool IsIdentity => IdentitySqlProperty != null; 79 | 80 | 81 | public string TableName { get; protected set; } = string.Empty; 82 | 83 | 84 | public string? TableSchema { get; protected set; } 85 | 86 | 87 | public SqlPropertyMetadata? IdentitySqlProperty { get; protected set; } 88 | 89 | 90 | public SqlPropertyMetadata[] KeySqlProperties { get; protected set; } = Array.Empty(); 91 | 92 | 93 | public SqlPropertyMetadata[] SqlProperties { get; protected set; } = Array.Empty(); 94 | 95 | 96 | public SqlJoinPropertyMetadata[] SqlJoinProperties { get; protected set; }= Array.Empty(); 97 | 98 | 99 | public bool LogicalDelete { get; protected set; } 100 | 101 | 102 | public Dictionary? JoinsLogicalDelete { get; protected set; } 103 | 104 | 105 | public string? StatusPropertyName { get; protected set; } 106 | 107 | 108 | public object? LogicalDeleteValue { get; protected set; } 109 | 110 | public bool LogicalDeleteValueNullable { get; protected set; } 111 | 112 | /// 113 | /// In Oracle parameter should be build with : instead of @. 114 | /// 115 | public string ParameterSymbol { get; protected set; } = "@"; 116 | 117 | private enum QueryType 118 | { 119 | Select, 120 | Delete, 121 | Update 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlJoinPropertyMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 3 | 4 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 5 | 6 | /// 7 | /// 8 | public class SqlJoinPropertyMetadata : SqlPropertyMetadata 9 | { 10 | 11 | /// 12 | /// Metadata for join property info 13 | /// 14 | /// Table property info 15 | /// Table column property info 16 | public SqlJoinPropertyMetadata(PropertyInfo joinPropertyInfo, PropertyInfo propertyInfo) 17 | : base(propertyInfo) 18 | { 19 | var joinAtttribute = joinPropertyInfo.GetCustomAttribute(); 20 | JoinPropertyInfo = joinPropertyInfo; 21 | 22 | if (joinAtttribute == null) return; 23 | 24 | TableSchema = joinAtttribute.TableSchema; 25 | TableName = joinAtttribute.TableName; 26 | TableAlias = joinAtttribute.TableAlias; 27 | } 28 | 29 | /// 30 | /// Table name 31 | /// 32 | public string? TableName { get; set; } 33 | 34 | /// 35 | /// Table alias 36 | /// 37 | public string? TableAlias { get; set; } 38 | 39 | /// 40 | /// Schema name 41 | /// 42 | public string? TableSchema { get; set; } 43 | 44 | /// 45 | /// Original join property info 46 | /// 47 | public PropertyInfo JoinPropertyInfo { get; set; } 48 | 49 | /// 50 | /// Join property name 51 | /// 52 | public string JoinPropertyName => JoinPropertyInfo.Name; 53 | 54 | /// 55 | /// Full property name 56 | /// 57 | public override string PropertyName => JoinPropertyName + base.PropertyName; 58 | } 59 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlPropertyMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using System.Reflection; 4 | using MicroOrm.Dapper.Repositories.Attributes; 5 | 6 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 7 | 8 | /// 9 | /// Metadata from PropertyInfo 10 | /// 11 | public class SqlPropertyMetadata 12 | { 13 | /// 14 | /// Constructor 15 | /// 16 | public SqlPropertyMetadata(PropertyInfo propertyInfo) 17 | { 18 | PropertyInfo = propertyInfo; 19 | var alias = PropertyInfo.GetCustomAttribute(); 20 | if (alias != null && !string.IsNullOrEmpty(alias.Name)) 21 | { 22 | Alias = alias.Name; 23 | ColumnName = Alias; 24 | } 25 | else 26 | { 27 | ColumnName = PropertyInfo.Name; 28 | } 29 | 30 | CleanColumnName = ColumnName; 31 | 32 | var ignoreUpdate = PropertyInfo.GetCustomAttribute(); 33 | if (ignoreUpdate != null) 34 | IgnoreUpdate = true; 35 | 36 | IsNullable = propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>); 37 | } 38 | 39 | /// 40 | /// Original PropertyInfo 41 | /// 42 | public PropertyInfo PropertyInfo { get; } 43 | 44 | /// 45 | /// Alias for ColumnName 46 | /// 47 | public string? Alias { get; set; } 48 | 49 | /// 50 | /// ColumnName 51 | /// 52 | public string ColumnName { get; set; } 53 | 54 | /// 55 | /// ColumnName 56 | /// 57 | public string CleanColumnName { get; set; } 58 | 59 | /// 60 | /// Exclude property from update 61 | /// 62 | public bool IgnoreUpdate { get; set; } 63 | 64 | /// 65 | /// Check if is nullable 66 | /// 67 | public bool IsNullable { get; set; } 68 | 69 | /// 70 | /// PropertyName 71 | /// 72 | public virtual string PropertyName => PropertyInfo.Name; 73 | } 74 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 2 | 3 | // ReSharper disable InconsistentNaming 4 | /// 5 | /// Type Sql provider 6 | /// 7 | public enum SqlProvider 8 | { 9 | /// 10 | /// MSSQL 11 | /// 12 | MSSQL, 13 | 14 | /// 15 | /// MySQL 16 | /// 17 | MySQL, 18 | 19 | /// 20 | /// PostgreSQL 21 | /// 22 | PostgreSQL, 23 | 24 | /// 25 | /// SQLite 26 | /// 27 | SQLite, 28 | 29 | /// 30 | /// Oracle 31 | /// 32 | Oracle 33 | } 34 | -------------------------------------------------------------------------------- /src/SqlGenerator/SqlQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace MicroOrm.Dapper.Repositories.SqlGenerator; 4 | 5 | /// 6 | /// A object with the generated sql and dynamic params. 7 | /// 8 | public class SqlQuery 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public SqlQuery() 14 | { 15 | SqlBuilder = new StringBuilder(); 16 | } 17 | 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The param. 23 | public SqlQuery(object param) 24 | : this() 25 | { 26 | Param = param; 27 | } 28 | 29 | /// 30 | /// SqlBuilder 31 | /// 32 | public StringBuilder SqlBuilder { get; } 33 | 34 | /// 35 | /// Gets the param 36 | /// 37 | public object? Param { get; private set; } 38 | 39 | /// 40 | /// Gets the SQL. 41 | /// 42 | public virtual string GetSql() 43 | { 44 | return SqlBuilder.ToString().Trim(); 45 | } 46 | 47 | /// 48 | /// Set alternative param 49 | /// 50 | /// The param. 51 | public virtual void SetParam(object param) 52 | { 53 | Param = param; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_diagnostic.CA2007.severity = none # Use ConfigureAwait (not needed in test projects) 3 | resharper_use_configure_await_false_highlighting=none 4 | -------------------------------------------------------------------------------- /tests/.env: -------------------------------------------------------------------------------- 1 | TEST_DB_PASS=Password12! 2 | -------------------------------------------------------------------------------- /tests/Repositories.Base/BaseDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Threading.Tasks; 3 | using MicroOrm.Dapper.Repositories.DbContext; 4 | using Xunit; 5 | 6 | namespace Repositories.Base; 7 | 8 | public abstract class BaseDbContextTests 9 | { 10 | protected abstract IDapperDbContext CreateContext(); 11 | 12 | [Fact] 13 | public void Connection_ReturnsOpenConnection() 14 | { 15 | using var context = CreateContext(); 16 | 17 | var connection = context.Connection; 18 | 19 | Assert.NotNull(connection); 20 | Assert.Equal(ConnectionState.Open, connection.State); 21 | } 22 | 23 | [Fact] 24 | public void OpenConnection_WhenConnectionClosed_OpensConnection() 25 | { 26 | using var context = CreateContext(); 27 | if (context.Connection.State != ConnectionState.Closed) 28 | context.Connection.Close(); 29 | 30 | context.OpenConnection(); 31 | 32 | Assert.Equal(ConnectionState.Open, context.Connection.State); 33 | } 34 | 35 | [Fact] 36 | public void OpenConnection_WhenConnectionAlreadyOpen_DoesNothing() 37 | { 38 | using var context = CreateContext(); 39 | context.OpenConnection(); 40 | 41 | context.OpenConnection(); 42 | 43 | Assert.Equal(ConnectionState.Open, context.Connection.State); 44 | } 45 | 46 | [Fact] 47 | public void BeginTransaction_ReturnsValidTransaction() 48 | { 49 | using var context = CreateContext(); 50 | 51 | using var transaction = context.BeginTransaction(); 52 | 53 | Assert.NotNull(transaction); 54 | Assert.Equal(ConnectionState.Open, context.Connection.State); 55 | 56 | transaction.Rollback(); 57 | } 58 | 59 | [Fact] 60 | public void Dispose_ClosesOpenConnection() 61 | { 62 | var context = CreateContext(); 63 | var connection = context.Connection; 64 | Assert.Equal(ConnectionState.Open, connection.State); 65 | 66 | context.Dispose(); 67 | 68 | Assert.Equal(ConnectionState.Closed, connection.State); 69 | } 70 | 71 | [Fact] 72 | public void Dispose_DoesNothingIfConnectionAlreadyClosed() 73 | { 74 | var context = CreateContext(); 75 | var connection = context.Connection; 76 | connection.Close(); 77 | 78 | context.Dispose(); 79 | 80 | Assert.Equal(ConnectionState.Closed, connection.State); 81 | } 82 | 83 | [Fact] 84 | public async Task OpenConnectionAsync_WhenConnectionClosed_OpensConnection() 85 | { 86 | using var context = CreateContext(); 87 | if (context.Connection.State != ConnectionState.Closed) 88 | context.Connection.Close(); 89 | 90 | await context.OpenConnectionAsync(); 91 | 92 | Assert.Equal(ConnectionState.Open, context.Connection.State); 93 | } 94 | 95 | [Fact] 96 | public async Task OpenConnectionAsync_WhenConnectionAlreadyOpen_DoesNothing() 97 | { 98 | using var context = CreateContext(); 99 | await context.OpenConnectionAsync(); 100 | 101 | await context.OpenConnectionAsync(); 102 | 103 | Assert.Equal(ConnectionState.Open, context.Connection.State); 104 | } 105 | 106 | [Fact] 107 | public async Task BeginTransactionAsync_ReturnsValidTransaction() 108 | { 109 | using var context = CreateContext(); 110 | 111 | using var transaction = await context.BeginTransactionAsync(); 112 | 113 | Assert.NotNull(transaction); 114 | Assert.Equal(ConnectionState.Open, context.Connection.State); 115 | 116 | transaction.Rollback(); 117 | } 118 | 119 | [Fact] 120 | public async Task BeginTransactionAsync_AutomaticallyOpensConnection() 121 | { 122 | using var context = CreateContext(); 123 | if (context.Connection.State != ConnectionState.Closed) 124 | context.Connection.Close(); 125 | 126 | using var transaction = await context.BeginTransactionAsync(); 127 | 128 | Assert.NotNull(transaction); 129 | Assert.Equal(ConnectionState.Open, context.Connection.State); 130 | 131 | transaction.Rollback(); 132 | } 133 | 134 | [Fact] 135 | public async Task ConnectionAndTransactionOperations_WorkTogether() 136 | { 137 | using var context = CreateContext(); 138 | 139 | var connection = context.Connection; 140 | Assert.Equal(ConnectionState.Open, connection.State); 141 | 142 | using var transaction = await context.BeginTransactionAsync(); 143 | Assert.NotNull(transaction); 144 | 145 | connection.Close(); 146 | Assert.Equal(ConnectionState.Closed, connection.State); 147 | 148 | await context.OpenConnectionAsync(); 149 | Assert.Equal(ConnectionState.Open, connection.State); 150 | 151 | using var transaction2 = context.BeginTransaction(); 152 | Assert.NotNull(transaction2); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tests/Repositories.Base/DotEnv.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Repositories.Base; 5 | 6 | public static class DotEnv 7 | { 8 | private const string _fileName = ".env"; 9 | 10 | static DotEnv() 11 | { 12 | if (!File.Exists(_fileName)) 13 | return; 14 | 15 | foreach (var line in File.ReadAllLines(_fileName)) 16 | { 17 | var parts = line.Split( 18 | '=', 19 | StringSplitOptions.RemoveEmptyEntries); 20 | 21 | if (parts.Length != 2) 22 | continue; 23 | 24 | Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1].Trim()); 25 | } 26 | } 27 | 28 | public static string GetTestDbPass() => Environment.GetEnvironmentVariable("TEST_DB_PASS"); 29 | } 30 | -------------------------------------------------------------------------------- /tests/Repositories.Base/InitData.cs: -------------------------------------------------------------------------------- 1 | using TestClasses; 2 | 3 | namespace Repositories.Base; 4 | 5 | public static class InitData 6 | { 7 | public static void Execute(TestDbContext db) 8 | { 9 | db.Address.Insert(new Address { Street = "Street0", CityId = "MSK" }); 10 | 11 | db.Phones.Insert(new Phone { PNumber = "123", IsActive = true, Code = "UK" }); 12 | db.Phones.Insert(new Phone { PNumber = "333", IsActive = false, Code = "UK" }); 13 | 14 | for (var i = 0; i < 10; i++) 15 | db.Users.Insert(new User 16 | { 17 | Name = $"TestName{i}", 18 | AddressId = 1, 19 | PhoneId = 1, 20 | OfficePhoneId = 2 21 | }); 22 | 23 | db.Users.Insert(new User { Name = "TestName0", PhoneId = 1 }); 24 | db.Cars.Insert(new Car { Name = "TestCar0", UserId = 1 }); 25 | db.Cars.Insert(new Car { Name = "TestCar1", UserId = 1 }); 26 | 27 | db.Reports.Insert(new Report() { Id = 1, AnotherId = 2, UserId = 1 }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Repositories.Base/RandomGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Repositories.Base; 5 | 6 | public static class RandomGenerator 7 | { 8 | private const string _chars = "0123456789abcdefghijklmnopqrstuvwxyz"; 9 | private static readonly Random _random = new(); 10 | 11 | public static string String() 12 | { 13 | var output = new StringBuilder(); 14 | 15 | for (int i = 0; i < 10; i++) 16 | output.Append(_chars[_random.Next(_chars.Length)]); 17 | 18 | return output.ToString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Repositories.Base/Repositories.Base.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/Repositories.Base/TestDbContext.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using MicroOrm.Dapper.Repositories; 3 | using MicroOrm.Dapper.Repositories.DbContext; 4 | using MicroOrm.Dapper.Repositories.SqlGenerator; 5 | using TestClasses; 6 | 7 | namespace Repositories.Base; 8 | 9 | public class TestDbContext : DapperDbContext 10 | { 11 | private IDapperRepository
_address; 12 | 13 | private IDapperRepository _cars; 14 | 15 | private IDapperRepository _users; 16 | 17 | private IDapperRepository _cities; 18 | 19 | private IDapperRepository _reports; 20 | 21 | private IDapperRepository _phones; 22 | 23 | private readonly SqlProvider _provider; 24 | private readonly bool _useQuotationMarks; 25 | 26 | public TestDbContext(IDbConnection connection, SqlProvider provider, bool useQuotationMarks = false) 27 | : base(connection) 28 | { 29 | _provider = provider; 30 | _useQuotationMarks = useQuotationMarks; 31 | } 32 | 33 | public IDapperRepository
Address => _address ??= new DapperRepository
(Connection, new SqlGenerator
(_provider, _useQuotationMarks)); 34 | 35 | public IDapperRepository Users => _users ??= new DapperRepository(Connection, new SqlGenerator(_provider, _useQuotationMarks)); 36 | 37 | public IDapperRepository Cars => _cars ??= new DapperRepository(Connection, new SqlGenerator(_provider, _useQuotationMarks)); 38 | 39 | public IDapperRepository Cities => _cities ??= new DapperRepository(Connection, new SqlGenerator(_provider, _useQuotationMarks)); 40 | 41 | public IDapperRepository Reports => _reports ??= new DapperRepository(Connection, new SqlGenerator(_provider, _useQuotationMarks)); 42 | 43 | public IDapperRepository Phones => _phones ??= new DapperRepository(Connection, new SqlGenerator(_provider, _useQuotationMarks)); 44 | } 45 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.MSSQL.Tests; 6 | 7 | public abstract class DatabaseFixture : IDisposable 8 | { 9 | protected DatabaseFixture(TestDbContext db) 10 | { 11 | Db = db; 12 | DropDatabase(); 13 | InitDb(); 14 | } 15 | 16 | private readonly string _dbName = "test_" + RandomGenerator.String(); 17 | public TestDbContext Db { get; } 18 | 19 | private void InitDb() 20 | { 21 | Db.Connection.Execute($"IF NOT EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = '{_dbName}') CREATE DATABASE [{_dbName}];"); 22 | Db.Connection.Execute($"USE [{_dbName}]"); 23 | 24 | void CreateSchema(string dbSchema) 25 | { 26 | Db.Connection.Execute($@"IF schema_id('{dbSchema}') IS NULL EXECUTE('CREATE SCHEMA {dbSchema}') "); 27 | } 28 | 29 | CreateSchema("DAB"); 30 | 31 | Db.Connection.Execute( 32 | @"CREATE TABLE Users (Id int IDENTITY(1,1) not null, Name varchar(256) not null, AddressId int not null, PhoneId int not null, OfficePhoneId int not null, Deleted bit, UpdatedAt datetime2, PRIMARY KEY (Id))"); 33 | Db.Connection.Execute( 34 | @"CREATE TABLE Cars (Id int IDENTITY(1,1) not null, Name varchar(256) not null, UserId int not null, Status int not null, Data binary(16) null, PRIMARY KEY (Id))"); 35 | 36 | Db.Connection.Execute(@"CREATE TABLE Addresses (Id int IDENTITY(1,1) not null, Street varchar(256) not null, CityId varchar(256) not null, PRIMARY KEY (Id))"); 37 | Db.Connection.Execute(@"CREATE TABLE Cities (Identifier uniqueidentifier not null, Name varchar(256) not null)"); 38 | Db.Connection.Execute(@"CREATE TABLE Reports (Id int not null, AnotherId int not null, UserId int not null, PRIMARY KEY (Id, AnotherId))"); 39 | Db.Connection.Execute( 40 | @"CREATE TABLE DAB.Phones (Id int IDENTITY(1,1) not null, PNumber varchar(256) not null, IsActive bit not null, Code varchar(256) not null, PRIMARY KEY (Id))"); 41 | 42 | InitData.Execute(Db); 43 | } 44 | 45 | private void DropDatabase() 46 | { 47 | Db.Connection.Execute($"USE master; DROP DATABASE IF EXISTS {_dbName}"); 48 | } 49 | 50 | public void Dispose() 51 | { 52 | DropDatabase(); 53 | Db.Dispose(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/MicrosoftDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.SqlGenerator; 2 | using Microsoft.Data.SqlClient; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.MSSQL.Tests; 6 | 7 | public class MicrosoftDatabaseFixture() : DatabaseFixture(new TestDbContext( 8 | new SqlConnection($"Server=localhost;Database=master;User ID=sa;Password={DotEnv.GetTestDbPass()};Trust Server Certificate=true"), SqlProvider.MSSQL)); 9 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/MicrosoftDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.MSSQL.Tests; 6 | 7 | public class MicrosoftDbContextTests(MicrosoftDatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/MicrosoftRepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Repositories.MSSQL.Tests; 2 | 3 | public class MicrosoftRepositoriesTests(MicrosoftDatabaseFixture fixture) : RepositoriesTests(fixture); 4 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/Repositories.MSSQL.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | opencover 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/SystemDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System.Data.SqlClient; 2 | using MicroOrm.Dapper.Repositories.SqlGenerator; 3 | using Repositories.Base; 4 | 5 | #pragma warning disable CS0618 // Type or member is obsolete 6 | 7 | namespace Repositories.MSSQL.Tests; 8 | 9 | public class SystemDatabaseFixture() 10 | : DatabaseFixture(new TestDbContext(new SqlConnection($"Server=localhost;Database=master;User ID=sa;Password={DotEnv.GetTestDbPass()}"), SqlProvider.MSSQL)); 11 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/SystemDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.MSSQL.Tests; 6 | 7 | public class SystemDbContextTests(SystemDatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.MSSQL.Tests/SystemRepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Repositories.MSSQL.Tests; 2 | 3 | public class SystemRepositoriesTests(SystemDatabaseFixture fixture) : RepositoriesTests(fixture); 4 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.MySql.Tests; 6 | 7 | public class DatabaseFixture : IDisposable 8 | { 9 | private const string _dbName = "test_main"; // DisableTestParallelization=true 10 | 11 | protected DatabaseFixture(TestDbContext db) 12 | { 13 | Db = db; 14 | DropDatabase(); 15 | InitDb(); 16 | } 17 | 18 | public TestDbContext Db { get; } 19 | 20 | public void Dispose() 21 | { 22 | DropDatabase(); 23 | Db.Dispose(); 24 | } 25 | 26 | private void InitDb() 27 | { 28 | Db.Connection.Execute($"CREATE DATABASE IF NOT EXISTS `{_dbName}`;"); 29 | Db.Connection.Execute($"CREATE DATABASE IF NOT EXISTS DAB;"); 30 | 31 | Db.Connection.Execute($"USE `{_dbName}`"); 32 | 33 | 34 | Db.Connection.Execute($"USE `DAB`"); 35 | Db.Connection.Execute("CREATE TABLE IF NOT EXISTS `Phones` " + 36 | "(`Id` int not null auto_increment, `PNumber` varchar(256) not null, " + 37 | "`IsActive` boolean not null, `Code` varchar(256) not null, PRIMARY KEY (`Id`));"); 38 | 39 | Db.Connection.Execute($"USE `{_dbName}`"); 40 | 41 | Db.Connection.Execute("CREATE TABLE IF NOT EXISTS `Users` " + 42 | "(`Id` int not null auto_increment, `Name` varchar(256) not null, `AddressId` int not null, `PhoneId` int not null, " + 43 | "`OfficePhoneId` int not null, `Deleted` boolean, `UpdatedAt` datetime, PRIMARY KEY (`Id`));"); 44 | 45 | Db.Connection.Execute("CREATE TABLE IF NOT EXISTS `Cars` " + 46 | "(`Id` int not null auto_increment, `Name` varchar(256) not null, " + 47 | "`UserId` int not null, `Status` int not null, Data binary(16) null, PRIMARY KEY (`Id`));"); 48 | 49 | Db.Connection.Execute("CREATE TABLE IF NOT EXISTS `Addresses`" + 50 | "(`Id` int not null auto_increment, `Street` varchar(256) not null, " + 51 | "`CityId` varchar(256) not null, PRIMARY KEY (`Id`));"); 52 | 53 | Db.Connection.Execute("CREATE TABLE IF NOT EXISTS `Cities`" + 54 | "(`Id` int not null auto_increment, `Name` varchar(256) not null, `Identifier` char(36) not null, " + 55 | "PRIMARY KEY (`Id`));"); 56 | 57 | Db.Connection.Execute("CREATE TABLE IF NOT EXISTS `Reports`" + 58 | "(`Id` int not null auto_increment, `AnotherId` int not null, `UserId` int not null, " + 59 | "PRIMARY KEY (`Id`, `AnotherId`));"); 60 | 61 | 62 | InitData.Execute(Db); 63 | } 64 | 65 | private void DropDatabase() 66 | { 67 | Db.Connection.Execute($"DROP DATABASE IF EXISTS {_dbName}"); 68 | Db.Connection.Execute("DROP DATABASE IF EXISTS DAB"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/MySqlClientDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.SqlGenerator; 2 | using MySql.Data.MySqlClient; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.MySql.Tests; 6 | 7 | public class MySqlClientDatabaseFixture() 8 | : DatabaseFixture(new TestDbContext(new MySqlConnection($"Server=localhost;Uid=root;Pwd={DotEnv.GetTestDbPass()}"), SqlProvider.MySQL)); 9 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/MySqlClientDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.MySql.Tests; 6 | 7 | public class MySqlDbContextTests(MySqlClientDatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/MySqlClientRepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Repositories.MySql.Tests; 2 | 3 | public class MySqlClientRepositoriesTests(MySqlClientDatabaseFixture fixture) : RepositoriesTests(fixture); 4 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/MySqlConnectorDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.SqlGenerator; 2 | using MySqlConnector; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.MySql.Tests; 6 | 7 | public class MySqlConnectorDatabaseFixture() 8 | : DatabaseFixture(new TestDbContext(new MySqlConnection($"Server=localhost;Uid=root;Pwd={DotEnv.GetTestDbPass()}"), SqlProvider.MySQL)); 9 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/MySqlConnectorDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.MySql.Tests; 6 | 7 | public class MySqlConnectorDbContextTests(MySqlConnectorDatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/MySqlConnectorRepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Repositories.MySql.Tests; 7 | 8 | public class MySqlConnectorRepositoriesTests(MySqlConnectorDatabaseFixture fixture) : RepositoriesTests(fixture) 9 | { 10 | [Fact] 11 | public async Task CancellationTokenSource_Cancel() 12 | { 13 | using var cts = new CancellationTokenSource(); 14 | 15 | await cts.CancelAsync(); 16 | 17 | await Assert.ThrowsAnyAsync(() => Db.Address.FindAllAsync(cts.Token)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/Repositories.MySql.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | opencover 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/Repositories.MySql.Tests/RepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Repositories.Base; 4 | using TestClasses; 5 | using Xunit; 6 | 7 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 8 | 9 | namespace Repositories.MySql.Tests; 10 | 11 | public abstract class RepositoriesTests(DatabaseFixture fixture) : BaseRepositoriesTests(fixture.Db), IClassFixture 12 | where TFixture : DatabaseFixture 13 | { 14 | [Fact] 15 | public void InsertAndUpdate_WithGuid_WithoutKey() 16 | { 17 | var identifier = Guid.NewGuid(); 18 | var city = new City { Identifier = identifier, Name = "Moscow" }; 19 | 20 | Db.Cities.Insert(city); 21 | city = Db.Cities.Find(q => q.Identifier == identifier); 22 | 23 | Assert.NotNull(city); 24 | city.Name = "Moscow1"; 25 | Db.Cities.Update(q => q.Identifier == identifier, city); 26 | 27 | city = Db.Cities.Find(q => q.Identifier == identifier); 28 | Assert.NotNull(city); 29 | Assert.Equal("Moscow1", city.Name); 30 | } 31 | 32 | [Fact] 33 | public async Task InsertAndUpdate_WithGuid_WithoutKey_Async() 34 | { 35 | var identifier = Guid.NewGuid(); 36 | var city = new City { Identifier = identifier, Name = "Moscow" }; 37 | 38 | await Db.Cities.InsertAsync(city, TestContext.Current.CancellationToken); 39 | city = await Db.Cities.FindAsync(q => q.Identifier == identifier, TestContext.Current.CancellationToken); 40 | 41 | Assert.NotNull(city); 42 | city.Name = "Moscow1"; 43 | await Db.Cities.UpdateAsync(q => q.Identifier == identifier, city); 44 | 45 | city = await Db.Cities.FindAsync(q => q.Identifier == identifier, TestContext.Current.CancellationToken); 46 | Assert.NotNull(city); 47 | Assert.Equal("Moscow1", city.Name); 48 | } 49 | 50 | [Fact] 51 | public void UpdateBinaryDataToNull() 52 | { 53 | var guid = Guid.NewGuid(); 54 | 55 | var car = new Car 56 | { 57 | Data = guid.ToByteArray(), 58 | Name = "Car", 59 | Status = StatusCar.Active 60 | }; 61 | 62 | var insert = Db.Cars.Insert(car); 63 | Assert.True(insert); 64 | var carFromDb = Db.Cars.Find(x => x.Id == car.Id); 65 | Assert.NotNull(carFromDb!.Data); 66 | 67 | car.Data = null; 68 | var update = Db.Cars.Update(car); 69 | Assert.True(update); 70 | carFromDb = Db.Cars.Find(x => x.Id == car.Id); 71 | Assert.Null(carFromDb!.Data); 72 | } 73 | 74 | [Fact] 75 | public async Task UpdateBinaryDataToNullAsync() 76 | { 77 | var guid = Guid.NewGuid(); 78 | 79 | var car = new Car 80 | { 81 | Data = guid.ToByteArray(), 82 | Name = "Car", 83 | Status = StatusCar.Active 84 | }; 85 | 86 | var insert = Db.Cars != null && await Db.Cars.InsertAsync(car, TestContext.Current.CancellationToken); 87 | Assert.True(insert); 88 | var carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 89 | Assert.NotNull(carFromDb!.Data); 90 | 91 | car.Data = null; 92 | var update = await Db.Cars.UpdateAsync(car); 93 | Assert.True(update); 94 | carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 95 | Assert.Null(carFromDb!.Data); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Repositories.Oracle.Tests/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using MicroOrm.Dapper.Repositories.SqlGenerator; 4 | using Oracle.ManagedDataAccess.Client; 5 | using Repositories.Base; 6 | 7 | namespace Repositories.Oracle.Tests; 8 | 9 | public class DatabaseFixture : IDisposable 10 | { 11 | public DatabaseFixture() 12 | { 13 | Db = new TestDbContext( 14 | new OracleConnection( 15 | $"DATA SOURCE=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XEPDB1)));User Id=system;Password={DotEnv.GetTestDbPass()}"), 16 | SqlProvider.Oracle); 17 | ClearDb(); 18 | InitDb(); 19 | } 20 | 21 | public TestDbContext Db { get; } 22 | 23 | public void Dispose() 24 | { 25 | ClearDb(); 26 | Db.Dispose(); 27 | } 28 | 29 | private void InitDb() 30 | { 31 | SetupSchema(); 32 | Db.Connection.Execute( 33 | @"CREATE TABLE USERS ( 34 | ID NUMBER GENERATED ALWAYS AS IDENTITY(START WITH 1 INCREMENT BY 1), 35 | NAME VARCHAR(256) NOT NULL, 36 | ADDRESSID NUMBER NOT NULL, 37 | PHONEID NUMBER NOT NULL, 38 | OFFICEPHONEID NUMBER NOT NULL, 39 | DELETED NUMBER, 40 | UPDATEDAT DATE, 41 | PRIMARY KEY (ID))"); 42 | 43 | Db.Connection.Execute( 44 | @"CREATE TABLE CARS ( 45 | ID NUMBER GENERATED ALWAYS AS IDENTITY(START WITH 1 INCREMENT BY 1), 46 | NAME VARCHAR(256) NOT NULL, 47 | USERID NUMBER NOT NULL, 48 | STATUS NUMBER NOT NULL, 49 | DATA RAW(16) NULL, 50 | PRIMARY KEY (ID))"); 51 | 52 | Db.Connection.Execute( 53 | @"CREATE TABLE ADDRESSES ( 54 | ID NUMBER GENERATED ALWAYS AS IDENTITY(START WITH 1 INCREMENT BY 1), 55 | STREET VARCHAR(256) NOT NULL, 56 | CITYID VARCHAR(256) NOT NULL, 57 | PRIMARY KEY (ID))"); 58 | 59 | Db.Connection.Execute( 60 | @"CREATE TABLE CITIES ( 61 | IDENTIFIER RAW(16) NOT NULL, 62 | NAME VARCHAR(256) NOT NULL)"); 63 | 64 | Db.Connection.Execute( 65 | @"CREATE TABLE DAB.PHONES ( 66 | ID NUMBER GENERATED ALWAYS AS IDENTITY(START WITH 1 INCREMENT BY 1), 67 | PNUMBER VARCHAR(256) NOT NULL, 68 | ISACTIVE NUMBER(1) NOT NULL, 69 | CODE VARCHAR(256) NOT NULL, 70 | PRIMARY KEY (ID))"); 71 | 72 | Db.Connection.Execute( 73 | @"CREATE TABLE REPORTS ( 74 | ID NUMBER NOT NULL, 75 | ANOTHERID NUMBER NOT NULL, 76 | USERID NUMBER NOT NULL, 77 | PRIMARY KEY (ID, ANOTHERID))"); 78 | 79 | InitData.Execute(Db); 80 | } 81 | 82 | private void ClearDb() 83 | { 84 | void DropTable(string name) 85 | { 86 | Db.Connection.Execute($@"BEGIN 87 | EXECUTE IMMEDIATE 'DROP TABLE ' || '{name}'; 88 | EXCEPTION 89 | WHEN OTHERS THEN 90 | IF SQLCODE != -942 THEN 91 | RAISE; 92 | END IF; 93 | END;"); 94 | } 95 | 96 | DropTable("USERS"); 97 | DropTable("CARS"); 98 | DropTable("ADDRESSES"); 99 | DropTable("CITIES"); 100 | DropTable("REPORTS"); 101 | DropTable("PHONES"); 102 | } 103 | 104 | private void SetupSchema() 105 | { 106 | Db.Connection.Execute($@"BEGIN 107 | EXECUTE IMMEDIATE 'DROP USER DAB CASCADE'; 108 | EXCEPTION 109 | WHEN OTHERS THEN 110 | IF SQLCODE != -1918 THEN 111 | RAISE; 112 | END IF; 113 | END;"); 114 | Db.Connection.Execute($"CREATE USER DAB IDENTIFIED BY pwd"); 115 | Db.Connection.Execute($"ALTER USER DAB QUOTA UNLIMITED ON USERS"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Repositories.Oracle.Tests/DbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.Oracle.Tests; 6 | 7 | public class DbContextTests(DatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.Oracle.Tests/Repositories.Oracle.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | opencover 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/Repositories.Oracle.Tests/RepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Repositories.Base; 5 | using TestClasses; 6 | using Xunit; 7 | 8 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 9 | 10 | namespace Repositories.Oracle.Tests; 11 | 12 | public class RepositoriesTests(DatabaseFixture fixture) : BaseRepositoriesTests(fixture.Db), IClassFixture 13 | { 14 | [Fact] 15 | public async Task CancellationTokenSource_Cancel() 16 | { 17 | using var cts = new CancellationTokenSource(); 18 | 19 | await cts.CancelAsync(); 20 | 21 | await Assert.ThrowsAnyAsync(() => Db.Address.FindAllAsync(cts.Token)); 22 | } 23 | 24 | [Fact] 25 | public void UpdateBinaryDataToNull() 26 | { 27 | var guid = Guid.NewGuid(); 28 | 29 | var car = new Car 30 | { 31 | Data = guid.ToByteArray(), 32 | Name = "Car", 33 | Status = StatusCar.Active 34 | }; 35 | 36 | var insert = Db.Cars.Insert(car); 37 | Assert.True(insert); 38 | var carFromDb = Db.Cars.Find(x => x.Id == car.Id); 39 | Assert.NotNull(carFromDb!.Data); 40 | 41 | car.Data = null; 42 | var update = Db.Cars.Update(car); 43 | Assert.True(update); 44 | carFromDb = Db.Cars.Find(x => x.Id == car.Id); 45 | Assert.Null(carFromDb!.Data); 46 | } 47 | 48 | [Fact] 49 | public async Task UpdateBinaryDataToNullAsync() 50 | { 51 | var guid = Guid.NewGuid(); 52 | 53 | var car = new Car 54 | { 55 | Data = guid.ToByteArray(), 56 | Name = "Car", 57 | Status = StatusCar.Active 58 | }; 59 | 60 | var insert = await Db.Cars.InsertAsync(car, TestContext.Current.CancellationToken); 61 | Assert.True(insert); 62 | var carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 63 | Assert.NotNull(carFromDb!.Data); 64 | 65 | car.Data = null; 66 | var update = await Db.Cars.UpdateAsync(car); 67 | Assert.True(update); 68 | carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 69 | Assert.Null(carFromDb!.Data); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Repositories.PostgreSQL.Tests/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using MicroOrm.Dapper.Repositories.SqlGenerator; 4 | using Npgsql; 5 | using Repositories.Base; 6 | 7 | namespace Repositories.PostgreSQL.Tests; 8 | 9 | public class DatabaseFixture : IDisposable 10 | { 11 | private readonly string _dbName = "test_" + RandomGenerator.String(); 12 | private readonly string _masterConnString = $"Host=localhost;Username=postgres;Password={DotEnv.GetTestDbPass()}"; 13 | 14 | public DatabaseFixture() 15 | { 16 | Db = new TestDbContext(new NpgsqlConnection(_masterConnString + ";Database=" + _dbName), SqlProvider.PostgreSQL); 17 | DropDatabase(); 18 | InitDb(); 19 | } 20 | 21 | public TestDbContext Db { get; } 22 | 23 | public void Dispose() 24 | { 25 | Db.Dispose(); 26 | DropDatabase(); 27 | } 28 | 29 | protected void InitDb() 30 | { 31 | using var conn = new NpgsqlConnection(_masterConnString); 32 | conn.Execute($"CREATE DATABASE {_dbName};"); 33 | 34 | void CreateSchema(string dbSchema) 35 | { 36 | Db.Connection.Execute($"CREATE SCHEMA {dbSchema} "); 37 | } 38 | 39 | CreateSchema("DAB"); 40 | 41 | Db.Connection.Execute( 42 | @"CREATE TABLE Users (Id SERIAL not null, Name varchar(256) not null, AddressId int not null, PhoneId int not null, OfficePhoneId int not null, Deleted bool, UpdatedAt timestamp, PRIMARY KEY (Id))"); 43 | Db.Connection.Execute( 44 | @"CREATE TABLE Cars (Id SERIAL not null, Name varchar(256) not null, UserId int not null, Status int not null, Data bytea null, PRIMARY KEY (Id))"); 45 | 46 | Db.Connection.Execute(@"CREATE TABLE Addresses (Id SERIAL not null, Street varchar(256) not null, CityId varchar(256) not null, PRIMARY KEY (Id))"); 47 | Db.Connection.Execute(@"CREATE TABLE Cities (Identifier uuid not null, Name varchar(256) not null)"); 48 | Db.Connection.Execute(@"CREATE TABLE Reports (Id int not null, AnotherId int not null, UserId int not null, PRIMARY KEY (Id, AnotherId))"); 49 | Db.Connection.Execute( 50 | @"CREATE TABLE DAB.Phones (Id SERIAL not null, PNumber varchar(256) not null, IsActive bool not null, Code varchar(256) not null, PRIMARY KEY (Id))"); 51 | 52 | InitData.Execute(Db); 53 | } 54 | 55 | private void DropDatabase() 56 | { 57 | using var conn = new NpgsqlConnection(_masterConnString); 58 | conn.Execute($"SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE datname='{_dbName}' AND pid <> pg_backend_pid();"); 59 | conn.Execute($"DROP DATABASE IF EXISTS {_dbName}"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Repositories.PostgreSQL.Tests/DbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.PostgreSQL.Tests; 6 | 7 | public class DbContextTests(DatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.PostgreSQL.Tests/Repositories.PostgreSQL.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | opencover 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/Repositories.PostgreSQL.Tests/RepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Repositories.Base; 5 | using TestClasses; 6 | using Xunit; 7 | 8 | namespace Repositories.PostgreSQL.Tests; 9 | 10 | public class RepositoriesTests(DatabaseFixture fixture) : BaseRepositoriesTests(fixture.Db), IClassFixture 11 | { 12 | [Fact] 13 | public async Task CancellationTokenSource_Cancel() 14 | { 15 | using var cts = new CancellationTokenSource(); 16 | 17 | await cts.CancelAsync(); 18 | 19 | await Assert.ThrowsAnyAsync(() => Db.Address.FindAllAsync(cts.Token)); 20 | } 21 | 22 | [Fact] 23 | public void UpdateBinaryDataToNull() 24 | { 25 | var guid = Guid.NewGuid(); 26 | 27 | var car = new Car 28 | { 29 | Data = guid.ToByteArray(), 30 | Name = "Car", 31 | Status = StatusCar.Active 32 | }; 33 | 34 | var insert = Db.Cars.Insert(car); 35 | Assert.True(insert); 36 | var carFromDb = Db.Cars.Find(x => x.Id == car.Id); 37 | Assert.NotNull(carFromDb!.Data); 38 | 39 | car.Data = null; 40 | var update = Db.Cars.Update(car); 41 | Assert.True(update); 42 | carFromDb = Db.Cars.Find(x => x.Id == car.Id); 43 | Assert.Null(carFromDb!.Data); 44 | } 45 | 46 | [Fact] 47 | public async Task UpdateBinaryDataToNullAsync() 48 | { 49 | var guid = Guid.NewGuid(); 50 | 51 | var car = new Car 52 | { 53 | Data = guid.ToByteArray(), 54 | Name = "Car", 55 | Status = StatusCar.Active 56 | }; 57 | 58 | var insert = await Db.Cars.InsertAsync(car, TestContext.Current.CancellationToken); 59 | Assert.True(insert); 60 | var carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 61 | Assert.NotNull(carFromDb!.Data); 62 | 63 | car.Data = null; 64 | var update = await Db.Cars.UpdateAsync(car); 65 | Assert.True(update); 66 | carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 67 | Assert.Null(carFromDb!.Data); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.SQLite.Tests; 6 | 7 | public abstract class DatabaseFixture : IDisposable 8 | { 9 | protected DatabaseFixture(TestDbContext db) 10 | { 11 | Db = db; 12 | InitDb(); 13 | } 14 | 15 | public TestDbContext Db { get; } 16 | 17 | public void Dispose() 18 | { 19 | Db.Dispose(); 20 | } 21 | 22 | private void InitDb() 23 | { 24 | Db.Connection.Execute("CREATE TABLE `DAB.Phones` " + 25 | "(`Id` integer not null primary key autoincrement, `PNumber` varchar(256) not null, " + 26 | "`IsActive` boolean not null, `Code` varchar(256) not null);"); 27 | 28 | Db.Connection.Execute("CREATE TABLE `Users` " + 29 | "(`Id` integer not null primary key autoincrement, `Name` varchar(256) not null, `AddressId` integer not null, `PhoneId` integer not null, " + 30 | "`OfficePhoneId` integer not null, `Deleted` boolean, `UpdatedAt` datetime);"); 31 | 32 | Db.Connection.Execute("CREATE TABLE `Cars` " + 33 | "(`Id` integer not null primary key autoincrement, `Name` varchar(256) not null, " + 34 | "`UserId` integer not null, `Status` integer not null, Data binary(16) null);"); 35 | 36 | Db.Connection.Execute("CREATE TABLE `Addresses`" + 37 | "(`Id` integer not null primary key autoincrement, `Street` varchar(256) not null, " + 38 | "`CityId` varchar(256) not null);"); 39 | 40 | Db.Connection.Execute("CREATE TABLE `Cities`" + 41 | "(`Id` integer not null primary key autoincrement, `Name` varchar(256) not null, `Identifier` UNIQUEIDENTIFIER not null);"); 42 | 43 | Db.Connection.Execute("CREATE TABLE `Reports`" + 44 | "(`Id` integer not null primary key autoincrement, `AnotherId` integer not null, `UserId` integer not null, " + 45 | "UNIQUE (`Id`, `AnotherId`));"); 46 | 47 | 48 | InitData.Execute(Db); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/MicrosoftDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.SqlGenerator; 2 | using Microsoft.Data.Sqlite; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.SQLite.Tests; 6 | 7 | public class MicrosoftDatabaseFixture() 8 | : DatabaseFixture(new TestDbContext(new SqliteConnection($"Data Source={RandomGenerator.String()};Mode=Memory;Cache=Shared"), SqlProvider.SQLite, true)); 9 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/MicrosoftDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.SQLite.Tests; 6 | 7 | public class MicrosoftDbContextTests(MicrosoftDatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/MicrosoftRepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Repositories.SQLite.Tests; 2 | 3 | public class MicrosoftRepositoriesTests(MicrosoftDatabaseFixture fixture) : RepositoriesTests(fixture); 4 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/Repositories.SQLite.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | opencover 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/RepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Repositories.Base; 5 | using TestClasses; 6 | using Xunit; 7 | 8 | namespace Repositories.SQLite.Tests; 9 | 10 | public abstract class RepositoriesTests(TFixture fixture) : BaseRepositoriesTests(fixture.Db), IClassFixture 11 | where TFixture : DatabaseFixture 12 | { 13 | [Fact] 14 | public async Task CancellationTokenSource_Cancel() 15 | { 16 | using var cts = new CancellationTokenSource(); 17 | 18 | await cts.CancelAsync(); 19 | 20 | await Assert.ThrowsAnyAsync(() => Db.Address.FindAllAsync(cts.Token)); 21 | } 22 | 23 | [Fact] 24 | public void UpdateBinaryDataToNull() 25 | { 26 | var guid = Guid.NewGuid(); 27 | 28 | var car = new Car 29 | { 30 | Data = guid.ToByteArray(), 31 | Name = "Car", 32 | Status = StatusCar.Active 33 | }; 34 | 35 | var insert = Db.Cars.Insert(car); 36 | Assert.True(insert); 37 | var carFromDb = Db.Cars.Find(x => x.Id == car.Id); 38 | Assert.NotNull(carFromDb!.Data); 39 | 40 | car.Data = null; 41 | var update = Db.Cars.Update(car); 42 | Assert.True(update); 43 | carFromDb = Db.Cars.Find(x => x.Id == car.Id); 44 | Assert.Null(carFromDb!.Data); 45 | } 46 | 47 | [Fact] 48 | public async Task UpdateBinaryDataToNullAsync() 49 | { 50 | var guid = Guid.NewGuid(); 51 | 52 | var car = new Car 53 | { 54 | Data = guid.ToByteArray(), 55 | Name = "Car", 56 | Status = StatusCar.Active 57 | }; 58 | 59 | var insert = await Db.Cars.InsertAsync(car, TestContext.Current.CancellationToken); 60 | Assert.True(insert); 61 | var carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 62 | Assert.NotNull(carFromDb!.Data); 63 | 64 | car.Data = null; 65 | var update = await Db.Cars.UpdateAsync(car); 66 | Assert.True(update); 67 | carFromDb = await Db.Cars.FindAsync(x => x.Id == car.Id, TestContext.Current.CancellationToken); 68 | Assert.Null(carFromDb!.Data); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/SystemDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System.Data.SQLite; 2 | using MicroOrm.Dapper.Repositories.SqlGenerator; 3 | using Repositories.Base; 4 | 5 | namespace Repositories.SQLite.Tests; 6 | 7 | public class SystemDatabaseFixture() 8 | : DatabaseFixture(new TestDbContext(new SQLiteConnection($"Data Source={RandomGenerator.String()};Mode=Memory;Cache=Shared"), SqlProvider.SQLite, true)); 9 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/SystemDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using MicroOrm.Dapper.Repositories.DbContext; 2 | using Repositories.Base; 3 | using Xunit; 4 | 5 | namespace Repositories.SQLite.Tests; 6 | 7 | public class SystemDbContextTests(SystemDatabaseFixture fixture) : BaseDbContextTests, IClassFixture 8 | { 9 | private readonly IDapperDbContext _context = fixture.Db; 10 | 11 | protected override IDapperDbContext CreateContext() 12 | { 13 | return _context; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Repositories.SQLite.Tests/SystemRepositoriesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Repositories.SQLite.Tests; 2 | 3 | public class SystemRepositoriesTests(SystemDatabaseFixture fixture) : RepositoriesTests(fixture); 4 | -------------------------------------------------------------------------------- /tests/SqlGenerator.Tests/ExpressionHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MicroOrm.Dapper.Repositories.SqlGenerator; 3 | using Xunit; 4 | 5 | namespace SqlGenerator.Tests; 6 | 7 | public class ExpressionHelperTests 8 | { 9 | [Fact] 10 | public void GetMethodCallSqlOperator_Contains() 11 | { 12 | var result = ExpressionHelper.GetMethodCallSqlOperator("Contains"); 13 | Assert.Equal("IN", result); 14 | } 15 | 16 | [Fact] 17 | public void GetMethodCallSqlOperator_NotContains() 18 | { 19 | var result = ExpressionHelper.GetMethodCallSqlOperator("Contains", true); 20 | Assert.Equal("NOT IN", result); 21 | } 22 | 23 | [Fact] 24 | public void GetMethodCallSqlOperator_Any() 25 | { 26 | var result = ExpressionHelper.GetMethodCallSqlOperator("Any"); 27 | Assert.Equal("ANY", result); 28 | } 29 | 30 | [Fact] 31 | public void GetMethodCallSqlOperator_All() 32 | { 33 | var result = ExpressionHelper.GetMethodCallSqlOperator("All"); 34 | Assert.Equal("ALL", result); 35 | } 36 | 37 | [Fact] 38 | public void GetMethodCallSqlOperator_all() 39 | { 40 | Assert.Throws(() => ExpressionHelper.GetMethodCallSqlOperator("all")); 41 | } 42 | 43 | [Fact] 44 | public void GetMethodCallSqlOperator_Like() 45 | { 46 | var result1 = ExpressionHelper.GetMethodCallSqlOperator("StartsWith"); 47 | Assert.Equal("LIKE", result1); 48 | 49 | var result2 = ExpressionHelper.GetMethodCallSqlOperator("StringContains", true); 50 | Assert.Equal("NOT LIKE", result2); 51 | 52 | var result3 = ExpressionHelper.GetMethodCallSqlOperator("EndsWith"); 53 | Assert.Equal("LIKE", result3); 54 | } 55 | 56 | [Fact] 57 | public void GetSqlLikeValue_Like() 58 | { 59 | var result1 = ExpressionHelper.GetSqlLikeValue("StartsWith", "123"); 60 | Assert.Equal("123%", result1); 61 | 62 | var result2 = ExpressionHelper.GetSqlLikeValue("StringContains", "456"); 63 | Assert.Equal("%456%", result2); 64 | 65 | var result3 = ExpressionHelper.GetSqlLikeValue("EndsWith", 789); 66 | Assert.Equal("%789", result3); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/SqlGenerator.Tests/GetKeysParamTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MicroOrm.Dapper.Repositories.SqlGenerator; 4 | using TestClasses; 5 | using Xunit; 6 | 7 | namespace SqlGenerator.Tests; 8 | 9 | public class GetKeysParamTests 10 | { 11 | [Fact] 12 | public void KeyValuesLengthNotEqualKeysCount() 13 | { 14 | var sqlGenerator = new SqlGenerator(); 15 | 16 | var ex = Assert.Throws(() => sqlGenerator.GetKeysParam(new[] { 1 })); 17 | 18 | Assert.Equal("id", ex.ParamName); 19 | Assert.StartsWith("GetSelectById id(Array) length not equals key properties count", ex.Message); 20 | } 21 | 22 | [Fact] 23 | public void KeyValuesContainsNull() 24 | { 25 | var sqlGenerator = new SqlGenerator(); 26 | 27 | var ex = Assert.Throws(() => sqlGenerator.GetKeysParam(new object[] { 1, null })); 28 | 29 | Assert.Equal("id", ex.ParamName); 30 | Assert.StartsWith("Key value is null in 1", ex.Message); 31 | } 32 | 33 | [Fact] 34 | public void SingleKeyMustConvertToDictionary() 35 | { 36 | var sqlGenerator = new SqlGenerator(); 37 | 38 | var id = new { Id = 1 }; 39 | 40 | var param = Assert.IsType>(sqlGenerator.GetKeysParam(id)); 41 | 42 | var keyValue = Assert.Single(param); 43 | 44 | Assert.Equal("Id", keyValue.Key); 45 | Assert.Equal(id, keyValue.Value); 46 | } 47 | 48 | [Fact] 49 | public void ArrayKeyValuesShouldConvertToDictionary() 50 | { 51 | var sqlGenerator = new SqlGenerator(); 52 | 53 | var param = Assert.IsAssignableFrom>(sqlGenerator.GetKeysParam(new[] { 1, 2 })); 54 | 55 | Assert.Equal(2, param.Count); 56 | 57 | Assert.Equal(1, Assert.Contains("Id", param)); 58 | Assert.Equal(2, Assert.Contains("AnotherId", param)); 59 | } 60 | 61 | [Fact] 62 | public void ObjectKeyValuesShouldNotConvert() 63 | { 64 | var sqlGenerator = new SqlGenerator(); 65 | 66 | var id = new { Id = 1, AnotherId = 2 }; 67 | 68 | var param = sqlGenerator.GetKeysParam(id); 69 | 70 | Assert.Equal(id, param); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/SqlGenerator.Tests/SQLiteGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MicroOrm.Dapper.Repositories.SqlGenerator; 4 | using MicroOrm.Dapper.Repositories.SqlGenerator.Filters; 5 | using TestClasses; 6 | using Xunit; 7 | 8 | namespace SqlGenerator.Tests; 9 | 10 | public class SQLiteGeneratorTests 11 | { 12 | private const SqlProvider _sqlConnector = SqlProvider.SQLite; 13 | 14 | [Fact] 15 | public void Count() 16 | { 17 | ISqlGenerator userSqlGenerator = new SqlGenerator(_sqlConnector, true); 18 | var sqlQuery = userSqlGenerator.GetCount(null); 19 | Assert.Equal("SELECT COUNT(*) FROM `Users` WHERE `Users`.`Deleted` IS NULL", sqlQuery.GetSql()); 20 | } 21 | 22 | [Fact] 23 | public void SelectOrderBy() 24 | { 25 | ISqlGenerator sqlGenerator = new SqlGenerator(_sqlConnector, false); 26 | var filterData = new FilterData(); 27 | var data = filterData.OrderInfo ?? new OrderInfo(); 28 | data.Columns = new List { "Name" }; 29 | data.Direction = OrderInfo.SortDirection.ASC; 30 | filterData.OrderInfo = data; 31 | 32 | var sqlQuery = sqlGenerator.GetSelectAll(x => x.Identifier == Guid.Empty, filterData); 33 | Assert.Equal("SELECT Cities.Identifier, Cities.Name FROM Cities WHERE Cities.Identifier = @Identifier_p0 ORDER BY Name ASC", sqlQuery.GetSql()); 34 | } 35 | 36 | [Fact] 37 | public void SelectPaged() 38 | { 39 | ISqlGenerator sqlGenerator = new SqlGenerator(_sqlConnector, false); 40 | var filterData = new FilterData(); 41 | var data = filterData.LimitInfo ?? new LimitInfo(); 42 | data.Limit = 10u; 43 | data.Offset = 5u; 44 | filterData.LimitInfo = data; 45 | 46 | var sqlQuery = sqlGenerator.GetSelectAll(x => x.Identifier == Guid.Empty, filterData); 47 | Assert.Equal("SELECT Cities.Identifier, Cities.Name FROM Cities WHERE Cities.Identifier = @Identifier_p0 LIMIT 10 OFFSET 5", sqlQuery.GetSql()); 48 | } 49 | 50 | [Fact] 51 | public void SelectFirst2() 52 | { 53 | ISqlGenerator sqlGenerator = new SqlGenerator(_sqlConnector, false); 54 | var sqlQuery = sqlGenerator.GetSelectFirst(x => x.Identifier == Guid.Empty && x.Name == "", null); 55 | Assert.Equal("SELECT Cities.Identifier, Cities.Name FROM Cities WHERE Cities.Identifier = @Identifier_p0 AND Cities.Name = @Name_p1 LIMIT 1", sqlQuery.GetSql()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/SqlGenerator.Tests/SqlGenerator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | opencover 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/TestClasses/Address.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using MicroOrm.Dapper.Repositories.Attributes; 5 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 6 | 7 | namespace TestClasses; 8 | 9 | [Table("Addresses")] 10 | public class Address 11 | { 12 | [Key] 13 | [Identity, IgnoreUpdate] 14 | public int Id { get; set; } 15 | 16 | public string Street { get; set; } 17 | 18 | [LeftJoin("Users", "Id", "AddressId")] 19 | public List Users { get; set; } 20 | 21 | public string CityId { get; set; } 22 | 23 | [InnerJoin("Cities", "CityId", "Identifier")] 24 | public City City { get; set; } 25 | } 26 | -------------------------------------------------------------------------------- /tests/TestClasses/AddressKeyAsIdentity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using MicroOrm.Dapper.Repositories.Attributes; 5 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 6 | 7 | namespace TestClasses; 8 | 9 | [Table("Addresses")] 10 | public class AddressKeyAsIdentity 11 | { 12 | [Key] 13 | [IgnoreUpdate] 14 | public int Id { get; set; } 15 | 16 | public string Street { get; set; } 17 | 18 | [LeftJoin("Users", "Id", "AddressId")] 19 | public List Users { get; set; } 20 | 21 | public string CityId { get; set; } 22 | 23 | [InnerJoin("Cities", "CityId", "Identifier")] 24 | public City City { get; set; } 25 | } 26 | -------------------------------------------------------------------------------- /tests/TestClasses/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using MicroOrm.Dapper.Repositories.Attributes; 4 | 5 | namespace TestClasses; 6 | 7 | public class BaseEntity 8 | { 9 | [Key, Identity] 10 | [Column(Order = 0)] 11 | public virtual TKey Id { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /tests/TestClasses/Car.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 3 | using MicroOrm.Dapper.Repositories.Attributes.LogicalDelete; 4 | 5 | namespace TestClasses; 6 | 7 | [Table("Cars")] 8 | public class Car : BaseEntity 9 | { 10 | public string Name { get; set; } 11 | 12 | public byte[] Data { get; set; } 13 | 14 | public int UserId { get; set; } 15 | 16 | [LeftJoin("Users", "UserId", "Id")] 17 | public User User { get; set; } 18 | 19 | [Status] 20 | public StatusCar Status { get; set; } 21 | } 22 | 23 | public enum StatusCar 24 | { 25 | Inactive = 0, 26 | 27 | Active = 1, 28 | 29 | [Deleted] 30 | Deleted = -1 31 | } 32 | -------------------------------------------------------------------------------- /tests/TestClasses/City.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace TestClasses; 5 | 6 | [Table("Cities")] 7 | public class City 8 | { 9 | public Guid Identifier { get; set; } 10 | 11 | public string Name { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /tests/TestClasses/ComplicatedObj.cs: -------------------------------------------------------------------------------- 1 | namespace TestClasses; 2 | 3 | public class ComplicatedObj 4 | { 5 | public const string ConstName = "CC-456"; 6 | 7 | public int Id = 456; 8 | public string Name = "FF-456"; 9 | public string PropertName { get; } = "PP-456"; 10 | 11 | public static string StaticName = "SS-456"; 12 | public static string StaticPropertyName { get; } = "SS-PP-789"; 13 | 14 | public string[] FieldNames = { "456", "654" }; 15 | public int[] FieldArIds = { 1, 2, 3 }; 16 | public string[] PropertyNames { get; } = { "456", "654" }; 17 | 18 | public static string[] StaticFieldNames = { "456", "654" }; 19 | public static int[] StaticFieldArIds = { 1, 2, 3 }; 20 | public static string[] StaticPropertyNames { get; } = { "456", "654" }; 21 | } 22 | -------------------------------------------------------------------------------- /tests/TestClasses/Phone.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using MicroOrm.Dapper.Repositories.Attributes; 4 | 5 | namespace TestClasses; 6 | 7 | [Table("Phones", Schema = "DAB")] 8 | public class Phone 9 | { 10 | [Key] 11 | [Identity] 12 | public int Id { get; set; } 13 | 14 | public string PNumber { get; set; } 15 | 16 | public bool IsActive { get; set; } 17 | 18 | [IgnoreUpdate] 19 | public string Code { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /tests/TestClasses/Report.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using MicroOrm.Dapper.Repositories.Attributes; 4 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 5 | 6 | namespace TestClasses; 7 | 8 | [Table("Reports")] 9 | public class Report 10 | { 11 | [Key] 12 | [IgnoreUpdate] 13 | public int Id { get; set; } 14 | 15 | [Key] 16 | public int AnotherId { get; set; } 17 | 18 | public int UserId { get; set; } 19 | 20 | [LeftJoin("Users", "UserId", "Id")] 21 | public User User { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /tests/TestClasses/TestClasses.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/TestClasses/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using MicroOrm.Dapper.Repositories.Attributes; 5 | using MicroOrm.Dapper.Repositories.Attributes.Joins; 6 | using MicroOrm.Dapper.Repositories.Attributes.LogicalDelete; 7 | 8 | namespace TestClasses; 9 | 10 | [Table("Users")] 11 | public class User : BaseEntity 12 | { 13 | public string ReadOnly => "test"; 14 | 15 | [Column(Order = 1)] 16 | public string Name { get; set; } 17 | 18 | public int AddressId { get; set; } 19 | 20 | public int PhoneId { get; set; } 21 | 22 | public int OfficePhoneId { get; set; } 23 | 24 | [LeftJoin("Cars", "Id", "UserId", TableAlias = "Cars_Id")] 25 | public List Cars { get; set; } 26 | 27 | [LeftJoin("Addresses", "AddressId", "Id")] 28 | public Address Addresses { get; set; } 29 | 30 | [InnerJoin("Phones", "PhoneId", "Id", "DAB", TableAlias = "Phones_PhoneId")] 31 | public Phone Phone { get; set; } 32 | 33 | [InnerJoin("Phones", "OfficePhoneId", "Id", "DAB", TableAlias = "OfficePhone")] 34 | public Phone OfficePhone { get; set; } 35 | 36 | [Status] 37 | [Deleted] 38 | public bool? Deleted { get; set; } 39 | 40 | [UpdatedAt] 41 | public DateTime? UpdatedAt { get; set; } 42 | } 43 | -------------------------------------------------------------------------------- /tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mysql: 3 | image: mysql:8.0-debian 4 | environment: 5 | MYSQL_ROOT_PASSWORD: ${TEST_DB_PASS} 6 | ports: 7 | - "3306:3306" 8 | mssql: 9 | image: mcr.microsoft.com/mssql/server:2025-latest 10 | environment: 11 | ACCEPT_EULA: "Y" 12 | SA_PASSWORD: ${TEST_DB_PASS} 13 | ports: 14 | - "1433:1433" 15 | oracle: 16 | image: gvenzl/oracle-xe:21-slim-faststart 17 | environment: 18 | ORACLE_PASSWORD: ${TEST_DB_PASS} 19 | ports: 20 | - "1521:1521" 21 | postgres: 22 | image: postgres:17.5-alpine 23 | ports: 24 | - "5432:5432" 25 | environment: 26 | POSTGRES_PASSWORD: ${TEST_DB_PASS} 27 | --------------------------------------------------------------------------------