├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .gitignore ├── AsyncGenerator.yml ├── LICENSE ├── NHibernate.Extensions.Tests ├── App.config ├── Async │ ├── BatchFetchTests.cs │ ├── DeepCloneTests.cs │ └── LinqIncludeTests.cs ├── BaseIncludeTest.cs ├── BatchFetchTests.cs ├── CascadeConvention.cs ├── DeepCloneTests.cs ├── Entities │ ├── Animal.cs │ ├── AnimalType.cs │ ├── BatchModel.cs │ ├── Cat.cs │ ├── EQBDrivingLicence.cs │ ├── EQBHouse.cs │ ├── EQBIdentity.cs │ ├── EQBIdentityCard.cs │ ├── EQBPerson.cs │ ├── EQBRoadWorthyTest.cs │ ├── EQBUser.cs │ ├── EQBVehicle.cs │ ├── IUser.cs │ └── TestEQBWheel.cs ├── IEntity.cs ├── LinqIncludeTests.cs ├── LogSpy.cs ├── NHConfig.cs ├── NHibernate.Extensions.Tests.csproj ├── QueryRelationTreeTests.cs ├── SessionSubscriptionTests.cs ├── Test.ldf ├── Test.mdf └── hibernate.cfg.xml ├── NHibernate.Extensions.sln ├── NHibernate.Extensions ├── Async │ ├── BatchFetchBuilder.cs │ ├── BatchFetchExtension.cs │ ├── DeepClone │ │ └── DeepCloneSessionExtension.cs │ └── IBatchFetchBuilder.cs ├── BatchFetchBuilder.cs ├── BatchFetchExtension.cs ├── DeepClone │ ├── DeepCloneMemberOptions.cs │ ├── DeepCloneOptions.cs │ ├── DeepCloneParentEntity.cs │ ├── DeepCloneSessionExtension.cs │ ├── DeepCloneTypeOptions.cs │ ├── IDeepCloneMemberOptions.cs │ ├── IDeepCloneTypeOptions.cs │ └── IEntityResolver.cs ├── IBatchFetchBuilder.cs ├── Internal │ ├── DelegateEntityResolver.cs │ ├── ExpressionExtensions.cs │ ├── ExpressionHelper.cs │ ├── TypeExtensions.cs │ └── TypeHelper.cs ├── Linq │ ├── ExpressionInfo.cs │ ├── IIncludeOptions.cs │ ├── IIncludeQueryable.cs │ ├── IncludeOptions.cs │ ├── IncludeQueryProvider.cs │ ├── IncludeQueryable.cs │ ├── IncludeRewriterVisitor.cs │ ├── LinqExtensions.cs │ └── SkipTakeVisitor.cs ├── NHibernate.Extensions.csproj ├── QueryRelationNode.cs ├── QueryRelationTree.cs └── Subscriptions │ ├── ISessionSubscription.cs │ ├── ITransactionSubscription.cs │ ├── SessionSubscription.cs │ ├── SessionSubscriptionExtension.cs │ ├── TransactionListener.cs │ └── TransactionSubscription.cs ├── README.md └── build.cake /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpasyncgenerator.tool": { 6 | "version": "0.19.1", 7 | "commands": [ 8 | "async-generator" 9 | ] 10 | }, 11 | "cake.tool": { 12 | "version": "1.0.0-rc0003", 13 | "commands": [ 14 | "dotnet-cake" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome:http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [*] 8 | indent_style = space 9 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 10 | 11 | # Code files 12 | [*.{cs,csx,vb,vbx}] 13 | indent_size = 4 14 | insert_final_newline = true 15 | charset = utf-8-bom 16 | 17 | # Xml project files 18 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 19 | indent_size = 4 20 | 21 | # Xml config files 22 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 23 | indent_size = 2 24 | 25 | [{src,scripts}/**.{ts,json,js}] 26 | end_of_line = crlf 27 | charset = utf-8 28 | trim_trailing_whitespace = true 29 | insert_final_newline = true 30 | indent_style = space 31 | indent_size = 4 32 | 33 | # Dotnet code style settings: 34 | [*.{cs,vb}] 35 | # Sort using and Import directives with System.* appearing first 36 | dotnet_sort_system_directives_first = true 37 | # Avoid "this." and "Me." if not necessary 38 | dotnet_style_qualification_for_field = false:suggestion 39 | dotnet_style_qualification_for_property = false:suggestion 40 | dotnet_style_qualification_for_method = false:suggestion 41 | dotnet_style_qualification_for_event = false:suggestion 42 | 43 | # Use language keywords instead of framework type names for type references 44 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 45 | dotnet_style_predefined_type_for_member_access = true:suggestion 46 | 47 | # Suggest more modern language features when available 48 | dotnet_style_object_initializer = true:suggestion 49 | dotnet_style_collection_initializer = true:suggestion 50 | dotnet_style_coalesce_expression = true:suggestion 51 | dotnet_style_null_propagation = true:suggestion 52 | dotnet_style_explicit_tuple_names = true:suggestion 53 | 54 | # CSharp code style settings: 55 | [*.cs] 56 | # Prefer "var" everywhere 57 | csharp_style_var_for_built_in_types = true:suggestion 58 | csharp_style_var_when_type_is_apparent = true:suggestion 59 | csharp_style_var_elsewhere = true:suggestion 60 | 61 | # Prefer method-like constructs to have a block body 62 | csharp_style_expression_bodied_methods = false:none 63 | csharp_style_expression_bodied_constructors = false:none 64 | csharp_style_expression_bodied_operators = false:none 65 | 66 | # Prefer property-like constructs to have an expression-body 67 | csharp_style_expression_bodied_properties = true:none 68 | csharp_style_expression_bodied_indexers = true:none 69 | csharp_style_expression_bodied_accessors = true:none 70 | 71 | # Suggest more modern language features when available 72 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 73 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 74 | csharp_style_inlined_variable_declaration = true:suggestion 75 | csharp_style_throw_expression = true:suggestion 76 | csharp_style_conditional_delegate_call = true:suggestion 77 | 78 | # Newline settings 79 | csharp_new_line_before_open_brace = all 80 | csharp_new_line_before_else = true 81 | csharp_new_line_before_catch = true 82 | csharp_new_line_before_finally = true 83 | csharp_new_line_before_members_in_object_initializers = true 84 | csharp_new_line_before_members_in_anonymous_types = true 85 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.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 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 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 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # LightSwitch generated files 234 | GeneratedArtifacts/ 235 | ModelManifest.xml 236 | 237 | # Paket dependency manager 238 | .paket/paket.exe 239 | 240 | # FAKE - F# Make 241 | .fake/ 242 | [Tt]ools/ 243 | .idea/ 244 | *.dev.props 245 | 246 | package/ -------------------------------------------------------------------------------- /AsyncGenerator.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | - filePath: Source/NHibernate.Extensions/NHibernate.Extensions.csproj 3 | targetFramework: net461 4 | concurrentRun: false 5 | applyChanges: true 6 | analyzation: 7 | methodConversion: 8 | - conversion: ToAsync 9 | containingTypeName: IBatchFetchBuilder 10 | name: Execute 11 | - conversion: Smart 12 | name: LoadEntity 13 | callForwarding: true 14 | cancellationTokens: 15 | guards: true 16 | methodParameter: 17 | - parameter: Optional 18 | scanMethodBody: true 19 | searchAsyncCounterpartsInInheritedTypes: true 20 | scanForMissingAsyncMembers: 21 | - all: true 22 | transformation: 23 | configureAwaitArgument: false 24 | localFunctions: true 25 | registerPlugin: 26 | - type: AsyncGenerator.Core.Plugins.EmptyRegionRemover 27 | assemblyName: AsyncGenerator.Core 28 | - filePath: Source/NHibernate.Extensions.Tests/NHibernate.Extensions.Tests.csproj 29 | targetFramework: net461 30 | concurrentRun: false 31 | applyChanges: true 32 | analyzation: 33 | methodConversion: 34 | - conversion: Ignore 35 | name: FillData 36 | cancellationTokens: 37 | enabled: true 38 | exceptionHandling: 39 | catchMethodBody: 40 | - all: true 41 | result: false 42 | scanMethodBody: true 43 | searchAsyncCounterpartsInInheritedTypes: true 44 | scanForMissingAsyncMembers: 45 | - all: true 46 | registerPlugin: 47 | - type: AsyncGenerator.Core.Plugins.NUnitPlugin 48 | parameters: 49 | - name: createNewTypes 50 | value: false 51 | assemblyName: AsyncGenerator.Core -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 maca88 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 | 23 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Async/BatchFetchTests.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by AsyncGenerator. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Diagnostics; 14 | using System.Linq; 15 | using NHibernate.Extensions.Tests.Entities; 16 | using NHibernate.Tool.hbm2ddl; 17 | using NUnit.Framework; 18 | 19 | namespace NHibernate.Extensions.Tests 20 | { 21 | using System.Threading.Tasks; 22 | public partial class BatchFetchTests 23 | { 24 | 25 | [Test] 26 | public async Task TestSelectAnonymousTypeAsync() 27 | { 28 | var keys = Enumerable.Range(1, 600).ToList(); 29 | 30 | using (var session = NHConfig.OpenSession()) 31 | { 32 | var stats = session.SessionFactory.Statistics; 33 | var queryCount = stats.PrepareStatementCount; 34 | var models = await (session.BatchFetch(50) 35 | .SetKeys(keys, o => o.Id) 36 | .BeforeQueryExecution(q => q.Where(o => o.Id > 400)) 37 | .Select(o => new { o.Name }) 38 | .ExecuteAsync()); 39 | 40 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / 50m); 41 | Assert.AreEqual(200, models.Count); 42 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 43 | } 44 | } 45 | 46 | [Test] 47 | public async Task TestSelectStringAsync() 48 | { 49 | var keys = Enumerable.Range(1, 600).ToList(); 50 | 51 | using (var session = NHConfig.OpenSession()) 52 | { 53 | var stats = session.SessionFactory.Statistics; 54 | var queryCount = stats.PrepareStatementCount; 55 | var models = await (session.BatchFetch(50) 56 | .SetKeys(keys, o => o.Id) 57 | .Select(o => o.Name) 58 | .BeforeQueryExecution(q => q.Where(o => o.Id > 400)) 59 | .ExecuteAsync()); 60 | 61 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / 50m); 62 | Assert.AreEqual(200, models.Count); 63 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Async/DeepCloneTests.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by AsyncGenerator. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using NHibernate.Extensions.Tests.Entities; 15 | using NHibernate.Linq; 16 | using NUnit.Framework; 17 | 18 | namespace NHibernate.Extensions.Tests 19 | { 20 | using System.Threading.Tasks; 21 | public partial class DeepCloneTests : BaseIncludeTest 22 | { 23 | [Test] 24 | public async Task TestSimplePropertiesAsync() 25 | { 26 | EQBPerson clone; 27 | EQBPerson petra; 28 | 29 | using (var session = NHConfig.OpenSession()) 30 | { 31 | petra = session.Query() 32 | .First(o => o.Name == "Petra"); 33 | clone = await (session.DeepCloneAsync(petra, o => o 34 | .ForType(t => 35 | t.ForMember(m => m.Name, opts => opts.Ignore()) 36 | ))); 37 | // Lazy load some relations after cloning 38 | var friend = petra.BestFriend; 39 | var card = petra.IdentityCard; 40 | 41 | } 42 | Assert.AreEqual(petra.Id, clone.Id); 43 | Assert.AreEqual(null, clone.Name); 44 | Assert.AreEqual(petra.LastName, clone.LastName); 45 | Assert.IsNotNull(petra.BestFriend); 46 | Assert.IsNotNull(petra.IdentityCard); 47 | Assert.IsNull(clone.MarriedWith); 48 | Assert.IsNull(clone.BestFriend); 49 | Assert.IsNull(clone.IdentityCard); 50 | Assert.AreEqual(0, clone.OwnedHouses.Count); 51 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.Count); 52 | } 53 | 54 | [Test] 55 | public async Task TestCloneAsReferenceAndIgnoreIdentifiersAsync() 56 | { 57 | EQBPerson clone; 58 | EQBPerson petra; 59 | 60 | using (var session = NHConfig.OpenSession()) 61 | { 62 | petra = session.Query() 63 | .Include(o => o.IdentityCard) 64 | .First(o => o.Name == "Petra"); 65 | clone = await (session.DeepCloneAsync(petra, o => o 66 | .ForType(t => t 67 | .ForMember(m => m.Name, opts => opts.Ignore()) 68 | .CloneIdentifier(false) 69 | ) 70 | .CloneIdentifier(true) 71 | .CanCloneAsReference(type => type == typeof(EQBIdentityCard)) 72 | )); 73 | 74 | } 75 | Assert.AreEqual(default(int), clone.Id); 76 | Assert.IsNull(clone.Name); 77 | Assert.AreEqual(petra.LastName, clone.LastName); 78 | Assert.AreEqual(petra.IdentityCard, clone.IdentityCard); 79 | } 80 | 81 | [Test] 82 | public async Task TestReferencesAsync() 83 | { 84 | EQBPerson clone; 85 | EQBPerson petra; 86 | 87 | using (var session = NHConfig.OpenSession()) 88 | { 89 | petra = session.Query() 90 | .Include(o => o.BestFriend.IdentityCard) 91 | .Include(o => o.BestFriend.BestFriend) 92 | .First(o => o.Name == "Petra"); 93 | 94 | clone = await (session.DeepCloneAsync(petra)); 95 | } 96 | 97 | Assert.AreEqual(petra.Id, clone.Id); 98 | Assert.IsNotNull(clone.BestFriend); 99 | Assert.IsNotNull(clone.BestFriend.IdentityCard); 100 | Assert.AreEqual(clone.BestFriend, clone.BestFriend.IdentityCard.Owner); 101 | Assert.IsNotNull(clone.BestFriend.BestFriend); 102 | 103 | Assert.IsNull(clone.BestFriend.BestFriend.BestFriend); 104 | Assert.IsNull(clone.IdentityCard); 105 | Assert.AreEqual(0, clone.OwnedHouses.Count); 106 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.Count); 107 | } 108 | 109 | [Test] 110 | public async Task TestEntityResolverAsync() 111 | { 112 | EQBPerson clone; 113 | EQBPerson petra; 114 | 115 | using (var session = NHConfig.OpenSession()) 116 | { 117 | petra = session.Query() 118 | .Include(o => o.BestFriend.IdentityCard) 119 | .Include(o => o.BestFriend.BestFriend) 120 | .First(o => o.Name == "Petra"); 121 | 122 | clone = await (session.DeepCloneAsync(petra, o => o 123 | .AddEntityResolver(t => true, (entity, persister) => persister.CreateProxy(persister.GetIdentifier(entity), null)))); 124 | 125 | Assert.AreEqual(petra.Id, clone.Id); 126 | Assert.False(NHibernateUtil.IsInitialized(clone)); 127 | Assert.Throws(() => 128 | { 129 | var friend = clone.BestFriend; 130 | }); 131 | } 132 | } 133 | 134 | [Test] 135 | public async Task TestEntityResolverSkipRootAsync() 136 | { 137 | EQBPerson clone; 138 | EQBPerson petra; 139 | 140 | using (var session = NHConfig.OpenSession()) 141 | { 142 | petra = session.Query() 143 | .Include(o => o.BestFriend.IdentityCard) 144 | .Include(o => o.BestFriend.BestFriend) 145 | .First(o => o.Name == "Petra"); 146 | 147 | clone = await (session.DeepCloneAsync(petra, o => o 148 | .AddEntityResolver((t, e) => e != petra, (entity, persister) => persister.CreateProxy(persister.GetIdentifier(entity), null)))); 149 | 150 | Assert.AreEqual(petra.Id, clone.Id); 151 | Assert.AreEqual(petra.Name, clone.Name); 152 | Assert.True(NHibernateUtil.IsInitialized(clone)); 153 | 154 | Assert.False(NHibernateUtil.IsInitialized(clone.BestFriend)); 155 | Assert.Throws(() => 156 | { 157 | var card = clone.BestFriend.IdentityCard; 158 | }); 159 | } 160 | } 161 | 162 | [Test] 163 | public async Task TestCollectionsAsync() 164 | { 165 | EQBPerson clone; 166 | EQBPerson petra; 167 | 168 | using (var session = NHConfig.OpenSession()) 169 | { 170 | petra = session.Query() 171 | .Include(o => o.CurrentOwnedVehicles.First().Wheels) 172 | .Include(o => o.CurrentOwnedVehicles.First().RoadworthyTests) 173 | .Include(o => o.CurrentOwnedVehicles.First().MileageHistory) 174 | .Include(o => o.PreviouslyOwnedVehicles) 175 | .First(o => o.Name == "Petra"); 176 | 177 | clone = await (session.DeepCloneAsync(petra)); 178 | } 179 | 180 | Assert.IsNull(clone.BestFriend); 181 | Assert.IsNull(clone.IdentityCard); 182 | Assert.IsNull(clone.MarriedWith); 183 | Assert.AreEqual(1, clone.CurrentOwnedVehicles.Count); 184 | Assert.AreEqual(clone, clone.CurrentOwnedVehicles.First().CurrentOwner); 185 | Assert.AreEqual(4, clone.CurrentOwnedVehicles.First().Wheels.Count); 186 | Assert.AreEqual(clone.CurrentOwnedVehicles.First(), clone.CurrentOwnedVehicles.First().Wheels.First().Vehicle); 187 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().RoadworthyTests.Count); 188 | Assert.AreEqual(clone.CurrentOwnedVehicles.First(), clone.CurrentOwnedVehicles.First().RoadworthyTests[new DateTime(2009, 2, 1)].Vehicle); 189 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().MileageHistory.Count); 190 | Assert.AreEqual(5000, clone.CurrentOwnedVehicles.First().MileageHistory[new DateTime(2010, 1, 1)]); 191 | 192 | Assert.AreEqual(2, clone.PreviouslyOwnedVehicles.Count); 193 | Assert.AreEqual(clone, clone.CurrentOwnedVehicles.First().CurrentOwner); 194 | } 195 | 196 | [Test] 197 | public async Task TestFilterAsync() 198 | { 199 | EQBPerson clone; 200 | EQBPerson petra; 201 | 202 | using (var session = NHConfig.OpenSession()) 203 | { 204 | petra = session.Query() 205 | .Include(o => o.PreviouslyOwnedVehicles) 206 | .First(o => o.Name == "Petra"); 207 | 208 | clone = await (session.DeepCloneAsync(petra, o => o 209 | .ForType(t => t 210 | .ForMember(m => m.Name, m => m.Filter(n => n + "2")) 211 | .ForMember(m => m.PreviouslyOwnedVehicles, m => m 212 | .Filter(col => new HashSet(col.Take(1))) 213 | ) 214 | ))); 215 | } 216 | 217 | Assert.AreEqual("Petra2", clone.Name); 218 | Assert.IsNull(clone.BestFriend); 219 | Assert.IsNull(clone.IdentityCard); 220 | Assert.IsNull(clone.MarriedWith); 221 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.Count); 222 | Assert.AreEqual(1, clone.PreviouslyOwnedVehicles.Count); 223 | } 224 | 225 | [Test] 226 | public async Task TestSkipEntityTypesAsync() 227 | { 228 | EQBPerson clone; 229 | EQBPerson petra; 230 | 231 | using (var session = NHConfig.OpenSession()) 232 | { 233 | petra = session.Query() 234 | .Include(o => o.IdentityCard) 235 | .Include(o => o.BestFriend.IdentityCard) 236 | .Include(o => o.CurrentOwnedVehicles.First().Wheels) 237 | .Include(o => o.CurrentOwnedVehicles.First().RoadworthyTests) 238 | .Include(o => o.CurrentOwnedVehicles.First().MileageHistory) 239 | .First(o => o.Name == "Petra"); 240 | 241 | clone = await (session.DeepCloneAsync(petra, o => o 242 | .SkipEntityTypes())); 243 | } 244 | 245 | Assert.IsNull(clone.IdentityCard); 246 | Assert.IsNull(clone.BestFriend); 247 | Assert.AreEqual(1, clone.CurrentOwnedVehicles.Count); 248 | Assert.IsNull(clone.CurrentOwnedVehicles.First().CurrentOwner); 249 | Assert.AreEqual(4, clone.CurrentOwnedVehicles.First().Wheels.Count); 250 | Assert.IsNull(clone.CurrentOwnedVehicles.First().Wheels.First().Vehicle); 251 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().RoadworthyTests.Count); 252 | Assert.IsNull(clone.CurrentOwnedVehicles.First().RoadworthyTests.First().Value.Vehicle); 253 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().MileageHistory.Count); 254 | Assert.IsNotNull(clone.CurrentOwnedVehicles.First().MileageHistory[new DateTime(2010, 1, 1)]); 255 | } 256 | 257 | [Test] 258 | public async Task TestWithoutIdentifiersAsync() 259 | { 260 | EQBPerson clone; 261 | 262 | using (var session = NHConfig.OpenSession()) 263 | { 264 | var petra = session.Query() 265 | .Include(o => o.IdentityCard) 266 | .Include(o => o.BestFriend.IdentityCard) 267 | .Include(o => o.CurrentOwnedVehicles.First().Wheels) 268 | .Include(o => o.CurrentOwnedVehicles.First().RoadworthyTests) 269 | .Include(o => o.CurrentOwnedVehicles.First().MileageHistory) 270 | .First(o => o.Name == "Petra"); 271 | 272 | clone = await (session.DeepCloneAsync(petra, o => o 273 | .CloneIdentifier(false))); 274 | } 275 | 276 | Assert.AreEqual(0, clone.Id); 277 | Assert.AreEqual(0, clone.IdentityCard.Id); 278 | Assert.AreEqual(0, clone.IdentityCard.Owner.Id); 279 | Assert.AreEqual(0, clone.BestFriend.Id); 280 | Assert.AreEqual(0, clone.BestFriend.IdentityCard.Id); 281 | Assert.AreEqual(1, clone.CurrentOwnedVehicles.Count); 282 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().Id); 283 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().CurrentOwner.Id); 284 | Assert.AreEqual(4, clone.CurrentOwnedVehicles.First().Wheels.Count); 285 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().Wheels.First().Id); 286 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().Wheels.First().Vehicle.Id); 287 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().RoadworthyTests.Count); 288 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().RoadworthyTests.First().Value.Id); 289 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().RoadworthyTests.First().Value.Vehicle.Id); 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Async/LinqIncludeTests.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by AsyncGenerator. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Reflection; 15 | using System.Threading.Tasks; 16 | using NHibernate.Extensions.Linq; 17 | using NHibernate.Extensions.Tests.Entities; 18 | using NHibernate.Linq; 19 | using NHibernate.Stat; 20 | using NUnit.Framework; 21 | 22 | namespace NHibernate.Extensions.Tests 23 | { 24 | public partial class LinqIncludeTests : BaseIncludeTest 25 | { 26 | 27 | [Test] 28 | public async Task TestUsingAndThenIncludeAsync() 29 | { 30 | EQBPerson petra; 31 | 32 | using (var session = NHConfig.OpenSession()) 33 | { 34 | var stats = session.SessionFactory.Statistics; 35 | stats.Clear(); 36 | var future = session.Query() 37 | .Include(o => o.BestFriend.IdentityCard) 38 | .Include(o => o.BestFriend.BestFriend.BestFriend.BestFriend) 39 | .Include(o => o.CurrentOwnedVehicles).ThenInclude(o => o.Wheels) 40 | .Include(o => o.CurrentOwnedVehicles).ThenInclude(o => o.RoadworthyTests) 41 | .Include(o => o.CurrentOwnedVehicles).ThenInclude(o => o.MileageHistory) 42 | .Include(o => o.DrivingLicence) 43 | .Include(o => o.CreatedBy) 44 | .Include(o => o.IdentityCard) 45 | .Include(o => o.MarriedWith) 46 | .Include(o => o.OwnedHouses) 47 | .Include(o => o.PreviouslyOwnedVehicles) 48 | .Where(o => o.Name == "Petra") 49 | .ToFutureValue(); 50 | petra = await (future.GetValueAsync()); 51 | CheckStatistics(stats, 5); 52 | } 53 | ValidateGetEntityResult(petra); 54 | } 55 | 56 | #region FutureValue 57 | 58 | #endregion 59 | #region SingleOrDefault 60 | 61 | #endregion 62 | #region Single 63 | 64 | #endregion 65 | #region FirstOrDefault 66 | 67 | #endregion 68 | #region First 69 | 70 | #endregion 71 | #region LastOrDefault 72 | 73 | #endregion 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/BaseIncludeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using NHibernate.Engine; 7 | using NHibernate.Extensions.Tests.Entities; 8 | using NHibernate.Tool.hbm2ddl; 9 | using NUnit.Framework; 10 | 11 | namespace NHibernate.Extensions.Tests 12 | { 13 | [TestFixture] 14 | public abstract class BaseIncludeTest 15 | { 16 | protected void ValidateGetEntityResult(EQBPerson petra) 17 | { 18 | Assert.AreEqual("Ana", petra.BestFriend.Name); 19 | Assert.AreEqual("Simon", petra.BestFriend.BestFriend.Name); 20 | Assert.AreEqual("1", petra.BestFriend.IdentityCard.Code); 21 | Assert.AreEqual("Rok", petra.BestFriend.BestFriend.BestFriend.Name); 22 | Assert.AreEqual("Petra", petra.BestFriend.BestFriend.BestFriend.BestFriend.Name); 23 | Assert.AreEqual("Ana", petra.BestFriend.BestFriend.BestFriend.BestFriend.BestFriend.Name); 24 | Assert.AreEqual(1, petra.CurrentOwnedVehicles.Count); 25 | Assert.AreEqual("3", petra.DrivingLicence.Code); 26 | Assert.AreEqual("4", petra.IdentityCard.Code); 27 | Assert.AreEqual("System", petra.CreatedBy.UserName); 28 | Assert.AreEqual(petra.BestFriend.BestFriend, petra.MarriedWith); 29 | Assert.AreEqual(1, petra.OwnedHouses.Count); 30 | Assert.AreEqual(2, petra.PreviouslyOwnedVehicles.Count); 31 | Assert.AreEqual("Audi", petra.CurrentOwnedVehicles.First().Model); 32 | Assert.AreEqual(2, petra.CurrentOwnedVehicles.First().RoadworthyTests.Count); 33 | Assert.AreEqual(2, petra.CurrentOwnedVehicles.First().MileageHistory.Count); 34 | Assert.AreEqual(5000, petra.CurrentOwnedVehicles.First().MileageHistory[new DateTime(2010, 1, 1)]); 35 | foreach (var wheel in petra.CurrentOwnedVehicles.First().Wheels) 36 | { 37 | Assert.AreEqual(235, wheel.Width); 38 | } 39 | } 40 | 41 | [OneTimeSetUp] 42 | public void Initialize() 43 | { 44 | var schema = new SchemaExport(NHConfig.Configuration); 45 | schema.Drop(false, true); 46 | schema.Create(false, true); 47 | FillData(); 48 | } 49 | 50 | [OneTimeTearDown] 51 | public void Cleanup() 52 | { 53 | var schema = new SchemaExport(NHConfig.Configuration); 54 | schema.Drop(false, true); 55 | } 56 | 57 | protected void FillData() 58 | { 59 | var system = new EQBUser { UserName = "System" }; 60 | var ana = new EQBPerson("Ana") { Age = 23, CreatedBy = system }; 61 | var rok = new EQBPerson("Rok") { Age = 24, CreatedBy = system }; 62 | var simon = new EQBPerson("Simon") { Age = 25, CreatedBy = system }; 63 | var petra = new EQBPerson("Petra") { Age = 22, CreatedBy = system }; 64 | 65 | //Setting best friends 66 | petra.BestFriend = ana; 67 | ana.BestFriend = simon; 68 | simon.BestFriend = rok; 69 | rok.BestFriend = petra; 70 | 71 | //Setting Identity card 72 | ana.IdentityCard = new EQBIdentityCard { Code = "1", Owner = ana }; 73 | ana.Identity = new EQBIdentity { Code = "1", Owner = ana }; 74 | rok.IdentityCard = new EQBIdentityCard { Code = "2", Owner = rok }; 75 | rok.Identity = new EQBIdentity { Code = "2", Owner = rok }; 76 | simon.IdentityCard = new EQBIdentityCard { Code = "3", Owner = simon }; 77 | simon.Identity = new EQBIdentity { Code = "3", Owner = simon }; 78 | petra.IdentityCard = new EQBIdentityCard { Code = "4", Owner = petra }; 79 | petra.Identity = new EQBIdentity { Code = "4", Owner = petra }; 80 | 81 | //Setting Driving licence 82 | rok.DrivingLicence = new EQBDrivingLicence { Code = "1", Owner = rok }; 83 | simon.DrivingLicence = new EQBDrivingLicence { Code = "2", Owner = simon }; 84 | petra.DrivingLicence = new EQBDrivingLicence { Code = "3", Owner = petra }; 85 | 86 | //Setting MerriedWith 87 | rok.MarriedWith = ana; 88 | ana.MarriedWith = rok; 89 | 90 | petra.MarriedWith = simon; 91 | simon.MarriedWith = petra; 92 | 93 | //Setting Vehicles 94 | var ferrari = new EQBVehicle { BuildYear = 2002, Model = "Ferrari" }; 95 | ferrari.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 320, Vehicle = ferrari }); 96 | ferrari.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 320, Vehicle = ferrari }); 97 | ferrari.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 260, Vehicle = ferrari }); 98 | ferrari.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 260, Vehicle = ferrari }); 99 | ferrari.RoadworthyTests.Add( 100 | new DateTime(2002, 2, 1), 101 | new EQBRoadworthyTest 102 | { 103 | Vehicle = ferrari, 104 | TestDate = new DateTime(2002, 2, 1), 105 | Passed = true, 106 | Comments = "I like the shade of red." 107 | }); 108 | ferrari.MileageHistory.Add(new DateTime(2002, 1, 1), 0); 109 | ferrari.MileageHistory.Add(new DateTime(2006, 1, 1), 60000); 110 | ferrari.MileageHistory.Add(new DateTime(2010, 1, 1), 100000); 111 | 112 | var audi = new EQBVehicle { BuildYear = 2009, Model = "Audi" }; 113 | audi.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 235, Vehicle = audi }); 114 | audi.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 235, Vehicle = audi }); 115 | audi.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 235, Vehicle = audi }); 116 | audi.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 235, Vehicle = audi }); 117 | audi.RoadworthyTests.Add( 118 | new DateTime(2009, 2, 1), 119 | new EQBRoadworthyTest 120 | { 121 | Vehicle = audi, 122 | TestDate = new DateTime(2009, 2, 1), 123 | Passed = false, 124 | Comments = "Brakes failing." 125 | }); 126 | audi.RoadworthyTests.Add( 127 | new DateTime(2009, 3, 1), 128 | new EQBRoadworthyTest 129 | { 130 | Vehicle = audi, 131 | TestDate = new DateTime(2009, 3, 1), 132 | Passed = true, 133 | Comments = "All good now." 134 | }); 135 | audi.MileageHistory.Add(new DateTime(2009, 1, 1), 0); 136 | audi.MileageHistory.Add(new DateTime(2010, 1, 1), 5000); 137 | 138 | var bmw = new EQBVehicle { BuildYear = 1993, Model = "Bmw" }; 139 | bmw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 205, Vehicle = bmw }); 140 | bmw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 205, Vehicle = bmw }); 141 | bmw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 205, Vehicle = bmw }); 142 | bmw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 205, Vehicle = bmw }); 143 | // Deliberately no roadworthy tests or mileage history 144 | 145 | var vw = new EQBVehicle { BuildYear = 2002, Model = "Vw" }; 146 | vw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 195, Vehicle = vw }); 147 | vw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 195, Vehicle = vw }); 148 | vw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 195, Vehicle = vw }); 149 | vw.Wheels.Add(new TestEQBWheel { Diameter = 45, Width = 195, Vehicle = vw }); 150 | vw.RoadworthyTests.Add( 151 | new DateTime(2002, 3, 1), 152 | new EQBRoadworthyTest 153 | { 154 | Vehicle = vw, 155 | TestDate = new DateTime(2002, 3, 1), 156 | Passed = true, 157 | Comments = "No problems." 158 | }); 159 | vw.MileageHistory.Add(new DateTime(2002, 1, 1), 0); 160 | vw.MileageHistory.Add(new DateTime(2015, 1, 1), 150000); 161 | 162 | petra.PreviouslyOwnedVehicles.Add(vw); 163 | petra.PreviouslyOwnedVehicles.Add(bmw); 164 | petra.CurrentOwnedVehicles.Add(audi); 165 | audi.CurrentOwner = petra; 166 | 167 | simon.PreviouslyOwnedVehicles.Add(bmw); 168 | simon.PreviouslyOwnedVehicles.Add(audi); 169 | simon.CurrentOwnedVehicles.Add(ferrari); 170 | ferrari.CurrentOwner = simon; 171 | 172 | //Setting Houses 173 | var house1 = new EQBHouse { Address = "Address1" }; 174 | var house2 = new EQBHouse { Address = "Address2" }; 175 | 176 | house1.Owners.Add(ana); 177 | ana.OwnedHouses.Add(house1); 178 | house1.Owners.Add(rok); 179 | rok.OwnedHouses.Add(house1); 180 | 181 | house2.Owners.Add(simon); 182 | simon.OwnedHouses.Add(house2); 183 | house2.Owners.Add(petra); 184 | petra.OwnedHouses.Add(house2); 185 | 186 | //Saving entities 187 | using (var session = NHConfig.OpenSession()) 188 | { 189 | session.Save(petra); 190 | session.Save(rok); 191 | session.Save(simon); 192 | session.Save(ana); 193 | session.Flush(); 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/BatchFetchTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using NHibernate.Extensions.Tests.Entities; 6 | using NHibernate.Tool.hbm2ddl; 7 | using NUnit.Framework; 8 | 9 | namespace NHibernate.Extensions.Tests 10 | { 11 | [TestFixture] 12 | public partial class BatchFetchTests 13 | { 14 | [Test] 15 | public void TestStringProperty() 16 | { 17 | var keys = new HashSet(); 18 | var r = new Random(); 19 | for (var i = 0; i < 600; i++) 20 | { 21 | keys.Add($"Batch{r.Next(0, 1300)}"); 22 | } 23 | 24 | using (var session = NHConfig.OpenSession()) 25 | { 26 | var stats = session.SessionFactory.Statistics; 27 | var queryCount = stats.PrepareStatementCount; 28 | var models = session.BatchFetch(keys.ToList(), o => o.Name, 50); 29 | 30 | var expectedQueryCount = (int) Math.Ceiling(keys.Count/50m); 31 | Assert.AreEqual(keys.Count, models.Count); 32 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 33 | 34 | foreach (var model in models) 35 | { 36 | Assert.IsTrue(keys.Contains(model.Name)); 37 | } 38 | } 39 | } 40 | 41 | [Test] 42 | public void TestIntProperty() 43 | { 44 | var keys = new HashSet(); 45 | var r = new Random(); 46 | for (var i = 0; i < 600; i++) 47 | { 48 | keys.Add(r.Next(1, 1200)); 49 | } 50 | 51 | using (var session = NHConfig.OpenSession()) 52 | { 53 | var stats = session.SessionFactory.Statistics; 54 | var queryCount = stats.PrepareStatementCount; 55 | var models = session.BatchFetch(keys.ToList(), o => o.Id, 50); 56 | 57 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / 50m); 58 | Assert.AreEqual(keys.Count, models.Count); 59 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 60 | 61 | foreach (var model in models) 62 | { 63 | Assert.IsTrue(keys.Contains(model.Id)); 64 | } 65 | } 66 | } 67 | 68 | [Test] 69 | public void TestFilter() 70 | { 71 | var keys = Enumerable.Range(1, 600).ToList(); 72 | 73 | using (var session = NHConfig.OpenSession()) 74 | { 75 | var stats = session.SessionFactory.Statistics; 76 | var queryCount = stats.PrepareStatementCount; 77 | var models = session.BatchFetch(keys, o => o.Id, 50, q => q.Where(o => o.Id > 400)); 78 | 79 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / 50m); 80 | Assert.AreEqual(200, models.Count); 81 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 82 | 83 | foreach (var model in models) 84 | { 85 | Assert.IsTrue(keys.Contains(model.Id)); 86 | } 87 | } 88 | } 89 | 90 | [Test] 91 | public void TestSelectAnonymousType() 92 | { 93 | var keys = Enumerable.Range(1, 600).ToList(); 94 | 95 | using (var session = NHConfig.OpenSession()) 96 | { 97 | var stats = session.SessionFactory.Statistics; 98 | var queryCount = stats.PrepareStatementCount; 99 | var models = session.BatchFetch(50) 100 | .SetKeys(keys, o => o.Id) 101 | .BeforeQueryExecution(q => q.Where(o => o.Id > 400)) 102 | .Select(o => new { o.Name }) 103 | .Execute(); 104 | 105 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / 50m); 106 | Assert.AreEqual(200, models.Count); 107 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 108 | } 109 | } 110 | 111 | [Test] 112 | public void TestSelectString() 113 | { 114 | var keys = Enumerable.Range(1, 600).ToList(); 115 | 116 | using (var session = NHConfig.OpenSession()) 117 | { 118 | var stats = session.SessionFactory.Statistics; 119 | var queryCount = stats.PrepareStatementCount; 120 | var models = session.BatchFetch(50) 121 | .SetKeys(keys, o => o.Id) 122 | .Select(o => o.Name) 123 | .BeforeQueryExecution(q => q.Where(o => o.Id > 400)) 124 | .Execute(); 125 | 126 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / 50m); 127 | Assert.AreEqual(200, models.Count); 128 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 129 | } 130 | } 131 | 132 | [Explicit] 133 | public void TestPerformance() 134 | { 135 | var keys = Enumerable.Range(1, 5000).ToList(); 136 | var batchSizes = new [] { 250, 1, 10, 50, 100, 250, 500, 1000}; 137 | var coldStart = true; // skip the first time as the time is always higher 138 | 139 | foreach (var batchSize in batchSizes) 140 | { 141 | using (var session = NHConfig.OpenSession()) 142 | { 143 | var stats = session.SessionFactory.Statistics; 144 | var queryCount = stats.PrepareStatementCount; 145 | var stopwatch = new Stopwatch(); 146 | stopwatch.Start(); 147 | var models = session.BatchFetch(keys, o => o.Id, batchSize); 148 | stopwatch.Stop(); 149 | if (coldStart) 150 | { 151 | coldStart = false; 152 | } 153 | else 154 | { 155 | Console.WriteLine($"Elapsed time for batch size {batchSize}: {stopwatch.ElapsedMilliseconds}ms"); 156 | } 157 | var expectedQueryCount = (int)Math.Ceiling(keys.Count / (decimal)batchSize); 158 | Assert.AreEqual(5000, models.Count); 159 | Assert.AreEqual(expectedQueryCount, stats.PrepareStatementCount - queryCount); 160 | 161 | foreach (var model in models) 162 | { 163 | Assert.IsTrue(keys.Contains(model.Id)); 164 | } 165 | } 166 | } 167 | } 168 | 169 | [OneTimeSetUp] 170 | public void Initialize() 171 | { 172 | var schema = new SchemaExport(NHConfig.Configuration); 173 | schema.Drop(false, true); 174 | schema.Create(false, true); 175 | FillData(); 176 | } 177 | 178 | [OneTimeTearDown] 179 | public void Cleanup() 180 | { 181 | var schema = new SchemaExport(NHConfig.Configuration); 182 | schema.Drop(false, true); 183 | } 184 | 185 | protected void FillData() 186 | { 187 | //Saving entities 188 | using (var session = NHConfig.SessionFactory.OpenStatelessSession()) 189 | using (var transaction = session.BeginTransaction()) 190 | { 191 | for (var i = 0; i < 5000; i++) 192 | { 193 | session.Insert(new BatchModel 194 | { 195 | Name = $"Batch{i}" 196 | }); 197 | } 198 | transaction.Commit(); 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/CascadeConvention.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using FluentNHibernate.Conventions; 8 | using FluentNHibernate.Conventions.Instances; 9 | 10 | namespace NHibernate.Extensions.Tests 11 | { 12 | public class CascadeConvention : IReferenceConvention, IHasManyConvention, IHasOneConvention, IHasManyToManyConvention 13 | { 14 | public void Apply(IManyToOneInstance instance) 15 | { 16 | instance.Cascade.SaveUpdate(); 17 | } 18 | 19 | public void Apply(IOneToManyCollectionInstance instance) 20 | { 21 | var type = GetUnderlyingType(instance.Member); 22 | if (!typeof(IDictionary).IsAssignableFrom(type) && !IsAssignableToGenericType(type, (typeof(IDictionary<,>)))) //Map must have inverse set to false 23 | instance.Inverse(); 24 | instance.Cascade.AllDeleteOrphan(); 25 | } 26 | 27 | public void Apply(IManyToManyCollectionInstance instance) 28 | { 29 | instance.Cascade.SaveUpdate(); 30 | } 31 | 32 | public void Apply(IOneToOneInstance instance) 33 | { 34 | instance.Cascade.SaveUpdate(); 35 | } 36 | 37 | public static System.Type GetUnderlyingType(MemberInfo member) 38 | { 39 | switch (member.MemberType) 40 | { 41 | case MemberTypes.Event: 42 | return ((EventInfo)member).EventHandlerType; 43 | case MemberTypes.Field: 44 | return ((FieldInfo)member).FieldType; 45 | case MemberTypes.Method: 46 | return ((MethodInfo)member).ReturnType; 47 | case MemberTypes.Property: 48 | return ((PropertyInfo)member).PropertyType; 49 | default: 50 | throw new ArgumentException 51 | ( 52 | "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo" 53 | ); 54 | } 55 | } 56 | 57 | public static bool IsAssignableToGenericType(System.Type givenType, System.Type genericType) 58 | { 59 | var interfaceTypes = givenType.GetInterfaces(); 60 | 61 | if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType)) 62 | { 63 | return true; 64 | } 65 | 66 | if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 67 | return true; 68 | 69 | var baseType = givenType.BaseType; 70 | return baseType != null && IsAssignableToGenericType(baseType, genericType); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/DeepCloneTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NHibernate.Extensions.Tests.Entities; 5 | using NHibernate.Linq; 6 | using NUnit.Framework; 7 | 8 | namespace NHibernate.Extensions.Tests 9 | { 10 | [TestFixture] 11 | public partial class DeepCloneTests : BaseIncludeTest 12 | { 13 | [Test] 14 | public void TestSimpleProperties() 15 | { 16 | EQBPerson clone; 17 | EQBPerson petra; 18 | 19 | using (var session = NHConfig.OpenSession()) 20 | { 21 | petra = session.Query() 22 | .First(o => o.Name == "Petra"); 23 | clone = session.DeepClone(petra, o => o 24 | .ForType(t => 25 | t.ForMember(m => m.Name, opts => opts.Ignore()) 26 | )); 27 | // Lazy load some relations after cloning 28 | var friend = petra.BestFriend; 29 | var card = petra.IdentityCard; 30 | 31 | } 32 | Assert.AreEqual(petra.Id, clone.Id); 33 | Assert.AreEqual(null, clone.Name); 34 | Assert.AreEqual(petra.LastName, clone.LastName); 35 | Assert.IsNotNull(petra.BestFriend); 36 | Assert.IsNotNull(petra.IdentityCard); 37 | Assert.IsNull(clone.MarriedWith); 38 | Assert.IsNull(clone.BestFriend); 39 | Assert.IsNull(clone.IdentityCard); 40 | Assert.AreEqual(0, clone.OwnedHouses.Count); 41 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.Count); 42 | } 43 | 44 | [Test] 45 | public void TestCloneAsReferenceAndIgnoreIdentifiers() 46 | { 47 | EQBPerson clone; 48 | EQBPerson petra; 49 | 50 | using (var session = NHConfig.OpenSession()) 51 | { 52 | petra = session.Query() 53 | .Include(o => o.IdentityCard) 54 | .First(o => o.Name == "Petra"); 55 | clone = session.DeepClone(petra, o => o 56 | .ForType(t => t 57 | .ForMember(m => m.Name, opts => opts.Ignore()) 58 | .CloneIdentifier(false) 59 | ) 60 | .CloneIdentifier(true) 61 | .CanCloneAsReference(type => type == typeof(EQBIdentityCard)) 62 | ); 63 | 64 | } 65 | Assert.AreEqual(default(int), clone.Id); 66 | Assert.IsNull(clone.Name); 67 | Assert.AreEqual(petra.LastName, clone.LastName); 68 | Assert.AreEqual(petra.IdentityCard, clone.IdentityCard); 69 | } 70 | 71 | [Test] 72 | public void TestReferences() 73 | { 74 | EQBPerson clone; 75 | EQBPerson petra; 76 | 77 | using (var session = NHConfig.OpenSession()) 78 | { 79 | petra = session.Query() 80 | .Include(o => o.BestFriend.IdentityCard) 81 | .Include(o => o.BestFriend.BestFriend) 82 | .First(o => o.Name == "Petra"); 83 | 84 | clone = session.DeepClone(petra); 85 | } 86 | 87 | Assert.AreEqual(petra.Id, clone.Id); 88 | Assert.IsNotNull(clone.BestFriend); 89 | Assert.IsNotNull(clone.BestFriend.IdentityCard); 90 | Assert.AreEqual(clone.BestFriend, clone.BestFriend.IdentityCard.Owner); 91 | Assert.IsNotNull(clone.BestFriend.BestFriend); 92 | 93 | Assert.IsNull(clone.BestFriend.BestFriend.BestFriend); 94 | Assert.IsNull(clone.IdentityCard); 95 | Assert.AreEqual(0, clone.OwnedHouses.Count); 96 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.Count); 97 | } 98 | 99 | [Test] 100 | public void TestEntityResolver() 101 | { 102 | EQBPerson clone; 103 | EQBPerson petra; 104 | 105 | using (var session = NHConfig.OpenSession()) 106 | { 107 | petra = session.Query() 108 | .Include(o => o.BestFriend.IdentityCard) 109 | .Include(o => o.BestFriend.BestFriend) 110 | .First(o => o.Name == "Petra"); 111 | 112 | clone = session.DeepClone(petra, o => o 113 | .AddEntityResolver(t => true, (entity, persister) => persister.CreateProxy(persister.GetIdentifier(entity), null))); 114 | 115 | Assert.AreEqual(petra.Id, clone.Id); 116 | Assert.False(NHibernateUtil.IsInitialized(clone)); 117 | Assert.Throws(() => 118 | { 119 | var friend = clone.BestFriend; 120 | }); 121 | } 122 | } 123 | 124 | [Test] 125 | public void TestEntityResolverSkipRoot() 126 | { 127 | EQBPerson clone; 128 | EQBPerson petra; 129 | 130 | using (var session = NHConfig.OpenSession()) 131 | { 132 | petra = session.Query() 133 | .Include(o => o.BestFriend.IdentityCard) 134 | .Include(o => o.BestFriend.BestFriend) 135 | .First(o => o.Name == "Petra"); 136 | 137 | clone = session.DeepClone(petra, o => o 138 | .AddEntityResolver((t, e) => e != petra, (entity, persister) => persister.CreateProxy(persister.GetIdentifier(entity), null))); 139 | 140 | Assert.AreEqual(petra.Id, clone.Id); 141 | Assert.AreEqual(petra.Name, clone.Name); 142 | Assert.True(NHibernateUtil.IsInitialized(clone)); 143 | 144 | Assert.False(NHibernateUtil.IsInitialized(clone.BestFriend)); 145 | Assert.Throws(() => 146 | { 147 | var card = clone.BestFriend.IdentityCard; 148 | }); 149 | } 150 | } 151 | 152 | [Test] 153 | public void TestCollections() 154 | { 155 | EQBPerson clone; 156 | EQBPerson petra; 157 | 158 | using (var session = NHConfig.OpenSession()) 159 | { 160 | petra = session.Query() 161 | .Include(o => o.CurrentOwnedVehicles.First().Wheels) 162 | .Include(o => o.CurrentOwnedVehicles.First().RoadworthyTests) 163 | .Include(o => o.CurrentOwnedVehicles.First().MileageHistory) 164 | .Include(o => o.PreviouslyOwnedVehicles) 165 | .First(o => o.Name == "Petra"); 166 | 167 | clone = session.DeepClone(petra); 168 | } 169 | 170 | Assert.IsNull(clone.BestFriend); 171 | Assert.IsNull(clone.IdentityCard); 172 | Assert.IsNull(clone.MarriedWith); 173 | Assert.AreEqual(1, clone.CurrentOwnedVehicles.Count); 174 | Assert.AreEqual(clone, clone.CurrentOwnedVehicles.First().CurrentOwner); 175 | Assert.AreEqual(4, clone.CurrentOwnedVehicles.First().Wheels.Count); 176 | Assert.AreEqual(clone.CurrentOwnedVehicles.First(), clone.CurrentOwnedVehicles.First().Wheels.First().Vehicle); 177 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().RoadworthyTests.Count); 178 | Assert.AreEqual(clone.CurrentOwnedVehicles.First(), clone.CurrentOwnedVehicles.First().RoadworthyTests[new DateTime(2009, 2, 1)].Vehicle); 179 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().MileageHistory.Count); 180 | Assert.AreEqual(5000, clone.CurrentOwnedVehicles.First().MileageHistory[new DateTime(2010, 1, 1)]); 181 | 182 | Assert.AreEqual(2, clone.PreviouslyOwnedVehicles.Count); 183 | Assert.AreEqual(clone, clone.CurrentOwnedVehicles.First().CurrentOwner); 184 | } 185 | 186 | [Test] 187 | public void TestFilter() 188 | { 189 | EQBPerson clone; 190 | EQBPerson petra; 191 | 192 | using (var session = NHConfig.OpenSession()) 193 | { 194 | petra = session.Query() 195 | .Include(o => o.PreviouslyOwnedVehicles) 196 | .First(o => o.Name == "Petra"); 197 | 198 | clone = session.DeepClone(petra, o => o 199 | .ForType(t => t 200 | .ForMember(m => m.Name, m => m.Filter(n => n + "2")) 201 | .ForMember(m => m.PreviouslyOwnedVehicles, m => m 202 | .Filter(col => new HashSet(col.Take(1))) 203 | ) 204 | )); 205 | } 206 | 207 | Assert.AreEqual("Petra2", clone.Name); 208 | Assert.IsNull(clone.BestFriend); 209 | Assert.IsNull(clone.IdentityCard); 210 | Assert.IsNull(clone.MarriedWith); 211 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.Count); 212 | Assert.AreEqual(1, clone.PreviouslyOwnedVehicles.Count); 213 | } 214 | 215 | [Test] 216 | public void TestSkipEntityTypes() 217 | { 218 | EQBPerson clone; 219 | EQBPerson petra; 220 | 221 | using (var session = NHConfig.OpenSession()) 222 | { 223 | petra = session.Query() 224 | .Include(o => o.IdentityCard) 225 | .Include(o => o.BestFriend.IdentityCard) 226 | .Include(o => o.CurrentOwnedVehicles.First().Wheels) 227 | .Include(o => o.CurrentOwnedVehicles.First().RoadworthyTests) 228 | .Include(o => o.CurrentOwnedVehicles.First().MileageHistory) 229 | .First(o => o.Name == "Petra"); 230 | 231 | clone = session.DeepClone(petra, o => o 232 | .SkipEntityTypes()); 233 | } 234 | 235 | Assert.IsNull(clone.IdentityCard); 236 | Assert.IsNull(clone.BestFriend); 237 | Assert.AreEqual(1, clone.CurrentOwnedVehicles.Count); 238 | Assert.IsNull(clone.CurrentOwnedVehicles.First().CurrentOwner); 239 | Assert.AreEqual(4, clone.CurrentOwnedVehicles.First().Wheels.Count); 240 | Assert.IsNull(clone.CurrentOwnedVehicles.First().Wheels.First().Vehicle); 241 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().RoadworthyTests.Count); 242 | Assert.IsNull(clone.CurrentOwnedVehicles.First().RoadworthyTests.First().Value.Vehicle); 243 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().MileageHistory.Count); 244 | Assert.IsNotNull(clone.CurrentOwnedVehicles.First().MileageHistory[new DateTime(2010, 1, 1)]); 245 | } 246 | 247 | [Test] 248 | public void TestWithoutIdentifiers() 249 | { 250 | EQBPerson clone; 251 | 252 | using (var session = NHConfig.OpenSession()) 253 | { 254 | var petra = session.Query() 255 | .Include(o => o.IdentityCard) 256 | .Include(o => o.BestFriend.IdentityCard) 257 | .Include(o => o.CurrentOwnedVehicles.First().Wheels) 258 | .Include(o => o.CurrentOwnedVehicles.First().RoadworthyTests) 259 | .Include(o => o.CurrentOwnedVehicles.First().MileageHistory) 260 | .First(o => o.Name == "Petra"); 261 | 262 | clone = session.DeepClone(petra, o => o 263 | .CloneIdentifier(false)); 264 | } 265 | 266 | Assert.AreEqual(0, clone.Id); 267 | Assert.AreEqual(0, clone.IdentityCard.Id); 268 | Assert.AreEqual(0, clone.IdentityCard.Owner.Id); 269 | Assert.AreEqual(0, clone.BestFriend.Id); 270 | Assert.AreEqual(0, clone.BestFriend.IdentityCard.Id); 271 | Assert.AreEqual(1, clone.CurrentOwnedVehicles.Count); 272 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().Id); 273 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().CurrentOwner.Id); 274 | Assert.AreEqual(4, clone.CurrentOwnedVehicles.First().Wheels.Count); 275 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().Wheels.First().Id); 276 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().Wheels.First().Vehicle.Id); 277 | Assert.AreEqual(2, clone.CurrentOwnedVehicles.First().RoadworthyTests.Count); 278 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().RoadworthyTests.First().Value.Id); 279 | Assert.AreEqual(0, clone.CurrentOwnedVehicles.First().RoadworthyTests.First().Value.Vehicle.Id); 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/Animal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NHibernate.Extensions.Tests.Entities 8 | { 9 | public class Animal : Entity 10 | { 11 | public virtual string Name { get; set; } 12 | 13 | public virtual AnimalType Type { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/AnimalType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NHibernate.Extensions.Tests.Entities 8 | { 9 | public class AnimalType : Entity 10 | { 11 | public virtual string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/BatchModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NHibernate.Extensions.Tests.Entities 8 | { 9 | public class BatchModel : Entity 10 | { 11 | public virtual string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/Cat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NHibernate.Extensions.Tests.Entities 8 | { 9 | public class Cat : Animal 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBDrivingLicence.cs: -------------------------------------------------------------------------------- 1 | namespace NHibernate.Extensions.Tests.Entities 2 | { 3 | public partial class EQBDrivingLicence : Entity 4 | { 5 | public virtual string Code { get; set; } 6 | 7 | public virtual EQBPerson Owner { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBHouse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentNHibernate.Automapping; 3 | using FluentNHibernate.Automapping.Alterations; 4 | 5 | namespace NHibernate.Extensions.Tests.Entities 6 | { 7 | public partial class EQBHouse : Entity 8 | { 9 | public EQBHouse() 10 | { 11 | Owners = new HashSet(); 12 | } 13 | 14 | public virtual string Address { get; set; } 15 | 16 | public virtual ISet Owners { get; set; } 17 | } 18 | 19 | public class EQBHouseMapping : IAutoMappingOverride 20 | { 21 | public void Override(AutoMapping mapping) 22 | { 23 | mapping.HasManyToMany(o => o.Owners).Inverse(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBIdentity.cs: -------------------------------------------------------------------------------- 1 | namespace NHibernate.Extensions.Tests.Entities 2 | { 3 | public partial class EQBIdentityCard : Entity 4 | { 5 | public virtual string Code { get; set; } 6 | 7 | public virtual EQBPerson Owner { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBIdentityCard.cs: -------------------------------------------------------------------------------- 1 | namespace NHibernate.Extensions.Tests.Entities 2 | { 3 | public partial class EQBIdentity : Entity 4 | { 5 | public virtual string Code { get; set; } 6 | 7 | public virtual EQBPerson Owner { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBPerson.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentNHibernate.Automapping; 3 | using FluentNHibernate.Automapping.Alterations; 4 | 5 | namespace NHibernate.Extensions.Tests.Entities 6 | { 7 | public interface IPerson 8 | { 9 | string Name { get; set; } 10 | 11 | IUser CreatedBy { get; set; } 12 | 13 | ISet CurrentOwnedVehicles { get; set; } 14 | } 15 | 16 | public partial class EQBPerson : Entity, IPerson 17 | { 18 | public EQBPerson(string name) : this() 19 | { 20 | Name = name; 21 | } 22 | 23 | protected EQBPerson() 24 | { 25 | CurrentOwnedVehicles = new HashSet(); 26 | OwnedHouses = new HashSet(); 27 | PreviouslyOwnedVehicles = new HashSet(); 28 | 29 | } 30 | 31 | public virtual string Name { get; set; } 32 | 33 | public virtual string LastName { get; set; } 34 | 35 | public virtual int Age { get; set; } 36 | 37 | #region OneToMany 38 | 39 | public virtual ISet CurrentOwnedVehicles { get; set; } 40 | 41 | public virtual ISet CurrentOwnedVehiclesOld { get; set; } 42 | 43 | #endregion 44 | 45 | #region ManyToMany 46 | 47 | public virtual ISet OwnedHouses { get; set; } 48 | 49 | public virtual ISet PreviouslyOwnedVehicles { get; set; } 50 | 51 | #endregion 52 | 53 | #region ManyToOne 54 | 55 | public virtual EQBPerson BestFriend { get; set; } 56 | 57 | public virtual IUser CreatedBy { get; set; } 58 | 59 | #endregion 60 | 61 | #region OneToOne 62 | 63 | public virtual EQBPerson MarriedWith { get; set; } 64 | 65 | public virtual EQBDrivingLicence DrivingLicence { get; set; } 66 | 67 | public virtual EQBIdentityCard IdentityCard { get; set; } 68 | 69 | public virtual EQBIdentity Identity { get; set; } 70 | 71 | #endregion 72 | 73 | } 74 | 75 | public class EQBPersonMapping : IAutoMappingOverride 76 | { 77 | public void Override(AutoMapping mapping) 78 | { 79 | mapping.HasMany(o => o.CurrentOwnedVehicles).KeyColumn("CurrentOwnerId"); 80 | mapping.HasMany(o => o.CurrentOwnedVehiclesOld).KeyColumn("OldOwnerId"); 81 | mapping.HasManyToMany(o => o.PreviouslyOwnedVehicles); 82 | mapping.HasManyToMany(o => o.OwnedHouses); 83 | mapping.References(o => o.CreatedBy).Class(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBRoadWorthyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NHibernate.Extensions.Tests.Entities 4 | { 5 | public partial class EQBRoadworthyTest : Entity 6 | { 7 | public virtual DateTime TestDate { get; set; } 8 | 9 | public virtual bool Passed { get; set; } 10 | 11 | public virtual string Comments { get; set; } 12 | 13 | public virtual EQBVehicle Vehicle { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NHibernate.Extensions.Tests.Entities 7 | { 8 | public class EQBUser : Entity, IUser 9 | { 10 | public virtual string UserName { get; set; } 11 | 12 | public virtual EQBUser Related { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/EQBVehicle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom; 3 | using System.Collections.Generic; 4 | using FluentNHibernate.Automapping; 5 | using FluentNHibernate.Automapping.Alterations; 6 | 7 | namespace NHibernate.Extensions.Tests.Entities 8 | { 9 | public partial class EQBVehicle : Entity 10 | { 11 | public EQBVehicle() 12 | { 13 | Wheels = new HashSet(); 14 | PreviousUsers = new HashSet(); 15 | RoadworthyTests = new Dictionary(); 16 | MileageHistory = new SortedDictionary(); 17 | } 18 | 19 | public virtual string Model { get; set; } 20 | 21 | public virtual int BuildYear { get; set; } 22 | 23 | public virtual EQBPerson CurrentOwner { get; set; } 24 | 25 | public virtual EQBPerson OldCurrentOwner { get; set; } 26 | 27 | public virtual ISet PreviousUsers { get; set; } 28 | 29 | public virtual ISet Wheels { get; set; } 30 | 31 | public virtual IDictionary RoadworthyTests { get; set; } 32 | 33 | public virtual IDictionary MileageHistory { get; set; } 34 | } 35 | 36 | public class EQBVehicleMapping : IAutoMappingOverride 37 | { 38 | public void Override(AutoMapping mapping) 39 | { 40 | mapping.HasManyToMany(o => o.PreviousUsers).Inverse(); 41 | mapping.HasMany(o => o.Wheels).KeyColumn("VehicleId"); 42 | mapping.HasMany(o => o.RoadworthyTests).AsMap(rwt => rwt.TestDate); 43 | mapping.HasMany(o => o.MileageHistory).AsMap("ObservationDate").Element("Mileage"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/IUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NHibernate.Extensions.Tests.Entities 7 | { 8 | public interface IUser 9 | { 10 | string UserName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Entities/TestEQBWheel.cs: -------------------------------------------------------------------------------- 1 | namespace NHibernate.Extensions.Tests.Entities 2 | { 3 | public partial class TestEQBWheel : Entity 4 | { 5 | public virtual int Diameter { get; set; } 6 | 7 | public virtual int Width { get; set; } 8 | 9 | public virtual EQBVehicle Vehicle { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/IEntity.cs: -------------------------------------------------------------------------------- 1 | namespace NHibernate.Extensions.Tests 2 | { 3 | public interface IEntity 4 | { 5 | } 6 | 7 | public abstract partial class Entity : IEntity 8 | { 9 | public virtual int Id { get; set; } 10 | 11 | public virtual bool IsTransient() 12 | { 13 | return Id.Equals(default(int)); 14 | } 15 | 16 | public virtual System.Type GetTypeUnproxied() 17 | { 18 | return GetType(); 19 | } 20 | 21 | public override bool Equals(object obj) 22 | { 23 | var compareTo = obj as Entity; 24 | if (ReferenceEquals(this, compareTo)) 25 | return true; 26 | if (compareTo == null || GetType() != compareTo.GetTypeUnproxied()) 27 | return false; 28 | return HasSameNonDefaultIdAs(compareTo); 29 | } 30 | 31 | public override int GetHashCode() 32 | { 33 | if(IsTransient()) 34 | // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode 35 | return base.GetHashCode(); 36 | // ReSharper disable once NonReadonlyMemberInGetHashCode 37 | return (GetType().GetHashCode() * 31) ^ Id.GetHashCode(); 38 | } 39 | 40 | private bool HasSameNonDefaultIdAs(Entity compareTo) 41 | { 42 | return !IsTransient() && !compareTo.IsTransient() && Id.Equals(compareTo.Id); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/LogSpy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using log4net; 7 | using log4net.Appender; 8 | using log4net.Core; 9 | using log4net.Repository.Hierarchy; 10 | 11 | namespace NHibernate.Extensions.Tests 12 | { 13 | public class LogSpy : IDisposable 14 | { 15 | private readonly MemoryAppender appender; 16 | private readonly Logger logger; 17 | private readonly Level prevLogLevel; 18 | 19 | public LogSpy(ILog log, Level level) 20 | { 21 | logger = log.Logger as Logger; 22 | if (logger == null) 23 | { 24 | throw new InvalidOperationException("Unable to get the logger"); 25 | } 26 | 27 | // Change the log level to DEBUG and temporarily save the previous log level 28 | prevLogLevel = logger.Level; 29 | logger.Level = level; 30 | 31 | // Add a new MemoryAppender to the logger. 32 | appender = new MemoryAppender(); 33 | logger.AddAppender(appender); 34 | } 35 | 36 | public LogSpy(ILog log, bool disable) 37 | : this(log, disable ? Level.Off : Level.Debug) 38 | { 39 | } 40 | 41 | public LogSpy(ILog log) : this(log, false) { } 42 | public LogSpy(System.Type loggerType) : this(LogManager.GetLogger(loggerType), false) { } 43 | public LogSpy(System.Type loggerType, bool disable) : this(LogManager.GetLogger(loggerType), disable) { } 44 | 45 | public LogSpy(string loggerName) : this(LogManager.GetLogger(typeof(LogSpy).Assembly, loggerName), false) { } 46 | public LogSpy(string loggerName, bool disable) : this(LogManager.GetLogger(typeof(LogSpy).Assembly, loggerName), disable) { } 47 | 48 | public MemoryAppender Appender 49 | { 50 | get { return appender; } 51 | } 52 | 53 | public virtual string GetWholeLog() 54 | { 55 | var wholeMessage = new StringBuilder(); 56 | foreach (LoggingEvent loggingEvent in Appender.GetEvents()) 57 | { 58 | wholeMessage 59 | .Append(loggingEvent.LoggerName) 60 | .Append(" ") 61 | .Append(loggingEvent.RenderedMessage) 62 | .AppendLine(); 63 | } 64 | return wholeMessage.ToString(); 65 | } 66 | 67 | #region IDisposable Members 68 | 69 | public void Dispose() 70 | { 71 | // Restore the previous log level of the SQL logger and remove the MemoryAppender 72 | logger.Level = prevLogLevel; 73 | logger.RemoveAppender(appender); 74 | } 75 | 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/NHConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FluentNHibernate.Automapping; 4 | using FluentNHibernate.Cfg; 5 | using FluentNHibernate.Conventions.Helpers; 6 | using log4net; 7 | using log4net.Config; 8 | using NHibernate.Cfg; 9 | using NHibernate.Tool.hbm2ddl; 10 | using Environment = NHibernate.Cfg.Environment; 11 | 12 | 13 | namespace NHibernate.Extensions.Tests 14 | { 15 | public class AutomappingConfiguration : DefaultAutomappingConfiguration 16 | { 17 | public override bool ShouldMap(System.Type type) 18 | { 19 | return base.ShouldMap(type) && typeof(Entity).IsAssignableFrom(type); 20 | } 21 | } 22 | 23 | public static class NHConfig 24 | { 25 | public static readonly ISessionFactory SessionFactory; 26 | 27 | public static readonly Configuration Configuration; 28 | 29 | static NHConfig() 30 | { 31 | XmlConfigurator.Configure(LogManager.GetRepository(typeof(NHConfig).Assembly)); 32 | #if DEBUG 33 | HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); 34 | #endif 35 | 36 | var modelAssembly = typeof(NHConfig).Assembly; 37 | var configuration = Configuration = new Configuration(); 38 | configuration.SetProperty(Environment.GenerateStatistics, "true"); 39 | configuration.SetProperty(Environment.UseSqlComments, "false"); 40 | configuration.SetProperty(Environment.ShowSql, "false"); 41 | configuration.Configure(); // Configure from the hibernate.cfg.config 42 | 43 | // We have to replace |DataDirectory| as it is not supported on .NET Core 44 | var connString = configuration.Properties[Environment.ConnectionString]; 45 | configuration.Properties[Environment.ConnectionString] = 46 | connString.Replace("|DataDirectory|", 47 | Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\.."))); 48 | 49 | var fluentConfig = Fluently.Configure(configuration); 50 | var autoPestModel = AutoMap 51 | .Assemblies(new AutomappingConfiguration(), new[] { modelAssembly }) 52 | .UseOverridesFromAssembly(modelAssembly) 53 | .IgnoreBase() 54 | .Conventions.Add() 55 | .Conventions.Add(PrimaryKey.Name.Is(o => "Id")) 56 | .Conventions.Add(ForeignKey.EndsWith("Id")); 57 | fluentConfig 58 | .Diagnostics(o => o.Enable(true)) 59 | .Mappings(m => 60 | { 61 | m.HbmMappings.AddFromAssembly(modelAssembly); 62 | m.AutoMappings.Add(autoPestModel); 63 | #if DEBUG 64 | var mappingsDirecotry = Path.Combine(Directory.GetCurrentDirectory(), "Mappings"); 65 | if (!Directory.Exists(mappingsDirecotry)) 66 | Directory.CreateDirectory(mappingsDirecotry); 67 | m.AutoMappings.ExportTo(mappingsDirecotry); 68 | m.FluentMappings.ExportTo(mappingsDirecotry); 69 | #endif 70 | }); 71 | 72 | SessionFactory = fluentConfig.BuildSessionFactory(); 73 | } 74 | 75 | public static ISession OpenSession() 76 | { 77 | return SessionFactory.OpenSession(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/NHibernate.Extensions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;netcoreapp3.1 5 | 7.2 6 | false 7 | maca88 8 | 9 | Copyright © 2017 10 | https://github.com/maca88/NHibernate.Extensions/blob/master/LICENSE 11 | https://github.com/maca88/NHibernate.Extensions 12 | https://github.com/maca88/NHibernate.Extensions 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | 36 | Test.mdf 37 | None 38 | 39 | 40 | None 41 | 42 | 43 | 44 | PreserveNewest 45 | ..\..\Test.ldf 46 | 47 | 48 | 49 | PreserveNewest 50 | ..\..\Test.mdf 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/QueryRelationTreeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using NHibernate.Extensions.Tests.Entities; 5 | using NUnit.Framework; 6 | 7 | namespace NHibernate.Extensions.Tests 8 | { 9 | [TestFixture] 10 | public class QueryRelationTreeTests 11 | { 12 | [Test] 13 | public void Test1() 14 | { 15 | var tree = new QueryRelationTree(); 16 | Expression> A = person => person.BestFriend; 17 | Expression> AB = person => person.BestFriend.IdentityCard; 18 | Expression> AA = person => person.BestFriend.BestFriend; 19 | Expression> AAA = person => person.BestFriend.BestFriend.BestFriend; 20 | Expression> AAAA = person => person.BestFriend.BestFriend.BestFriend.BestFriend; 21 | 22 | //Input 23 | tree.AddNode(A); 24 | tree.AddNode(AB); 25 | tree.AddNode(AA); 26 | tree.AddNode(AAA); 27 | tree.AddNode(AAAA); 28 | 29 | var results = tree.DeepFirstSearch(); 30 | //Output 31 | Assert.AreEqual("BestFriend", results[0][0]); 32 | Assert.AreEqual("BestFriend.IdentityCard", results[0][1]); 33 | 34 | Assert.AreEqual("BestFriend", results[1][0]); 35 | Assert.AreEqual("BestFriend.BestFriend", results[1][1]); 36 | Assert.AreEqual("BestFriend.BestFriend.BestFriend", results[1][2]); 37 | Assert.AreEqual("BestFriend.BestFriend.BestFriend.BestFriend", results[1][3]); 38 | } 39 | 40 | [Test] 41 | public void Test2() 42 | { 43 | var tree = new QueryRelationTree(); 44 | Expression> AB = person => person.BestFriend.IdentityCard; 45 | Expression> AAAA = person => person.BestFriend.BestFriend.BestFriend.BestFriend; 46 | Expression> CD = person => person.CurrentOwnedVehicles.First().Wheels; 47 | Expression> CE = person => person.CurrentOwnedVehicles.First().RoadworthyTests; 48 | 49 | //Input 50 | tree.AddNode(AB); 51 | tree.AddNode(AAAA); 52 | tree.AddNode(CD); 53 | tree.AddNode(CE); 54 | 55 | var results = tree.DeepFirstSearch(); 56 | //Output 57 | Assert.AreEqual("BestFriend", results[0][0]); 58 | Assert.AreEqual("BestFriend.IdentityCard", results[0][1]); 59 | 60 | Assert.AreEqual("BestFriend", results[1][0]); 61 | Assert.AreEqual("BestFriend.BestFriend", results[1][1]); 62 | Assert.AreEqual("BestFriend.BestFriend.BestFriend", results[1][2]); 63 | Assert.AreEqual("BestFriend.BestFriend.BestFriend.BestFriend", results[1][3]); 64 | 65 | Assert.AreEqual("CurrentOwnedVehicles", results[2][0]); 66 | Assert.AreEqual("CurrentOwnedVehicles.Wheels", results[2][1]); 67 | 68 | Assert.AreEqual("CurrentOwnedVehicles", results[3][0]); 69 | Assert.AreEqual("CurrentOwnedVehicles.RoadworthyTests", results[3][1]); 70 | } 71 | 72 | [Test] 73 | public void Test3() 74 | { 75 | var tree = new QueryRelationTree(); 76 | Expression> AB = person => person.Identity; 77 | Expression> CD = person => person.IdentityCard; 78 | 79 | //Input 80 | tree.AddNode(AB); 81 | tree.AddNode(CD); 82 | 83 | var results = tree.DeepFirstSearch(); 84 | //Output 85 | Assert.AreEqual("Identity", results[0][0]); 86 | Assert.AreEqual("IdentityCard", results[1][0]); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/SessionSubscriptionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NHibernate.Tool.hbm2ddl; 4 | using NUnit.Framework; 5 | 6 | namespace NHibernate.Extensions.Tests 7 | { 8 | [TestFixture] 9 | public class SessionSubscriptionTests 10 | { 11 | [Test] 12 | public void TestTransactionSubscription() 13 | { 14 | using (var session = NHConfig.OpenSession()) 15 | using (var transaction = session.BeginTransaction()) 16 | { 17 | var beforeCommitExecuted = false; 18 | var afterCommitExecuted = false; 19 | 20 | session.Subscribe(o => o.Transaction 21 | .BeforeCommit(s => 22 | { 23 | Assert.AreEqual(session, s); 24 | Assert.IsTrue(transaction.IsActive); 25 | beforeCommitExecuted = true; 26 | }) 27 | .AfterCommit((s, success) => 28 | { 29 | Assert.IsTrue(success); 30 | Assert.AreEqual(session, s); 31 | Assert.IsFalse(transaction.IsActive); 32 | afterCommitExecuted = true; 33 | })); 34 | 35 | Assert.IsFalse(beforeCommitExecuted); 36 | Assert.IsFalse(afterCommitExecuted); 37 | transaction.Commit(); 38 | Assert.IsTrue(beforeCommitExecuted); 39 | Assert.IsTrue(afterCommitExecuted); 40 | } 41 | } 42 | 43 | [Test] 44 | public async Task TestTransactionSubscriptionAsync() 45 | { 46 | using (var session = NHConfig.OpenSession()) 47 | using (var transaction = session.BeginTransaction()) 48 | { 49 | var beforeCommitExecuted = false; 50 | var afterCommitExecuted = false; 51 | 52 | session.Subscribe(o => o.Transaction 53 | .BeforeCommit(async s => 54 | { 55 | await Task.Delay(0); 56 | Assert.AreEqual(session, s); 57 | Assert.IsTrue(transaction.IsActive); 58 | beforeCommitExecuted = true; 59 | }) 60 | .AfterCommit(async (s, success) => 61 | { 62 | await Task.Delay(0); 63 | Assert.IsTrue(success); 64 | Assert.AreEqual(session, s); 65 | Assert.IsFalse(transaction.IsActive); 66 | afterCommitExecuted = true; 67 | })); 68 | 69 | Assert.IsFalse(beforeCommitExecuted); 70 | Assert.IsFalse(afterCommitExecuted); 71 | await transaction.CommitAsync(); 72 | Assert.IsTrue(beforeCommitExecuted); 73 | Assert.IsTrue(afterCommitExecuted); 74 | } 75 | } 76 | 77 | [Test] 78 | public void TestTransactionSubscriptionRollback() 79 | { 80 | using (var session = NHConfig.OpenSession()) 81 | using (var transaction = session.BeginTransaction()) 82 | { 83 | var beforeCommitExecuted = false; 84 | var afterCommitExecuted = false; 85 | 86 | // BeforeCommit wont be executed on rollback 87 | session.Subscribe(o => o.Transaction 88 | .BeforeCommit(s => 89 | { 90 | Assert.AreEqual(session, s); 91 | Assert.IsTrue(transaction.IsActive); 92 | beforeCommitExecuted = true; 93 | }) 94 | .AfterCommit((s, success) => 95 | { 96 | Assert.IsFalse(success); 97 | Assert.AreEqual(session, s); 98 | Assert.IsFalse(transaction.IsActive); 99 | afterCommitExecuted = true; 100 | })); 101 | 102 | Assert.IsFalse(beforeCommitExecuted); 103 | Assert.IsFalse(afterCommitExecuted); 104 | transaction.Rollback(); 105 | Assert.IsFalse(beforeCommitExecuted); 106 | Assert.IsTrue(afterCommitExecuted); 107 | } 108 | } 109 | 110 | [Test] 111 | public async Task TestTransactionSubscriptionRollbackAsync() 112 | { 113 | using (var session = NHConfig.OpenSession()) 114 | using (var transaction = session.BeginTransaction()) 115 | { 116 | var beforeCommitExecuted = false; 117 | var afterCommitExecuted = false; 118 | 119 | // BeforeCommit wont be executed on rollback 120 | session.Subscribe(o => o.Transaction 121 | .BeforeCommit(async s => 122 | { 123 | await Task.Delay(0); 124 | Assert.AreEqual(session, s); 125 | Assert.IsTrue(transaction.IsActive); 126 | beforeCommitExecuted = true; 127 | }) 128 | .AfterCommit(async (s, success) => 129 | { 130 | await Task.Delay(0); 131 | Assert.IsFalse(success); 132 | Assert.AreEqual(session, s); 133 | Assert.IsFalse(transaction.IsActive); 134 | afterCommitExecuted = true; 135 | })); 136 | 137 | Assert.IsFalse(beforeCommitExecuted); 138 | Assert.IsFalse(afterCommitExecuted); 139 | await transaction.RollbackAsync(); 140 | Assert.IsFalse(beforeCommitExecuted); 141 | Assert.IsTrue(afterCommitExecuted); 142 | } 143 | } 144 | 145 | [Test] 146 | public void TestTransactionSubscriptionWithoutTransaction() 147 | { 148 | using (var session = NHConfig.OpenSession()) 149 | { 150 | Assert.Throws(() => 151 | { 152 | session.Subscribe(o => o.Transaction 153 | .BeforeCommit(s => { }) 154 | .AfterCommit((s, success) => { })); 155 | }); 156 | } 157 | } 158 | 159 | [Test] 160 | public void TestAsyncTransactionSubscriptionInSyncCommit() 161 | { 162 | using (var session = NHConfig.OpenSession()) 163 | using (var transaction = session.BeginTransaction()) 164 | { 165 | session.Subscribe(o => o.Transaction 166 | .BeforeCommit(s => Task.CompletedTask) 167 | .AfterCommit((s, success) => Task.CompletedTask)); 168 | 169 | Assert.Throws(() => 170 | { 171 | transaction.Commit(); 172 | }); 173 | } 174 | } 175 | 176 | [OneTimeSetUp] 177 | public void Initialize() 178 | { 179 | var schema = new SchemaExport(NHConfig.Configuration); 180 | schema.Drop(false, true); 181 | schema.Create(false, true); 182 | } 183 | 184 | [OneTimeTearDown] 185 | public void Cleanup() 186 | { 187 | var schema = new SchemaExport(NHConfig.Configuration); 188 | schema.Drop(false, true); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Test.ldf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maca88/NHibernate.Extensions/66c84026616f9f88996f16d08ba07d297c067e0b/NHibernate.Extensions.Tests/Test.ldf -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/Test.mdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maca88/NHibernate.Extensions/66c84026616f9f88996f16d08ba07d297c067e0b/NHibernate.Extensions.Tests/Test.mdf -------------------------------------------------------------------------------- /NHibernate.Extensions.Tests/hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | NHibernate.Connection.DriverConnectionProvider 5 | NHibernate.Driver.SqlClientDriver 6 | Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=NHibernate;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\Test.mdf 7 | NHibernate.Dialect.MsSql2008Dialect 8 | 9 | 10 | -------------------------------------------------------------------------------- /NHibernate.Extensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Extensions.Tests", "NHibernate.Extensions.Tests\NHibernate.Extensions.Tests.csproj", "{DC0789BE-E6C7-4F62-9B97-4D6F1343FE2F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Extensions", "NHibernate.Extensions\NHibernate.Extensions.csproj", "{ECE5FA2C-A6B7-4577-AD3C-DD13098FCE37}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {DC0789BE-E6C7-4F62-9B97-4D6F1343FE2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {DC0789BE-E6C7-4F62-9B97-4D6F1343FE2F}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {DC0789BE-E6C7-4F62-9B97-4D6F1343FE2F}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {DC0789BE-E6C7-4F62-9B97-4D6F1343FE2F}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {ECE5FA2C-A6B7-4577-AD3C-DD13098FCE37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {ECE5FA2C-A6B7-4577-AD3C-DD13098FCE37}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {ECE5FA2C-A6B7-4577-AD3C-DD13098FCE37}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {ECE5FA2C-A6B7-4577-AD3C-DD13098FCE37}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Async/BatchFetchBuilder.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by AsyncGenerator. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | using NHibernate.Linq; 12 | using System; 13 | using System.Collections.Generic; 14 | using System.Linq; 15 | using System.Linq.Expressions; 16 | using System.Threading; 17 | using System.Threading.Tasks; 18 | 19 | namespace NHibernate.Extensions 20 | { 21 | 22 | public partial class BatchFetchBuilder : IBatchFetchBuilder 23 | { 24 | 25 | Task> IBatchFetchBuilder.ExecuteAsync(CancellationToken cancellationToken) 26 | { 27 | if (cancellationToken.IsCancellationRequested) 28 | { 29 | return Task.FromCanceled>(cancellationToken); 30 | } 31 | return ExecuteAsync(q => q, cancellationToken); 32 | } 33 | 34 | protected async Task> ExecuteAsync(Func, IQueryable> convertQuery, CancellationToken cancellationToken = default(CancellationToken)) 35 | { 36 | cancellationToken.ThrowIfCancellationRequested(); 37 | var parameter = KeyExpresion.Parameters[0]; 38 | var method = BatchFetchExtension.ContainsMethodInfo.MakeGenericMethod(typeof(TKey)); 39 | var result = new List(); 40 | var currIndex = 0; 41 | var itemsCount = Keys.Count; 42 | while (currIndex < itemsCount) 43 | { 44 | var batchNum = Math.Min(BatchSize, itemsCount - currIndex); 45 | var batchItems = Keys.Skip(currIndex).Take(batchNum).ToList(); 46 | var value = Expression.Constant(batchItems, typeof(IEnumerable)); 47 | var containsMethod = Expression.Call(method, value, KeyExpresion.Body); 48 | var predicate = Expression.Lambda>(containsMethod, parameter); 49 | var query = Session.Query() 50 | .Where(predicate); 51 | 52 | if (BeforeQueryExecutionFunction != null) 53 | { 54 | query = BeforeQueryExecutionFunction(query); 55 | } 56 | 57 | result.AddRange(await (ToListAsync(convertQuery(query), cancellationToken)).ConfigureAwait(false)); 58 | currIndex += batchNum; 59 | } 60 | 61 | return result; 62 | } 63 | } 64 | 65 | 66 | public partial class BatchFetchBuilder : BatchFetchBuilder, IBatchFetchBuilder 67 | { 68 | 69 | Task> IBatchFetchBuilder.ExecuteAsync(CancellationToken cancellationToken) 70 | { 71 | if (cancellationToken.IsCancellationRequested) 72 | { 73 | return Task.FromCanceled>(cancellationToken); 74 | } 75 | return ExecuteAsync(q => q.Select(SelectExpression), cancellationToken); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Async/BatchFetchExtension.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by AsyncGenerator. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | using System; 12 | using System.CodeDom; 13 | using System.Collections.Generic; 14 | using System.Linq; 15 | using System.Linq.Expressions; 16 | using System.Reflection; 17 | 18 | namespace NHibernate.Extensions 19 | { 20 | using System.Threading.Tasks; 21 | using System.Threading; 22 | public static partial class BatchFetchExtension 23 | { 24 | 25 | /// 26 | /// Batch fetching a collecion of keys by using ISession Linq provider 27 | /// 28 | /// 29 | /// 30 | /// NHibernate session 31 | /// Collection of keys that will be retrieved from the database 32 | /// Expression pointing to the property that represents the key 33 | /// Number of records that will be retrieved within one execution 34 | /// Function to modify the query prior execution 35 | /// A cancellation token that can be used to cancel the work 36 | /// The fetched entites. 37 | public static Task> BatchFetchAsync(this ISession session, ICollection keys, 38 | Expression> propertyExpr, 39 | int batchSize, 40 | Func, IQueryable> queryFunc = null, CancellationToken cancellationToken = default(CancellationToken)) 41 | { 42 | if (propertyExpr == null) 43 | { 44 | throw new ArgumentNullException(nameof(propertyExpr)); 45 | } 46 | 47 | if (keys == null) 48 | { 49 | throw new ArgumentNullException(nameof(keys)); 50 | } 51 | if (cancellationToken.IsCancellationRequested) 52 | { 53 | return Task.FromCanceled>(cancellationToken); 54 | } 55 | try 56 | { 57 | 58 | return session.BatchFetch(batchSize) 59 | .SetKeys(keys, propertyExpr) 60 | .BeforeQueryExecution(queryFunc) 61 | .ExecuteAsync(cancellationToken); 62 | } 63 | catch (Exception ex) 64 | { 65 | return Task.FromException>(ex); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Async/IBatchFetchBuilder.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by AsyncGenerator. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Linq.Expressions; 15 | 16 | namespace NHibernate.Extensions 17 | { 18 | using System.Threading.Tasks; 19 | using System.Threading; 20 | 21 | public partial interface IBatchFetchBuilder 22 | { 23 | 24 | Task> ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); 25 | } 26 | 27 | public partial interface IBatchFetchBuilder 28 | { 29 | 30 | Task> ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /NHibernate.Extensions/BatchFetchBuilder.cs: -------------------------------------------------------------------------------- 1 | using NHibernate.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace NHibernate.Extensions 10 | { 11 | public class BatchFetchBuilder : IBatchFetchBuilder 12 | { 13 | private readonly ISession _session; 14 | private readonly int _batchSize; 15 | 16 | public BatchFetchBuilder(ISession session, int batchSize) 17 | { 18 | _session = session; 19 | _batchSize = batchSize; 20 | } 21 | 22 | IBatchFetchBuilder IBatchFetchBuilder.SetKeys(ICollection keys, Expression> keyExpresion) 23 | { 24 | return new BatchFetchBuilder(_session, keys, keyExpresion, _batchSize); 25 | } 26 | } 27 | 28 | public partial class BatchFetchBuilder : IBatchFetchBuilder 29 | { 30 | protected readonly ISession Session; 31 | protected readonly ICollection Keys; 32 | protected readonly Expression> KeyExpresion; 33 | protected readonly int BatchSize; 34 | 35 | public BatchFetchBuilder(ISession session, ICollection keys, Expression> keyExpresion, 36 | int batchSize) 37 | { 38 | Session = session; 39 | Keys = keys; 40 | KeyExpresion = keyExpresion; 41 | BatchSize = batchSize; 42 | } 43 | 44 | public Func, IQueryable> BeforeQueryExecutionFunction { get; protected set; } 45 | 46 | IBatchFetchBuilder IBatchFetchBuilder.BeforeQueryExecution(Func, IQueryable> queryFunc) 47 | { 48 | return new BatchFetchBuilder(Session, Keys.ToList(), KeyExpresion, BatchSize) 49 | { 50 | BeforeQueryExecutionFunction = queryFunc 51 | }; 52 | } 53 | 54 | IBatchFetchBuilder IBatchFetchBuilder.Select(Expression> selectExpr) 55 | { 56 | return new BatchFetchBuilder(Session, Keys.ToList(), KeyExpresion, BatchSize, selectExpr) 57 | { 58 | BeforeQueryExecutionFunction = BeforeQueryExecutionFunction 59 | }; 60 | } 61 | 62 | List IBatchFetchBuilder.Execute() 63 | { 64 | return Execute(q => q); 65 | } 66 | 67 | protected List Execute(Func, IQueryable> convertQuery) 68 | { 69 | var parameter = KeyExpresion.Parameters[0]; 70 | var method = BatchFetchExtension.ContainsMethodInfo.MakeGenericMethod(typeof(TKey)); 71 | var result = new List(); 72 | var currIndex = 0; 73 | var itemsCount = Keys.Count; 74 | while (currIndex < itemsCount) 75 | { 76 | var batchNum = Math.Min(BatchSize, itemsCount - currIndex); 77 | var batchItems = Keys.Skip(currIndex).Take(batchNum).ToList(); 78 | var value = Expression.Constant(batchItems, typeof(IEnumerable)); 79 | var containsMethod = Expression.Call(method, value, KeyExpresion.Body); 80 | var predicate = Expression.Lambda>(containsMethod, parameter); 81 | var query = Session.Query() 82 | .Where(predicate); 83 | 84 | if (BeforeQueryExecutionFunction != null) 85 | { 86 | query = BeforeQueryExecutionFunction(query); 87 | } 88 | 89 | result.AddRange(ToList(convertQuery(query))); 90 | currIndex += batchNum; 91 | } 92 | 93 | return result; 94 | } 95 | 96 | private List ToList(IQueryable query) 97 | { 98 | return query.ToList(); 99 | } 100 | 101 | private Task> ToListAsync(IQueryable query, CancellationToken cancellationToken) 102 | { 103 | return query.ToListAsync(cancellationToken); 104 | } 105 | } 106 | 107 | 108 | public partial class BatchFetchBuilder : BatchFetchBuilder, IBatchFetchBuilder 109 | { 110 | public BatchFetchBuilder(ISession session, ICollection keys, Expression> keyExpresion, int batchSize, 111 | Expression> selectExpression) 112 | : base(session, keys, keyExpresion, batchSize) 113 | { 114 | SelectExpression = selectExpression; 115 | } 116 | 117 | public Expression> SelectExpression { get; } 118 | 119 | 120 | 121 | IBatchFetchBuilder IBatchFetchBuilder.BeforeQueryExecution(Func, IQueryable> queryFunc) 122 | { 123 | return new BatchFetchBuilder(Session, Keys.ToList(), KeyExpresion, BatchSize, SelectExpression) 124 | { 125 | BeforeQueryExecutionFunction = queryFunc 126 | }; 127 | } 128 | 129 | List IBatchFetchBuilder.Execute() 130 | { 131 | return Execute(q => q.Select(SelectExpression)); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /NHibernate.Extensions/BatchFetchExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace NHibernate.Extensions 9 | { 10 | public static partial class BatchFetchExtension 11 | { 12 | internal static readonly MethodInfo ContainsMethodInfo; 13 | 14 | static BatchFetchExtension() 15 | { 16 | ContainsMethodInfo = typeof(Enumerable) 17 | .GetMethods() 18 | .Where(x => x.Name == "Contains") 19 | .Single(x => x.GetParameters().Length == 2); 20 | } 21 | 22 | /// 23 | /// Batch fetching a collecion of keys by using ISession Linq provider 24 | /// 25 | /// 26 | /// 27 | /// NHibernate session 28 | /// Collection of keys that will be retrieved from the database 29 | /// Expression pointing to the property that represents the key 30 | /// Number of records that will be retrieved within one execution 31 | /// Function to modify the query prior execution 32 | /// The fetched entites. 33 | public static List BatchFetch(this ISession session, ICollection keys, 34 | Expression> propertyExpr, 35 | int batchSize, 36 | Func, IQueryable> queryFunc = null) 37 | { 38 | if (propertyExpr == null) 39 | { 40 | throw new ArgumentNullException(nameof(propertyExpr)); 41 | } 42 | 43 | if (keys == null) 44 | { 45 | throw new ArgumentNullException(nameof(keys)); 46 | } 47 | 48 | return session.BatchFetch(batchSize) 49 | .SetKeys(keys, propertyExpr) 50 | .BeforeQueryExecution(queryFunc) 51 | .Execute(); 52 | } 53 | 54 | /// 55 | /// Batch fetching a collecion of keys by using ISession Linq provider 56 | /// 57 | /// 58 | /// NHibernate session 59 | /// Number of records that will be retrieved within one execution 60 | /// The batch fetch builder. 61 | public static IBatchFetchBuilder BatchFetch(this ISession session, int batchSize) 62 | { 63 | return new BatchFetchBuilder(session, batchSize); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/DeepCloneMemberOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NHibernate.Extensions 4 | { 5 | public class DeepCloneMemberOptions : IDeepCloneMemberOptions 6 | { 7 | public string MemberName { get; set; } 8 | 9 | public Func ResolveUsing { get; set; } 10 | 11 | public bool Ignore { get; set; } 12 | 13 | public bool CloneAsReference { get; set; } 14 | 15 | public Func Filter { get; set; } 16 | 17 | IDeepCloneMemberOptions IDeepCloneMemberOptions.ResolveUsing(Func func) 18 | { 19 | ResolveUsing = func; 20 | return this; 21 | } 22 | 23 | IDeepCloneMemberOptions IDeepCloneMemberOptions.Ignore(bool value) 24 | { 25 | Ignore = value; 26 | return this; 27 | } 28 | 29 | IDeepCloneMemberOptions IDeepCloneMemberOptions.CloneAsReference(bool value) 30 | { 31 | CloneAsReference = value; 32 | return this; 33 | } 34 | 35 | IDeepCloneMemberOptions IDeepCloneMemberOptions.Filter(Func func) 36 | { 37 | Filter = func; 38 | return this; 39 | } 40 | } 41 | 42 | public class DeepCloneMemberOptions : DeepCloneMemberOptions, IDeepCloneMemberOptions 43 | { 44 | IDeepCloneMemberOptions IDeepCloneMemberOptions.ResolveUsing(Func func) 45 | { 46 | ResolveUsing = func == null ? (Func)null : o => func((TType)o); 47 | return this; 48 | } 49 | 50 | IDeepCloneMemberOptions IDeepCloneMemberOptions.Ignore(bool value) 51 | { 52 | Ignore = value; 53 | return this; 54 | } 55 | 56 | IDeepCloneMemberOptions IDeepCloneMemberOptions.CloneAsReference(bool value) 57 | { 58 | CloneAsReference = value; 59 | return this; 60 | } 61 | 62 | IDeepCloneMemberOptions IDeepCloneMemberOptions.Filter(Func func) 63 | { 64 | Filter = func == null ? (Func)null : o => func((TMember)o); 65 | return this; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/DeepCloneOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NHibernate.Extensions.DeepClone; 5 | using NHibernate.Extensions.Internal; 6 | using NHibernate.Persister.Entity; 7 | 8 | namespace NHibernate.Extensions 9 | { 10 | public class DeepCloneOptions 11 | { 12 | internal bool CloneIdentifierValue { get; set; } = true; 13 | 14 | internal bool UseSessionLoadFunction { get; set; } 15 | 16 | internal bool? SkipEntityTypesValue { get; set; } 17 | 18 | internal Func CanCloneAsReferenceFunc { get; set; } 19 | 20 | internal List EntityResolvers { get; } = new List(); 21 | 22 | internal Dictionary TypeOptions { get; } = new Dictionary(); 23 | 24 | public DeepCloneOptions CloneIdentifier(bool value) 25 | { 26 | CloneIdentifierValue = value; 27 | return this; 28 | } 29 | 30 | public DeepCloneOptions SkipEntityTypes(bool value = true) 31 | { 32 | SkipEntityTypesValue = value; 33 | return this; 34 | } 35 | 36 | public DeepCloneOptions UseSessionLoad(bool value = true) 37 | { 38 | UseSessionLoadFunction = value; 39 | return this; 40 | } 41 | 42 | public DeepCloneOptions CanCloneAsReference(Func func) 43 | { 44 | CanCloneAsReferenceFunc = func; 45 | return this; 46 | } 47 | 48 | public DeepCloneOptions AddEntityResolver(IEntityResolver resolver) 49 | { 50 | EntityResolvers.Add(resolver); 51 | return this; 52 | } 53 | 54 | public DeepCloneOptions AddEntityResolver(Predicate entityTypePredicate, Func resolver) 55 | { 56 | EntityResolvers.Add(new DelegateEntityResolver(entityTypePredicate, resolver)); 57 | return this; 58 | } 59 | 60 | public DeepCloneOptions AddEntityResolver(Func canResolveFunc, Func resolver) 61 | { 62 | EntityResolvers.Add(new DelegateEntityResolver(canResolveFunc, resolver)); 63 | return this; 64 | } 65 | 66 | public DeepCloneOptions ForType(Action> action) 67 | { 68 | var type = typeof(TType); 69 | if (!TypeOptions.ContainsKey(typeof(TType))) 70 | { 71 | var typeOpts = new DeepCloneTypeOptions 72 | { 73 | CloneIdentifier = CloneIdentifierValue 74 | }; 75 | TypeOptions.Add(type, typeOpts); 76 | } 77 | action(TypeOptions[type] as IDeepCloneTypeOptions); 78 | return this; 79 | } 80 | 81 | public DeepCloneOptions ForTypes(IEnumerable types, Action action) 82 | { 83 | foreach (var type in types) 84 | { 85 | if (!TypeOptions.ContainsKey(type)) 86 | { 87 | var typeOpts = new DeepCloneTypeOptions(type) 88 | { 89 | CloneIdentifier = CloneIdentifierValue 90 | }; 91 | TypeOptions.Add(type, typeOpts); 92 | } 93 | action(type, TypeOptions[type]); 94 | } 95 | return this; 96 | } 97 | 98 | private readonly Dictionary> _cachedIgnoredMembersResults = new Dictionary>(); 99 | 100 | internal HashSet GetIgnoreMembers(System.Type type) 101 | { 102 | if (_cachedIgnoredMembersResults.ContainsKey(type)) 103 | { 104 | return _cachedIgnoredMembersResults[type]; 105 | } 106 | var result = new HashSet(); 107 | var pairs = TypeOptions.Where(pair => pair.Key.IsAssignableFrom(type)).ToList(); 108 | if (pairs.Any()) 109 | { 110 | //subclasses have higher priority 111 | pairs.Sort((pair, valuePair) => pair.Key.IsAssignableFrom(valuePair.Key) ? -1 : 1); 112 | foreach (var member in pairs 113 | .Select(o => o.Value) 114 | .SelectMany(o => o.Members) 115 | .Select(o => o.Value)) 116 | { 117 | if (member.Ignore) 118 | result.Add(member.MemberName); 119 | else 120 | result.Remove(member.MemberName); 121 | } 122 | } 123 | _cachedIgnoredMembersResults.Add(type, result); 124 | return result; 125 | } 126 | 127 | private readonly Dictionary>> _cachedResolveFunctions = 128 | new Dictionary>>(); 129 | 130 | internal Func GetResolveFunction(System.Type type, string propName) 131 | { 132 | if (_cachedResolveFunctions.ContainsKey(type) && _cachedResolveFunctions[type].ContainsKey(propName)) 133 | return _cachedResolveFunctions[type][propName]; 134 | 135 | var pairs = TypeOptions.Where(pair => pair.Key.IsAssignableFrom(type)).ToList(); 136 | Func result = null; 137 | if (pairs.Any()) 138 | { 139 | //subclasses have higher priority 140 | pairs.Sort((pair, valuePair) => pair.Key.IsAssignableFrom(valuePair.Key) ? -1 : 1); 141 | foreach (var memberOpt in pairs 142 | .Select(o => o.Value) 143 | .SelectMany(o => o.Members) 144 | .Select(o => o.Value) 145 | .Where(o => o.MemberName == propName && o.ResolveUsing != null)) 146 | { 147 | result = memberOpt.ResolveUsing; 148 | } 149 | } 150 | 151 | if (!_cachedResolveFunctions.ContainsKey(type)) 152 | _cachedResolveFunctions.Add(type, new Dictionary>()); 153 | if (!_cachedResolveFunctions[type].ContainsKey(propName)) 154 | _cachedResolveFunctions[type].Add(propName, null); 155 | 156 | _cachedResolveFunctions[type][propName] = result; 157 | return result; 158 | } 159 | 160 | private readonly Dictionary>> _cachedFilterFunctions = 161 | new Dictionary>>(); 162 | 163 | internal Func GetFilterFunction(System.Type type, string propName) 164 | { 165 | if (_cachedFilterFunctions.ContainsKey(type) && _cachedFilterFunctions[type].ContainsKey(propName)) 166 | return _cachedFilterFunctions[type][propName]; 167 | 168 | var pairs = TypeOptions.Where(pair => pair.Key.IsAssignableFrom(type)).ToList(); 169 | Func result = null; 170 | if (pairs.Any()) 171 | { 172 | //subclasses have higher priority 173 | pairs.Sort((pair, valuePair) => pair.Key.IsAssignableFrom(valuePair.Key) ? -1 : 1); 174 | foreach (var memberOpt in pairs 175 | .Select(o => o.Value) 176 | .SelectMany(o => o.Members) 177 | .Select(o => o.Value) 178 | .Where(o => o.MemberName == propName && o.Filter != null)) 179 | { 180 | result = memberOpt.Filter; 181 | } 182 | } 183 | 184 | if (!_cachedFilterFunctions.ContainsKey(type)) 185 | _cachedFilterFunctions.Add(type, new Dictionary>()); 186 | if (!_cachedFilterFunctions[type].ContainsKey(propName)) 187 | _cachedFilterFunctions[type].Add(propName, null); 188 | 189 | _cachedFilterFunctions[type][propName] = result; 190 | return result; 191 | } 192 | 193 | internal bool CanCloneIdentifier(System.Type entityType) 194 | { 195 | return TypeOptions.ContainsKey(entityType) && TypeOptions[entityType].CloneIdentifier.HasValue 196 | ? TypeOptions[entityType].CloneIdentifier.Value 197 | : CloneIdentifierValue; 198 | } 199 | 200 | internal bool CanCloneAsReference(System.Type entityType, string propertyName) 201 | { 202 | if (!TypeOptions.ContainsKey(entityType) || !TypeOptions[entityType].Members.ContainsKey(propertyName)) return false; 203 | return TypeOptions[entityType].Members[propertyName].CloneAsReference; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/DeepCloneParentEntity.cs: -------------------------------------------------------------------------------- 1 | using NHibernate.Persister.Entity; 2 | using NHibernate.Type; 3 | 4 | namespace NHibernate.Extensions 5 | { 6 | public class DeepCloneParentEntity 7 | { 8 | public DeepCloneParentEntity( 9 | object entity, 10 | IType childType, 11 | string[] referencedColumns) 12 | { 13 | Entity = entity; 14 | ChildType = childType; 15 | ReferencedColumns = referencedColumns; 16 | } 17 | 18 | public object Entity { get; } 19 | 20 | public IType ChildType { get; } 21 | 22 | public string[] ReferencedColumns { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/DeepCloneTypeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using NHibernate.Extensions.Internal; 6 | 7 | namespace NHibernate.Extensions 8 | { 9 | public class DeepCloneTypeOptions : IDeepCloneTypeOptions 10 | { 11 | public DeepCloneTypeOptions(System.Type type) 12 | { 13 | Members = new Dictionary(); 14 | CloneIdentifier = true; 15 | Type = type; 16 | } 17 | 18 | public System.Type Type { get; set; } 19 | 20 | public Dictionary Members { get; set; } 21 | 22 | public bool? CloneIdentifier { get; set; } 23 | 24 | IDeepCloneTypeOptions IDeepCloneTypeOptions.CloneIdentifier(bool value) 25 | { 26 | CloneIdentifier = value; 27 | return this; 28 | } 29 | 30 | IDeepCloneTypeOptions IDeepCloneTypeOptions.ForMember(string memberName, Action action) 31 | { 32 | if (!Members.ContainsKey(memberName)) 33 | Members.Add(memberName, new DeepCloneMemberOptions 34 | { 35 | MemberName = memberName 36 | }); 37 | action(Members[memberName]); 38 | return this; 39 | } 40 | 41 | IDeepCloneTypeOptions IDeepCloneTypeOptions.ForMembers(Func func, Action action) 42 | { 43 | foreach (var propertyInfo in Type.GetProperties()) 44 | { 45 | if (!func(propertyInfo)) 46 | { 47 | continue; 48 | } 49 | var memberName = propertyInfo.Name; 50 | if (!Members.ContainsKey(memberName)) 51 | Members.Add(memberName, new DeepCloneMemberOptions 52 | { 53 | MemberName = memberName 54 | }); 55 | action(Members[memberName]); 56 | } 57 | return this; 58 | } 59 | } 60 | 61 | public class DeepCloneTypeOptions : DeepCloneTypeOptions, IDeepCloneTypeOptions 62 | { 63 | public DeepCloneTypeOptions() 64 | : base(typeof(TType)) 65 | { 66 | } 67 | 68 | IDeepCloneTypeOptions IDeepCloneTypeOptions.CloneIdentifier(bool value) 69 | { 70 | CloneIdentifier = value; 71 | return this; 72 | } 73 | 74 | public IDeepCloneTypeOptions ForMember(Expression> memberExpr, 75 | Action> action) 76 | { 77 | var memberName = memberExpr.GetFullPropertyName(); 78 | if (!Members.ContainsKey(memberName)) 79 | Members.Add(memberName, new DeepCloneMemberOptions 80 | { 81 | MemberName = memberName 82 | }); 83 | action(Members[memberName] as IDeepCloneMemberOptions); 84 | return this; 85 | } 86 | 87 | public IDeepCloneTypeOptions ForMembers(Func func, Action> action) 88 | { 89 | foreach (var propertyInfo in Type.GetProperties()) 90 | { 91 | if (!func(propertyInfo)) 92 | { 93 | continue; 94 | } 95 | var memberName = propertyInfo.Name; 96 | if (!Members.ContainsKey(memberName)) 97 | Members.Add(memberName, new DeepCloneMemberOptions 98 | { 99 | MemberName = memberName 100 | }); 101 | action(Members[memberName] as IDeepCloneMemberOptions); 102 | } 103 | return this; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/IDeepCloneMemberOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NHibernate.Extensions 4 | { 5 | public interface IDeepCloneMemberOptions 6 | { 7 | IDeepCloneMemberOptions ResolveUsing(Func func); 8 | 9 | IDeepCloneMemberOptions Ignore(bool value = true); 10 | 11 | IDeepCloneMemberOptions CloneAsReference(bool value = true); 12 | 13 | IDeepCloneMemberOptions Filter(Func func); 14 | } 15 | 16 | public interface IDeepCloneMemberOptions 17 | { 18 | IDeepCloneMemberOptions ResolveUsing(Func func); 19 | 20 | IDeepCloneMemberOptions Ignore(bool value = true); 21 | 22 | IDeepCloneMemberOptions CloneAsReference(bool value = true); 23 | 24 | IDeepCloneMemberOptions Filter(Func func); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/IDeepCloneTypeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace NHibernate.Extensions 6 | { 7 | public interface IDeepCloneTypeOptions 8 | { 9 | IDeepCloneTypeOptions CloneIdentifier(bool value); 10 | 11 | IDeepCloneTypeOptions ForMember(string memberName, Action action); 12 | 13 | IDeepCloneTypeOptions ForMembers(Func func, Action action); 14 | } 15 | 16 | public interface IDeepCloneTypeOptions 17 | { 18 | IDeepCloneTypeOptions CloneIdentifier(bool value); 19 | 20 | IDeepCloneTypeOptions ForMember(Expression> memberExpr, 21 | Action> action); 22 | 23 | IDeepCloneTypeOptions ForMembers(Func func, Action> action); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /NHibernate.Extensions/DeepClone/IEntityResolver.cs: -------------------------------------------------------------------------------- 1 | using NHibernate.Persister.Entity; 2 | 3 | namespace NHibernate.Extensions.DeepClone 4 | { 5 | public interface IEntityResolver 6 | { 7 | object Resolve(object entity, AbstractEntityPersister entityPersister); 8 | 9 | bool CanResolve(System.Type entityType, object entity); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NHibernate.Extensions/IBatchFetchBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace NHibernate.Extensions 7 | { 8 | public interface IBatchFetchBuilder 9 | { 10 | IBatchFetchBuilder SetKeys(ICollection keys, Expression> keyExpresion); 11 | } 12 | 13 | public partial interface IBatchFetchBuilder 14 | { 15 | IBatchFetchBuilder BeforeQueryExecution(Func, IQueryable> queryFunc); 16 | 17 | IBatchFetchBuilder Select(Expression> selectExpr); 18 | 19 | List Execute(); 20 | } 21 | 22 | public partial interface IBatchFetchBuilder 23 | { 24 | IBatchFetchBuilder BeforeQueryExecution(Func, IQueryable> queryFunc); 25 | 26 | List Execute(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Internal/DelegateEntityResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NHibernate.Extensions.DeepClone; 3 | using NHibernate.Persister.Entity; 4 | 5 | namespace NHibernate.Extensions.Internal 6 | { 7 | internal class DelegateEntityResolver : IEntityResolver 8 | { 9 | private Func _canResolve; 10 | private Func _resolver; 11 | 12 | public DelegateEntityResolver(Func canResolve, Func resolver) 13 | { 14 | _canResolve = canResolve; 15 | _resolver = resolver; 16 | } 17 | 18 | public DelegateEntityResolver(Predicate canResolve, Func resolver) 19 | { 20 | _canResolve = (type, o) => canResolve(type); 21 | _resolver = resolver; 22 | } 23 | 24 | public object Resolve(object entity, AbstractEntityPersister entityPersister) 25 | { 26 | return _resolver(entity, entityPersister); 27 | } 28 | 29 | public bool CanResolve(System.Type entityType, object entity) 30 | { 31 | return _canResolve(entityType, entity); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Internal/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace NHibernate.Extensions.Internal 6 | { 7 | internal static class ExpressionExtensions 8 | { 9 | public static string GetFullPropertyName(this Expression> exp) 10 | { 11 | MemberExpression memberExp; 12 | if (!TryFindMemberExpression(exp.Body, out memberExp)) 13 | return string.Empty; 14 | 15 | var memberNames = new Stack(); 16 | do 17 | { 18 | memberNames.Push(memberExp.Member.Name); 19 | } 20 | while (TryFindMemberExpression(memberExp.Expression, out memberExp)); 21 | 22 | return string.Join(".", memberNames.ToArray()); 23 | } 24 | 25 | private static bool TryFindMemberExpression(Expression exp, out MemberExpression memberExp) 26 | { 27 | memberExp = exp as MemberExpression; 28 | if (memberExp != null) 29 | { 30 | // heyo! that was easy enough 31 | return true; 32 | } 33 | 34 | // if the compiler created an automatic conversion, 35 | // it'll look something like... 36 | // obj => Convert(obj.Property) [e.g., int -> object] 37 | // OR: 38 | // obj => ConvertChecked(obj.Property) [e.g., int -> long] 39 | // ...which are the cases checked in IsConversion 40 | if (IsConversion(exp) && exp is UnaryExpression) 41 | { 42 | memberExp = ((UnaryExpression)exp).Operand as MemberExpression; 43 | if (memberExp != null) 44 | { 45 | return true; 46 | } 47 | } 48 | 49 | return false; 50 | } 51 | 52 | private static bool IsConversion(Expression exp) 53 | { 54 | return ( 55 | exp.NodeType == ExpressionType.Convert || 56 | exp.NodeType == ExpressionType.ConvertChecked 57 | ); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Internal/ExpressionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using NHibernate.Util; 4 | 5 | namespace NHibernate.Extensions.Internal 6 | { 7 | internal static class ExpressionHelper 8 | { 9 | public static string GetFullPath(Expression expression) 10 | { 11 | string fullPath; 12 | var memberExpression = expression as MemberExpression; 13 | if (memberExpression != null) 14 | { 15 | if (memberExpression.Expression.NodeType == ExpressionType.MemberAccess 16 | || memberExpression.Expression.NodeType == ExpressionType.Call) 17 | { 18 | if (memberExpression.Member.DeclaringType.IsNullable()) 19 | { 20 | // it's a Nullable, so ignore any .Value 21 | if (memberExpression.Member.Name == "Value") 22 | { 23 | fullPath = GetFullPath(memberExpression.Expression); 24 | return fullPath; 25 | } 26 | } 27 | fullPath = GetFullPath(memberExpression.Expression) + "." + memberExpression.Member.Name; 28 | return fullPath; 29 | } 30 | if (IsConversion(memberExpression.Expression.NodeType)) 31 | { 32 | fullPath = (GetFullPath(memberExpression.Expression) + "." + memberExpression.Member.Name).TrimStart('.'); ; 33 | return fullPath; 34 | } 35 | 36 | fullPath = memberExpression.Member.Name; 37 | return fullPath; 38 | } 39 | 40 | var unaryExpression = expression as UnaryExpression; 41 | if (unaryExpression != null) 42 | { 43 | if (!IsConversion(unaryExpression.NodeType)) 44 | throw new Exception("Cannot interpret member from " + expression); 45 | fullPath = GetFullPath(unaryExpression.Operand); 46 | return fullPath; 47 | } 48 | 49 | var methodCallExpression = expression as MethodCallExpression; 50 | if (methodCallExpression != null) 51 | { 52 | /* 53 | if (methodCallExpression.Method.Name == "GetType") 54 | return ClassMember(methodCallExpression.Object); 55 | */ 56 | if (methodCallExpression.Method.Name == "get_Item") 57 | { 58 | fullPath = GetFullPath(methodCallExpression.Object); 59 | return fullPath; 60 | } 61 | 62 | if (methodCallExpression.Method.Name == "First") 63 | { 64 | fullPath = GetFullPath(methodCallExpression.Arguments[0]); 65 | return fullPath; 66 | } 67 | 68 | throw new Exception("Unrecognised method call in expression " + methodCallExpression); 69 | } 70 | 71 | if (expression is ParameterExpression) 72 | return ""; 73 | 74 | throw new Exception("Could not determine member from " + expression); 75 | } 76 | 77 | private static bool IsConversion(ExpressionType expressionType) 78 | { 79 | if (expressionType != ExpressionType.Convert) 80 | { 81 | return (bool)(expressionType == ExpressionType.ConvertChecked); 82 | } 83 | return true; 84 | } 85 | /* 86 | private static string ClassMember(Expression expression, SessionFactoryInfo sessionFactoryInfo) 87 | { 88 | if (expression.NodeType == ExpressionType.MemberAccess) 89 | { 90 | return (GetFullPath(expression, sessionFactoryInfo) + ".class"); 91 | } 92 | return "class"; 93 | }*/ 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Internal/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace NHibernate.Extensions.Internal 5 | { 6 | internal static class TypeExtensions 7 | { 8 | public static System.Type GetGenericType(this System.Type givenType, System.Type genericType) 9 | { 10 | while (true) 11 | { 12 | if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 13 | { 14 | return givenType; 15 | } 16 | 17 | var type = givenType.GetInterfaces().FirstOrDefault(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType); 18 | if (type != null) 19 | { 20 | return type; 21 | } 22 | 23 | var baseType = givenType.BaseType; 24 | if (baseType == null) 25 | { 26 | return null; 27 | } 28 | givenType = baseType; 29 | } 30 | } 31 | 32 | public static bool IsAssignableToGenericType(this System.Type givenType, System.Type genericType) 33 | { 34 | var interfaceTypes = givenType.GetInterfaces(); 35 | 36 | if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType)) 37 | { 38 | return true; 39 | } 40 | 41 | if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 42 | return true; 43 | 44 | var baseType = givenType.BaseType; 45 | return baseType != null && IsAssignableToGenericType(baseType, genericType); 46 | } 47 | 48 | /// 49 | /// http://stackoverflow.com/questions/2490244/default-value-of-a-type-at-runtime 50 | /// 51 | /// 52 | /// 53 | public static object GetDefaultValue(this System.Type t) 54 | { 55 | return t.IsValueType ? Activator.CreateInstance(t) : null; 56 | } 57 | 58 | public static bool IsSimpleType(this System.Type type) 59 | { 60 | return 61 | type.IsPrimitive || 62 | type.IsValueType || 63 | type.IsEnum || 64 | type == typeof(String); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Internal/TypeHelper.cs: -------------------------------------------------------------------------------- 1 | using NHibernate.Intercept; 2 | using NHibernate.Proxy; 3 | using NHibernate.Proxy.DynamicProxy; 4 | 5 | namespace NHibernate.Extensions.Internal 6 | { 7 | internal static class TypeHelper 8 | { 9 | public static bool IsSubclassOfRawGeneric(System.Type generic, System.Type toCheck) 10 | { 11 | while (toCheck != null && toCheck != typeof(object)) 12 | { 13 | var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; 14 | if (generic == cur) 15 | { 16 | return true; 17 | } 18 | toCheck = toCheck.BaseType; 19 | } 20 | return false; 21 | } 22 | 23 | /// 24 | /// Gets the underlying class type of a persistent object that may be proxied 25 | /// 26 | public static System.Type GetUnproxiedType(this object entity, bool allowInitialization) 27 | { 28 | if (entity is INHibernateProxy nhProxy) 29 | { 30 | if (nhProxy.HibernateLazyInitializer.IsUninitialized && !allowInitialization) 31 | { 32 | return nhProxy.HibernateLazyInitializer.PersistentClass; 33 | } 34 | 35 | // We have to initialize in case of a subclass to get the concrete type 36 | entity = nhProxy.HibernateLazyInitializer.GetImplementation(); 37 | } 38 | 39 | switch (entity) 40 | { 41 | case IFieldInterceptorAccessor interceptorAccessor: 42 | return interceptorAccessor.FieldInterceptor.MappedClass; 43 | default: 44 | return entity.GetType(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/ExpressionInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Runtime.InteropServices; 5 | using NHibernate.Metadata; 6 | using NHibernate.Persister.Collection; 7 | using NHibernate.Persister.Entity; 8 | 9 | namespace NHibernate.Extensions.Linq 10 | { 11 | internal class ExpressionInfo 12 | { 13 | private readonly Expression _originalQuery; 14 | private string _currentPath; 15 | private readonly Dictionary _includedPaths = new Dictionary(); 16 | 17 | public ExpressionInfo(Expression expression, IClassMetadata metadata) 18 | { 19 | _originalQuery = expression; 20 | Expression = expression; 21 | Metadata = metadata; 22 | TotalColumns = GetTotalColumns(metadata); 23 | } 24 | 25 | public IClassMetadata Metadata { get; } 26 | 27 | public int TotalColumns { get; private set; } 28 | 29 | public ExpressionInfo Next { get; private set; } 30 | 31 | public ExpressionInfo Previous { get; private set; } 32 | 33 | public Expression Expression { get; set; } 34 | 35 | public string CollectionPath { get; set; } 36 | 37 | public bool IsExpressionModified => Expression != _originalQuery; 38 | 39 | public ExpressionInfo GetFirst() 40 | { 41 | var current = this; 42 | while (current.Previous != null) 43 | { 44 | current = current.Previous; 45 | } 46 | return current; 47 | } 48 | 49 | public string AddIncludedProperty(string propertyName, IClassMetadata propertyTypeMetadata, IQueryableCollection collectionMetadata, bool root) 50 | { 51 | if (root) 52 | { 53 | _currentPath = propertyName; 54 | } 55 | else if (!string.IsNullOrEmpty(_currentPath)) 56 | { 57 | _currentPath = $"{_currentPath}.{propertyName}"; 58 | } 59 | 60 | if (_includedPaths.ContainsKey(_currentPath)) 61 | { 62 | return null; 63 | } 64 | 65 | var columns = propertyTypeMetadata == null 66 | ? GetTotalColumns(collectionMetadata) 67 | : GetTotalColumns(propertyTypeMetadata); 68 | 69 | _includedPaths.Add(_currentPath, columns); 70 | TotalColumns += columns; 71 | 72 | return _currentPath; 73 | } 74 | 75 | public void RemoveIncludedProperty(string includedPath) 76 | { 77 | TotalColumns -= _includedPaths[includedPath]; 78 | _includedPaths.Remove(includedPath); 79 | } 80 | 81 | public List GetExpressions() 82 | { 83 | var current = GetFirst(); 84 | var queries = new List { current.Expression }; 85 | 86 | while (current.Next != null) 87 | { 88 | current = current.Next; 89 | queries.Add(current.Expression); 90 | } 91 | return queries; 92 | } 93 | 94 | public ExpressionInfo GetOrCreateNext() 95 | { 96 | if (Next != null) return Next; 97 | var next = new ExpressionInfo(_originalQuery, Metadata); 98 | Next = next; 99 | next.Previous = this; 100 | return next; 101 | } 102 | 103 | private static int GetTotalColumns(IClassMetadata metadata) 104 | { 105 | var entityPersister = (AbstractEntityPersister)metadata; 106 | var totalColumns = entityPersister.IdentifierColumnNames.Length; 107 | for (int i = 0, length = metadata.PropertyNames.Length; i < length; i++) 108 | { 109 | totalColumns += entityPersister.GetPropertyColumnNames(i).Length; 110 | } 111 | 112 | return totalColumns; 113 | } 114 | 115 | private static int GetTotalColumns(IQueryableCollection collection) 116 | { 117 | return collection.ElementColumnNames.Length + 118 | collection.KeyColumnNames.Length + 119 | collection.IndexColumnNames.Length; 120 | } 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/IIncludeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using NHibernate.Type; 7 | 8 | namespace NHibernate.Extensions.Linq 9 | { 10 | public interface IIncludeOptions 11 | { 12 | /// 13 | /// Set a function that ignores a relation from being fetched when true is returned. When a relation is ignored 14 | /// within a path, all relations that are after the ignored one, will also be ignored. 15 | /// 16 | IIncludeOptions SetIgnoreIncludedRelationFunction(Func func); 17 | 18 | /// 19 | /// Set the maximum columns that are allowed in a single sql query when combining multiple included paths into a single query. 20 | /// 21 | IIncludeOptions SetMaximumColumnsPerQuery(int value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/IIncludeQueryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace NHibernate.Extensions.Linq 7 | { 8 | public interface IIncludeQueryable : IQueryable 9 | { 10 | IIncludeQueryable WithIncludeOptions(Action action); 11 | } 12 | 13 | public interface IIncludeQueryable : IQueryable 14 | { 15 | IIncludeQueryable WithIncludeOptions(Action action); 16 | } 17 | 18 | public interface IIncludeQueryable : IIncludeQueryable 19 | { 20 | IQueryable ThenInclude(Expression> include); 21 | 22 | IIncludeQueryable ThenInclude(Expression>> include); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/IncludeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using NHibernate.Type; 7 | 8 | namespace NHibernate.Extensions.Linq 9 | { 10 | public class IncludeOptions : IIncludeOptions 11 | { 12 | public static IncludeOptions Default = new IncludeOptions(); 13 | 14 | internal IncludeOptions Clone() 15 | { 16 | return new IncludeOptions 17 | { 18 | IgnoreIncludedRelationFunction = IgnoreIncludedRelationFunction, 19 | MaximumColumnsPerQuery = MaximumColumnsPerQuery 20 | }; 21 | } 22 | 23 | public int? MaximumColumnsPerQuery { get; set; } 24 | 25 | public Func IgnoreIncludedRelationFunction { get; set; } 26 | 27 | /// 28 | IIncludeOptions IIncludeOptions.SetIgnoreIncludedRelationFunction(Func func) 29 | { 30 | IgnoreIncludedRelationFunction = func; 31 | return this; 32 | } 33 | 34 | /// 35 | IIncludeOptions IIncludeOptions.SetMaximumColumnsPerQuery(int value) 36 | { 37 | MaximumColumnsPerQuery = value; 38 | return this; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/IncludeQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.ExceptionServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using NHibernate.Engine; 10 | using NHibernate.Extensions.Internal; 11 | using NHibernate.Linq; 12 | using NHibernate.Metadata; 13 | using NHibernate.Persister.Collection; 14 | using NHibernate.Type; 15 | using NHibernate.Util; 16 | 17 | namespace NHibernate.Extensions.Linq 18 | { 19 | public class IncludeQueryProvider : INhQueryProvider, IQueryProviderWithOptions 20 | { 21 | protected static readonly MethodInfo CreateQueryMethod; 22 | protected static readonly MethodInfo ExecuteInternalMethod; 23 | protected static readonly MethodInfo ExecuteInternalAsyncMethod; 24 | 25 | protected static readonly MethodInfo WhereMethod; 26 | protected static readonly MethodInfo ContainsMethod; 27 | 28 | protected static readonly MethodInfo FetchMethod; 29 | protected static readonly MethodInfo FetchManyMethod; 30 | protected static readonly MethodInfo ThenFetchMethod; 31 | protected static readonly MethodInfo ThenFetchManyMethod; 32 | 33 | private static readonly Func SessionProvider; 34 | 35 | static IncludeQueryProvider() 36 | { 37 | CreateQueryMethod = 38 | ReflectHelper.GetMethodDefinition((INhQueryProvider p) => p.CreateQuery(null)); 39 | ExecuteInternalMethod = 40 | ReflectHelper.GetMethodDefinition((IncludeQueryProvider p) => p.ExecuteInternal(null, null)); 41 | ExecuteInternalAsyncMethod = 42 | #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed 43 | ReflectHelper.GetMethodDefinition((IncludeQueryProvider p) => p.ExecuteInternalAsync(null, null, default)); 44 | #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed 45 | 46 | FetchMethod = 47 | ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.Fetch(null, null)); 48 | FetchManyMethod = 49 | ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.FetchMany(null, null)); 50 | ThenFetchMethod = 51 | ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.ThenFetch(null, null)); 52 | ThenFetchManyMethod = 53 | ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.ThenFetchMany(null, null)); 54 | 55 | WhereMethod = ReflectHelper.GetMethodDefinition(() => Queryable.Where(null, o => true)); 56 | ContainsMethod = ReflectHelper.GetMethodDefinition(() => Queryable.Contains(null, null)); 57 | 58 | var param = Expression.Parameter(typeof(DefaultQueryProvider)); 59 | var sessionProp = typeof(DefaultQueryProvider).GetProperty("Session", 60 | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 61 | if (sessionProp == null) 62 | { 63 | throw new InvalidOperationException($"{typeof(DefaultQueryProvider)} does not have a Session property."); 64 | } 65 | SessionProvider = Expression.Lambda>( 66 | Expression.Property(param, sessionProp), param).Compile(); 67 | } 68 | 69 | private static T GetValue(IEnumerable items, string methodName) 70 | { 71 | switch (methodName) 72 | { 73 | case "First": 74 | return items.First(); 75 | case "FirstOrDefault": 76 | return items.FirstOrDefault(); 77 | case "Single": 78 | return items.Single(); 79 | case "SingleOrDefault": 80 | return items.SingleOrDefault(); 81 | case "Last": 82 | return items.Last(); 83 | case "LastOrDefault": 84 | return items.LastOrDefault(); 85 | default: 86 | throw new InvalidOperationException($"Unknown method: {methodName}"); 87 | } 88 | } 89 | 90 | protected static LambdaExpression CreatePropertyExpression(System.Type type, string propName, 91 | System.Type convertToType = null) 92 | { 93 | var p = Expression.Parameter(type); 94 | var body = Expression.Property(p, propName); 95 | if (convertToType == null) return Expression.Lambda(body, p); 96 | var converted = Expression.Convert(body, convertToType); 97 | return Expression.Lambda(converted, p); 98 | } 99 | 100 | protected System.Type Type; 101 | private readonly DefaultQueryProvider _queryProvider; 102 | private readonly IncludeOptions _includeOptions = IncludeOptions.Default; 103 | public readonly List IncludePaths = new List(); 104 | 105 | public IncludeQueryProvider(System.Type type, DefaultQueryProvider queryProvider) 106 | { 107 | Type = type; 108 | _queryProvider = queryProvider; 109 | } 110 | 111 | public IncludeQueryProvider(System.Type type, IncludeQueryProvider includeQueryProvider) 112 | : this( 113 | type, 114 | includeQueryProvider._queryProvider, 115 | includeQueryProvider.IncludePaths, 116 | includeQueryProvider._includeOptions.Clone()) 117 | { 118 | } 119 | 120 | private IncludeQueryProvider(System.Type type, DefaultQueryProvider queryProvider, IEnumerable includePaths, IncludeOptions includeOptions) 121 | : this(type, queryProvider) 122 | { 123 | IncludePaths.AddRange(includePaths); 124 | _includeOptions = includeOptions; 125 | } 126 | 127 | public IncludeQueryProvider Include(string path) 128 | { 129 | IncludePaths.Add(path); 130 | return this; 131 | } 132 | 133 | public ISessionImplementor Session => SessionProvider(_queryProvider); 134 | 135 | public IQueryable CreateQuery(Expression expression) 136 | { 137 | var newQuery = new NhQueryable(this, expression); 138 | if (!typeof (T).IsAssignableFrom(Type)) //Select and other methods that returns other types 139 | throw new NotSupportedException("IncludeQueryProvider does not support mixing types"); 140 | Type = typeof (T); //Possbile typecast to a base type 141 | return newQuery; 142 | } 143 | 144 | public IQueryable CreateQuery(Expression expression) 145 | { 146 | var m = CreateQueryMethod.MakeGenericMethod(expression.Type.GetGenericArguments()[0]); 147 | return (IQueryable)m.Invoke(this, new object[] { expression }); 148 | } 149 | 150 | public async Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 151 | { 152 | cancellationToken.ThrowIfCancellationRequested(); 153 | 154 | var resultVisitor = new IncludeRewriterVisitor(); 155 | expression = resultVisitor.Modify(expression); 156 | 157 | if (resultVisitor.Count) 158 | { 159 | return await _queryProvider.ExecuteAsync(expression, cancellationToken); 160 | } 161 | 162 | if (resultVisitor.SkipTake) 163 | { 164 | expression = RemoveSkipAndTake(expression); 165 | } 166 | 167 | try 168 | { 169 | dynamic task = ExecuteInternalAsyncMethod.MakeGenericMethod(Type) 170 | .Invoke(this, new object[] {expression, resultVisitor, cancellationToken}); 171 | return await task.ConfigureAwait(false); 172 | } 173 | catch (TargetInvocationException e) 174 | { 175 | ExceptionDispatchInfo.Capture(e.InnerException).Throw(); 176 | throw; 177 | } 178 | } 179 | 180 | public object Execute(Expression expression) 181 | { 182 | var resultVisitor = new IncludeRewriterVisitor(); 183 | expression = resultVisitor.Modify(expression); 184 | if (resultVisitor.Count) 185 | { 186 | return _queryProvider.Execute(expression); 187 | } 188 | 189 | if (resultVisitor.SkipTake) 190 | { 191 | expression = RemoveSkipAndTake(expression); 192 | } 193 | 194 | try 195 | { 196 | return ExecuteInternalMethod.MakeGenericMethod(Type).Invoke(this, new object[] {expression, resultVisitor}); 197 | } 198 | catch (TargetInvocationException e) 199 | { 200 | ExceptionDispatchInfo.Capture(e.InnerException).Throw(); 201 | throw; 202 | } 203 | } 204 | 205 | public TResult Execute(Expression expression) 206 | { 207 | return (TResult)Execute(expression); 208 | } 209 | 210 | [Obsolete("Replaced by ISupportFutureBatchNhQueryProvider interface")] 211 | public IFutureEnumerable ExecuteFuture(Expression expression) 212 | { 213 | var resultVisitor = new IncludeRewriterVisitor(); 214 | expression = resultVisitor.Modify(expression); 215 | if (resultVisitor.SkipTake) 216 | { 217 | expression = RemoveSkipAndTake(expression); 218 | } 219 | 220 | return ExecuteQueryTreeFuture(expression); 221 | } 222 | 223 | [Obsolete("Replaced by ISupportFutureBatchNhQueryProvider interface")] 224 | public IFutureValue ExecuteFutureValue(Expression expression) 225 | { 226 | var resultVisitor = new IncludeRewriterVisitor(); 227 | expression = resultVisitor.Modify(expression); 228 | 229 | if (resultVisitor.Count) 230 | { 231 | return _queryProvider.ExecuteFutureValue(expression); 232 | } 233 | 234 | if (resultVisitor.SkipTake) 235 | { 236 | expression = RemoveSkipAndTake(expression); 237 | } 238 | 239 | return ExecuteQueryTreeFutureValue(expression); 240 | } 241 | 242 | public IQueryProvider WithOptions(Action setOptions) 243 | { 244 | return new IncludeQueryProvider(Type, (DefaultQueryProvider) _queryProvider.WithOptions(setOptions), 245 | IncludePaths, _includeOptions.Clone()); 246 | } 247 | 248 | public IncludeQueryProvider WithIncludeOptions(Action setOptions) 249 | { 250 | var newOptions = _includeOptions.Clone(); 251 | setOptions(newOptions); 252 | return new IncludeQueryProvider(Type, _queryProvider, IncludePaths, newOptions); 253 | } 254 | 255 | public Task ExecuteDmlAsync(QueryMode queryMode, Expression expression, CancellationToken cancellationToken) 256 | { 257 | throw new NotSupportedException("Dml is not supported when using Include."); 258 | } 259 | 260 | public int ExecuteDml(QueryMode queryMode, Expression expression) 261 | { 262 | throw new NotSupportedException("Dml is not supported when using Include."); 263 | } 264 | 265 | public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary> parameters) 266 | { 267 | _queryProvider.SetResultTransformerAndAdditionalCriteria(query, nhExpression, parameters); 268 | } 269 | 270 | internal object ExecuteInternal(Expression expression, IncludeRewriterVisitor visitor) 271 | { 272 | var future = ExecuteQueryTreeFuture(expression); 273 | var items = future.GetEnumerable(); 274 | if (visitor.SingleResult) 275 | { 276 | return GetValue(items, visitor.SingleResultMethodName); 277 | } 278 | return items; 279 | } 280 | 281 | internal async Task ExecuteInternalAsync(Expression expression, IncludeRewriterVisitor visitor, CancellationToken cancellationToken) 282 | { 283 | cancellationToken.ThrowIfCancellationRequested(); 284 | 285 | var future = ExecuteQueryTreeFuture(expression); 286 | var items = await future.GetEnumerableAsync(cancellationToken).ConfigureAwait(false); 287 | if (visitor.SingleResult) 288 | { 289 | return GetValue(items, visitor.SingleResultMethodName); 290 | } 291 | return items; 292 | } 293 | 294 | private IFutureEnumerable ExecuteQueryTreeFuture(Expression queryExpression) 295 | { 296 | IFutureEnumerable result = null; 297 | foreach (var expression in GetExpressions(queryExpression)) 298 | { 299 | if (result == null) 300 | { 301 | result = new NhQueryable(_queryProvider, expression).ToFuture(); 302 | } 303 | else 304 | { 305 | new NhQueryable(_queryProvider, expression).ToFuture(); 306 | } 307 | } 308 | return result; 309 | } 310 | 311 | [Obsolete("Replaced by ISupportFutureBatchNhQueryProvider interface")] 312 | private IFutureValue ExecuteQueryTreeFutureValue(Expression queryExpression) 313 | { 314 | IFutureValue result = null; 315 | var i = 0; 316 | foreach (var expression in GetExpressions(queryExpression)) 317 | { 318 | if (i == 0) 319 | result = _queryProvider.ExecuteFutureValue(expression); 320 | else 321 | _queryProvider.ExecuteFuture(expression); 322 | i++; 323 | } 324 | return result; 325 | } 326 | 327 | private List GetExpressions(Expression queryExpression) 328 | { 329 | IClassMetadata meta; 330 | var metas = Session.Factory.GetAllClassMetadata() 331 | .Select(o => o.Value) 332 | .Where(o => Type.IsAssignableFrom(o.MappedClass)) 333 | .ToList(); 334 | if (!metas.Any()) 335 | { 336 | throw new HibernateException($"Metadata for type '{Type}' was not found"); 337 | } 338 | 339 | if (metas.Count > 1) 340 | { 341 | meta = metas.FirstOrDefault(o => o.MappedClass == Type); 342 | if (meta == null) 343 | { 344 | throw new HibernateException( 345 | $"Unable to find the the correct candidate for type '{Type}'. Candidates: {string.Join(", ", metas.Select(o => o.MappedClass))}"); 346 | } 347 | } 348 | else 349 | { 350 | meta = metas.First(); 351 | } 352 | 353 | var tree = new QueryRelationTree(); 354 | foreach (var path in IncludePaths) 355 | { 356 | tree.AddNode(path); 357 | } 358 | 359 | var leafs = tree.GetLeafs(); 360 | leafs.Sort(); 361 | return leafs.Aggregate(new ExpressionInfo(queryExpression, meta), FetchFromPath).GetExpressions(); 362 | } 363 | 364 | private Expression RemoveSkipAndTake(Expression queryExpression) 365 | { 366 | var query = _queryProvider.CreateQuery(queryExpression); 367 | var pe = Expression.Parameter(Type); 368 | var contains = Expression.Call(null, 369 | ContainsMethod.MakeGenericMethod(Type), 370 | new Expression[] 371 | { 372 | Expression.Constant(query), 373 | pe 374 | }); 375 | return Expression.Call(null, 376 | WhereMethod.MakeGenericMethod(Type), 377 | new[] 378 | { 379 | new SkipTakeVisitor().RemoveSkipAndTake(queryExpression), 380 | Expression.Lambda(contains, pe) 381 | }); 382 | } 383 | 384 | private ExpressionInfo FetchFromPath(ExpressionInfo expressionInfo, string path) 385 | { 386 | var meta = expressionInfo.Metadata; 387 | var root = true; 388 | var expression = expressionInfo.Expression; 389 | var currentType = Type; 390 | string collectionPath = null; 391 | var includedPaths = new List(); 392 | 393 | var paths = path.Split('.'); 394 | var index = 0; 395 | foreach (var propName in paths) 396 | { 397 | if (meta == null) 398 | { 399 | throw new InvalidOperationException($"Unable to fetch property {propName} from path '{path}', no class metadata found"); 400 | } 401 | 402 | var propType = meta.GetPropertyType(propName); 403 | if (propType == null) 404 | { 405 | throw new Exception($"Property '{propName}' does not exist in the type '{meta.MappedClass.FullName}'"); 406 | } 407 | 408 | if (!(propType is IAssociationType assocType)) 409 | { 410 | throw new Exception($"Property '{propName}' does not implement IAssociationType interface"); 411 | } 412 | 413 | if (_includeOptions.IgnoreIncludedRelationFunction?.Invoke(Session.Factory, assocType) == true) 414 | { 415 | break; 416 | } 417 | 418 | IQueryableCollection collectionPersister = null; 419 | if (assocType.IsCollectionType) 420 | { 421 | var collectionType = (CollectionType) assocType; 422 | collectionPersister = (IQueryableCollection) Session.Factory.GetCollectionPersister(collectionType.Role); 423 | meta = collectionPersister.ElementType.IsEntityType 424 | ? Session.Factory.GetClassMetadata(collectionPersister.ElementPersister.EntityName) 425 | : null; // Will happen for dictionaries 426 | var collPath = string.Join(".", paths.Take(index + 1)); 427 | //Check if we can fetch the collection without create a cartesian product 428 | //Fetch can occur only for nested collection 429 | if (!string.IsNullOrEmpty(expressionInfo.CollectionPath) && 430 | !collPath.StartsWith(expressionInfo.CollectionPath + ".")) 431 | { 432 | //We have to continue fetching within a new base query 433 | return FetchFromPath(expressionInfo.GetOrCreateNext(), path); 434 | } 435 | 436 | collectionPath = collPath; 437 | } 438 | else 439 | { 440 | meta = Session.Factory.GetClassMetadata(assocType.GetAssociatedEntityName(Session.Factory)); 441 | } 442 | 443 | var includedPath = expressionInfo.AddIncludedProperty(propName, meta, collectionPersister, root); 444 | if (includedPath != null) 445 | { 446 | includedPaths.Add(includedPath); 447 | } 448 | 449 | MethodInfo fetchMethod; 450 | 451 | //Try to get the actual property type (so we can skip casting as relinq will throw an exception) 452 | var relatedProp = currentType.GetProperty(propName); 453 | 454 | var relatedType = relatedProp != null ? relatedProp.PropertyType : meta?.MappedClass; 455 | if (propType.IsCollectionType && relatedProp != null && relatedType.IsGenericType) 456 | { 457 | relatedType = propType.GetType().IsAssignableToGenericType(typeof(GenericMapType<,>)) 458 | ? typeof(KeyValuePair<,>).MakeGenericType(relatedType.GetGenericArguments()) 459 | : relatedType.GetGenericArguments()[0]; 460 | } 461 | 462 | var convertToType = propType.IsCollectionType 463 | ? typeof (IEnumerable<>).MakeGenericType(relatedType) 464 | : null; 465 | 466 | var propertyExpression = CreatePropertyExpression(currentType, propName, convertToType); 467 | //No fetch before 468 | if (root) 469 | { 470 | fetchMethod = propType.IsCollectionType 471 | ? FetchManyMethod.MakeGenericMethod(Type, relatedType) 472 | : FetchMethod.MakeGenericMethod(Type, relatedType); 473 | } 474 | else 475 | { 476 | fetchMethod = propType.IsCollectionType 477 | ? ThenFetchManyMethod.MakeGenericMethod(Type, currentType, relatedType) 478 | : ThenFetchMethod.MakeGenericMethod(Type, currentType, relatedType); 479 | } 480 | 481 | expression = Expression.Call(fetchMethod, expression, propertyExpression); 482 | currentType = relatedType; 483 | index++; 484 | root = false; 485 | } 486 | 487 | if (_includeOptions.MaximumColumnsPerQuery.HasValue && 488 | expressionInfo.TotalColumns > _includeOptions.MaximumColumnsPerQuery.Value && 489 | expressionInfo.IsExpressionModified) 490 | { 491 | // Remove the included paths as we have to rebuild the expression from start 492 | foreach (var includedPath in includedPaths) 493 | { 494 | expressionInfo.RemoveIncludedProperty(includedPath); 495 | } 496 | 497 | return FetchFromPath(expressionInfo.GetOrCreateNext(), path); 498 | } 499 | 500 | if (!string.IsNullOrEmpty(collectionPath)) 501 | { 502 | expressionInfo.CollectionPath = collectionPath; 503 | } 504 | 505 | expressionInfo.Expression = expression; 506 | 507 | return expressionInfo; 508 | } 509 | } 510 | 511 | } 512 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/IncludeQueryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using NHibernate.Extensions.Internal; 6 | using NHibernate.Linq; 7 | 8 | namespace NHibernate.Extensions.Linq 9 | { 10 | public class IncludeQueryable : NhQueryable, IIncludeQueryable, IIncludeQueryable 11 | { 12 | public IncludeQueryable(IQueryProvider provider, Expression expression) 13 | : base(provider, expression) 14 | { 15 | } 16 | 17 | IIncludeQueryable IIncludeQueryable.WithIncludeOptions(Action action) 18 | { 19 | if (Provider is IncludeQueryProvider includeQueryProvider) 20 | { 21 | return new IncludeQueryable(includeQueryProvider.WithIncludeOptions(action), Expression); 22 | } 23 | 24 | return new IncludeQueryable(Provider, Expression); 25 | } 26 | 27 | IIncludeQueryable IIncludeQueryable.WithIncludeOptions(Action action) 28 | { 29 | if (Provider is IncludeQueryProvider includeQueryProvider) 30 | { 31 | return new IncludeQueryable(includeQueryProvider.WithIncludeOptions(action), Expression); 32 | } 33 | 34 | return new IncludeQueryable(Provider, Expression); 35 | } 36 | } 37 | 38 | public class IncludeQueryable : NhQueryable, IIncludeQueryable 39 | { 40 | public IncludeQueryable(string basePath, IQueryProvider provider, Expression expression) 41 | : base(provider, expression) 42 | { 43 | BasePath = basePath; 44 | } 45 | 46 | public string BasePath { get; } 47 | 48 | public IQueryable ThenInclude(Expression> include) 49 | { 50 | var path = ExpressionHelper.GetFullPath(include.Body); 51 | return this.Include($"{BasePath}.{path}"); 52 | } 53 | 54 | public IIncludeQueryable ThenInclude(Expression>> include) 55 | { 56 | var path = ExpressionHelper.GetFullPath(include.Body); 57 | var newPath = $"{BasePath}.{path}".Trim('.'); 58 | return new IncludeQueryable(newPath, this.IncludeInternal(newPath), Expression); 59 | } 60 | 61 | IIncludeQueryable IIncludeQueryable.WithIncludeOptions(Action action) 62 | { 63 | return new IncludeQueryable(((IncludeQueryProvider)Provider).WithIncludeOptions(action), Expression); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/IncludeRewriterVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace NHibernate.Extensions.Linq 7 | { 8 | public class IncludeRewriterVisitor : ExpressionVisitor 9 | { 10 | public static readonly HashSet SingleResultMethods = new HashSet 11 | { 12 | "FirstOrDefault", 13 | "First", 14 | "SingleOrDefault", 15 | "Single", 16 | "Last", 17 | "LastOrDefault" 18 | }; 19 | 20 | public static readonly HashSet SkipTakeMethods = new HashSet 21 | { 22 | "Skip", 23 | "Take" 24 | }; 25 | 26 | public static readonly HashSet CountMethods = new HashSet 27 | { 28 | "Count", 29 | "LongCount" 30 | }; 31 | 32 | private static readonly MethodInfo WhereMethod; 33 | 34 | static IncludeRewriterVisitor() 35 | { 36 | WhereMethod = typeof(Queryable).GetMethods().First(o => o.Name == "Where"); 37 | } 38 | 39 | public bool SingleResult { get; set; } 40 | 41 | public string SingleResultMethodName { get; set; } 42 | 43 | public bool SkipTake { get; set; } 44 | 45 | public bool Count { get; set; } 46 | 47 | public Expression Modify(Expression expression) 48 | { 49 | //Check if is the last called method is a Count method 50 | if (expression is MethodCallExpression methodCallExpr && CountMethods.Contains(methodCallExpr.Method.Name)) 51 | { 52 | Count = true; 53 | } 54 | 55 | return Visit(expression); 56 | } 57 | 58 | protected override Expression VisitMethodCall(MethodCallExpression node) 59 | { 60 | if (SkipTakeMethods.Contains(node.Method.Name)) 61 | SkipTake = true; 62 | 63 | if (SingleResultMethods.Contains(node.Method.Name)) 64 | { 65 | SingleResult = true; 66 | SingleResultMethodName = node.Method.Name; 67 | if (node.Arguments.Count == 2) 68 | { 69 | return Visit(Expression.Call(null, 70 | WhereMethod.MakeGenericMethod(node.Method.GetGenericArguments()[0]), 71 | new[] 72 | { 73 | node.Arguments[0], 74 | node.Arguments[1] 75 | })); 76 | } 77 | return Visit(node.Arguments[0]); 78 | } 79 | return base.VisitMethodCall(node); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/LinqExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using NHibernate.Engine; 8 | using NHibernate.Extensions.Internal; 9 | using NHibernate.Extensions.Linq; 10 | using NHibernate.Util; 11 | using Remotion.Linq; 12 | 13 | namespace NHibernate.Linq 14 | { 15 | public static class LinqExtensions 16 | { 17 | private static readonly MethodInfo EnumerableToListMethod; 18 | private static readonly MethodInfo IncludeMethod; 19 | 20 | static LinqExtensions() 21 | { 22 | EnumerableToListMethod = ReflectHelper.GetMethodDefinition(() => Enumerable.ToList(new object[0])) 23 | .GetGenericMethodDefinition(); 24 | IncludeMethod = ReflectHelper.GetMethodDefinition(() => Include(default(IQueryable), "")) 25 | .GetGenericMethodDefinition(); 26 | } 27 | 28 | public static IIncludeQueryable Include(this IQueryable query, Expression> include) 29 | { 30 | var path = ExpressionHelper.GetFullPath(include.Body); 31 | return Include(query, path); 32 | } 33 | 34 | public static IIncludeQueryable Include(this IQueryable query, Expression>> include) 35 | { 36 | var path = ExpressionHelper.GetFullPath(include.Body); 37 | return new IncludeQueryable(path, IncludeInternal(query, path), query.Expression); 38 | } 39 | 40 | public static IIncludeQueryable Include(this IQueryable query, string path) 41 | { 42 | return new IncludeQueryable(IncludeInternal(query, path), query.Expression); 43 | } 44 | 45 | public static IIncludeQueryable Include(this IQueryable query, string path) 46 | { 47 | var queryType = query.GetType(); 48 | queryType = queryType.GetGenericType(typeof(IQueryable<>)); 49 | if (queryType == null) 50 | { 51 | throw new InvalidOperationException($"Query of type {query.GetType()} cannot be converted to {typeof(IQueryable<>)}."); 52 | } 53 | 54 | return (IIncludeQueryable) IncludeMethod.MakeGenericMethod(queryType.GetGenericArguments()[0]) 55 | .Invoke(null, new object[] {query, path}); 56 | } 57 | 58 | internal static IQueryProvider IncludeInternal(this IQueryable query, string path) 59 | { 60 | if (query.Provider is IncludeQueryProvider includeQueryProvider) 61 | { 62 | includeQueryProvider = new IncludeQueryProvider(typeof(T), includeQueryProvider); 63 | } 64 | else if (query.Provider is DefaultQueryProvider defaultQueryProvider) 65 | { 66 | includeQueryProvider = new IncludeQueryProvider(typeof(T), defaultQueryProvider); 67 | } 68 | else 69 | { 70 | return query.Provider; 71 | } 72 | 73 | includeQueryProvider.Include(path); 74 | return includeQueryProvider; 75 | } 76 | 77 | private static IEnumerable ToList(this IQueryable query) 78 | { 79 | var type = query.GetType().GetGenericArguments()[0]; 80 | 81 | var methodInfo = EnumerableToListMethod.MakeGenericMethod(type); 82 | return (IEnumerable)methodInfo.Invoke(null, new object[] {query}); 83 | } 84 | 85 | public static List ToList(this IQueryable query) 86 | { 87 | return ToList(query).Cast().ToList(); 88 | } 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Linq/SkipTakeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | 4 | namespace NHibernate.Extensions.Linq 5 | { 6 | internal class SkipTakeVisitor : ExpressionVisitor 7 | { 8 | public static readonly HashSet SkipTakeMethods = new HashSet 9 | { 10 | "Skip", 11 | "Take" 12 | }; 13 | 14 | public Expression RemoveSkipAndTake(Expression expression) 15 | { 16 | return Visit(expression); 17 | } 18 | 19 | protected override Expression VisitMethodCall(MethodCallExpression node) 20 | { 21 | if (SkipTakeMethods.Contains(node.Method.Name)) 22 | return base.Visit(node.Arguments[0]); 23 | 24 | return base.VisitMethodCall(node); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /NHibernate.Extensions/NHibernate.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461 5 | 7.2 6 | 3.0.0.0 7 | 3.0.0.0 8 | maca88 9 | 10 | Copyright © 2018 11 | NHibernate.Extensions 12 | false 13 | MIT 14 | https://github.com/maca88/NHibernate.Extensions 15 | https://github.com/maca88/NHibernate.Extensions 16 | Various additions for NHibernate like the Include method from EntityFramework 17 | NHibernate Include 18 | true 19 | 20 | 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /NHibernate.Extensions/QueryRelationNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace NHibernate.Extensions 6 | { 7 | public class QueryRelationNode 8 | { 9 | public QueryRelationNode() 10 | { 11 | Children = new Dictionary(); 12 | } 13 | 14 | public string FullPath 15 | { 16 | get { return GetFullPath(this); } 17 | } 18 | 19 | public string GetFullPath(QueryRelationNode node) 20 | { 21 | if (node.Parent == null) return ""; 22 | var fPath = GetFullPath(node.Parent); 23 | return string.IsNullOrEmpty(fPath) ? node.Path : String.Format("{0}.{1}", fPath, node.Path); 24 | } 25 | 26 | public string Path { get; private set; } 27 | 28 | public bool IsLeaf { get { return Children.Count == 0; } } 29 | 30 | public QueryRelationNode Parent { get; set; } 31 | 32 | public Dictionary Children { get; private set; } 33 | 34 | public void Add(string fullPath) 35 | { 36 | var paths = fullPath.Split('.'); 37 | var path = paths.First(); 38 | var key = Children.Keys.FirstOrDefault(o => o == path); 39 | if (key == null) 40 | { 41 | Children.Add(path, new QueryRelationNode { Path = path, Parent= this }); 42 | } 43 | if (paths.Length > 1) 44 | Children[path].Add(string.Join(".", paths.Skip(1))); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /NHibernate.Extensions/QueryRelationTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using NHibernate.Extensions.Internal; 6 | 7 | namespace NHibernate.Extensions 8 | { 9 | public class QueryRelationTree 10 | { 11 | public QueryRelationTree() 12 | { 13 | Node = new QueryRelationNode(); 14 | } 15 | 16 | private QueryRelationNode Node { get; set; } 17 | 18 | public void AddNode(Expression> expression) 19 | { 20 | var fullPath = ExpressionHelper.GetFullPath(expression.Body); 21 | Node.Add(fullPath); 22 | } 23 | 24 | public void AddNode(string path) 25 | { 26 | Node.Add(path); 27 | } 28 | 29 | public Dictionary> DeepFirstSearch() 30 | { 31 | var result = new Dictionary>(); 32 | var idx = 0; 33 | DeepFirstSearchRecursive(Node.Children, result, ref idx); 34 | return result; 35 | } 36 | 37 | public List GetLeafs() 38 | { 39 | return DeepFirstSearch().Select(pair => pair.Value.Last()).ToList(); 40 | } 41 | 42 | private static void DeepFirstSearchRecursive(Dictionary children, Dictionary> result, ref int idx) 43 | { 44 | foreach (var child in children) 45 | { 46 | if (!result.ContainsKey(idx)) 47 | result.Add(idx, new List()); 48 | var node = child.Value; 49 | if (!result[idx].Any()) 50 | { 51 | result[idx].Add(node.FullPath); 52 | } 53 | else 54 | { 55 | var lst = result[idx].Last() + "."; 56 | if (node.FullPath.StartsWith(lst)) 57 | { 58 | result[idx].Add(node.FullPath); 59 | } 60 | else 61 | { 62 | idx++; 63 | result.Add(idx, new List()); 64 | var relPaths = node.FullPath.Split('.'); 65 | for (var i = 1; i <= relPaths.Length; i++) 66 | { 67 | result[idx].Add(string.Join(".", relPaths.Take(i))); 68 | } 69 | } 70 | 71 | } 72 | DeepFirstSearchRecursive(node.Children, result, ref idx); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Subscriptions/ISessionSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NHibernate.Extensions 8 | { 9 | public interface ISessionSubscription 10 | { 11 | ITransactionSubscription Transaction { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Subscriptions/ITransactionSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace NHibernate.Extensions 5 | { 6 | public interface ITransactionSubscription 7 | { 8 | ITransactionSubscription AfterCommit(Action action); 9 | 10 | ITransactionSubscription AfterCommit(Func action); 11 | 12 | ITransactionSubscription AfterCommit(Action action); 13 | 14 | ITransactionSubscription AfterCommit(Func action); 15 | 16 | ITransactionSubscription BeforeCommit(Action action); 17 | 18 | ITransactionSubscription BeforeCommit(Func action); 19 | 20 | ITransactionSubscription BeforeCommit(System.Action action); 21 | 22 | ITransactionSubscription BeforeCommit(Func action); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Subscriptions/SessionSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NHibernate.Extensions 8 | { 9 | internal class SessionSubscription : ISessionSubscription 10 | { 11 | public SessionSubscription() 12 | { 13 | Transaction = new TransactionSubscription(); 14 | } 15 | 16 | public TransactionSubscription Transaction { get; } 17 | 18 | ITransactionSubscription ISessionSubscription.Transaction => Transaction; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Subscriptions/SessionSubscriptionExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NHibernate.Extensions 4 | { 5 | public static class SessionSubscriptionExtension 6 | { 7 | /// 8 | /// Fluently subscribe to various NHiberante session events 9 | /// 10 | /// NHibernate session 11 | /// Action for configure the subscriptions 12 | public static void Subscribe(this ISession session, Action configure) 13 | { 14 | var config = new SessionSubscription(); 15 | configure(config); 16 | 17 | var transaction = session.GetCurrentTransaction(); 18 | if (transaction == null && config.Transaction.IsSet) 19 | { 20 | throw new InvalidOperationException("The session has not an active transaction to subscribe the before/after commit actions."); 21 | } 22 | 23 | transaction.RegisterSynchronization(new TransactionListener(session, config.Transaction)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Subscriptions/TransactionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NHibernate.Transaction; 5 | 6 | namespace NHibernate.Extensions 7 | { 8 | internal class TransactionListener : ITransactionCompletionSynchronization 9 | { 10 | private readonly ISession _session; 11 | private readonly TransactionSubscription _subscription; 12 | 13 | public TransactionListener(ISession session, TransactionSubscription subscription) 14 | { 15 | _session = session; 16 | _subscription = subscription; 17 | } 18 | 19 | public void ExecuteBeforeTransactionCompletion() 20 | { 21 | if (_subscription.BeforeCommitAsyncAction != null) 22 | { 23 | throw new NotSupportedException("An async before commit action cannot be executed when using ISession.Commit."); 24 | } 25 | 26 | _subscription.BeforeCommitAction?.Invoke(_session); 27 | } 28 | 29 | public async Task ExecuteBeforeTransactionCompletionAsync(CancellationToken cancellationToken) 30 | { 31 | var action = _subscription.BeforeCommitAsyncAction; 32 | if (action != null) 33 | { 34 | await action(_session).ConfigureAwait(false); 35 | } 36 | else 37 | { 38 | _subscription.BeforeCommitAction?.Invoke(_session); 39 | } 40 | } 41 | 42 | public void ExecuteAfterTransactionCompletion(bool success) 43 | { 44 | if (_subscription.AfterCommitAsyncAction != null) 45 | { 46 | throw new NotSupportedException("An async after commit action cannot be executed when using ISession.Commit."); 47 | } 48 | 49 | _subscription.AfterCommitAction?.Invoke(_session, success); 50 | } 51 | 52 | public async Task ExecuteAfterTransactionCompletionAsync(bool success, CancellationToken cancellationToken) 53 | { 54 | var action = _subscription.AfterCommitAsyncAction; 55 | if (action != null) 56 | { 57 | await action(_session, success).ConfigureAwait(false); 58 | } 59 | else 60 | { 61 | _subscription.AfterCommitAction?.Invoke(_session, success); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /NHibernate.Extensions/Subscriptions/TransactionSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace NHibernate.Extensions 5 | { 6 | internal class TransactionSubscription : ITransactionSubscription 7 | { 8 | public Action BeforeCommitAction { get; private set; } 9 | 10 | public Func BeforeCommitAsyncAction { get; private set; } 11 | 12 | public Action AfterCommitAction { get; private set; } 13 | 14 | public Func AfterCommitAsyncAction { get; private set; } 15 | 16 | public ITransactionSubscription AfterCommit(Action action) 17 | { 18 | AfterCommitAction = action; 19 | return this; 20 | } 21 | 22 | public ITransactionSubscription AfterCommit(Func action) 23 | { 24 | AfterCommitAsyncAction = action; 25 | return this; 26 | } 27 | 28 | public ITransactionSubscription AfterCommit(Action action) 29 | { 30 | AfterCommitAction = (session, success) => action(success); 31 | return this; 32 | } 33 | 34 | public ITransactionSubscription AfterCommit(Func action) 35 | { 36 | AfterCommitAsyncAction = (session, success) => action(success); 37 | return this; 38 | } 39 | 40 | public ITransactionSubscription BeforeCommit(Action action) 41 | { 42 | BeforeCommitAction = action; 43 | return this; 44 | } 45 | 46 | public ITransactionSubscription BeforeCommit(Func action) 47 | { 48 | BeforeCommitAsyncAction = action; 49 | return this; 50 | } 51 | 52 | public ITransactionSubscription BeforeCommit(System.Action action) 53 | { 54 | BeforeCommitAction = session => action(); 55 | return this; 56 | } 57 | 58 | public ITransactionSubscription BeforeCommit(Func action) 59 | { 60 | BeforeCommitAsyncAction = session => action(); 61 | return this; 62 | } 63 | 64 | public bool IsSet => BeforeCommitAction != null || BeforeCommitAsyncAction != null || 65 | AfterCommitAction != null || AfterCommitAsyncAction != null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | NHibernate.Extensions 3 | ===================== 4 | 5 | Various additions for NHibernate like the Include method from EntityFramework and a smart deep cloning method. 6 | 7 | ## Install via NuGet 8 | 9 | If you want to include NHibernate.Extensions in your project, you can [install it directly from NuGet](https://www.nuget.org/packages/NHibernate.Extensions). 10 | 11 | To install NHibernate.Extensions, run the following command in the Package Manager Console. 12 | 13 | ``` 14 | PM> Install-Package NHibernate.Extensions 15 | ``` 16 | 17 | ## Include method 18 | 19 | Is an extension method for Linq used to eager load entity relations without worrying about cartesian product in sql. Under the hood this method uses NHibernate Fetch methods in conjunction with NHibernate Future methods. The order of the Include methods is not important as there is a logic that calculates the minimum number of queries that are needed to fetch all relations without having any cartesian product. Let's look at an example: 20 | 21 | ```cs 22 | var people = session.Query() 23 | .Include(o => o.BestFriend.IdentityCard) /* nested many to one relations */ 24 | .Include(o => o.BestFriend.BestFriend.BestFriend.BestFriend) /* nested many to one relations */ 25 | .Include(o => o.CurrentOwnedVehicles).ThenInclude(o => o.Wheels) /* nested one to many relations */ 26 | .Include(o => o.DrivingLicence) /* many to one relation */ 27 | .Include(o => o.IdentityCard) /* many to one relation */ 28 | .Include(o => o.MarriedWith) /* many to one relation */ 29 | .Include(o => o.OwnedHouses) /* one to many relation */ 30 | .Include(o => o.PreviouslyOwnedVehicles) /* one to many relation */ 31 | .ToList(); 32 | ``` 33 | 34 | In the above example we are eager loading a lot relations but if we want to calculate the minimum number of queries we need to worry about one to many relations in order to prevent cartesian products. When we are eager loading nested one to many relations we won't have a cartesian product so we can load them in one query. So for the above example the minimum number of queries to fetch all relations without having cartesian products is 3. Let's see now the equivalent code using Fetch and Future methods: 35 | 36 | ```cs 37 | var query = session.Query() 38 | .Fetch(o => o.BestFriend) 39 | .ThenFetch(o => o.IdentityCard) 40 | .Fetch(o => o.BestFriend) 41 | .ThenFetch(o => o.BestFriend) 42 | .ThenFetch(o => o.BestFriend) 43 | .ThenFetch(o => o.BestFriend) 44 | .FetchMany(o => o.CurrentOwnedVehicles) 45 | .ThenFetchMany(o => o.Wheels) 46 | .Fetch(o => o.DrivingLicence) 47 | .Fetch(o => o.IdentityCard) 48 | .Fetch(o => o.MarriedWith) 49 | .ToFuture(); 50 | session.Query() 51 | .FetchMany(o => o.OwnedHouses) 52 | .ToFuture(); 53 | session.Query() 54 | .FetchMany(o => o.PreviouslyOwnedVehicles) 55 | .ToFuture(); 56 | var people = query.ToList(); 57 | ``` 58 | 59 | The whole idea of the Include method is to simplify eager loading in NHibernate. 60 | 61 | 62 | ## DeepClone 63 | 64 | Is a extension method for NHibernate Session for deep cloning an entity with its relations that are currently loaded. This method can be used in various scenarios. Serialization is one of them as we can serialize a deep cloned entity without worrying about lazy loadings upon serialization as by default the deep cloned entity does not contain any proxies. Let's see a simple example: 65 | 66 | ```cs 67 | EQBPerson petra; 68 | using (var session = NHConfig.OpenSession()) 69 | { 70 | petra = session.Query() 71 | .First(o => o.Name == "Petra"); 72 | clone = session.DeepClone(petra); 73 | // Lazy load some relations after cloning 74 | var friend = petra.BestFriend; 75 | var card = petra.IdentityCard; 76 | 77 | } 78 | Assert.AreEqual(petra.Id, clone.Id); 79 | Assert.AreEqual(petra.Name, clone.Name); 80 | Assert.AreEqual(petra.LastName, clone.LastName); 81 | Assert.IsNotNull(petra.BestFriend); 82 | Assert.IsNotNull(petra.IdentityCard); 83 | Assert.IsNull(clone.BestFriend); 84 | Assert.IsNull(clone.IdentityCard); 85 | ``` 86 | 87 | In the above example we fetched a person without including any additional relation so the DeepClone method will only clone the person itself. In addition the cloned entity relations are not proxies so it can be easily serialized. The DeepClone method accepts an additional parameter in order to tune the cloning. Let's take a look: 88 | 89 | ```cs 90 | EQBPerson clone; 91 | EQBPerson petra; 92 | using (var session = NHConfig.OpenSession()) 93 | { 94 | petra = session.Query() 95 | .Include(o => o.IdentityCard) 96 | .First(o => o.Name == "Petra"); 97 | clone = session.DeepClone(petra, o => o 98 | .ForType(t => t 99 | .ForMember(m => m.Name, opts => opts.Ignore()) 100 | .CloneIdentifier(false) 101 | ) 102 | .CanCloneAsReference(type => type == typeof(EQBIdentityCard)) 103 | ); 104 | 105 | } 106 | Assert.AreEqual(clone.Id, default(int)); 107 | Assert.IsNull(clone.Name); 108 | Assert.AreEqual(petra.LastName, clone.LastName); 109 | Assert.AreEqual(petra.IdentityCard, clone.IdentityCard); 110 | ``` 111 | 112 | As seen in the above example we can tune cloning on a global level or type level. The type level tuning will override the global one when using both. 113 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #tool nuget:?package=NUnit.ConsoleRunner&version=3.12.0 2 | #tool nuget:?package=CSharpAsyncGenerator.CommandLine&version=0.19.1 3 | ////////////////////////////////////////////////////////////////////// 4 | // ARGUMENTS 5 | ////////////////////////////////////////////////////////////////////// 6 | 7 | var target = Argument("target", "Default"); 8 | var configuration = Argument("configuration", "Release"); 9 | var netfx = Argument("netfx", "net461"); 10 | 11 | ////////////////////////////////////////////////////////////////////// 12 | // CONSTANTS 13 | ////////////////////////////////////////////////////////////////////// 14 | 15 | var PROJECT_DIR = Context.Environment.WorkingDirectory.FullPath + "/"; 16 | var PACKAGE_DIR = PROJECT_DIR + "package/"; 17 | 18 | ////////////////////////////////////////////////////////////////////// 19 | // PREPARATION 20 | ////////////////////////////////////////////////////////////////////// 21 | 22 | // Define directories. 23 | var buildDirs = new List() 24 | { 25 | Directory("./NHibernate.Extensions/bin") + Directory(configuration), 26 | Directory("./NHibernate.Extensions.Tests/bin") + Directory(configuration) 27 | }; 28 | 29 | ////////////////////////////////////////////////////////////////////// 30 | // TASKS 31 | ////////////////////////////////////////////////////////////////////// 32 | 33 | Task("Clean") 34 | .Does(() => 35 | { 36 | foreach(var buildDir in buildDirs) 37 | { 38 | CleanDirectory(buildDir); 39 | } 40 | }); 41 | 42 | Task("Restore") 43 | .IsDependentOn("Clean") 44 | .Does(() => 45 | { 46 | NuGetRestore("./NHibernate.Extensions.sln"); 47 | }); 48 | 49 | Task("RestoreCore") 50 | .IsDependentOn("Clean") 51 | .Does(() => 52 | { 53 | DotNetCoreRestore("./NHibernate.Extensions.sln"); 54 | }); 55 | 56 | Task("Build") 57 | .IsDependentOn("Restore") 58 | .Does(() => 59 | { 60 | MSBuild("./NHibernate.Extensions.sln", settings => 61 | settings.SetConfiguration(configuration)); 62 | }); 63 | 64 | Task("BuildCore") 65 | .IsDependentOn("RestoreCore") 66 | .Does(() => 67 | { 68 | DotNetCoreBuild("./NHibernate.Extensions.sln", new DotNetCoreBuildSettings 69 | { 70 | Configuration = configuration, 71 | ArgumentCustomization = args => args.Append("--no-restore"), 72 | }); 73 | }); 74 | 75 | Task("Test") 76 | .IsDependentOn("Build") 77 | .Does(() => 78 | { 79 | NUnit3("./NHibernate.Extensions.Tests/bin/" + configuration + $"/{netfx}/*.Tests.dll", new NUnit3Settings 80 | { 81 | NoResults = true 82 | }); 83 | }); 84 | 85 | Task("TestCore") 86 | .IsDependentOn("BuildCore") 87 | .Does(() => 88 | { 89 | DotNetCoreTest("./NHibernate.Extensions.Tests/NHibernate.Extensions.Tests.csproj", new DotNetCoreTestSettings 90 | { 91 | Configuration = configuration, 92 | NoBuild = true 93 | }); 94 | }); 95 | 96 | ////////////////////////////////////////////////////////////////////// 97 | // PACKAGE 98 | ////////////////////////////////////////////////////////////////////// 99 | 100 | Task("CleanPackages") 101 | .Does(() => 102 | { 103 | CleanDirectory(PACKAGE_DIR); 104 | }); 105 | 106 | Task("Pack") 107 | .IsDependentOn("BuildCore") 108 | .IsDependentOn("CleanPackages") 109 | .Description("Creates NuGet packages") 110 | .Does(() => 111 | { 112 | CreateDirectory(PACKAGE_DIR); 113 | 114 | var projects = new string[] 115 | { 116 | "NHibernate.Extensions/NHibernate.Extensions.csproj" 117 | }; 118 | 119 | foreach(var project in projects) 120 | { 121 | MSBuild(project, new MSBuildSettings { 122 | Configuration = configuration, 123 | ArgumentCustomization = args => args 124 | .Append("/t:pack") 125 | .Append("/p:PackageOutputPath=\"" + PACKAGE_DIR + "\"") 126 | }); 127 | } 128 | }); 129 | 130 | Task("Async") 131 | .IsDependentOn("Restore") 132 | .Does(() => 133 | { 134 | DotNetCoreExecute("./Tools/CSharpAsyncGenerator.CommandLine.0.19.1/tools/net472/AsyncGenerator.CommandLine.dll"); 135 | }); 136 | 137 | Task("Publish") 138 | .IsDependentOn("Pack") 139 | .Does(() => 140 | { 141 | foreach(var package in System.IO.Directory.GetFiles(PACKAGE_DIR, "*.nupkg").Where(o => !o.Contains("symbols"))) 142 | { 143 | NuGetPush(package, new NuGetPushSettings() 144 | { 145 | Source = "https://api.nuget.org/v3/index.json" 146 | }); 147 | } 148 | }); 149 | 150 | 151 | 152 | ////////////////////////////////////////////////////////////////////// 153 | // TASK TARGETS 154 | ////////////////////////////////////////////////////////////////////// 155 | 156 | Task("Default") 157 | .IsDependentOn("Test"); 158 | 159 | ////////////////////////////////////////////////////////////////////// 160 | // EXECUTION 161 | ////////////////////////////////////////////////////////////////////// 162 | 163 | RunTarget(target); 164 | --------------------------------------------------------------------------------