├── .gitattributes ├── .github └── workflows │ ├── publish-docs.yml │ └── publish.yml ├── .gitignore ├── EasyAbp.Abp.RelatedDtoLoader.sln ├── EasyAbp.Abp.RelatedDtoLoader.sln.DotSettings ├── LICENSE ├── README.md ├── common.props ├── docs ├── CustomDtoSource.md └── README.md ├── nupkg ├── common.ps1 ├── pack.ps1 └── push_packages.ps1 ├── src ├── EasyAbp.Abp.RelatedDtoLoader.Abstractions │ ├── EasyAbp.Abp.RelatedDtoLoader.Abstractions.csproj │ ├── EasyAbp │ │ └── Abp │ │ │ └── RelatedDtoLoader │ │ │ └── RelatedDtoAttribute.cs │ ├── FodyWeavers.xml │ └── FodyWeavers.xsd └── EasyAbp.Abp.RelatedDtoLoader │ ├── EasyAbp.Abp.RelatedDtoLoader.csproj │ ├── EasyAbp │ └── Abp │ │ └── RelatedDtoLoader │ │ ├── AbpRelatedDtoLoaderModule.cs │ │ ├── Configurations │ │ ├── DtoLoaderConfiguration.cs │ │ ├── DtoLoaderConfigurationExpression.cs │ │ ├── IDtoLoaderConfigurationProvider.cs │ │ ├── IRelatedDtoLoaderConfigurationContext.cs │ │ ├── RelatedDtoLoaderAssemblyOptions.cs │ │ ├── RelatedDtoLoaderConfigurationContext.cs │ │ └── RelatedDtoLoaderOptions.cs │ │ ├── DtoLoadRule │ │ ├── DtoLoadRule.cs │ │ └── IDtoLoadRule.cs │ │ ├── Exceptions │ │ ├── MissingIdPropertyNameException.cs │ │ └── UnsupportedTargetTypeException.cs │ │ ├── RelatedDtoLoader │ │ ├── IRelatedDtoLoader.cs │ │ ├── RelatedDtoLoader.cs │ │ └── RelatedDtoLoaderExtensions.cs │ │ ├── RelatedDtoLoaderProfile │ │ ├── IRelatedDtoLoaderProfile.cs │ │ └── RelatedDtoLoaderProfile.cs │ │ └── RelatedDtoProperty │ │ ├── RelatedDtoProperty.cs │ │ ├── RelatedDtoPropertyCollection.cs │ │ └── RelatedValueType.cs │ ├── FodyWeavers.xml │ └── FodyWeavers.xsd └── test ├── EasyAbp.Abp.RelatedDtoLoader.IntegratedTests ├── EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.csproj ├── GuidKey │ ├── MyGuidDbContext.cs │ ├── MyGuidEntityFrameworkCoreTestModule.cs │ ├── MyGuidRelatedDtoLoaderProfile.cs │ ├── MyGuidRelatedDtoLoaderTestModule.cs │ ├── MyGuidTestAutoMapperProfile.cs │ ├── MyGuidTestData.cs │ ├── MyGuidTestDataBuilder.cs │ └── Tests │ │ └── RelatedDtoLoader_Basic_Test_Guid.cs ├── IntegerKey │ ├── MyIntegerDbContext.cs │ ├── MyIntegerEntityFrameworkCoreTestModule.cs │ ├── MyIntegerRelatedDtoLoaderProfile.cs │ ├── MyIntegerRelatedDtoLoaderTestModule.cs │ ├── MyIntegerTestAutoMapperProfile.cs │ ├── MyIntegerTestData.cs │ ├── MyIntegerTestDataBuilder.cs │ └── Tests │ │ └── RelatedDtoLoader_Basic_Test_Integer.cs └── RelatedDtoLoaderTestBase.cs ├── EasyAbp.Abp.RelatedDtoLoader.TestBase ├── Application │ ├── IProductAppService.cs │ └── ProductAppService.cs ├── Domain │ ├── IntegerKey │ │ ├── IntOrder.cs │ │ ├── IntOrderDto.cs │ │ ├── IntProduct.cs │ │ └── IntProductDto.cs │ ├── Order.cs │ ├── OrderDto.cs │ ├── Product.cs │ ├── ProductCommentDto.cs │ ├── ProductDto.cs │ └── UnsupportedOrderDto.cs ├── EasyAbp.Abp.RelatedDtoLoader.TestBase.csproj └── RelatedDtoLoaderTestBaseModule.cs └── EasyAbp.Abp.RelatedDtoLoader.UnitTests ├── EasyAbp.Abp.RelatedDtoLoader.UnitTests.csproj ├── Loader ├── MyUnitTestDtoLoaderProfile.cs ├── RelatedDtoLoaderConfigurationTest.cs └── RelatedDtoLoaderTest.cs ├── MyUnitTestData.cs └── obj └── Release └── netcoreapp3.1 └── EasyAbp.Abp.RelatedDtoLoader.UnitTests.AssemblyInfo.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | **/wwwroot/libs/** linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: publish to easyabp.io 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | with: 13 | persist-credentials: false 14 | fetch-depth: 0 15 | 16 | - name: Find and Replace 17 | uses: jacobtomlinson/gha-find-replace@master 18 | with: 19 | find: \]\((/docs/) 20 | replace: "](/modules/${{ github.event.repository.name }}/" 21 | include: docs/ 22 | 23 | - name: Pull repo and change files 24 | id: change 25 | run: | 26 | ls 27 | git clone https://github.com/EasyAbp/easyabp.github.io 28 | cd easyabp.github.io 29 | rm -rf docs/modules/${{ github.event.repository.name }} 30 | rm -rf docs/.vuepress/public/modules/${{ github.event.repository.name }} 31 | cp -rf ../docs/ docs/modules/${{ github.event.repository.name }} 32 | cp -rf ../docs/ docs/.vuepress/public/modules/${{ github.event.repository.name }} 33 | git add --all 34 | echo "##[set-output name=diff;]$(git diff --staged)" 35 | - name: Commit files 36 | if: steps.change.outputs.diff 37 | run: | 38 | ls 39 | cd easyabp.github.io 40 | git config --local user.email "action@github.com" 41 | git config --local user.name "GitHub Action" 42 | git commit -m "Update the docs of ${{ github.event.repository.name }}" -a 43 | - name: Push changes 44 | if: steps.change.outputs.diff 45 | uses: ad-m/github-push-action@master 46 | with: 47 | github_token: ${{ secrets.EASYABP_IO_ACCESS_TOKEN }} 48 | repository: EasyAbp/easyabp.github.io 49 | directory: easyabp.github.io 50 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish to nuget 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | jobs: 8 | publish: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: NuGet/setup-nuget@v1.0.5 13 | 14 | - name: read common.props 15 | id: commonProps 16 | uses: juliangruber/read-file-action@v1 17 | with: 18 | path: ./common.props 19 | 20 | - name: get version 21 | id: getVersion 22 | uses: AsasInnab/regex-action@v1 23 | with: 24 | regex_pattern: '(?<=>)[^<>]+(?=)' 25 | regex_flags: 'gim' 26 | search_string: '${{ steps.commonProps.outputs.content }}' 27 | 28 | - name: dotnet build 29 | run: dotnet build -c Release 30 | 31 | - name: dotnet pack 32 | run: dotnet pack -c Release --no-build -o dest 33 | 34 | - name: remove unused packages 35 | run: | 36 | cd dest 37 | del * -Exclude EasyAbp.* 38 | del * -Exclude *.${{ steps.getVersion.outputs.first_match }}.nupkg 39 | del *.HttpApi.Client.ConsoleTestApp* 40 | del *.Host.Shared* 41 | dir -name 42 | 43 | - name: dotnet nuget push to GitHub 44 | uses: tanaka-takayoshi/nuget-publish-to-github-packages-action@v2.1 45 | with: 46 | nupkg-path: 'dest\*.nupkg' 47 | repo-owner: 'EasyAbp' 48 | gh-user: 'EasyAbp' 49 | token: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | - name: dotnet nuget push to NuGet 52 | run: dotnet nuget push dest\*.nupkg -k ${{secrets.NUGET_API_KEY}} -s https://api.nuget.org/v3/index.json --skip-duplicate 53 | 54 | - name: determine if the tag exists 55 | uses: mukunku/tag-exists-action@v1.0.0 56 | id: checkTag 57 | with: 58 | tag: ${{ steps.getVersion.outputs.first_match }} 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | - name: add git tag 63 | if: ${{ steps.checkTag.outputs.exists == 'false' }} 64 | uses: tvdias/github-tagger@v0.0.1 65 | with: 66 | repo-token: ${{ secrets.GITHUB_TOKEN }} 67 | tag: ${{ steps.getVersion.outputs.first_match }} 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | # RelatedEntityDtoLoader 255 | host/AwesomeAbp.RelatedEntityDtoLoader.IdentityServer/Logs/logs.txt 256 | host/AwesomeAbp.RelatedEntityDtoLoader.HttpApi.Host/Logs/logs.txt 257 | host/AwesomeAbp.RelatedEntityDtoLoader.Web.Host/Logs/logs.txt 258 | host/AwesomeAbp.RelatedEntityDtoLoader.Web.Unified/Logs/logs.txt 259 | obj 260 | -------------------------------------------------------------------------------- /EasyAbp.Abp.RelatedDtoLoader.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29001.49 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.Abp.RelatedDtoLoader.Abstractions", "src\EasyAbp.Abp.RelatedDtoLoader.Abstractions\EasyAbp.Abp.RelatedDtoLoader.Abstractions.csproj", "{BD65D04F-08D5-40C1-8C24-77CA0BACB877}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.Abp.RelatedDtoLoader", "src\EasyAbp.Abp.RelatedDtoLoader\EasyAbp.Abp.RelatedDtoLoader.csproj", "{78040F9E-3501-4A40-82DF-00A597710F35}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{649A3FFA-182F-4E56-9717-E6A9A2BEC545}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{34FD671D-3B3F-498A-ACF5-A73E03111E4E}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.Abp.RelatedDtoLoader.UnitTests", "test\EasyAbp.Abp.RelatedDtoLoader.UnitTests\EasyAbp.Abp.RelatedDtoLoader.UnitTests.csproj", "{71FAA9F6-62CA-4DFC-BBD2-135C7B1412A0}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.Abp.RelatedDtoLoader.TestBase", "test\EasyAbp.Abp.RelatedDtoLoader.TestBase\EasyAbp.Abp.RelatedDtoLoader.TestBase.csproj", "{FADAFEA9-E6FD-4519-9419-8169E8387774}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.Abp.RelatedDtoLoader.IntegratedTests", "test\EasyAbp.Abp.RelatedDtoLoader.IntegratedTests\EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.csproj", "{0A0DC5ED-BF43-4219-A141-B9ED118A3C86}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {71FAA9F6-62CA-4DFC-BBD2-135C7B1412A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {71FAA9F6-62CA-4DFC-BBD2-135C7B1412A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {71FAA9F6-62CA-4DFC-BBD2-135C7B1412A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {71FAA9F6-62CA-4DFC-BBD2-135C7B1412A0}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {FADAFEA9-E6FD-4519-9419-8169E8387774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {FADAFEA9-E6FD-4519-9419-8169E8387774}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {FADAFEA9-E6FD-4519-9419-8169E8387774}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {FADAFEA9-E6FD-4519-9419-8169E8387774}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {0A0DC5ED-BF43-4219-A141-B9ED118A3C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {0A0DC5ED-BF43-4219-A141-B9ED118A3C86}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {0A0DC5ED-BF43-4219-A141-B9ED118A3C86}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {0A0DC5ED-BF43-4219-A141-B9ED118A3C86}.Release|Any CPU.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {BD65D04F-08D5-40C1-8C24-77CA0BACB877} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} 52 | {78040F9E-3501-4A40-82DF-00A597710F35} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} 53 | {71FAA9F6-62CA-4DFC-BBD2-135C7B1412A0} = {34FD671D-3B3F-498A-ACF5-A73E03111E4E} 54 | {FADAFEA9-E6FD-4519-9419-8169E8387774} = {34FD671D-3B3F-498A-ACF5-A73E03111E4E} 55 | {0A0DC5ED-BF43-4219-A141-B9ED118A3C86} = {34FD671D-3B3F-498A-ACF5-A73E03111E4E} 56 | EndGlobalSection 57 | GlobalSection(ExtensibilityGlobals) = postSolution 58 | SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} 59 | EndGlobalSection 60 | EndGlobal 61 | -------------------------------------------------------------------------------- /EasyAbp.Abp.RelatedDtoLoader.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | WARNING 4 | WARNING 5 | WARNING 6 | WARNING 7 | WARNING 8 | WARNING 9 | WARNING 10 | WARNING 11 | Required 12 | Required 13 | Required 14 | Required 15 | False 16 | True 17 | False 18 | False 19 | True 20 | False 21 | False 22 | SQL 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 EasyAbp Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | /docs/README.md -------------------------------------------------------------------------------- /common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | latest 4 | 0.4.0 5 | $(NoWarn);CS1591 6 | true 7 | EasyAbp Team 8 | An Abp module to help you automatically load related DTO (like ProductDto in OrderDto) under DDD. 9 | https://github.com/EasyAbp/Abp.RelatedDtoLoader 10 | https://github.com/EasyAbp/Abp.RelatedDtoLoader 11 | abp module easyabp dto related loader ddd 12 | EasyAbp 13 | MIT 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/CustomDtoSource.md: -------------------------------------------------------------------------------- 1 | # Example 1 2 | 3 | Get DTOs from application service 4 | ```csharp 5 | public class MyProjectRelatedDtoLoaderProfile : RelatedDtoLoaderProfile 6 | { 7 | public MyProjectRelatedDtoLoaderProfile(IServiceProvider serviceProvider) : base(serviceProvider) 8 | { 9 | CreateRule(async ids => 10 | { 11 | var dtos = new List(); 12 | 13 | using (var scope = serviceProvider.CreateScope()) 14 | { 15 | var productAppService = scope.ServiceProvider.GetService(); 16 | 17 | foreach (var id in ids) 18 | { 19 | dtos.Add(await productAppService.GetAsync(id)); 20 | } 21 | } 22 | 23 | return dtos; 24 | }); 25 | } 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Abp.RelatedDtoLoader 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/EasyAbp.Abp.RelatedDtoLoader.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.Abp.RelatedDtoLoader) 4 | [![NuGet Download](https://img.shields.io/nuget/dt/EasyAbp.Abp.RelatedDtoLoader.svg?style=flat-square)](https://www.nuget.org/packages/EasyAbp.Abp.RelatedDtoLoader) 5 | 6 | An Abp module that help you automatically load related DTO (like ProductDto in OrderDto) under DDD. 7 | 8 | ## Installation 9 | 10 | 1. Install the following NuGet packages. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-nuget-packages)) 11 | 12 | * EasyAbp.Abp.RelatedDtoLoader 13 | * EasyAbp.Abp.RelatedDtoLoader.Abstractions 14 | 15 | 1. Add `DependsOn(typeof(AbpRelatedDtoLoaderModule))` attribute to configure the module dependencies. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-module-dependencies)) 16 | 17 | ## Usage 18 | 19 | 1. Make your `Order` **entity** (or aggregate root) like this. 20 | ```csharp 21 | public class Order : AggregateRoot 22 | { 23 | public virtual Guid ProductId { get; protected set; } 24 | 25 | // do not add navigation properties to other aggregate roots! 26 | // public virtual Product Product { get; set; } 27 | 28 | protected Order() { } 29 | 30 | public Order(Guid id, Guid productId) : base(id) 31 | { 32 | ProductId = productId; 33 | } 34 | } 35 | ``` 36 | 37 | 1. Add `RelatedDto` attribute in `OrderDto`. 38 | ```csharp 39 | public class OrderDto : EntityDto 40 | { 41 | public Guid ProductId { get; set; } 42 | 43 | //Automatic matching rules $"{nameof(Product)}Id" 44 | [RelatedDto] 45 | public ProductDto Product { get; set; } 46 | 47 | //Manually matching the ProductId 48 | [RelatedDto(nameof(ProductId))] 49 | public ProductDto ProductInfo { get; set; } 50 | } 51 | ``` 52 | 53 | 1. Create `MyProjectRelatedDtoLoaderProfile` and add a rule. 54 | ```csharp 55 | public class MyProjectRelatedDtoLoaderProfile : RelatedDtoLoaderProfile 56 | { 57 | public MyRelatedDtoLoaderProfile() 58 | { 59 | // the following example gets entities from a repository and maps them to DTOs. 60 | UseRepositoryLoader(); 61 | 62 | // or load it by a customized function. 63 | UseLoader(GetOrderDtosAsync); 64 | 65 | // a target type need to be enabled to load its related Dtos properties. 66 | // LoadForDto(); 67 | } 68 | } 69 | ``` 70 | 71 | 1. Configure the RelatedDtoLoader to use the profile. 72 | ```csharp 73 | public override void ConfigureServices(ServiceConfigurationContext context) 74 | { 75 | // ... 76 | 77 | Configure(options => 78 | { 79 | // add the Profile 80 | options.AddProfile(); 81 | }); 82 | 83 | // ... 84 | } 85 | ``` 86 | 87 | 1. Enable the target type to load its related Dto properties. 88 | 89 | either in the Profile 90 | ```csharp 91 | public class MyProjectRelatedDtoLoaderProfile : RelatedDtoLoaderProfile 92 | { 93 | public MyRelatedDtoLoaderProfile() 94 | { 95 | // ... 96 | 97 | // a target type need to be enabled to load its related Dtos properties. 98 | LoadForDto(); 99 | } 100 | } 101 | ``` 102 | 103 | or via `RegisterTargetDtosInModule method` of RelatedDtoLoaderOptions 104 | ```csharp 105 | public override void ConfigureServices(ServiceConfigurationContext context) 106 | { 107 | // ... 108 | 109 | Configure(options => 110 | { 111 | // adding module will auto register all the target dto types which contain any property with RelatedDto attribute. 112 | options.LoadForDtosInModule(); 113 | }); 114 | 115 | // ... 116 | } 117 | ``` 118 | 119 | 1. Try to get OrderDto with ProductDto. 120 | ```csharp 121 | public class OrderAppService : ApplicationService, IOrderAppService 122 | { 123 | private readonly IRelatedDtoLoader _relatedDtoLoader; 124 | private readonly IRepository _orderRepository; 125 | 126 | public OrderAppService(IRelatedDtoLoader relatedDtoLoader, IRepository orderRepository) 127 | { 128 | _relatedDtoLoader = relatedDtoLoader; 129 | _orderRepository = orderRepository; 130 | } 131 | 132 | public async Task GetAsync(Guid id) 133 | { 134 | var order = await _orderRepository.GetAsync(id); 135 | 136 | var orderDto = ObjectMapper.Map(order); 137 | 138 | //LoadAsync 139 | return await _relatedDtoLoader.LoadAsync(orderDto); // orderDto.Product should have been loaded. 140 | } 141 | 142 | protected override async Task> MapToGetListOutputDtosAsync(List entities) 143 | { 144 | var orderDtos = await base.MapToGetListOutputDtosAsync(entities); 145 | 146 | //LoadListAsync 147 | return (await _relatedDtoLoader.LoadListAsync(orderDtos)).ToList(); // OrderDto.Product should have been loaded. 148 | } 149 | } 150 | ``` 151 | 152 | See more: [Custom DTO source examples](/docs/CustomDtoSource.md). 153 | 154 | ## Roadmap 155 | 156 | - [x] Custom DTO source 157 | - [x] Support one-to-many relation 158 | - [x] Support non Guid keys 159 | - [x] Support multi module development 160 | - [ ] Support nested DTOs loading 161 | - [ ] Get duplicate DTO from memory 162 | - [ ] DTO cache 163 | - [ ] An option to enable loading deleted DTO 164 | - [x] Unit test 165 | 166 | Thanks [@wakuflair](https://github.com/wakuflair) and [@itryan](https://github.com/itryan) for their contribution in the first version. 167 | -------------------------------------------------------------------------------- /nupkg/common.ps1: -------------------------------------------------------------------------------- 1 | # Paths 2 | $packFolder = (Get-Item -Path "./" -Verbose).FullName 3 | $rootFolder = Join-Path $packFolder "../" 4 | 5 | # List of projects 6 | $projects = ( 7 | 8 | "src/EasyAbp.Abp.RelatedDtoLoader", 9 | "src/EasyAbp.Abp.RelatedDtoLoader.Abstractions" 10 | ) -------------------------------------------------------------------------------- /nupkg/pack.ps1: -------------------------------------------------------------------------------- 1 | . ".\common.ps1" 2 | 3 | # Rebuild solution 4 | Set-Location $rootFolder 5 | & dotnet restore 6 | 7 | # Create all packages 8 | foreach($project in $projects) { 9 | 10 | $projectFolder = Join-Path $rootFolder $project 11 | 12 | # Create nuget pack 13 | Set-Location $projectFolder 14 | Remove-Item -Recurse (Join-Path $projectFolder "bin/Release") 15 | & dotnet build -c Release 16 | & dotnet pack -c Release 17 | 18 | if (-Not $?) { 19 | Write-Host ("Packaging failed for the project: " + $projectFolder) 20 | exit $LASTEXITCODE 21 | } 22 | 23 | # Copy nuget package 24 | $projectName = $project.Substring($project.LastIndexOf("/") + 1) 25 | $projectPackPath = Join-Path $projectFolder ("/bin/Release/" + $projectName + ".*.nupkg") 26 | Move-Item $projectPackPath $packFolder 27 | 28 | } 29 | 30 | # Go back to the pack folder 31 | Set-Location $packFolder -------------------------------------------------------------------------------- /nupkg/push_packages.ps1: -------------------------------------------------------------------------------- 1 | . ".\common.ps1" 2 | 3 | # Get the version 4 | [xml]$commonPropsXml = Get-Content (Join-Path $rootFolder "common.props") 5 | $version = $commonPropsXml.Project.PropertyGroup.Version 6 | 7 | # Publish all packages 8 | foreach($project in $projects) { 9 | $projectName = $project.Substring($project.LastIndexOf("/") + 1) 10 | & dotnet nuget push ($projectName + "." + $version + ".nupkg") -s https://api.nuget.org/v3/index.json 11 | } 12 | 13 | # Go back to the pack folder 14 | Set-Location $packFolder 15 | -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader.Abstractions/EasyAbp.Abp.RelatedDtoLoader.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netstandard2.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader.Abstractions/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader 4 | { 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class RelatedDtoAttribute : Attribute 7 | { 8 | public readonly string IdPropertyName; 9 | 10 | public RelatedDtoAttribute() 11 | { 12 | } 13 | 14 | public RelatedDtoAttribute(string idPropertyName) 15 | { 16 | IdPropertyName = idPropertyName; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader.Abstractions/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader.Abstractions/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp.Abp.RelatedDtoLoader.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netstandard2.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/AbpRelatedDtoLoaderModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EasyAbp.Abp.RelatedDtoLoader.Configurations; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Options; 5 | using Volo.Abp; 6 | using Volo.Abp.Modularity; 7 | 8 | namespace EasyAbp.Abp.RelatedDtoLoader 9 | { 10 | public class AbpRelatedDtoLoaderModule : AbpModule 11 | { 12 | public override void ConfigureServices(ServiceConfigurationContext context) 13 | { 14 | context.Services.AddSingleton(CreateDtoLoaderConfiguration); 15 | } 16 | 17 | public override void OnPreApplicationInitialization(ApplicationInitializationContext context) 18 | { 19 | } 20 | 21 | private DtoLoaderConfiguration CreateDtoLoaderConfiguration(IServiceProvider serviceProvider) 22 | { 23 | var options = serviceProvider.GetRequiredService>().Value; 24 | 25 | var configuration = new DtoLoaderConfiguration(configurationExpression => 26 | { 27 | var context = new RelatedDtoLoaderConfigurationContext(configurationExpression, serviceProvider); 28 | 29 | foreach (var configurator in options.Configurators) 30 | { 31 | configurator(context); 32 | } 33 | }); 34 | 35 | return configuration; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/DtoLoaderConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule; 5 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile; 6 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty; 7 | 8 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 9 | { 10 | public class DtoLoaderConfiguration : IDtoLoaderConfigurationProvider 11 | { 12 | private readonly Dictionary _dtoLoaderRules = new Dictionary(); 13 | 14 | private readonly IEnumerable _profiles; 15 | 16 | private readonly ConcurrentDictionary _targetDtoPropertyCollections = 17 | new ConcurrentDictionary(); 18 | 19 | public DtoLoaderConfiguration(DtoLoaderConfigurationExpression configurationExpression) 20 | { 21 | _profiles = configurationExpression.Profiles; 22 | 23 | FinalizeProfiles(); 24 | } 25 | 26 | public DtoLoaderConfiguration(Action configure) 27 | : this(Build(configure)) 28 | { 29 | } 30 | 31 | public RelatedDtoPropertyCollection GetRelatedDtoProperties(Type targetDtoType) 32 | { 33 | return _targetDtoPropertyCollections.ContainsKey(targetDtoType) 34 | ? _targetDtoPropertyCollections[targetDtoType] 35 | : null; 36 | } 37 | 38 | public IDtoLoadRule GetLoadRule(Type type) 39 | { 40 | return _dtoLoaderRules.ContainsKey(type) ? _dtoLoaderRules[type] : null; 41 | } 42 | 43 | private static DtoLoaderConfigurationExpression Build(Action configure) 44 | { 45 | var expr = new DtoLoaderConfigurationExpression(); 46 | configure(expr); 47 | return expr; 48 | } 49 | 50 | private void FinalizeProfiles() 51 | { 52 | foreach (var profile in _profiles) 53 | { 54 | foreach (var dtoLoaderRule in profile.DtoLoaderRules) 55 | { 56 | _dtoLoaderRules[dtoLoaderRule.Key] = dtoLoaderRule.Value; 57 | } 58 | 59 | foreach (var relatedDtoProps in profile.TargetDtoPropertyCollections) 60 | { 61 | _targetDtoPropertyCollections[relatedDtoProps.Key] = relatedDtoProps.Value; 62 | } 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/DtoLoaderConfigurationExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile; 6 | using Volo.Abp.Application.Dtos; 7 | 8 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 9 | { 10 | public class DtoLoaderConfigurationExpression 11 | { 12 | private static readonly Type EntityDtoType = typeof(IEntityDto); 13 | private static readonly Type ProfileType = typeof(IRelatedDtoLoaderProfile); 14 | 15 | private readonly IList _profiles = new List(); 16 | 17 | public bool AutoUseRepositoryLoader { get; set; } 18 | 19 | public IEnumerable Profiles => _profiles; 20 | 21 | public void AddProfile(IRelatedDtoLoaderProfile profile) 22 | { 23 | _profiles.Add(profile); 24 | } 25 | 26 | public void AddAssemblies(RelatedDtoLoaderAssemblyOptions options, params Assembly[] assembliesToScan) 27 | { 28 | AddAssembliesCore(options, assembliesToScan); 29 | } 30 | 31 | private void AddAssembliesCore(RelatedDtoLoaderAssemblyOptions options, IEnumerable assembliesToScan) 32 | { 33 | options = options ?? new RelatedDtoLoaderAssemblyOptions(); 34 | 35 | var allTypes = assembliesToScan.Where(a => !a.IsDynamic && a != typeof(NamedProfile).Assembly) 36 | .SelectMany(a => a.DefinedTypes) 37 | .Where(x => !x.IsAbstract) 38 | .ToArray(); 39 | 40 | var profileTypes = allTypes.Where(x => ProfileType.IsAssignableFrom(x)).ToArray(); 41 | 42 | foreach (var type in profileTypes) 43 | { 44 | var profile = (IRelatedDtoLoaderProfile) Activator.CreateInstance(type); 45 | AddProfile(profile); 46 | } 47 | 48 | var dynamicLoaderProfile = new NamedProfile(); 49 | 50 | if (options.AutoEnableTargetDtoTypes) 51 | { 52 | var dtoTypes = allTypes.Where(x => EntityDtoType.IsAssignableFrom(x)).ToArray(); 53 | 54 | foreach (var type in dtoTypes) 55 | { 56 | if (EntityDtoType.IsAssignableFrom(type)) 57 | { 58 | dynamicLoaderProfile.LoadForDto(type); 59 | } 60 | } 61 | } 62 | 63 | AddProfile(dynamicLoaderProfile); 64 | } 65 | 66 | private class NamedProfile : RelatedDtoLoaderProfile.RelatedDtoLoaderProfile 67 | { 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/IDtoLoaderConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule; 3 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 6 | { 7 | public interface IDtoLoaderConfigurationProvider 8 | { 9 | RelatedDtoPropertyCollection GetRelatedDtoProperties(Type targetDtoType); 10 | 11 | IDtoLoadRule GetLoadRule(Type type); 12 | } 13 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/IRelatedDtoLoaderConfigurationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 4 | { 5 | public interface IRelatedDtoLoaderConfigurationContext 6 | { 7 | DtoLoaderConfigurationExpression ConfigurationExpression { get; } 8 | IServiceProvider ServiceProvider { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/RelatedDtoLoaderAssemblyOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 2 | { 3 | public class RelatedDtoLoaderAssemblyOptions 4 | { 5 | public bool AutoEnableTargetDtoTypes { get; set; } = true; 6 | } 7 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/RelatedDtoLoaderConfigurationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 4 | { 5 | public class RelatedDtoLoaderConfigurationContext : IRelatedDtoLoaderConfigurationContext 6 | { 7 | public RelatedDtoLoaderConfigurationContext( 8 | DtoLoaderConfigurationExpression configurationExpression, 9 | IServiceProvider serviceProvider) 10 | { 11 | ConfigurationExpression = configurationExpression; 12 | ServiceProvider = serviceProvider; 13 | } 14 | 15 | public DtoLoaderConfigurationExpression ConfigurationExpression { get; } 16 | 17 | public IServiceProvider ServiceProvider { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Configurations/RelatedDtoLoaderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.Configurations 6 | { 7 | public class RelatedDtoLoaderOptions 8 | { 9 | public RelatedDtoLoaderOptions() 10 | { 11 | Configurators = new List>(); 12 | } 13 | 14 | public List> Configurators { get; } 15 | 16 | public RelatedDtoLoaderAssemblyOptions LoadForDtosInModule() 17 | { 18 | var assembly = typeof(TModule).Assembly; 19 | 20 | Configurators.Add(context => { context.ConfigurationExpression.AddAssemblies(null, assembly); }); 21 | 22 | return null; 23 | } 24 | 25 | public void AddProfile() 26 | where TProfile : IRelatedDtoLoaderProfile, new() 27 | { 28 | Configurators.Add(context => { context.ConfigurationExpression.AddProfile(new TProfile()); }); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/DtoLoadRule/DtoLoadRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Volo.Abp.Application.Dtos; 6 | 7 | namespace EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule 8 | { 9 | public class DtoLoadRule : IDtoLoadRule 10 | where TDto : class, IEntityDto 11 | { 12 | public DtoLoadRule(Func, Task>> rule) 13 | : this() 14 | { 15 | Rule = rule; 16 | } 17 | 18 | protected DtoLoadRule() 19 | { 20 | } 21 | 22 | protected Func, Task>> Rule { get; set; } 23 | 24 | public async Task> LoadAsObjectAsync(IServiceProvider serviceProvider, 25 | IEnumerable ids) 26 | { 27 | var convertedIds = ids.Select(x => (TKey) x); 28 | 29 | return (await Rule(serviceProvider, convertedIds)).AsEnumerable().ToArray(); 30 | } 31 | 32 | public object GetKey(object dto) 33 | { 34 | return ((TDto) dto).Id; 35 | } 36 | 37 | public async Task> LoadAsync(IServiceProvider serviceProvider, IEnumerable ids) 38 | { 39 | return await Rule(serviceProvider, ids); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/DtoLoadRule/IDtoLoadRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule 6 | { 7 | public interface IDtoLoadRule 8 | { 9 | object GetKey(object dto); 10 | Task> LoadAsObjectAsync(IServiceProvider serviceProvider, IEnumerable ids); 11 | } 12 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Exceptions/MissingIdPropertyNameException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.Exceptions 4 | { 5 | public class MissingIdPropertyNameException : Exception 6 | { 7 | public MissingIdPropertyNameException(string targetTypeName, string propertyName) 8 | : base(GetMessage(targetTypeName, propertyName)) 9 | { 10 | } 11 | 12 | private static string GetMessage(string targetTypeName, string propertyName) 13 | { 14 | return $"Missing Id Property Name for {targetTypeName}.{propertyName}."; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/Exceptions/UnsupportedTargetTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.Exceptions 4 | { 5 | public class UnsupportedTargetTypeException : Exception 6 | { 7 | public UnsupportedTargetTypeException(Type type) 8 | : base(GetMessage(type)) 9 | { 10 | } 11 | 12 | private static string GetMessage(Type type) 13 | { 14 | return $"Unsupported target dto type {type.FullName}."; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoLoader/IRelatedDtoLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader 5 | { 6 | public interface IRelatedDtoLoader 7 | { 8 | Task> LoadListAsync(IEnumerable targetDtos, 9 | IEnumerable keyProviders) 10 | where TTargetDto : class 11 | where TKeyProvider : class; 12 | } 13 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoLoader/RelatedDtoLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using EasyAbp.Abp.RelatedDtoLoader.Configurations; 8 | using EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule; 9 | using EasyAbp.Abp.RelatedDtoLoader.Exceptions; 10 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty; 11 | using Volo.Abp.DependencyInjection; 12 | 13 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader 14 | { 15 | public class RelatedDtoLoader : IRelatedDtoLoader, ITransientDependency 16 | { 17 | private readonly IDtoLoaderConfigurationProvider _configuration; 18 | private readonly IServiceProvider _serviceProvider; 19 | 20 | public RelatedDtoLoader(IServiceProvider serviceProvider, IDtoLoaderConfigurationProvider profile) 21 | { 22 | _serviceProvider = serviceProvider; 23 | _configuration = profile; 24 | } 25 | 26 | public async Task> LoadListAsync( 27 | IEnumerable targetDtos, IEnumerable keyProviders) 28 | where TTargetDto : class 29 | where TKeyProvider : class 30 | { 31 | var targetDtoType = typeof(TTargetDto); 32 | 33 | var relatedDtoProperties = _configuration.GetRelatedDtoProperties(targetDtoType); 34 | 35 | if (relatedDtoProperties == null) 36 | { 37 | throw new UnsupportedTargetTypeException(targetDtoType); 38 | } 39 | 40 | var keyProviderType = typeof(TKeyProvider); 41 | var isKeyProviderSameType = targetDtoType == keyProviderType; 42 | var arrTargetDtos = targetDtos.ToArray(); 43 | var arrKeyProviders = keyProviders.ToArray(); 44 | 45 | foreach (var relatedProperty in relatedDtoProperties) 46 | { 47 | var dtoType = relatedProperty.DtoType; 48 | var dtoProperty = dtoType.Property; 49 | var attribute = relatedProperty.Attribute; 50 | 51 | RelatedValueType idType = null; 52 | 53 | if (isKeyProviderSameType) 54 | { 55 | idType = relatedProperty.IdType; 56 | } 57 | else 58 | { 59 | if (dtoType.GenericType != null || dtoType.IsArray) 60 | { 61 | throw new MissingIdPropertyNameException(targetDtoType.Name, dtoProperty.Name); 62 | } 63 | 64 | var idProp = keyProviderType.GetProperty(attribute.IdPropertyName ?? dtoProperty.Name + "Id", 65 | BindingFlags.Public | BindingFlags.Instance); 66 | 67 | if (idProp != null) 68 | { 69 | idType = new RelatedValueType(idProp); 70 | } 71 | } 72 | 73 | if (idType == null) 74 | { 75 | continue; 76 | } 77 | 78 | var loaderRule = _configuration.GetLoadRule(dtoType.ElementType); 79 | 80 | if (loaderRule == null) 81 | { 82 | continue; 83 | } 84 | 85 | if (dtoType.GenericType != null) 86 | { 87 | await InternalLoadDtoEnumerableAsync(relatedProperty, idType, loaderRule, arrTargetDtos, 88 | arrKeyProviders); 89 | } 90 | else if (dtoType.IsArray) 91 | { 92 | await InternalLoadDtoArrayAsync(relatedProperty, idType, loaderRule, arrTargetDtos, 93 | arrKeyProviders); 94 | } 95 | else 96 | { 97 | await InternalLoadDtoAsync(relatedProperty, idType, loaderRule, arrTargetDtos, arrKeyProviders); 98 | } 99 | } 100 | 101 | return arrTargetDtos; 102 | } 103 | 104 | private async Task InternalLoadDtoAsync(RelatedDtoProperty.RelatedDtoProperty relatedProperty, 105 | RelatedValueType idType, IDtoLoadRule loaderRule, TTargetDto[] arrTargetDtos, 106 | TKeyProvider[] arrKeyProviders) 107 | where TTargetDto : class 108 | where TKeyProvider : class 109 | { 110 | var dtoType = relatedProperty.DtoType; 111 | 112 | var keyProviderWithIds = 113 | arrKeyProviders.ToDictionary(x => x, keyProvider => idType.Property.GetValue(keyProvider)); 114 | 115 | var idsToLoad = keyProviderWithIds.Values.Where(x => x != null).Distinct().ToArray(); 116 | 117 | Dictionary dictLoadedDtos = null; 118 | 119 | if (idsToLoad.Any()) 120 | { 121 | var relatedDtos = (await loaderRule.LoadAsObjectAsync(_serviceProvider, idsToLoad)).ToArray(); 122 | dictLoadedDtos = relatedDtos.ToDictionary(x => loaderRule.GetKey(x), x => x); 123 | } 124 | 125 | for (var index = 0; index < arrTargetDtos.Length; index++) 126 | { 127 | var targetDto = arrTargetDtos[index]; 128 | var keyProvider = arrKeyProviders[index]; 129 | 130 | object propValue = null; 131 | 132 | var desiredDtoKey = keyProviderWithIds[keyProvider]; 133 | 134 | if (desiredDtoKey != null && dictLoadedDtos.ContainsKey(desiredDtoKey)) 135 | { 136 | propValue = dictLoadedDtos[desiredDtoKey]; 137 | } 138 | 139 | dtoType.Property.SetValue(targetDto, propValue); 140 | } 141 | } 142 | 143 | private async Task InternalLoadDtoEnumerableAsync(RelatedDtoProperty.RelatedDtoProperty relatedProperty, 144 | RelatedValueType idType, IDtoLoadRule loaderRule, TTargetDto[] arrTargetDtos, 145 | TKeyProvider[] arrKeyProviders) 146 | where TTargetDto : class 147 | where TKeyProvider : class 148 | { 149 | var dtoType = relatedProperty.DtoType; 150 | 151 | var keyProviderWithIds = arrKeyProviders.ToDictionary(x => x, 152 | keyProvider => ((IEnumerable) idType.Property.GetValue(keyProvider)).Cast().ToArray()); 153 | 154 | var idsToLoad = keyProviderWithIds.Values.Where(x => x != null).SelectMany(x => x).Where(x => x != null) 155 | .Distinct().ToArray(); 156 | 157 | Dictionary dictLoadedDtos = null; 158 | 159 | var dtoElementType = dtoType.ElementType; 160 | 161 | if (idsToLoad.Any()) 162 | { 163 | var relatedDtos = (await loaderRule.LoadAsObjectAsync(_serviceProvider, idsToLoad)).ToArray(); 164 | dictLoadedDtos = relatedDtos.ToDictionary(x => loaderRule.GetKey(x), x => x); 165 | } 166 | 167 | for (var index = 0; index < arrTargetDtos.Length; index++) 168 | { 169 | var targetDto = arrTargetDtos[index]; 170 | var keyProvider = arrKeyProviders[index]; 171 | 172 | object propValue = null; 173 | 174 | var desiredDtoKeys = keyProviderWithIds[keyProvider]; 175 | 176 | if (desiredDtoKeys != null) 177 | { 178 | var dtoList = Activator.CreateInstance(relatedProperty.DtoListType); 179 | 180 | foreach (var desiredDtoKey in desiredDtoKeys) 181 | { 182 | object dto = null; 183 | 184 | if (desiredDtoKey != null && dictLoadedDtos.ContainsKey(desiredDtoKey)) 185 | { 186 | dto = dictLoadedDtos[desiredDtoKey]; 187 | } 188 | 189 | relatedProperty.Add.Invoke(dtoList, new[] {dto}); 190 | } 191 | 192 | propValue = dtoList; 193 | } 194 | 195 | dtoType.Property.SetValue(targetDto, propValue); 196 | } 197 | } 198 | 199 | private async Task InternalLoadDtoArrayAsync(RelatedDtoProperty.RelatedDtoProperty relatedProperty, 200 | RelatedValueType idType, IDtoLoadRule loaderRule, TTargetDto[] arrTargetDtos, 201 | TKeyProvider[] arrKeyProviders) 202 | where TTargetDto : class 203 | where TKeyProvider : class 204 | { 205 | var dtoType = relatedProperty.DtoType; 206 | 207 | var keyProviderWithIds = arrKeyProviders.ToDictionary(x => x, 208 | keyProvider => ((IEnumerable) idType.Property.GetValue(keyProvider)).Cast().ToArray()); 209 | 210 | var idsToLoad = keyProviderWithIds.Values.Where(x => x != null).SelectMany(x => x).Where(x => x != null) 211 | .Distinct().ToArray(); 212 | 213 | Dictionary dictLoadedDtos = null; 214 | 215 | var dtoElementType = dtoType.ElementType; 216 | 217 | if (idsToLoad.Any()) 218 | { 219 | var relatedDtos = (await loaderRule.LoadAsObjectAsync(_serviceProvider, idsToLoad)).ToArray(); 220 | dictLoadedDtos = relatedDtos.ToDictionary(loaderRule.GetKey, x => x); 221 | } 222 | 223 | for (var index = 0; index < arrTargetDtos.Length; index++) 224 | { 225 | var targetDto = arrTargetDtos[index]; 226 | var keyProvider = arrKeyProviders[index]; 227 | 228 | object propValue = null; 229 | 230 | var desiredDtoKeys = keyProviderWithIds[keyProvider]; 231 | 232 | if (desiredDtoKeys != null) 233 | { 234 | var dtoArray = Array.CreateInstance(dtoType.ElementType, desiredDtoKeys.Length); 235 | 236 | for (var i = 0; i < desiredDtoKeys.Length; i++) 237 | { 238 | var desiredDtoKey = desiredDtoKeys[i]; 239 | 240 | object dto = null; 241 | 242 | if (desiredDtoKey != null && dictLoadedDtos.ContainsKey(desiredDtoKey)) 243 | { 244 | dto = dictLoadedDtos[desiredDtoKey]; 245 | } 246 | 247 | dtoArray.SetValue(dto, i); 248 | } 249 | 250 | propValue = dtoArray; 251 | } 252 | 253 | dtoType.Property.SetValue(targetDto, propValue); 254 | } 255 | } 256 | } 257 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoLoader/RelatedDtoLoaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader 6 | { 7 | public static class RelatedDtoLoaderExtensions 8 | { 9 | public static async Task LoadAsync(this IRelatedDtoLoader loader, TTargetDto targetDto) 10 | where TTargetDto : class 11 | { 12 | return (await loader.LoadListAsync(new[] {targetDto})).First(); 13 | } 14 | 15 | public static async Task LoadAsync(this IRelatedDtoLoader loader, 16 | TTargetDto targetDto, TKeyProvider keyProvider) 17 | where TTargetDto : class 18 | where TKeyProvider : class 19 | { 20 | return (await loader.LoadListAsync(new[] {targetDto}, new[] {keyProvider})).First(); 21 | } 22 | 23 | public static async Task> LoadListAsync(this IRelatedDtoLoader loader, 24 | IEnumerable targetDtos) 25 | where TTargetDto : class 26 | { 27 | var arrTargetDtos = targetDtos.ToArray(); 28 | return await loader.LoadListAsync(arrTargetDtos, arrTargetDtos); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoLoaderProfile/IRelatedDtoLoaderProfile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule; 4 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty; 5 | 6 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile 7 | { 8 | public interface IRelatedDtoLoaderProfile 9 | { 10 | IReadOnlyDictionary DtoLoaderRules { get; } 11 | 12 | IReadOnlyDictionary TargetDtoPropertyCollections { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoLoaderProfile/RelatedDtoLoaderProfile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule; 6 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Volo.Abp.Application.Dtos; 9 | using Volo.Abp.Application.Services; 10 | using Volo.Abp.Domain.Entities; 11 | using Volo.Abp.Domain.Repositories; 12 | using Volo.Abp.ObjectMapping; 13 | 14 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile 15 | { 16 | public abstract class RelatedDtoLoaderProfile : IRelatedDtoLoaderProfile 17 | { 18 | private readonly Dictionary _dtoLoaderRules = new Dictionary(); 19 | 20 | private readonly Dictionary _targetDtoPropertyCollection = 21 | new Dictionary(); 22 | 23 | public IReadOnlyDictionary DtoLoaderRules => _dtoLoaderRules; 24 | 25 | public IReadOnlyDictionary TargetDtoPropertyCollections => 26 | _targetDtoPropertyCollection; 27 | 28 | public IDtoLoadRule UseRepositoryLoader() 29 | where TDto : class, IEntityDto 30 | where TEntity : class, IEntity 31 | { 32 | return UseRepositoryLoader(); 33 | } 34 | 35 | public IDtoLoadRule UseRepositoryLoader() 36 | where TDto : class, IEntityDto 37 | where TEntity : class, IEntity 38 | { 39 | var source = BuildRepositoryLoader(); 40 | 41 | var rule = UseLoader(source); 42 | 43 | return rule; 44 | } 45 | 46 | public static Func, Task>> BuildRepositoryLoader() 48 | where TEntity : class, IEntity 49 | where TDto : class, IEntityDto 50 | { 51 | return async (serviceProvider, ids) => 52 | { 53 | var repository = serviceProvider.GetService>(); 54 | var objectMapper = serviceProvider.GetService(); 55 | 56 | var relatedEntities = new List(); 57 | 58 | foreach (var id in ids) 59 | { 60 | relatedEntities.Add(id == null ? null : await repository.GetAsync(id)); 61 | } 62 | 63 | return objectMapper.Map, TDto[]>(relatedEntities); 64 | }; 65 | } 66 | 67 | public IDtoLoadRule UseAppServiceLoader(Func> itemSource) 68 | where TDto : class, IEntityDto 69 | where TAppService : IApplicationService 70 | { 71 | return UseAppServiceLoader(itemSource); 72 | } 73 | 74 | public IDtoLoadRule UseAppServiceLoader(Func> itemSource) 75 | where TDto : class, IEntityDto 76 | where TAppService : IApplicationService 77 | { 78 | var source = BuildAppServiceLoader(itemSource); 79 | 80 | var rule = UseLoader(source); 81 | 82 | return rule; 83 | } 84 | 85 | public static Func, Task>> BuildAppServiceLoader(Func> itemSource) 87 | where TAppService : IApplicationService 88 | where TDto : class, IEntityDto 89 | { 90 | return async (serviceProvider, ids) => 91 | { 92 | var appService = serviceProvider.GetService(); 93 | 94 | var relatedDtos = new List(); 95 | 96 | foreach (var id in ids) 97 | { 98 | relatedDtos.Add(id == null ? null : await itemSource(appService, id)); 99 | } 100 | 101 | return relatedDtos; 102 | }; 103 | } 104 | 105 | public IDtoLoadRule UseLoader(Func, Task>> source) 106 | where TDto : class, IEntityDto 107 | { 108 | return UseLoader(source); 109 | } 110 | 111 | public IDtoLoadRule UseLoader( 112 | Func, Task>> source) 113 | where TDto : class, IEntityDto 114 | { 115 | var rule = new DtoLoadRule(source); 116 | 117 | _dtoLoaderRules[typeof(TDto)] = rule; 118 | 119 | return rule; 120 | } 121 | 122 | public void LoadForDto() 123 | { 124 | LoadForDto(typeof(TTargetDto)); 125 | } 126 | 127 | public void LoadForDto(Type targetDtoType) 128 | { 129 | var props = new RelatedDtoPropertyCollection(targetDtoType); 130 | 131 | if (props.Any()) 132 | { 133 | _targetDtoPropertyCollection[targetDtoType] = props; 134 | } 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoProperty/RelatedDtoProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty 7 | { 8 | public class RelatedDtoProperty 9 | { 10 | public RelatedDtoProperty(RelatedDtoAttribute attribute, RelatedValueType dtoType, RelatedValueType idType) 11 | { 12 | Attribute = attribute; 13 | DtoType = dtoType; 14 | IdType = idType; 15 | 16 | BuildDtoListType(); 17 | } 18 | 19 | public RelatedDtoAttribute Attribute { get; } 20 | 21 | public RelatedValueType DtoType { get; } 22 | 23 | public RelatedValueType IdType { get; } 24 | 25 | public Type DtoListType { get; private set; } 26 | 27 | public MethodInfo Add { get; private set; } 28 | 29 | private void BuildDtoListType() 30 | { 31 | var dtoListType = typeof(List<>).MakeGenericType(DtoType.ElementType); 32 | 33 | DtoListType = dtoListType; 34 | 35 | Add = dtoListType.GetMethod(nameof(IList.Add), BindingFlags.Public | BindingFlags.Instance); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoProperty/RelatedDtoPropertyCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using EasyAbp.Abp.RelatedDtoLoader.Exceptions; 7 | 8 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty 9 | { 10 | public class RelatedDtoPropertyCollection : IEnumerable 11 | { 12 | private readonly Type _targetType; 13 | 14 | private readonly Dictionary _rules; 15 | 16 | public RelatedDtoPropertyCollection(Type targetDtoType) 17 | { 18 | _targetType = targetDtoType; 19 | 20 | _rules = BuildRules(_targetType); 21 | } 22 | 23 | public IEnumerator GetEnumerator() 24 | { 25 | return _rules.Values.GetEnumerator(); 26 | } 27 | 28 | IEnumerator IEnumerable.GetEnumerator() 29 | { 30 | return GetEnumerator(); 31 | } 32 | 33 | private Dictionary BuildRules(Type targetDtoType) 34 | { 35 | var rules = new Dictionary(); 36 | 37 | var propsForRelatedDto = targetDtoType.GetProperties() 38 | .Select(x => new 39 | { 40 | Property = x, 41 | RelatedDtoAttribute = (RelatedDtoAttribute) x.GetCustomAttribute(typeof(RelatedDtoAttribute), true) 42 | }) 43 | .Where(x => x.RelatedDtoAttribute != null) 44 | .ToArray(); 45 | 46 | foreach (var propForRelatedDto in propsForRelatedDto) 47 | { 48 | var attribute = propForRelatedDto.RelatedDtoAttribute; 49 | var dtoProperty = propForRelatedDto.Property; 50 | var dtoPropertyType = dtoProperty.PropertyType; 51 | 52 | var dtoType = new RelatedValueType(dtoProperty); 53 | 54 | var idPropertyName = attribute.IdPropertyName; 55 | 56 | var isList = dtoType.GenericType != null || dtoType.IsArray; 57 | 58 | if (idPropertyName == null) 59 | { 60 | idPropertyName = isList ? $"{dtoPropertyType.Name}Ids" : $"{dtoProperty.Name}Id"; 61 | } 62 | 63 | PropertyInfo idProperty = null; 64 | 65 | if (!string.IsNullOrEmpty(idPropertyName)) 66 | { 67 | idProperty = _targetType.GetProperty(idPropertyName, BindingFlags.Public | BindingFlags.Instance); 68 | } 69 | 70 | if (idProperty == null && isList) 71 | { 72 | throw new MissingIdPropertyNameException(targetDtoType.Name, dtoProperty.Name); 73 | } 74 | 75 | var idType = idProperty == null ? null : new RelatedValueType(idProperty); 76 | 77 | var rule = new RelatedDtoProperty(attribute, dtoType, idType); 78 | 79 | rules.Add(dtoProperty.Name, rule); 80 | } 81 | 82 | return rules; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/EasyAbp/Abp/RelatedDtoLoader/RelatedDtoProperty/RelatedValueType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Reflection; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty 6 | { 7 | public class RelatedValueType 8 | { 9 | public RelatedValueType(PropertyInfo property) 10 | { 11 | Property = property; 12 | DetectType(); 13 | } 14 | 15 | public PropertyInfo Property { get; } 16 | 17 | public Type GenericType { get; private set; } 18 | 19 | public Type ElementType { get; private set; } 20 | 21 | public bool IsArray { get; private set; } 22 | 23 | private void DetectType() 24 | { 25 | var propType = Property.PropertyType; 26 | 27 | var isEnumerable = typeof(IEnumerable).IsAssignableFrom(propType); 28 | 29 | if (propType.HasElementType && propType.IsArray) 30 | { 31 | IsArray = true; 32 | 33 | ElementType = propType.GetElementType(); 34 | } 35 | else if (isEnumerable) 36 | { 37 | GenericType = propType.GetGenericTypeDefinition(); 38 | 39 | ElementType = propType.GetGenericArguments()[0]; 40 | } 41 | else 42 | { 43 | ElementType = propType; 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/EasyAbp.Abp.RelatedDtoLoader/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | EasyAbp.Abp.RelatedDtoLoader.Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidDbContext.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 2 | using Microsoft.EntityFrameworkCore; 3 | using Volo.Abp.EntityFrameworkCore; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 6 | { 7 | public class MyGuidDbContext : AbpDbContext, IEfCoreDbContext 8 | { 9 | public MyGuidDbContext(DbContextOptions options) 10 | : base(options) 11 | { 12 | } 13 | 14 | public DbSet Orders { get; set; } 15 | 16 | public DbSet Products { get; set; } 17 | 18 | protected override void OnModelCreating(ModelBuilder modelBuilder) 19 | { 20 | base.OnModelCreating(modelBuilder); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidEntityFrameworkCoreTestModule.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.TestBase; 2 | using Microsoft.Data.Sqlite; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Volo.Abp; 8 | using Volo.Abp.EntityFrameworkCore; 9 | using Volo.Abp.Modularity; 10 | 11 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 12 | { 13 | [DependsOn( 14 | typeof(AbpEntityFrameworkCoreModule), 15 | typeof(RelatedDtoLoaderTestBaseModule) 16 | )] 17 | public class MyGuidEntityFrameworkCoreTestModule : AbpModule 18 | { 19 | private SqliteConnection _sqliteConnection; 20 | 21 | public override void ConfigureServices(ServiceConfigurationContext context) 22 | { 23 | context.Services.AddAbpDbContext(options => { options.AddDefaultRepositories(true); }); 24 | 25 | _sqliteConnection = CreateDatabaseAndGetConnection(); 26 | 27 | Configure(options => 28 | { 29 | options.Configure(abpDbContextConfigurationContext => 30 | { 31 | abpDbContextConfigurationContext.DbContextOptions.UseSqlite(_sqliteConnection); 32 | }); 33 | }); 34 | } 35 | 36 | private static SqliteConnection CreateDatabaseAndGetConnection() 37 | { 38 | var connection = new SqliteConnection("Data Source=:memory:"); 39 | connection.Open(); 40 | 41 | var options = new DbContextOptionsBuilder().UseSqlite(connection).Options; 42 | using (var context = new MyGuidDbContext(options)) 43 | { 44 | context.GetService().CreateTables(); 45 | } 46 | 47 | return connection; 48 | } 49 | 50 | public override void OnApplicationShutdown(ApplicationShutdownContext context) 51 | { 52 | _sqliteConnection.Dispose(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidRelatedDtoLoaderProfile.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Application; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 5 | { 6 | public class MyGuidRelatedDtoLoaderProfile : RelatedDtoLoaderProfile.RelatedDtoLoaderProfile 7 | { 8 | public MyGuidRelatedDtoLoaderProfile() 9 | { 10 | // UseRepositoryLoader(); 11 | UseAppServiceLoader((service, id) => service.GetAsync(id)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidRelatedDtoLoaderTestModule.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.Configurations; 2 | using EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey; 3 | using EasyAbp.Abp.RelatedDtoLoader.TestBase; 4 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Application; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Volo.Abp; 7 | using Volo.Abp.Autofac; 8 | using Volo.Abp.AutoMapper; 9 | using Volo.Abp.Modularity; 10 | using Volo.Abp.Threading; 11 | 12 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 13 | { 14 | [DependsOn( 15 | typeof(AbpAutofacModule), 16 | typeof(AbpTestBaseModule), 17 | typeof(MyGuidEntityFrameworkCoreTestModule), 18 | typeof(AbpAutoMapperModule), 19 | typeof(AbpRelatedDtoLoaderModule) 20 | )] 21 | public class MyGuidRelatedDtoLoaderTestModule : AbpModule 22 | { 23 | public override void ConfigureServices(ServiceConfigurationContext context) 24 | { 25 | Configure(options => { options.AddProfile(); }); 26 | 27 | Configure(options => 28 | { 29 | options.AddProfile(); 30 | options.LoadForDtosInModule(); 31 | }); 32 | 33 | context.Services 34 | .AddTransient() 35 | .AddSingleton() 36 | .AddSingleton(); 37 | } 38 | 39 | public override void OnApplicationInitialization(ApplicationInitializationContext context) 40 | { 41 | SeedTestData(context); 42 | } 43 | 44 | private static void SeedTestData(ApplicationInitializationContext context) 45 | { 46 | using (var scope = context.ServiceProvider.CreateScope()) 47 | { 48 | AsyncHelper.RunSync(() => scope.ServiceProvider 49 | .GetRequiredService() 50 | .BuildAsync()); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidTestAutoMapperProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 5 | { 6 | public class MyGuidTestAutoMapperProfile : Profile 7 | { 8 | public MyGuidTestAutoMapperProfile() 9 | { 10 | CreateMap(); 11 | CreateMap(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Volo.Abp.DependencyInjection; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 5 | { 6 | public class MyGuidTestData : ISingletonDependency 7 | { 8 | public Guid ProductId { get; } = Guid.NewGuid(); 9 | public Guid OrderId { get; } = Guid.NewGuid(); 10 | } 11 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/MyGuidTestDataBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 3 | using Volo.Abp.DependencyInjection; 4 | using Volo.Abp.Domain.Repositories; 5 | using Volo.Abp.Threading; 6 | 7 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey 8 | { 9 | public class MyGuidTestDataBuilder : ITransientDependency 10 | { 11 | private readonly IRepository _orderRepository; 12 | private readonly IRepository _productRepository; 13 | private readonly MyGuidTestData _testData; 14 | 15 | public MyGuidTestDataBuilder( 16 | MyGuidTestData testData, 17 | IRepository productRepository, 18 | IRepository orderRepository 19 | ) 20 | { 21 | _testData = testData; 22 | _productRepository = productRepository; 23 | _orderRepository = orderRepository; 24 | } 25 | 26 | public void Build() 27 | { 28 | AsyncHelper.RunSync(BuildAsync); 29 | } 30 | 31 | public async Task BuildAsync() 32 | { 33 | await _productRepository.InsertAsync(new Product(_testData.ProductId, "The First Product")); 34 | await _orderRepository.InsertAsync(new Order(_testData.OrderId, _testData.ProductId)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/GuidKey/Tests/RelatedDtoLoader_Basic_Test_Guid.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader; 4 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 5 | using Shouldly; 6 | using Volo.Abp.Domain.Repositories; 7 | using Xunit; 8 | 9 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.GuidKey.Tests 10 | { 11 | public class RelatedDtoLoader_Basic_Test_Guid : RelatedDtoLoaderTestBase 12 | { 13 | public RelatedDtoLoader_Basic_Test_Guid() 14 | { 15 | TestData = GetRequiredService(); 16 | 17 | _productRepository = GetRequiredService>(); 18 | _orderRepository = GetRequiredService>(); 19 | } 20 | 21 | protected MyGuidTestData TestData { get; } 22 | protected IRepository _productRepository; 23 | protected IRepository _orderRepository; 24 | 25 | [Fact] 26 | public async Task Should_Load_Related_ProductDto() 27 | { 28 | await WithUnitOfWorkAsync(async () => 29 | { 30 | var order = _orderRepository.FirstOrDefault(); 31 | 32 | var orderDto = ObjectMapper.Map(order); 33 | 34 | await _relatedDtoLoader.LoadAsync(orderDto); 35 | 36 | orderDto.Product.ShouldNotBeNull(); 37 | }); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerDbContext.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey; 2 | using Microsoft.EntityFrameworkCore; 3 | using Volo.Abp.EntityFrameworkCore; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 6 | { 7 | public class MyIntegerDbContext : AbpDbContext, IEfCoreDbContext 8 | { 9 | public MyIntegerDbContext(DbContextOptions options) 10 | : base(options) 11 | { 12 | } 13 | 14 | public DbSet Orders { get; set; } 15 | 16 | public DbSet Products { get; set; } 17 | 18 | protected override void OnModelCreating(ModelBuilder modelBuilder) 19 | { 20 | base.OnModelCreating(modelBuilder); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerEntityFrameworkCoreTestModule.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.Sqlite; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Storage; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Volo.Abp; 7 | using Volo.Abp.EntityFrameworkCore; 8 | using Volo.Abp.Modularity; 9 | 10 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 11 | { 12 | [DependsOn( 13 | typeof(AbpEntityFrameworkCoreModule) 14 | )] 15 | public class MyIntegerEntityFrameworkCoreTestModule : AbpModule 16 | { 17 | private SqliteConnection _sqliteConnection; 18 | 19 | public override void ConfigureServices(ServiceConfigurationContext context) 20 | { 21 | context.Services.AddAbpDbContext(options => { options.AddDefaultRepositories(true); }); 22 | 23 | _sqliteConnection = CreateDatabaseAndGetConnection(); 24 | 25 | Configure(options => 26 | { 27 | options.Configure(abpDbContextConfigurationContext => 28 | { 29 | abpDbContextConfigurationContext.DbContextOptions.UseSqlite(_sqliteConnection); 30 | }); 31 | }); 32 | } 33 | 34 | private static SqliteConnection CreateDatabaseAndGetConnection() 35 | { 36 | var connection = new SqliteConnection("Data Source=:memory:"); 37 | connection.Open(); 38 | 39 | var options = new DbContextOptionsBuilder().UseSqlite(connection).Options; 40 | using (var context = new MyIntegerDbContext(options)) 41 | { 42 | context.GetService().CreateTables(); 43 | } 44 | 45 | return connection; 46 | } 47 | 48 | public override void OnApplicationShutdown(ApplicationShutdownContext context) 49 | { 50 | _sqliteConnection.Dispose(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerRelatedDtoLoaderProfile.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 4 | { 5 | public class MyIntegerRelatedDtoLoaderProfile : RelatedDtoLoaderProfile.RelatedDtoLoaderProfile 6 | { 7 | public MyIntegerRelatedDtoLoaderProfile() 8 | { 9 | UseRepositoryLoader(); 10 | 11 | LoadForDto(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerRelatedDtoLoaderTestModule.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.Configurations; 2 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Volo.Abp; 5 | using Volo.Abp.Autofac; 6 | using Volo.Abp.AutoMapper; 7 | using Volo.Abp.Modularity; 8 | using Volo.Abp.Threading; 9 | 10 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 11 | { 12 | [DependsOn( 13 | typeof(AbpAutofacModule), 14 | typeof(AbpTestBaseModule), 15 | typeof(MyIntegerEntityFrameworkCoreTestModule), 16 | typeof(AbpAutoMapperModule), 17 | typeof(AbpRelatedDtoLoaderModule) 18 | )] 19 | public class MyIntegerRelatedDtoLoaderTestModule : AbpModule 20 | { 21 | public override void ConfigureServices(ServiceConfigurationContext context) 22 | { 23 | Configure(options => { options.AddProfile(); }); 24 | 25 | Configure(options => 26 | { 27 | options.AddProfile(); 28 | options.LoadForDtosInModule(); 29 | }); 30 | 31 | context.Services 32 | .AddSingleton() 33 | .AddSingleton() 34 | .AddSingleton() 35 | ; 36 | } 37 | 38 | public override void OnApplicationInitialization(ApplicationInitializationContext context) 39 | { 40 | SeedTestData(context); 41 | } 42 | 43 | private static void SeedTestData(ApplicationInitializationContext context) 44 | { 45 | using (var scope = context.ServiceProvider.CreateScope()) 46 | { 47 | AsyncHelper.RunSync(() => scope.ServiceProvider 48 | .GetRequiredService() 49 | .BuildAsync()); 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerTestAutoMapperProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 5 | { 6 | public class MyIntegerTestAutoMapperProfile : Profile 7 | { 8 | public MyIntegerTestAutoMapperProfile() 9 | { 10 | CreateMap(); 11 | CreateMap(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerTestData.cs: -------------------------------------------------------------------------------- 1 | using Volo.Abp.DependencyInjection; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 4 | { 5 | public class MyIntegerTestData : ISingletonDependency 6 | { 7 | public int ProductId { get; } = 123; 8 | public int OrderId { get; } = 456; 9 | } 10 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/MyIntegerTestDataBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey; 3 | using Volo.Abp.DependencyInjection; 4 | using Volo.Abp.Domain.Repositories; 5 | using Volo.Abp.Threading; 6 | 7 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey 8 | { 9 | public class MyIntegerTestDataBuilder : ITransientDependency 10 | { 11 | private readonly IRepository _orderRepository; 12 | private readonly IRepository _productRepository; 13 | private readonly MyIntegerTestData _testData; 14 | 15 | public MyIntegerTestDataBuilder( 16 | MyIntegerTestData testData, 17 | IRepository productRepository, 18 | IRepository orderRepository 19 | ) 20 | { 21 | _testData = testData; 22 | _productRepository = productRepository; 23 | _orderRepository = orderRepository; 24 | } 25 | 26 | public void Build() 27 | { 28 | AsyncHelper.RunSync(BuildAsync); 29 | } 30 | 31 | public async Task BuildAsync() 32 | { 33 | await _productRepository.InsertAsync(new IntProduct(_testData.ProductId, "The First Product")); 34 | await _orderRepository.InsertAsync(new IntOrder(_testData.OrderId, _testData.ProductId)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/IntegerKey/Tests/RelatedDtoLoader_Basic_Test_Integer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader; 4 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey; 5 | using Shouldly; 6 | using Volo.Abp.Domain.Repositories; 7 | using Xunit; 8 | 9 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests.IntegerKey.Tests 10 | { 11 | public class RelatedDtoLoader_Basic_Test_Integer : RelatedDtoLoaderTestBase 12 | { 13 | public RelatedDtoLoader_Basic_Test_Integer() 14 | { 15 | TestData = GetRequiredService(); 16 | 17 | _productRepository = GetRequiredService>(); 18 | _orderRepository = GetRequiredService>(); 19 | } 20 | 21 | protected MyIntegerTestData TestData { get; } 22 | protected IRepository _productRepository; 23 | protected IRepository _orderRepository; 24 | 25 | [Fact] 26 | public async Task Should_Load_Related_ProductDto() 27 | { 28 | await WithUnitOfWorkAsync(async () => 29 | { 30 | var order = _orderRepository.FirstOrDefault(); 31 | 32 | var orderDto = ObjectMapper.Map(order); 33 | 34 | await _relatedDtoLoader.LoadAsync(orderDto); 35 | 36 | orderDto.Product.ShouldNotBeNull(); 37 | }); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.IntegratedTests/RelatedDtoLoaderTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Volo.Abp; 6 | using Volo.Abp.Modularity; 7 | using Volo.Abp.ObjectMapping; 8 | using Volo.Abp.Testing; 9 | using Volo.Abp.Uow; 10 | 11 | namespace EasyAbp.Abp.RelatedDtoLoader.IntegratedTests 12 | { 13 | public abstract class RelatedDtoLoaderTestBase : AbpIntegratedTest 14 | where TTestModule : AbpModule 15 | { 16 | protected IRelatedDtoLoader _relatedDtoLoader; 17 | 18 | protected RelatedDtoLoaderTestBase() 19 | { 20 | ObjectMapper = GetRequiredService(); 21 | _relatedDtoLoader = GetRequiredService(); 22 | } 23 | 24 | protected IObjectMapper ObjectMapper { get; } 25 | 26 | protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) 27 | { 28 | options.UseAutofac(); 29 | } 30 | 31 | protected virtual Task WithUnitOfWorkAsync(Func func) 32 | { 33 | return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); 34 | } 35 | 36 | protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) 37 | { 38 | using (var scope = ServiceProvider.CreateScope()) 39 | { 40 | var uowManager = scope.ServiceProvider.GetRequiredService(); 41 | 42 | using (var uow = uowManager.Begin(options)) 43 | { 44 | await action(); 45 | 46 | await uow.CompleteAsync(); 47 | } 48 | } 49 | } 50 | 51 | protected virtual Task WithUnitOfWorkAsync(Func> func) 52 | { 53 | return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); 54 | } 55 | 56 | protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, 57 | Func> func) 58 | { 59 | using (var scope = ServiceProvider.CreateScope()) 60 | { 61 | var uowManager = scope.ServiceProvider.GetRequiredService(); 62 | 63 | using (var uow = uowManager.Begin(options)) 64 | { 65 | var result = await func(); 66 | await uow.CompleteAsync(); 67 | return result; 68 | } 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Application/IProductAppService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 3 | using Volo.Abp.Application.Services; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Application 6 | { 7 | public interface IProductAppService : ICrudAppService 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Application/ProductAppService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 3 | using Volo.Abp.Application.Services; 4 | using Volo.Abp.Domain.Repositories; 5 | 6 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Application 7 | { 8 | public class ProductAppService : CrudAppService, IProductAppService 9 | { 10 | public ProductAppService(IRepository repository) : base(repository) 11 | { 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/IntegerKey/IntOrder.cs: -------------------------------------------------------------------------------- 1 | using Volo.Abp.Domain.Entities; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey 4 | { 5 | public class IntOrder : Entity 6 | { 7 | protected IntOrder() 8 | { 9 | } 10 | 11 | public IntOrder(int id, int productId) 12 | : base(id) 13 | { 14 | ProductId = productId; 15 | } 16 | 17 | public virtual int ProductId { get; protected set; } 18 | } 19 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/IntegerKey/IntOrderDto.cs: -------------------------------------------------------------------------------- 1 | using Volo.Abp.Application.Dtos; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey 4 | { 5 | public class IntOrderDto : EntityDto 6 | { 7 | public int ProductId { get; set; } 8 | 9 | [RelatedDto] public IntProductDto Product { get; set; } 10 | 11 | public IntOrderDto Clone() 12 | { 13 | return (IntOrderDto) MemberwiseClone(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/IntegerKey/IntProduct.cs: -------------------------------------------------------------------------------- 1 | using Volo.Abp.Domain.Entities; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey 4 | { 5 | public class IntProduct : Entity 6 | { 7 | protected IntProduct() 8 | { 9 | } 10 | 11 | public IntProduct(int id, string name) 12 | : base(id) 13 | { 14 | Name = name; 15 | } 16 | 17 | public virtual string Name { get; protected set; } 18 | } 19 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/IntegerKey/IntProductDto.cs: -------------------------------------------------------------------------------- 1 | using Volo.Abp.Application.Dtos; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain.IntegerKey 4 | { 5 | public class IntProductDto : EntityDto 6 | { 7 | public string Name { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Volo.Abp.Domain.Entities; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain 5 | { 6 | public class Order : Entity 7 | { 8 | protected Order() 9 | { 10 | } 11 | 12 | public Order(Guid id, Guid productId) 13 | : base(id) 14 | { 15 | ProductId = productId; 16 | } 17 | 18 | public virtual Guid ProductId { get; protected set; } 19 | } 20 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/OrderDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Volo.Abp.Application.Dtos; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain 5 | { 6 | public class OrderDto : EntityDto 7 | { 8 | public Guid ProductId { get; set; } 9 | 10 | [RelatedDto] public ProductDto Product { get; set; } 11 | 12 | public Guid? OptionalProductId { get; set; } 13 | 14 | [RelatedDto] public ProductDto OptionalProduct { get; set; } 15 | 16 | public OrderDto Clone() 17 | { 18 | return (OrderDto) MemberwiseClone(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Volo.Abp.Domain.Entities; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain 5 | { 6 | public class Product : Entity 7 | { 8 | protected Product() 9 | { 10 | } 11 | 12 | public Product(Guid id, string name) 13 | : base(id) 14 | { 15 | Name = name; 16 | } 17 | 18 | public virtual string Name { get; protected set; } 19 | } 20 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/ProductCommentDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Volo.Abp.Application.Dtos; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain 5 | { 6 | public class ProductCommentDto : EntityDto 7 | { 8 | public string Content { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/ProductDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Volo.Abp.Application.Dtos; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain 6 | { 7 | public class ProductDto : EntityDto 8 | { 9 | public string Name { get; set; } 10 | 11 | public Guid[] CommentIds { get; set; } 12 | 13 | [RelatedDto(nameof(CommentIds))] public IEnumerable Comments { get; set; } 14 | 15 | [RelatedDto(nameof(CommentIds))] public List CommentsList { get; set; } 16 | 17 | [RelatedDto(nameof(CommentIds))] public IReadOnlyList CommentsReadOnlyList { get; set; } 18 | 19 | [RelatedDto(nameof(CommentIds))] public ProductCommentDto[] CommentsArray { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/Domain/UnsupportedOrderDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Volo.Abp.Application.Dtos; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain 5 | { 6 | public class UnsupportedOrderDto : EntityDto 7 | { 8 | public Guid ProductId { get; set; } 9 | 10 | public ProductDto Product { get; set; } 11 | 12 | public Guid? OptionalProductId { get; set; } 13 | 14 | public ProductDto OptionalProduct { get; set; } 15 | 16 | public UnsupportedOrderDto Clone() 17 | { 18 | return (UnsupportedOrderDto) MemberwiseClone(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/EasyAbp.Abp.RelatedDtoLoader.TestBase.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.TestBase/RelatedDtoLoaderTestBaseModule.cs: -------------------------------------------------------------------------------- 1 | using Volo.Abp.Application; 2 | using Volo.Abp.Modularity; 3 | 4 | namespace EasyAbp.Abp.RelatedDtoLoader.TestBase 5 | { 6 | [DependsOn(typeof(AbpDddApplicationModule))] 7 | public class RelatedDtoLoaderTestBaseModule : AbpModule 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.UnitTests/EasyAbp.Abp.RelatedDtoLoader.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | EasyAbp.Abp.RelatedDtoLoader.UnitTests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.UnitTests/Loader/MyUnitTestDtoLoaderProfile.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 2 | 3 | namespace EasyAbp.Abp.RelatedDtoLoader.UnitTests.Loader 4 | { 5 | public class MyUnitTestDtoLoaderProfile : RelatedDtoLoaderProfile.RelatedDtoLoaderProfile 6 | { 7 | public MyUnitTestDtoLoaderProfile() 8 | { 9 | UseRepositoryLoader(); 10 | 11 | LoadForDto(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.UnitTests/Loader/RelatedDtoLoaderConfigurationTest.cs: -------------------------------------------------------------------------------- 1 | using EasyAbp.Abp.RelatedDtoLoader.Configurations; 2 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace EasyAbp.Abp.RelatedDtoLoader.UnitTests.Loader 7 | { 8 | public class RelatedDtoLoaderConfigurationTest 9 | { 10 | private void Check_RelatedDtoPropertie_By_Profile(DtoLoaderConfiguration config) 11 | { 12 | config.GetRelatedDtoProperties(typeof(OrderDto)).ShouldNotBeNull(); 13 | 14 | config.GetRelatedDtoProperties(typeof(UnsupportedOrderDto)).ShouldBeNull(); 15 | config.GetRelatedDtoProperties(typeof(Order)).ShouldBeNull(); 16 | config.GetRelatedDtoProperties(typeof(Product)).ShouldBeNull(); 17 | 18 | config.GetRelatedDtoProperties(typeof(Product)).ShouldBeNull(); 19 | 20 | config.GetRelatedDtoProperties(typeof(ProductDto)).ShouldBeNull(); 21 | } 22 | 23 | private void Check_LoadRule_By_Profile(DtoLoaderConfiguration config) 24 | { 25 | config.GetLoadRule(typeof(OrderDto)).ShouldBeNull(); 26 | 27 | config.GetLoadRule(typeof(ProductDto)).ShouldNotBeNull(); 28 | } 29 | 30 | private static DtoLoaderConfiguration GetDtoLoaderConfiguration() 31 | { 32 | var configurationExpress = new DtoLoaderConfigurationExpression(); 33 | configurationExpress.AddProfile(new MyUnitTestDtoLoaderProfile()); 34 | 35 | var config = new DtoLoaderConfiguration(configurationExpress); 36 | return config; 37 | } 38 | 39 | [Fact] 40 | public void Should_Have_Correct_RelatedDtoProperties_By_Assembly() 41 | { 42 | var configurationExpress = new DtoLoaderConfigurationExpression(); 43 | var assemblyOptions = new RelatedDtoLoaderAssemblyOptions(); 44 | 45 | configurationExpress.AddAssemblies(assemblyOptions, typeof(RelatedDtoLoaderConfigurationTest).Assembly); 46 | var config = new DtoLoaderConfiguration(configurationExpress); 47 | 48 | Check_RelatedDtoPropertie_By_Profile(config); 49 | Check_LoadRule_By_Profile(config); 50 | } 51 | 52 | [Fact] 53 | public void Should_Have_Correct_RelatedDtoProperties_By_Profile() 54 | { 55 | var config = GetDtoLoaderConfiguration(); 56 | 57 | Check_RelatedDtoPropertie_By_Profile(config); 58 | Check_LoadRule_By_Profile(config); 59 | } 60 | 61 | [Fact] 62 | public void Should_Have_Null_RelatedDtoProperty_When_Undefined() 63 | { 64 | var config = GetDtoLoaderConfiguration(); 65 | 66 | var dtoProperties = config.GetRelatedDtoProperties(typeof(ProductDto)); 67 | 68 | dtoProperties.ShouldBeNull(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.UnitTests/Loader/RelatedDtoLoaderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using EasyAbp.Abp.RelatedDtoLoader.Configurations; 6 | using EasyAbp.Abp.RelatedDtoLoader.DtoLoadRule; 7 | using EasyAbp.Abp.RelatedDtoLoader.Exceptions; 8 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoader; 9 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoLoaderProfile; 10 | using EasyAbp.Abp.RelatedDtoLoader.RelatedDtoProperty; 11 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 12 | using Moq; 13 | using Shouldly; 14 | using Xunit; 15 | 16 | namespace EasyAbp.Abp.RelatedDtoLoader.UnitTests.Loader 17 | { 18 | public class RelatedDtoLoader_Test 19 | { 20 | private readonly MyUnitTestData _testData = new MyUnitTestData(); 21 | 22 | private static RelatedDtoLoader.RelatedDtoLoader GetRelatedDtoLoader(MyUnitTestData testData) 23 | { 24 | var fakeConfig = new Mock(); 25 | 26 | var fakeProfile = new Mock(); 27 | 28 | FackProduct(testData, fakeConfig); 29 | FackProductComment(testData, fakeConfig); 30 | 31 | var orderRelatedDtoProperties = new RelatedDtoPropertyCollection(typeof(OrderDto)); 32 | fakeConfig.Setup(x => x.GetRelatedDtoProperties(typeof(OrderDto))) 33 | .Returns(orderRelatedDtoProperties); 34 | 35 | var dtoLoader = new RelatedDtoLoader.RelatedDtoLoader(null, fakeConfig.Object); 36 | 37 | return dtoLoader; 38 | } 39 | 40 | private static void FackProduct(MyUnitTestData testData, Mock fakeProfile) 41 | { 42 | var fakeRule = new Mock(); 43 | 44 | fakeRule.Setup(x => x.LoadAsObjectAsync(It.IsAny(), It.IsAny>())) 45 | .Returns>((serviceProvider, ids) => 46 | { 47 | return Task.FromResult(testData.ProductDtos.Where(x => ids.Contains(x.Id)) 48 | .Select(x => (object) x)); 49 | }); 50 | 51 | fakeRule.Setup(x => x.GetKey(It.IsAny())) 52 | .Returns(x => { return ((ProductDto) x).Id; }); 53 | 54 | fakeProfile.Setup(x => x.GetLoadRule(typeof(ProductDto))) 55 | .Returns(fakeRule.Object); 56 | 57 | var productRelatedDtoProperties = new RelatedDtoPropertyCollection(typeof(ProductDto)); 58 | 59 | fakeProfile.Setup(x => x.GetRelatedDtoProperties(typeof(ProductDto))) 60 | .Returns(productRelatedDtoProperties); 61 | } 62 | 63 | private static void FackProductComment(MyUnitTestData testData, 64 | Mock fakeProfile) 65 | { 66 | var fakeRule = new Mock(); 67 | 68 | fakeRule.Setup(x => x.LoadAsObjectAsync(It.IsAny(), It.IsAny>())) 69 | .Returns>((serviceProvider, ids) => 70 | { 71 | return Task.FromResult(testData.ProductCommentDtos.Where(x => ids.Contains(x.Id)) 72 | .Select(x => (object) x)); 73 | }); 74 | 75 | fakeRule.Setup(x => x.GetKey(It.IsAny())) 76 | .Returns(x => { return ((ProductCommentDto) x).Id; }); 77 | 78 | fakeProfile.Setup(x => x.GetLoadRule(typeof(ProductCommentDto))) 79 | .Returns(fakeRule.Object); 80 | } 81 | 82 | [Fact] 83 | public async Task Should_Load_Related_OptionalProductDto_for_Single() 84 | { 85 | var testData = _testData; 86 | 87 | var dtoLoader = GetRelatedDtoLoader(testData); 88 | 89 | var orders = testData.OrderDtos.ToArray(); 90 | var firstOrder = orders.FirstOrDefault(); 91 | 92 | await dtoLoader.LoadAsync(firstOrder); 93 | 94 | firstOrder.OptionalProduct.ShouldNotBeNull(); 95 | firstOrder.Product.Id.ShouldBe(testData.SecondProduct.Id); 96 | } 97 | 98 | [Fact] 99 | public async Task Should_Load_Related_ProductCommentDtos() 100 | { 101 | var testData = _testData; 102 | 103 | var dtoLoader = GetRelatedDtoLoader(testData); 104 | 105 | var products = testData.ProductDtos.ToArray(); 106 | var secondProduct = products.Skip(1).FirstOrDefault(); 107 | 108 | await dtoLoader.LoadAsync(secondProduct); 109 | 110 | secondProduct.Comments.ShouldNotBeNull(); 111 | secondProduct.Comments.Count().ShouldBe(2); 112 | 113 | secondProduct.CommentsList.ShouldNotBeNull(); 114 | secondProduct.CommentsList.Count().ShouldBe(2); 115 | 116 | secondProduct.CommentsReadOnlyList.ShouldNotBeNull(); 117 | secondProduct.CommentsReadOnlyList.Count().ShouldBe(2); 118 | 119 | secondProduct.CommentsArray.ShouldNotBeNull(); 120 | secondProduct.CommentsArray.Count().ShouldBe(2); 121 | } 122 | 123 | [Fact] 124 | public async Task Should_Load_Related_ProductDto_for_Single() 125 | { 126 | var testData = _testData; 127 | 128 | var dtoLoader = GetRelatedDtoLoader(testData); 129 | 130 | var orders = testData.OrderDtos.ToArray(); 131 | var firstOrder = orders[0]; 132 | var secondOrder = orders[1]; 133 | 134 | await dtoLoader.LoadAsync(firstOrder); 135 | 136 | firstOrder.Product.ShouldNotBeNull(); 137 | firstOrder.Product.Id.ShouldBe(testData.SecondProduct.Id); 138 | firstOrder.OptionalProduct.ShouldNotBeNull(); 139 | firstOrder.OptionalProduct.Id.ShouldBe(testData.SecondProduct.Id); 140 | 141 | secondOrder.OptionalProduct.ShouldBeNull(); 142 | } 143 | 144 | [Fact] 145 | public async Task Should_Load_Related_ProductDtos_for_Multi() 146 | { 147 | var testData = _testData; 148 | 149 | var dtoLoader = GetRelatedDtoLoader(testData); 150 | 151 | var orders = testData.OrderDtos.ToArray(); 152 | var firstOrder = orders[0]; 153 | var secondOrder = orders[1]; 154 | 155 | await dtoLoader.LoadListAsync(orders); 156 | 157 | firstOrder.Product.ShouldNotBeNull(); 158 | firstOrder.Product.Id.ShouldBe(testData.SecondProduct.Id); 159 | firstOrder.OptionalProduct.ShouldNotBeNull(); 160 | firstOrder.OptionalProduct.Id.ShouldBe(testData.SecondProduct.Id); 161 | 162 | secondOrder.OptionalProduct.ShouldBeNull(); 163 | } 164 | 165 | [Fact] 166 | public async Task Should_Throw_Exception_for_Unsupported_TargetDto() 167 | { 168 | var testData = _testData; 169 | 170 | var dtoLoader = GetRelatedDtoLoader(testData); 171 | 172 | var unsupportedOrder = new UnsupportedOrderDto(); 173 | 174 | await Should.ThrowAsync(dtoLoader.LoadAsync(unsupportedOrder)); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.UnitTests/MyUnitTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using EasyAbp.Abp.RelatedDtoLoader.TestBase.Domain; 4 | 5 | namespace EasyAbp.Abp.RelatedDtoLoader.UnitTests 6 | { 7 | public class MyUnitTestData 8 | { 9 | private readonly OrderDto[] _orderDtos; 10 | public readonly ProductDto FirstProduct; 11 | public readonly ProductCommentDto[] ProductCommentDtos; 12 | 13 | public readonly ProductDto[] ProductDtos; 14 | public readonly ProductDto SecondProduct; 15 | 16 | public MyUnitTestData() 17 | { 18 | var commentDto1 = new ProductCommentDto {Id = Guid.NewGuid(), Content = "Comment 1"}; 19 | var commentDto2 = new ProductCommentDto {Id = Guid.NewGuid(), Content = "Comment 1"}; 20 | 21 | ProductCommentDtos = new[] 22 | { 23 | commentDto1, 24 | commentDto2 25 | }; 26 | 27 | var productDto1 = new ProductDto {Id = Guid.NewGuid(), Name = "Product 1"}; 28 | var productDto2 = new ProductDto 29 | {Id = Guid.NewGuid(), Name = "Product 2", CommentIds = new[] {commentDto1.Id, commentDto2.Id}}; 30 | 31 | ProductDtos = new[] 32 | { 33 | productDto1, 34 | productDto2 35 | }; 36 | 37 | FirstProduct = productDto1; 38 | SecondProduct = productDto2; 39 | 40 | var orderDto1 = new OrderDto 41 | {Id = Guid.NewGuid(), ProductId = productDto2.Id, OptionalProductId = productDto2.Id}; 42 | var orderDto2 = new OrderDto {Id = Guid.NewGuid(), OptionalProductId = null}; 43 | 44 | _orderDtos = new[] 45 | { 46 | orderDto1, 47 | orderDto2 48 | }; 49 | } 50 | 51 | public OrderDto[] OrderDtos 52 | { 53 | get 54 | { 55 | var items = _orderDtos.Select(x => x.Clone()).ToArray(); 56 | return items; 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /test/EasyAbp.Abp.RelatedDtoLoader.UnitTests/obj/Release/netcoreapp3.1/EasyAbp.Abp.RelatedDtoLoader.UnitTests.AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | using System; 11 | using System.Reflection; 12 | 13 | [assembly: System.Reflection.AssemblyCompanyAttribute("EasyAbp.Abp.RelatedDtoLoader.UnitTests")] 14 | [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] 15 | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] 16 | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] 17 | [assembly: System.Reflection.AssemblyProductAttribute("EasyAbp.Abp.RelatedDtoLoader.UnitTests")] 18 | [assembly: System.Reflection.AssemblyTitleAttribute("EasyAbp.Abp.RelatedDtoLoader.UnitTests")] 19 | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] 20 | 21 | // 由 MSBuild WriteCodeFragment 类生成。 22 | 23 | --------------------------------------------------------------------------------