diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000000..529caf149f7 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpasyncgenerator.tool": { + "version": "0.19.1", + "commands": [ + "async-generator" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 67c20a705b8..cdafebe60ab 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,13 @@ insert_final_newline = true indent_style = tab dotnet_sort_system_directives_first = true csharp_space_after_cast = true +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true [*.xsd] indent_style = tab diff --git a/.gitignore b/.gitignore index 977afb3cb6b..678ae179fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ TestResult.xml .idea/ .vs/ /build-common/NHibernate.dev.props +/doc/reference/master.xml diff --git a/.travis.yml b/.travis.yml index 748f57bd794..dddf94fa59c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ +os: linux +dist: xenial language: csharp -mono: latest +mono: none dotnet: 2.1.300 -sudo: required services: - mysql - postgresql @@ -12,27 +13,24 @@ env: - DB=Firebird - DB=MySQL CONNECTION_STRING="Server=127.0.0.1;Uid=root;Database=nhibernate;Old Guids=True;" - DB=SQLite -matrix: - allow_failures: - - env: DB=MySQL CONNECTION_STRING="Server=127.0.0.1;Uid=root;Database=nhibernate;Old Guids=True;" before_install: - - curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - - curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - sudo apt-get update -qq - sudo apt-get install -y powershell - |- if [[ "$DB" == "MySQL" ]] then - echo -e '[server]\nlower_case_table_names=1' | sudo tee -a /etc/mysql/my.cnf + echo -e '[server]\nlower_case_table_names=1\n[mysqld]\ncharacter-set-server=utf8\ncollation-server=utf8_general_ci' | sudo tee -a /etc/mysql/my.cnf sudo service mysql restart fi - |- if [[ "$DB" == "Firebird" ]] then - sudo apt-get install -y libicu-dev libtommath-dev curl - curl -L -O https://github.com/FirebirdSQL/firebird/releases/download/R3_0_3/Firebird-3.0.3.32900-0.amd64.tar.gz - tar xzvf Firebird-3.0.3.32900-0.amd64.tar.gz - pushd Firebird-3.0.3.32900-0.amd64 + sudo apt-get install -y libtommath-dev + # This would be required on bionic and above + # sudo ln -s /usr/lib/x86_64-linux-gnu/libtommath.so.1 /usr/lib/x86_64-linux-gnu/libtommath.so.0 + wget -q https://github.com/FirebirdSQL/firebird/releases/download/R3_0_5/Firebird-3.0.5.33220-0.amd64.tar.gz + tar xzvf Firebird-3.0.5.33220-0.amd64.tar.gz + pushd Firebird-3.0.5.33220-0.amd64 sudo ./install.sh -silent popd export $(sudo cat /opt/firebird/SYSDBA.password | grep -v ^# | xargs) @@ -40,7 +38,7 @@ before_install: echo -e "nhibernate = /tmp/firebird/nhibernate.fdb" | sudo tee -a /opt/firebird/databases.conf echo -e "AuthServer = Srp\nAuthClient = Srp\nUserManager = Srp\nWireCrypt = Enabled" | sudo tee -a /opt/firebird/firebird.conf sudo /opt/firebird/bin/gsec -modify SYSDBA -pw masterkey -admin yes - sudo service firebird restart + sudo systemctl restart firebird-superserver fi before_script: - if [[ "$DB" == "SqlServer2008" ]]; then docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=P@ssw0rd" -e "MSSQL_PID=Express" -p 1433:1433 -d --name sqlexpress microsoft/mssql-server-linux:latest; fi diff --git a/HowInstall.txt b/HowInstall.txt index 1893c600540..febc1b2f8ba 100644 --- a/HowInstall.txt +++ b/HowInstall.txt @@ -4,4 +4,4 @@ Required Bins : Minimal required assemblies to work with NHibernate Required for LazyLoading : NHibernate 2.1 has a new important feature regarding dynamic-proxy systems for lazy-loading Details are available in releasenotes.txt and in this post -http://nhibernate.info/blog/2008/11/09/nh2-1-0-bytecode-providers.html \ No newline at end of file +https://nhibernate.info/blog/2008/11/09/nh2-1-0-bytecode-providers.html \ No newline at end of file diff --git a/README.md b/README.md index 04280734b54..8da992df161 100644 --- a/README.md +++ b/README.md @@ -4,24 +4,42 @@ Welcome to NHibernate NHibernate is a mature, open source object-relational mapper for the .NET framework. It is actively developed, fully featured and used in thousands of successful projects. -The NHibernate community website - - has a range of resources to help you get started, +The NHibernate community website - - has a range of resources to help you get started, including [howtos][A1], [blogs][A2] and [reference documentation][A3]. -[A1]: http://nhibernate.info/doc/ -[A2]: http://nhibernate.info/blog/ -[A3]: http://nhibernate.info/doc/nh/en/index.html +[A1]: https://nhibernate.info/doc/ +[A2]: https://nhibernate.info/blog/ +[A3]: https://nhibernate.info/doc/nh/en/index.html -Latest Version +Latest Release Version -------------- The quickest way to get the latest release of NHibernate is to add it to your project using -NuGet (). +NuGet (). Alternatively binaries are available from SourceForge at . You are encouraged to review the release notes ([releasenotes.txt](releasenotes.txt)), particularly when upgrading to a later version. The release notes will generally document any breaking changes. +Nightly Development Builds +-------------------------- + +The quickest way to get the latest development build of NHibernate is to add it to your project using +NuGet from MyGet feed (). + +In order to make life a little bit easier you can register the package source in the NuGet.Config +file in the top folder of your project, similar to the following. + +```xml + + + + + + +``` + Community Forums ---------------- @@ -40,8 +58,8 @@ If you find any bugs, please report them using the [GitHub issue tracker][C1]. A test-case that demonstrates the issue is usually required. Instructions on providing a test-case can be found in [contributing guidelines][C3] or [here][C2]. -[C1]: http://github.com/nhibernate/nhibernate-core/issues -[C2]: http://nhibernate.info/blog/2008/10/04/the-best-way-to-solve-nhibernate-bugs-submit-good-unit-test.html +[C1]: https://github.com/nhibernate/nhibernate-core/issues +[C2]: https://nhibernate.info/blog/2008/10/04/the-best-way-to-solve-nhibernate-bugs-submit-good-unit-test.html [C3]: CONTRIBUTING.md Licenses @@ -85,7 +103,7 @@ of NHibernate (in no particular order): [Apache Software Foundation]: http://www.apache.org [JetBrains]: http://www.jetbrains.com [ReSharper]: http://www.jetbrains.com/resharper -[LinFu]: http://code.google.com/p/linfu +[LinFu]: https://github.com/philiplaureano/LinFu [article]: http://www.codeproject.com/KB/recipes/sets.aspx -[Relinq]: http://relinq.codeplex.com/ +[Relinq]: https://github.com/re-motion/Relinq [AsyncGenerator]: http://github.com/maca88/AsyncGenerator diff --git a/ReleaseProcedure.txt b/ReleaseProcedure.txt index 753cce5c66a..b61cf9809b5 100644 --- a/ReleaseProcedure.txt +++ b/ReleaseProcedure.txt @@ -4,11 +4,11 @@ These are the tasks typically needed to create an official NHibernate release. that is about to happen (as these will show in the milestone). * Create a draft release in Github with GitReleaseManager. If you have used - the NHibernate build menu, it should be available in Tools\gitreleasemanage.x.x.x\ + the NHibernate build menu, it should be available in Tools\gitreleasemanager\x.x.x\ (change x.x.x by its current version in tools). By example: -Tools\gitreleasemanager.0.7.0\tools\GitReleaseManager.exe create -o nhibernate -r nhibernate-core -m 5.1 -u username -p password +Tools\gitreleasemanager\0.11.0\tools\GitReleaseManager.exe create -o nhibernate -r nhibernate-core -m 5.3 --token yourGitHubTokenWithRepoScope (Adjust the -m milestone parameter above, and add "-c branchname" if releasing another branch than master) @@ -25,9 +25,8 @@ Tools\gitreleasemanager.0.7.0\tools\GitReleaseManager.exe create -o nhibernate - If the release tag does not match the released version with major.minor.release formalism, the NuGet package will have an invalid link to release notes. - * Check/update version number in common.xml and NHibernate.props under - build-common folder, in master.xml under doc\reference folder, and in - appveyor.yml in the NHibernate root. + * Check/update version number in NHibernate.props under build-common folder. + Clear VersionSuffix if present. * Don't forget to commit the above. @@ -55,3 +54,10 @@ Tools\gitreleasemanager.0.7.0\tools\GitReleaseManager.exe create -o nhibernate - * If this was a stable branch, merge it forward to master. Perhaps some changes need to be edited out in the merge, but this will reduce the risk of some issue being left unfixed on master. + + * If this was the master branch, create the stable branch for the new release, named + according to its version with "x" instead of its patch number. By example, "5.3.x". + + * If next version is decided set it in NHibernate.props under build-common folder + (in most cases - increment minor version) and set VersionSuffix to "dev". + Commit it directly (without going through a PR). diff --git a/ShowBuildMenu.bat b/ShowBuildMenu.bat index 38ed467ae77..4bbb640c761 100644 --- a/ShowBuildMenu.bat +++ b/ShowBuildMenu.bat @@ -1,15 +1,21 @@ @echo off pushd %~dp0 +for /f "tokens=* USEBACKQ delims= " %%i in (`findstr /c:"NUnit.Console" "Tools\packages.csproj"`) do set NUNIT_VERSION=%%i +set NUNIT_VERSION=%NUNIT_VERSION:~51% +set NUNIT_VERSION=%NUNIT_VERSION:" />=% + set NANT="%~dp0Tools\nant\bin\NAnt.exe" -t:net-4.0 set BUILD_TOOL_PATH=%~dp0Tools\BuildTool\bin\BuildTool.dll set BUILDTOOL=dotnet %BUILD_TOOL_PATH% set AVAILABLE_CONFIGURATIONS=%~dp0available-test-configurations set CURRENT_CONFIGURATION=%~dp0current-test-configuration -set NUNIT="%~dp0Tools\NUnit.ConsoleRunner.3.7.0\tools\nunit3-console.exe" +set NUNIT="%~dp0Tools\NUnit.ConsoleRunner\%NUNIT_VERSION%\tools\nunit3-console.exe" if not exist %BUILD_TOOL_PATH% ( - dotnet build %~dp0Tools\BuildTool\BuildTool.sln -c Release -o bin + pushd %~dp0Tools\BuildTool + dotnet build BuildTool.sln -c Release -o bin + popd ) :main-menu @@ -182,7 +188,8 @@ SET NUNITPLATFORM= goto test-run :test-run -start "nunit3-console" cmd /K %NUNIT% %NUNITPLATFORM% --agents=1 --process=separate NHibernate.nunit +%NANT% common.tools-restore +start "nunit3-console" cmd /K %NUNIT% %NUNITPLATFORM% --agents=1 NHibernate.nunit goto main-menu rem :build-test diff --git a/ShowBuildMenu.sh b/ShowBuildMenu.sh index 4fef99c721a..066cfae5ac4 100755 --- a/ShowBuildMenu.sh +++ b/ShowBuildMenu.sh @@ -1,5 +1,7 @@ #!/bin/sh +cd "$(dirname "$0")" +SCRIPT_PATH="$(pwd)" BUILD_TOOL_PATH="./Tools/BuildTool/bin/BuildTool.dll" BUILD_TOOL="dotnet $BUILD_TOOL_PATH" AVAILABLE_CONFIGURATIONS="available-test-configurations" @@ -10,11 +12,14 @@ LIB_FILES2="" CURRENT_CONFIGURATION="./current-test-configuration" OPTION=0 async_generator_path="" +async_generator_version="" if [ ! -f $BUILD_TOOL_PATH ] then - dotnet build ./Tools/BuildTool/BuildTool.sln -c Release -o bin + cd ./Tools/BuildTool + dotnet build BuildTool.sln -c Release -o bin fi +cd "$SCRIPT_PATH" buildDebug(){ dotnet build ./src/NHibernate.sln @@ -173,48 +178,15 @@ testRun(){ } generateAsync(){ - dotnet msbuild /t:Restore ./src/NHibernate.sln - - getAsyncGeneratorPath cd src - mono ../"$async_generator_path" + dotnet tool restore + dotnet restore ./NHibernate.sln + dotnet async-generator cd .. mainMenu } -getAsyncGeneratorPath(){ - if [ "$async_generator_path" ] - then - return - fi - - cd Tools - - if [ ! -f nuget.exe ] - then - wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe - fi - - async_generator_path="CSharpAsyncGenerator.CommandLine.$(cat packages.config | grep id=\"CSharpAsyncGenerator.CommandLine | cut -d\" -f4)/tools" - - if [ ! -d $async_generator_path ] - then - mono nuget.exe install - fi - - if [ ! -f $async_generator_path/SQLitePCLRaw.core.dll ] - then - # This "hidden" dependency causes a failure under some Mono setup, add it explicitly - mono nuget.exe install SQLitePCLRaw.core -Version 1.0.0 - cp SQLitePCLRaw.core.1.0.0/lib/net45/SQLitePCLRaw.core.dll $async_generator_path/ - fi - - async_generator_path="Tools/$async_generator_path/AsyncGenerator.CommandLine.exe" - - cd .. -} - mainMenu() { echo "========================= NHIBERNATE BUILD MENU ==========================" echo "--- TESTING ---" diff --git a/Tools/.gitignore b/Tools/.gitignore index 1128a77fed4..c98efa933bc 100644 --- a/Tools/.gitignore +++ b/Tools/.gitignore @@ -1,6 +1,6 @@ -nuget.exe -NUnit.* -vswhere.* -CSharpAsyncGenerator.CommandLine.* -gitreleasemanager.* -SQLitePCLRaw.core.* +nunit.* +vswhere/ +csharpasyncgenerator.commandline/ +gitreleasemanager/ +obj/ +microsoft.* diff --git a/Tools/actual_msbuild.cmd b/Tools/actual_msbuild.cmd index 7db65390fe3..7596a39ba5b 100644 --- a/Tools/actual_msbuild.cmd +++ b/Tools/actual_msbuild.cmd @@ -1,6 +1,6 @@ @echo off -for /f "usebackq tokens=*" %%i in (`%~dp0\vswhere.2.1.4\tools\vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath`) do ( +for /f "usebackq tokens=*" %%i in (`%~dp0\vswhere\2.1.4\tools\vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath`) do ( set InstallDir=%%i ) diff --git a/Tools/packages.config b/Tools/packages.config deleted file mode 100644 index 6d41f85d78e..00000000000 --- a/Tools/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/packages.csproj b/Tools/packages.csproj new file mode 100644 index 00000000000..dd520651934 --- /dev/null +++ b/Tools/packages.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index bd4cb525ae6..da4e1a33bfd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 5.2.5.{build} +version: '{build}' image: Visual Studio 2017 environment: matrix: @@ -13,9 +13,6 @@ environment: init: # Required for having windows endlines in sources zip - git config --global core.autocrlf true -matrix: - allow_failures: - - DB: MySQL build: off before_test: - ps: |- @@ -25,7 +22,7 @@ before_test: # Install Firebird New-Item -ItemType Directory -Force $FireBirdPath > $null Push-Location $FireBirdPath - Invoke-WebRequest 'https://github.com/FirebirdSQL/firebird/releases/download/R3_0_3/Firebird-3.0.3.32900-0_x64.zip' -OutFile firebird.zip + Invoke-WebRequest 'https://github.com/FirebirdSQL/firebird/releases/download/R3_0_5/Firebird-3.0.5.33220-0_x64.zip' -OutFile firebird.zip Unblock-File firebird.zip 7z x firebird.zip New-Item -ItemType Directory -Force Data diff --git a/build-common/DotNetSdkMono.props b/build-common/DotNetSdkMono.props deleted file mode 100644 index 908be710f81..00000000000 --- a/build-common/DotNetSdkMono.props +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - true - - - /Library/Frameworks/Mono.framework/Versions/Current/lib/mono - /usr/lib/mono - /usr/local/lib/mono - - - $(BaseFrameworkPathOverrideForMono)/4.5-api - $(BaseFrameworkPathOverrideForMono)/4.5.1-api - $(BaseFrameworkPathOverrideForMono)/4.5.2-api - $(BaseFrameworkPathOverrideForMono)/4.6-api - $(BaseFrameworkPathOverrideForMono)/4.6.1-api - $(BaseFrameworkPathOverrideForMono)/4.6.2-api - $(BaseFrameworkPathOverrideForMono)/4.7-api - $(BaseFrameworkPathOverrideForMono)/4.7.1-api - true - - - $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) - - diff --git a/build-common/NHibernate.props b/build-common/NHibernate.props index 5e7cabbbd7a..fb0b9750621 100644 --- a/build-common/NHibernate.props +++ b/build-common/NHibernate.props @@ -1,16 +1,17 @@ - - 5 - 2 - 5 - + 5.4 + 0 + + dev - $(VersionMajor).$(VersionMinor).$(VersionPatch) - $(VersionMajor).$(VersionMinor).0.0 - $(VersionPrefix).0 + $(NhVersion).$(VersionPatch) + $(VersionSuffix).$(BuildNumber) + $(NhVersion).0.0 + $(VersionPrefix).$(BuildNumber) + $(VersionPrefix).0 net461;netcoreapp2.0 net461;netcoreapp2.0;netstandard2.0 @@ -24,11 +25,13 @@ NHibernate community, Hibernate community en-US - http://nhibernate.info + https://nhibernate.info https://raw.githubusercontent.com/nhibernate/nhibernate-core/master/logo/NHibernate-NuGet.png + NHibernate-NuGet.png false LGPL-2.1-only - https://github.com/nhibernate/nhibernate-core/blob/$(VersionPrefix)/releasenotes.txt + https://github.com/nhibernate/nhibernate-core/blob/$(VersionPrefix)/releasenotes.txt + This is development version for testing purposes only true true snupkg @@ -38,4 +41,7 @@ True + + + diff --git a/build-common/common.xml b/build-common/common.xml index 26e5302a66d..6985723bd35 100644 --- a/build-common/common.xml +++ b/build-common/common.xml @@ -11,15 +11,31 @@ - - - - + + + + + + + + + + + - + @@ -31,18 +47,13 @@ - - - - - - - - + + + + + + + diff --git a/default.build b/default.build index d9c5afd369d..8ea8ec5e4f9 100644 --- a/default.build +++ b/default.build @@ -2,7 +2,7 @@ @@ -21,14 +21,14 @@ - - + + @@ -39,6 +39,8 @@ + + @@ -46,7 +48,7 @@ - + @@ -54,24 +56,18 @@ - + - + file="${tools.dir}/packages.csproj" + xpath="/Project/ItemGroup/PackageReference[@Include = '${tool.id}']/@Version" + property="tool.version" /> + - - - - - - - - + + + + @@ -150,9 +146,9 @@ - - - + + + @@ -310,11 +306,9 @@ - - - @@ -323,12 +317,12 @@ - + - @@ -338,7 +332,8 @@ - + + diff --git a/doc/NHibernate.shfbproj.template b/doc/NHibernate.shfbproj.template index 3e8e83d2363..de6d308eba5 100644 --- a/doc/NHibernate.shfbproj.template +++ b/doc/NHibernate.shfbproj.template @@ -18,12 +18,12 @@ NHibernateAPI HtmlHelp1x NHibernate - nhibernate.info - http://groups.google.com/group/nhibernate-development + https://nhibernate.info + https://groups.google.com/group/nhibernate-development nhibernate team NHibernate API Reference False - ${project.version.numeric} + ${project.version} diff --git a/doc/documentation.build b/doc/documentation.build index 86104ac767b..52cf503cd6d 100644 --- a/doc/documentation.build +++ b/doc/documentation.build @@ -9,7 +9,7 @@ - + diff --git a/doc/reference/master.xml b/doc/reference/master.template.xml similarity index 98% rename from doc/reference/master.xml rename to doc/reference/master.template.xml index 25d73df2047..23df7a16535 100644 --- a/doc/reference/master.xml +++ b/doc/reference/master.template.xml @@ -39,7 +39,7 @@ NHibernate - Relational Persistence for Idiomatic .NET NHibernate Reference Documentation - 5.2 + diff --git a/doc/reference/modules/basic_mapping.xml b/doc/reference/modules/basic_mapping.xml index f30fa154507..30166e03d4a 100644 --- a/doc/reference/modules/basic_mapping.xml +++ b/doc/reference/modules/basic_mapping.xml @@ -13,11 +13,13 @@ Note that, even though many NHibernate users choose to define XML mappings by hand, - a number of tools exist to generate the mapping document, including - NHibernate.Mapping.Attributes library and various template-based code - generators (CodeSmith, MyGeneration). You may also use - NHibernate.Mapping.ByCode available since NHibernate 3.2, or - Fluent NHibernate. + a number of tools exist to generate the mapping document, even transparently at runtime. + This includes the NHibernate.Mapping.Attributes + library which allows to directly annotate your entities with mapping declarations, + various template-based code generators (CodeSmith, MyGeneration), the built-in + NHibernate.Mapping.ByCode API available since NHibernate 3.2, or + the Fluent NHibernate + independent library. diff --git a/doc/reference/modules/best_practices.xml b/doc/reference/modules/best_practices.xml index 21af3661540..4e0e3b327d2 100644 --- a/doc/reference/modules/best_practices.xml +++ b/doc/reference/modules/best_practices.xml @@ -18,7 +18,7 @@ NHibernate makes identifier properties optional. There are all sorts of reasons why you should use them. We recommend that identifiers be 'synthetic' (generated, with - no business meaning) and of a non-primitive type. For maximum flexibility, use + no business meaning) and of a primitive type. For maximum flexibility, use Int64 or String. diff --git a/doc/reference/modules/configuration.xml b/doc/reference/modules/configuration.xml index a97775563d5..8540764df70 100644 --- a/doc/reference/modules/configuration.xml +++ b/doc/reference/modules/configuration.xml @@ -433,6 +433,45 @@ var session = sessions.OpenSession(conn); + + Using a custom configuration provider + + + By default, NHibernate attempts to read the hibernate-configuration section + through the .Net ConfigurationManager. Some environments do not support it, so + NHibernate provides a way to set a custom configuration provider, through the + NHibernate.Cfg.ConfigurationProvider.Current property. + + + + To disable the configuration provider, in case you configure NHibernate entirely programmatically, + set this property to null. + + + + + + To provide directly the System.Configuration.Configuration instance to use, assign + the Current property with an instance of + NHibernate.Cfg.SystemConfigurationProvider built with your + Configuration instance. + + + + + + You may also derive a custom provider from NHibernate.Cfg.ConfigurationProvider, + implements its abstract methods, and assign an instance of your custom provider to the + NHibernate.Cfg.ConfigurationProvider.Current property. + + + + Changes of the ConfigurationProvider.Current property value are to be done very + early in the application lifecycle, before any other call on a NHibernate API. Otherwise they + may not be taken into account. + + + Optional configuration properties @@ -703,6 +742,19 @@ var session = sessions.OpenSession(conn); + + + query.pre_transformer_registrar + + + The class name of the LINQ query pre-transformer registrar, implementing + IExpressionTransformerRegistrar. Defaults to null (no registrar). + + eg. + classname.of.ExpressionTransformerRegistrar, assembly + + + linqtohql.generatorsregistry @@ -717,6 +769,57 @@ var session = sessions.OpenSession(conn); + + + linqtohql.legacy_preevaluation + + + Whether to use the legacy pre-evaluation or not in Linq queries. Defaults to true. + + eg. + true | false + + + Legacy pre-evaluation is causing special properties or functions like DateTime.Now + or Guid.NewGuid() to be always evaluated with the .Net runtime and replaced in the + query by parameter values. + + + The new pre-evaluation allows them to be converted to HQL function calls which will be run on the db + side. This allows for example to retrieve the server time instead of the client time, or to generate + UUIDs for each row instead of an unique one for all rows. + + + The new pre-evaluation will likely be enabled by default in the next major version (6.0). + + + + + + linqtohql.fallback_on_preevaluation + + + When the new pre-evaluation is enabled, should methods which translation is not supported by the current + dialect fallback to pre-evaluation? Defaults to false. + + eg. + true | false + + + When this fallback option is enabled while legacy pre-evaluation is disabled, properties or functions + like DateTime.Now or Guid.NewGuid() used in Linq expressions + will not fail when the dialect does not support them, but will instead be pre-evaluated. + + + When this fallback option is disabled while legacy pre-evaluation is disabled, properties or functions + like DateTime.Now or Guid.NewGuid() used in Linq expressions + will fail when the dialect does not support them. + + + This option has no effect if the legacy pre-evaluation is enabled. + + + sql_exception_converter @@ -778,7 +881,7 @@ var session = sessions.OpenSession(conn); is closed explicitly. eg. - create | create-drop + create | create-drop | update | validate @@ -807,6 +910,20 @@ var session = sessions.OpenSession(conn); + + + hbm2ddl.throw_on_update + + + When hbm2ddl.auto is update, whether to throw or not on schema auto-update failures. + + Disabled by default. + + eg. + true | false + + + use_proxy_validator @@ -905,6 +1022,25 @@ var session = sessions.OpenSession(conn); + + + transaction.auto_join + + + Should sessions check on every operation whether there is an ongoing system transaction or not, and enlist + into it if any? + + Default is true. It can also be controlled at session opening, with + ISessionFactory.WithOptions. A session can also be instructed to explicitly join the current + transaction by calling ISession.JoinTransaction. This setting has no effect when using a + transaction factory that is not system transactions aware. + + + eg. + false + + + default_flush_mode @@ -1081,6 +1217,20 @@ in the parameter binding. + + + sqlite.binaryguid + + + SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid + connection string parameter (default is 'true'). The BinaryGuid setting will affect + how to cast GUID to string in SQL. NHibernate will attempt to detect this + setting automatically from the connection string, but if the connection + or connection string is being handled by the application instead of by NHibernate, + you can use the sqlite.binaryguid NHibernate setting to override the behavior. + The value can be true or false. + + nhibernate-logger diff --git a/doc/reference/modules/nhibernate_caches.xml b/doc/reference/modules/nhibernate_caches.xml index f7304d94c2f..997b5ff1a0b 100644 --- a/doc/reference/modules/nhibernate_caches.xml +++ b/doc/reference/modules/nhibernate_caches.xml @@ -2,7 +2,6 @@ NHibernate.Caches - What is NHibernate.Caches? @@ -108,7 +107,17 @@ Uses System.Runtime.Caching.MemoryCache.Default as the cache provider. This provider - is available for the .Net Framework only. See . + is available as a .Net Standard NuGet package. See . + + + + + NHibernate.Caches.StackExchangeRedis + + + Uses StackExchange.Redis. This provider is available as a .Net Standard + NuGet package. It can batch together puts and reads, reducing incurred IOs. + See . @@ -233,6 +242,43 @@ +
+ Using a custom configuration provider + + + Most caches have their own configuration section. By default, the cache attempts to read its configuration section + through the .Net ConfigurationManager. Some environments do not support it, especially when + running under .Net Core. The .Net Core compatible caches provide a way to set a custom configuration provider, through + their NHibernate.Caches. cache specific namespace .ConfigurationProvider class. + + + + To disable the configuration provider, in case you configure the cache entirely programmatically, + set its ConfigurationProvider.Current property to null. + + + + + + To provide directly the System.Configuration.Configuration instance to use, call + the cache ConfigurationProvider.SetConfiguration method. + + + + + + You may also derive a custom provider from the ConfigurationProvider of the cache, + implements its abstract method GetConfiguration, and assign an instance of your + custom provider to the cache ConfigurationProvider.Current property. + + + + Changes of the ConfigurationProvider.Current property value are to be done very + early in the application lifecycle, before building any session factory using the cache provider or + before any other call on the cache API. Otherwise they will not be taken into account. + +
+
Prevalence Cache Configuration @@ -241,7 +287,7 @@ full path. If the directory doesn't exist, it will be created. - The prevalenceBase setting can only be set programmatically through on the NHibernate + The prevalenceBase setting can only be set programmatically through the NHibernate configuration object, by example with Configuration.SetProperty.
@@ -250,6 +296,11 @@ SysCache Configuration SysCache relies on System.Web.Caching.Cache for the underlying implementation. + This is a .Net Framework only library, since System.Web.Caching is available neither + in the .Net Standard nor in .Net Core. + + + The following NHibernate configuration settings are available: @@ -306,12 +357,18 @@ SysCache2 Configuration - SysCache2 can use SqlCacheDependencies to invalidate cache regions when data in an underlying SQL Server + SysCache2 uses System.Web.Caching like SysCache, but can use SqlCacheDependencies + to invalidate cache regions when data in an underlying SQL Server table or query changes. Query dependencies are only available for SQL Server 2005 or higher. To use the cache provider, the application must be setup and configured to support SQL notifications as described in the MSDN documentation. + + This is a .Net Framework only library, since System.Web.Caching is available neither + in the .Net Standard nor in .Net Core. + + The following NHibernate configuration settings are available: @@ -615,6 +672,7 @@ Its configuration relies on the EnyimMemcached library own configuration, through its enyim.com/memcached configuration section. See project site. + This is a .Net Framework only library. @@ -623,6 +681,9 @@ RtMemoryCache relies on System.Runtime.Caching.MemoryCache for the underlying implementation. + + + The following NHibernate configuration settings are available: @@ -664,6 +725,327 @@ ]]> + + + The loading of this section can be customized with the + NHibernate.Caches.RtMemoryCache.ConfigurationProvider class. See + . + + + +
+ NHibernate.Caches.StackExchangeRedis Configuration + + NHibernate.Caches.StackExchangeRedis relies on StackExchange.Redis for the + underlying implementation. + + + + The following NHibernate configuration settings are available (also defined in + NHibernate.Caches.StackExchangeRedis.RedisEnvironment): + + + + + cache.default_expiration + + Number of seconds to wait before expiring each item. + Defaults to 300. It can also be set programmatically on the NHibernate + configuration object under the name expiration, which then takes precedence + over cache.default_expiration. + + + + cache.use_sliding_expiration + + Should the expiration be sliding? A sliding expiration is reinitialized at each get. Can be overriden for each region by using + sliding attribute. + Defaults to false. + + + + cache.database + + The default Redis database index, that can be overriden for each region by using database attribute. + Defaults to -1. + + + + cache.strategy + + The assembly qualified name of the region strategy, that can be overriden for each region by using strategy attribute. + NHibernate.Caches.StackExchangeRedis provides the following strategies: + + + NHibernate.Caches.StackExchangeRedis.DefaultRegionStrategy + + Uses a special key that contains the region current version number which is appended after the region prefix. + Each time a clear operation is performed the version number is increased and an event is send to all clients + so that they can update their local versions. Even if the event was not sent to all clients, each operation has a + version check in order to prevent working with stale data. This strategy has additional settings: + + cache.region_strategy.default.max_allowed_version + + The max allowed version number. When the max value is reached, the next value will be reset to zero. + Defaults to 10000. + + + + cache.region_strategy.default.use_pubsub + + Whether to use Redis pub/sub mechanism in order to notify other cache instances when the clear operation was performed. + Defaults to true. + + + + cache.region_strategy.default.retry_times + + Total retry times for read and lock operations, when concurrent clear operations are performed. + Defaults to 1. + + + + + + NHibernate.Caches.StackExchangeRedis.FastRegionStrategy + + Uses very simple read/write operations but does not support ICache.Clear operation. + + + + NHibernate.Caches.StackExchangeRedis.TwoLayerCacheRegionStrategy + + Extends NHibernate.Caches.StackExchangeRedis.DefaultRegionStrategy and uses + an additional local memory cache for faster readings. The local caches are invalidated by using Redis pub/sub mechanism. + This strategy should be used only for regions that have few write operations and a high expiration time. + This strategy inherits additional settings from DefaultRegionStrategy and also has its own settings: + + cache.region_strategy.two_layer_cache.use_pipelining + + Whether to use StackExchange.Redis pipelining feature. + Defaults to false. + + + + cache.region_strategy.two_layer_cache.client_id + + The client id used for cache invalidation. + Defaults to a random number. + + + + cache.region_strategy.two_layer_cache.max_synchronization_time + + The max synchronization time between caches in seconds. + Defaults to 10. + + + + + + NHibernate.Caches.StackExchangeRedis.FastTwoLayerCacheRegionStrategy + + Extends NHibernate.Caches.StackExchangeRedis.FastRegionStrategy and uses + an additional local memory cache for faster readings. The local caches are invalidated by using Redis pub/sub mechanism. + This strategy does not support ICache.Clear operation and should be used only for regions that have + few write operations and a high expiration time. This strategy has additional settings: + + cache.region_strategy.fast_two_layer_cache.use_pipelining + + Whether to use StackExchange.Redis pipelining feature. + Defaults to false. + + + + cache.region_strategy.fast_two_layer_cache.client_id + + The client id used for cache invalidation. + Defaults to a random number. + + + + cache.region_strategy.fast_two_layer_cache.max_synchronization_time + + The max synchronization time between caches in seconds. + Defaults to 10. + + + + + + NHibernate.Caches.StackExchangeRedis.DistributedLocalCacheRegionStrategy + + Uses only a memory cache to store the values and uses Redis pub/sub mechanism to synchronize data between other local caches. + The synchronization between caches is done by comparing the UTC DateTime.Ticks, which represent when the + operation was performed. When two operations have the same DateTime.Ticks, then the client with the highest + id wins. This strategy should be used only for regions that have few write operations and a high expiration time. It is recommended + to use NHibernate.Caches.StackExchangeRedis.TwoLayerCacheRegionStrategy, when the instances where the strategy + would run are often restarted/recycled. In order to use this strategy a custom ICacheRegionStrategyFactory + has to be provided (see cache.region_strategy_factory setting), where the strategy is created with a custom + RegionMemoryCacheBase implementation. This strategy has additional settings: + + cache.region_strategy.distributed_local_cache.use_pipelining + + Whether to use StackExchange.Redis pipelining feature. + Defaults to false. + + + + cache.region_strategy.distributed_local_cache.client_id + + The client id used for cache invalidation. + Defaults to a random number. + + + + cache.region_strategy.distributed_local_cache.max_synchronization_time + + The max synchronization time between caches in seconds. + Defaults to 10. + + + + + + Defaults to NHibernate.Caches.StackExchangeRedis.DefaultRegionStrategy. + + + + cache.append_hashcode + + Whether the hash code of the key should be added to the cache key. Can be overriden for each region by using append-hashcode attribute. + Defaults to false. + + + + cache.key_prefix + + The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients uses the same Redis database. + Defaults to NHibernate-Cache:. + + + + cache.environment_name + + The name of the environment that will be prepended before each cache key in order to allow having multiple environments on the same Redis database. + Defaults to null. + + + + cache.serializer + + The assembly qualified name of the serializer that is used to serialize/deserialize the key values. Optionally, a faster json serializer can be + used by installing NHibernate.Caches.Util.JsonSerializer package and setting + NHibernate.Caches.Util.JsonSerializer.JsonCacheSerializer, NHibernate.Caches.Util.JsonSerializer value instead. + Defaults to NHibernate.Caches.Common.BinaryCacheSerializer, NHibernate.Caches.Common. + + + + cache.region_strategy_factory + + The assembly qualified name of the region strategy factory. + Defaults to NHibernate.Caches.StackExchangeRedis.DefaultCacheRegionStrategyFactory. + + + + cache.connection_multiplexer_provider + + The assembly qualified name of the connection multiplexer provider. + Defaults to NHibernate.Caches.StackExchangeRedis.DefaultConnectionMultiplexerProvider. + + + + cache.database_provider + + The assembly qualified name of the database provider. + Defaults to NHibernate.Caches.StackExchangeRedis.DefaultDatabaseProvider. + + + + cache.lock.key_timeout + + The timeout for a lock key to expire in seconds. + Defaults to 5. + + + + cache.lock.acquire_timeout + + The time limit to acquire the lock in seconds. + Defaults to 5. + + + + cache.lock.retry_times + + The number of retries for acquiring the lock. + Defaults to 3. + + + + cache.lock.max_retry_delay + + The maximum delay before retrying to acquire the lock in milliseconds. + Defaults to 400. + + + + cache.lock.min_retry_delay + + The minimum delay before retrying to acquire the lock in milliseconds. + Defaults to 10. + + + + cache.lock.value_provider + + The assembly qualified name of the lock value provider. + Defaults to NHibernate.Caches.StackExchangeRedis.DefaultCacheLockValueProvider. + + + + cache.lock.retry_delay_provider + + The assembly qualified name of the lock retry delay provider. + Defaults to NHibernate.Caches.StackExchangeRedis.DefaultCacheLockRetryDelayProvider. + + + + cache.lock.key_suffix + + The suffix for the lock key. + Defaults to :lock. + + + + + + NHibernate.Caches.StackExchangeRedis has a config file section handler to allow configuring different expirations for + different regions. Here is an example: + + + + + + +
+ + + + + + + +]]> + + + + The loading of this section can be customized with the + NHibernate.Caches.StackExchangeRedis.ConfigurationProvider class. See + . +
@@ -671,6 +1053,9 @@ CoreMemoryCache relies on Microsoft.Extensions.Caching.Memory.MemoryCache for the underlying implementation. + + + The following NHibernate configuration settings are available: @@ -714,6 +1099,12 @@ ]]> + + + The loading of this section can be customized with the + NHibernate.Caches.CoreMemoryCache.ConfigurationProvider class. See + . +
@@ -723,6 +1114,9 @@ implementations. The implementation has to be provided through an IDistributedCacheFactory, either supplied through configuration or programmatically by affecting CoreDistributedCacheProvider.CacheFactory before building a session factory. + + + The following NHibernate configuration settings are available: @@ -743,6 +1137,16 @@ Defaults to false. + + cache.serializer + + The assembly qualified name of the serializer that is used to serialize/deserialize the key values. Optionally, a faster json serializer can be + used by installing NHibernate.Caches.Util.JsonSerializer package and setting + NHibernate.Caches.Util.JsonSerializer.JsonCacheSerializer, NHibernate.Caches.Util.JsonSerializer value instead. + Defaults to NHibernate.Caches.Common.BinaryCacheSerializer, NHibernate.Caches.Common. + This setting is available since the 5.7 version of CoreDistributedCache. + + @@ -767,13 +1171,23 @@ 00:10:00 1048576 + NHibernate.Caches.Util.JsonSerializer.JsonCacheSerializer, NHibernate.Caches.Util.JsonSerializer + ]]> + + The loading of this section can be customized with the + NHibernate.Caches.CoreDistributedCache.ConfigurationProvider class. See + . + + CoreDistributedCache does not support NHibernate.Cache.ICache.Clear. Clearing the NHibernate cache has no effects with CoreDistributedCache. diff --git a/doc/reference/modules/nhibernate_mapping_attributes.xml b/doc/reference/modules/nhibernate_mapping_attributes.xml index baef69b80b3..7b6ae34deb3 100644 --- a/doc/reference/modules/nhibernate_mapping_attributes.xml +++ b/doc/reference/modules/nhibernate_mapping_attributes.xml @@ -2,59 +2,56 @@ NHibernate.Mapping.Attributes - What is NHibernate.Mapping.Attributes? - NHibernate.Mapping.Attributes is an add-in for <ulink url="http://nhibernate.info/">NHibernate</ulink> contributed by Pierre Henri Kuaté (aka <emphasis>KPixel</emphasis>); the former implementation was made by John Morris. + NHibernate.Mapping.Attributes is an add-in for <ulink url="https://nhibernate.info/">NHibernate</ulink> contributed by Pierre Henri Kuaté + (aka <emphasis>KPixel</emphasis>); the former implementation was made by John Morris. NHibernate require mapping streams to bind your domain model to your database. Usually, they are written (and maintained) in separated hbm.xml files. With NHibernate.Mapping.Attributes, you can use .NET attributes to decorate your entities and these attributes will be used to generate these mapping .hbm.xml (as files or streams). So you will no longer have to bother with these nasty xml files ;). - - Content of this library - - - - - NHibernate.Mapping.Attributes: That the only project you need (as end-user) - - - - Test: a working sample using attributes and HbmSerializer as NUnit TestFixture - - - - Generator: The program used to generate attributes and HbmWriter - - - - - Refly - : Thanks to Jonathan de Halleux for this library which make it so easy to generate code - - - - + + Content of this library project: + + + + + + NHibernate.Mapping.Attributes: that is the only assembly you need (as an end-user). + + + + Test: a working sample using attributes and HbmSerializer for a NUnit TestFixture. + + + + Generator: the program used to generate the attributes and the HbmWriter of + the end-user assembly. + + + + + Refly + : thanks to Jonathan de Halleux for this library which makes it so easy to generate code. + + + + This library is generated using the file /src/NHibernate.Mapping.Attributes/nhibernate-mapping.xsd (which is embedded in the assembly to be able to validate generated XML streams). - As this file can change at each new release of NHibernate, you should regenerate it before using it - with a different version (open the Generator solution, compile and run the Generator project). - But, no test has been done with versions prior to 0.8. + As this file can change at each new release of NHibernate, a new release of NHibernate.Mapping.Attributes + should be regenerated before using it with a different version. It can be done by opening the Generator solution, + compiling and running the Generator project. -
What's new? - - NHibernate - introduces many new features, improvements and changes: - It is possible to import classes by simply decorating them with [Import] class ImportedClass1 {}. Note that you must use HbmSerializer.Serialize(assembly); The <import/> mapping will be added before the classes mapping. If you prefer to keep these imports in the class using them, you can specify them all on the class: [Import(ClassType=typeof(ImportedClass1))] class Query {}. @@ -69,17 +66,29 @@ public class Base { [AttributeIdentifier(Name="Id.Column", Value="ID")] // Default value public int Id { ... } } + [AttributeIdentifier(Name="Id.Column", Value="SUB_ID")] -[Class] public class MappedSubClass : Base { } +[Class] +public class MappedSubClass : Base { } The idea is that, when you have a mapping which is shared by many subclasses but which has minor differences (like different column names), you can put the mapping in the base class with place holders on these fields and give their values in subclasses. Note that this is possible for any mapping field taking a string (column, name, type, access, etc.). And, instead of Value, you can use ValueType or ValueObject (if you use an enum, you can control its formatting with ValueObject). The "place holder" is defined like this: {{XXX}}. If you don't want to use these double curly brackets, you can change them using the properties StartQuote and EndQuote of the class HbmWriter. - It is possible to register patterns (using Regular Expressions) to automatically transform fully qualified names of properties types into something else. Eg: HbmSerializer.Default.HbmWriter.Patterns.Add(@"Namespace.(\S+), Assembly", "$1"); will map all properties with a not-qualified type name. + + It is possible to register patterns (using Regular Expressions) to automatically transform fully qualified names of properties types + into something else. Eg: HbmSerializer.Default.HbmWriter.Patterns.Add(@"Namespace\.(\S+), Assembly", "$1"); will + map all properties with a not-qualified type name. + - Two methods have been added to allow writing: cfg.AddInputStream( HbmSerializer.Default.Serialize(typeof(XXX)) ) and cfg.AddInputStream( HbmSerializer.Default.Serialize(typeof(XXX).Assembly) ). So it is no longer required to create a MemoryStream for these simple cases. + + Two methods have been added to the HbmSerializer class, allowing generating mappings from a type or an assembly: + HbmSerializer.Default.Serialize(typeof(XXX)) and + HbmSerializer.Default.Serialize(typeof(XXX).Assembly). So it is no longer required to create a MemoryStream for + these simple cases. The output of these call can be directly added to your NHibernate Configuration instance: + cfg.AddInputStream(HbmSerializer.Default.Serialize(typeof(XXX))). + Two WriteUserDefinedContent() methods have been added to HbmWriter. They improve the extensibility of this library; it is now very easy to create a .NET attribute and integrate it in the mapping. @@ -91,57 +100,77 @@ public class Base { Interfaces can be mapped (just like classes and structs). - A notable "bug" fix is the re-ordering of (joined-)subclasses; This operation may be required when a subclass extends another subclass. In this case, the extended class mapping must come before the extending class mapping. Note that the re-ordering takes place only for "top-level" classes (that is not nested in other mapped classes). Anyway, it is quite unusual to put a interdependent mapped subclasses in a mapped class. + + A notable "bug" fix is the re-ordering of (joined-)subclasses. This operation may be required when a subclass extends another subclass. + In this case, the extended class mapping must come before the extending class mapping. Note that the re-ordering takes place only for + "top-level" classes (that is not nested in other mapped classes). Anyway, it is quite unusual to put an interdependent mapped subclasses + in a mapped class. + - There are also many other little changes; refer to the release notes for more details. + There are also many other little changes: refer to the release notes for more details.
- -
How to use it? The <emphasis>end-user class</emphasis> is <classname>NHibernate.Mapping.Attributes.HbmSerializer</classname> - This class serialize your domain model to mapping streams. You can either serialize classes one by one or an assembly. Look at NHibernate.Mapping.Attributes.Test project for a working sample. + + This class serialize your domain model to mapping streams. You can either serialize classes one by one, + or serialize a whole assembly. Look at NHibernate.Mapping.Attributes.Test project for a working sample. + - The first step is to decorate your entities with attributes; you can use: [Class], [Subclass], [JoinedSubclass] or [Component]. Then, you decorate your members (fields/properties); they can take as many attributes as required by your mapping. Eg: + + The first step is to decorate your entities with attributes. You can use: [Class], + [Subclass], [JoinedSubclass] or [Component]. Then, you decorate + your members (fields/properties); they can take as many attributes as required by your mapping. Eg: + - [NHibernate.Mapping.Attributes.Class] - public class Example - { - [NHibernate.Mapping.Attributes.Property] - public string Name; - } - After this step, you use NHibernate.Mapping.Attributes.HbmSerializer: (here, we use Default which is an instance you can use if you don't need/want to create it yourself). - NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration(); - cfg.Configure(); - // Enable validation (optional) - NHibernate.Mapping.Attributes.HbmSerializer.Default.Validate = true; - // Here, we serialize all decorated classes (but you can also do it class by class) - cfg.AddInputStream( NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize( - System.Reflection.Assembly.GetExecutingAssembly() ); ); - // Now you can use this configuration to build your SessionFactory... +[NHibernate.Mapping.Attributes.Class] +public class Example +{ + [NHibernate.Mapping.Attributes.Property] + public string Name; +} + + After this step, you use NHibernate.Mapping.Attributes.HbmSerializer (here, we use its + Default property, which is an instance you can use if you don't need/want to create it yourself): + + var cfg = new NHibernate.Cfg.Configuration(); +cfg.Configure(); +// Enable validation (optional) +HbmSerializer.Default.Validate = true; +// Here, we serialize all decorated classes (but you can also do it class by class) +cfg.AddInputStream(HbmSerializer.Default.Serialize( + System.Reflection.Assembly.GetExecutingAssembly())); +// Now you can use this configuration to build your SessionFactory... - As you can see here: NHibernate.Mapping.Attributes is not (really) intrusive. + As you can see here, NHibernate.Mapping.Attributes is not (really) intrusive. Setting attributes on your objects doesn't force you to use them with NHibernate and doesn't break any constraint on your architecture. Attributes are purely informative (like documentation)!
-
Tips - In production, it is recommended to generate a XML mapping file from NHibernate.Mapping.Attributes and use this file each time the SessionFactory need to be built. Use: HbmSerializer.Default.Serialize(typeof(XXX).Assembly, "DomainModel.hbm.xml"); It is slightly faster. + + In production, you may want to generate a XML mapping file from NHibernate.Mapping.Attributes and use this file each time + the SessionFactory need to be built. Use: HbmSerializer.Default.Serialize(typeof(XXX).Assembly, "DomainModel.hbm.xml");. + It is slightly faster. + - Use HbmSerializer.Validate to enable/disable the validation of generated xml streams (against NHibernate mapping schema); this is useful to quickly find errors (they are written in StringBuilder HbmSerializer.Error). If the error is due to this library then see if it is a know issue and report it; you can contribute a solution if you solve the problem :) + + Use HbmSerializer.Validate to enable/disable the validation of generated xml streams (against + NHibernate mapping schema). This is useful to quickly find errors. (They are written in the StringBuilder property + HbmSerializer.Error.) If the error is due to this library, then see if it is a known issue and report it. + You are welcome to contribute a solution if you solve the trouble :). Your classes, fields and properties (members) can be private; just make sure that you have the permission to access private members using reflection (ReflectionPermissionFlag.MemberAccess). @@ -156,7 +185,12 @@ public class Base { By default, .NET attributes don't keep the order of attributes; so you need to set it yourself when the order matter (using the first parameter of each attribute); it is highly recommended to set it when you have more than one attribute on the same member. - As long as there is no ambiguity, you can decorate a member with many unrelated attributes. A good example is to put class-related attributes (like <discriminator>) on the identifier member. But don't forget that the order matters (the <discriminator> must be after the <id>). The order used comes from the order of elements in the NHibernate mapping schema. Personally, I prefer using negative numbers for these attributes (if they come before!). + + As long as there is no ambiguity, you can decorate a member with many unrelated attributes. A good example is to put + class-related attributes (like <discriminator>) on the identifier member. But don't forget + that the order matters (the <discriminator> must be after the <id>). + The order to use comes from the order of elements in the NHibernate mapping schema. Personally, I prefer using negative + numbers for these attributes (if they come first!). You can add [HibernateMapping] on your classes to specify <hibernate-mapping> attributes (used when serializing the class in its stream). You can also use HbmSerializer.Hbm* properties (used when serializing an assembly or a type that is not decorated with [HibernateMapping]). @@ -188,24 +222,23 @@ public class Base { Use the property HbmSerializer.HbmWriter to change the writer used (you may set a subclass of HbmWriter). - Example using some this tips: (0, 1 and 2 are position indexes) + Example using some of these tips: (0, 1 and 2 are position indexes) // Don't put it after [ManyToOne] !!! - [NHibernate.Mapping.Attributes.Id(0, TypeType=typeof(int))] - [NHibernate.Mapping.Attributes.Generator(1, Class="uuid.hex")] - [NHibernate.Mapping.Attributes.ManyToOne(2, - ClassType=typeof(Foo), OuterJoin=OuterJoinStrategy.True)] - private Foo Entity; +[NHibernate.Mapping.Attributes.Id(0, TypeType=typeof(int))] + [NHibernate.Mapping.Attributes.Generator(1, Class="uuid.hex")] +[NHibernate.Mapping.Attributes.ManyToOne(2, + ClassType=typeof(Foo), OuterJoin=OuterJoinStrategy.True)] +private Foo Entity; Generates: - - - + + + + ]]>
-
Known issues and TODOs First, read TODOs in the source code ;) @@ -213,17 +246,17 @@ public class Base { A Position property has been added to all attributes to order them. But there is still a problem: When a parent element "p" has a child element "x" that is also the child element of another child element "c" of "p" (preceding "x") :D Illustration: - - - + + +

]]>
In this case, when writing: [Attributes.P(0)] - [Attributes.C(1)] - [Attributes.X(2)] - [Attributes.X(3)] +[Attributes.C(1)] + [Attributes.X(2)] +[Attributes.X(3)] public MyType MyProperty; X(3) will always belong to C(1) ! (as X(2)). @@ -232,8 +265,8 @@ public MyType MyProperty; Anyway, the solution would be to add a int ParentNode property to BaseAttribute so that you can create a real graph... For now, you can fallback on [RawXml]. - Actually, there is no other know issue nor planned modification. This library should be stable and complete; but if you find a bug or think of an useful improvement, contact us! - On side note, it would be nice to write a better TestFixture than NHibernate.Mapping.Attributes.Test :D + Actually, there is no other know issue nor planned modification. This library should be stable and complete. But if you find a bug or think of an useful improvement, contact us! + As a side note, it would be nice to write a better TestFixture than NHibernate.Mapping.Attributes.Test :D.
@@ -242,19 +275,33 @@ public MyType MyProperty; Any change to the schema (nhibernate-mapping.xsd) implies: - Checking if there is any change to do in the Generator (like updating KnowEnums / AllowMultipleValue / IsRoot / IsSystemType / IsSystemEnum / CanContainItself) + + Checking if there is any change to do in the Generator (like updating KnowEnums / + AllowMultipleValue / IsRoot / IsSystemType / + IsSystemEnum / CanContainItself). + - Updating /src/NHibernate.Mapping.Attributes/nhibernate-mapping.xsd (copy/paste) and running the Generator again (even if it wasn't modified) + + Updating /src/NHibernate.Mapping.Attributes/nhibernate-mapping.xsd (copy/paste) + and running the Generator again (even if it wasn't modified). + - Running the Test project and make sure that no exception is thrown. A class/property should be modified/added in this project to be sure that any new breaking change will be caught (=> update the reference hbm.xml files and/or the project NHibernate.Mapping.Attributes.csproj) + + Running the Test project and make sure that no exception is thrown. A class/property should be modified/added + in this project to be sure that any new breaking change will be caught (=> update the reference hbm.xml files + and/or the project NHibernate.Mapping.Attributes.csproj). + - This implementation is based on NHibernate mapping schema; so there is probably lot of "standard schema features" that are not supported... + This implementation is based on NHibernate mapping schema. So there is probably lot of "standard schema features" that are not supported... The version of NHibernate.Mapping.Attributes should be the version of the NHibernate schema used to generate it (=> the version of NHibernate library). - In the design of this project, performance is a (very) minor goal :) Easier implementation and maintenance are far more important because you can (and should) avoid to use this library in production (Cf. the first tip in ). + + In the design of this project, performance is a (very) minor goal :). Easier implementation and maintenance + are far more important because you can use this library to generate statically the mapping files and use them instead in production. + (Cf. the first tip in .) +
- diff --git a/doc/reference/modules/preface.xml b/doc/reference/modules/preface.xml index f8ed4cc4284..d282538f558 100644 --- a/doc/reference/modules/preface.xml +++ b/doc/reference/modules/preface.xml @@ -66,7 +66,7 @@ - The Community Area on the NHibernate website is a good source for + The Community Area on the NHibernate website is a good source for design patterns and various integration solutions (ASP.NET, Windows Forms). diff --git a/doc/reference/modules/quickstart.xml b/doc/reference/modules/quickstart.xml index 1185ae2cdfa..f949f5c422e 100644 --- a/doc/reference/modules/quickstart.xml +++ b/doc/reference/modules/quickstart.xml @@ -154,6 +154,10 @@ Mapping the cat + + This tutorial directly uses xml mapping files. This is just one among many mapping solutions + NHibernate provides, see . + The Cat.hbm.xml mapping file contains the metadata required for the object/relational mapping. The metadata includes declaration diff --git a/doc/reference/modules/toolset_guide.xml b/doc/reference/modules/toolset_guide.xml index 1e885781bcf..023a3152294 100644 --- a/doc/reference/modules/toolset_guide.xml +++ b/doc/reference/modules/toolset_guide.xml @@ -370,7 +370,7 @@ new SchemaExport(cfg).Create(false, true);]]>
A more detailed guide of hbm2net is available in - http://nhibernate.info/blog/2009/12/12/t4-hbm2net-alpha-2.html + https://nhibernate.info/blog/2009/12/12/t4-hbm2net-alpha-2.html diff --git a/doc/reference/modules/transactions.xml b/doc/reference/modules/transactions.xml index 47340ebb931..5a6a7c8dc5a 100644 --- a/doc/reference/modules/transactions.xml +++ b/doc/reference/modules/transactions.xml @@ -633,9 +633,11 @@ finally will then fail to use it. - As of NHibernate v5.0, session auto-enlistment can be disabled from the session builder + Session auto-enlistment can be controlled from the session builder obtained with ISessionFactory.WithOptions(), using the - AutoJoinTransaction option. The connection may still enlist itself + AutoJoinTransaction option. It can also be controlled at the configuration level, + see transaction.auto_join in . + When auto-join is disabled, the connection may still enlist itself if connection string Enlist setting is not false. A session can explicitly join the current system transaction by calling ISession.JoinTransaction(). diff --git a/doc/reference/reference.build b/doc/reference/reference.build index 9a07ff295bb..2a4c52aa851 100644 --- a/doc/reference/reference.build +++ b/doc/reference/reference.build @@ -8,7 +8,7 @@ - + diff --git a/releasenotes.txt b/releasenotes.txt index 35275bdd3b3..92f92abb7a0 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -1,3 +1,459 @@ +Build 5.3.5 +============================= + +Release notes - NHibernate - Version 5.3.5 + +2 issues were resolved in this release. + +** Bug + + * #2599 WrongClassException in Linq query + +** Task + + * #2606 Release 5.3.5 + +Build 5.3.4 +============================= + +Release notes - NHibernate - Version 5.3.4 + +6 issues were resolved in this release. + +** Bug + + * #2580 InvalidWithClauseException when join polymorphic entity + * #2559 Regression in caching linq query with ThenFetchMany statement. + * #2549 ApplyFilter does not work on join statements in LINQ + * #2537 Unable to cast "System.Linq.Expressions.UnaryExpression" to "System.Linq.Expressions.LambdaExpression". + +** Task + + * #2578 Add missing possible breaking changes for #2365 + * #2587 Release 5.3.4 + +As part of releasing 5.3.4, one missing 5.3.0 possible breaking change has been added, about +custom method generators for Linq. See 5.3.0 possible breaking changes. + +Build 5.3.3 +============================= + +Release notes - NHibernate - Version 5.3.3 + +16 issues were resolved in this release. + +** Bug + + * #2519 Fix parameter caching for Linq provider + * #2515 InvalidCastException for Linq query with subquery + * #2514 Entity with field interceptor are not correctly passed as Linq parameters + * #2512 Linq queries with a condition after a projection on a collection fail + * #2511 Linq Fetch over component after fetching a many-to-one throws exception + * #2508 OnPreUpdateCollection - Passed entity instance X is not of expected type Y + * #2499 Cast operation fails when an enum is mapped as an AnsiString + * #2490 Unnecessary cast in sql with Linq are causing performance issues + * #2488 Fix parameter detection for Equals and CompareTo methods for Linq provider + * #2485 Throw entity not mapped exception for entity join in hql if possible + * #2484 Entity Joins are not polymorphic in hql + * #2476 Hashset add returns true instead of false + * #2474 Fetch all lazy properties when entity is already loaded fails + * #2471 AsQueryable() on collection throws if applied after Where statement + +** Task + + * #2482 Add missing possible breaking changes for #2010 + * #2527 Release 5.3.3 + +As part of releasing 5.3.3, two missing 5.3.0 possible breaking changes have been added, about +uninitialized extra lazy collections and SQLite schema validation. See 5.3.0 possible breaking changes. + +Build 5.3.2 +============================= + +Release notes - NHibernate - Version 5.3.2 + +6 issues were resolved in this release. + +** Bug + + * #2468 Null reference at NHibernate.Util.AsyncReaderWriterLock.ReadLock() + * #2465 Linq contains on a value collection is failing + * #2463 Path expected for join + * #2458 Evaluatable expressions with parameters are no more pre-evaluated + * #2453 Fail to cast enum as nvarchar for Linq Contains + +** Task + + * #2472 Release 5.3.2 + +Build 5.3.1 +============================= + +Release notes - NHibernate - Version 5.3.1 + +7 issues were resolved in this release. + +** Bug + + * #2445 LINQ queries with a cast from int to uint fail + * #2440 InvalidCastException for Future Criteria with aliased fetches + * #2439 Invalid parameter conversion for enums + * #2437 Invalid cast on nullable custom type with Linq + +** Task + + * #2450 Release 5.3.1 + * #2436 Fix old http://nhibernate.info URIs + * #2435 Fix iconUrl warning + +Build 5.3.0 +============================= + +Release notes - NHibernate - Version 5.3.0 + +220 issues were resolved in this release. + + ##### Possible Breaking Changes ##### + * A distributed cache may hold conflicting timestamps after upgrade for as much as twelve hours. + Consider flushing a distributed cache after upgrade to avoid any issue. Do not share a distributed + cache with applications using an earlier version of NHibernate. + * The counter id generator may generate conflicting ids for as much as twelve hours after upgrade. + * `update` and `delete` statements will now take into account any enabled filter on the entities + they update or delete, while previously they were ignoring them. (`insert` statements will also take + them into account, but previously they were failing instead of ignoring enabled filters.) + * ISession.Persist will no more trigger immediate generation of identifier. + * Bags will no more be loaded with "null" entities, they will be filtered out. + * Setting the value of an uninitialized lazy property will no more trigger loading of all the lazy + properties of the entity. + * If an uninitialized lazy property has got its value set, without any other subsequent lazy property + load on the entity, a dynamic update will occur on flush, even if the entity has dynamic updates + disabled. This update will occur even if the set value is identical to the currently persisted + property value. + * Assigning an uninitialized proxy to a `no-proxy` property will no more trigger the proxy + initialization. Moreover, reading the property afterwards will no more unwrap the assigned proxy, + but will yield it. + * A class having an explicitly implemented interface declaring a member with the same name than the + class id will have its proxies trigger a lazy load if the interface "id" is accessed. + * SQLite: in order to avoid a floating point division bug losing the fractional part, decimal are now + stored as `REAL` instead of `NUMERIC`. Both are binary floating point types, excepted that `NUMERIC` + stores integral values as `INTEGER`. This change may cause big integral decimal values to lose more + precision in SQLite. + * SQLite: non supported SQL type names previously used by NHibernate, resulting in unexpected actual typing, + have been fixed. This causes databases generated by a previous NHibernate version to fail schema validation + by 5.3 or higher versions. See #2507 for more information. + * Custom dialects used for databases that do not support cross join will have to override + `SupportsCrossJoin` property and set it to `false`. + * `VisitorParameters.ConstantToParameterMap` may contain the same parameter for multiple constant + expressions. + * `ICache` caches yielded by the session factory will be `CacheBase` wrappers around the cache actually + provided by the cache provider, if it was not deriving from `CacheBase`. + * Calling `IList.RemoveAt` or `IList<>.RemoveAt` on an uninitialized list with a negative number + will now throw an `ArgumentOutOfRangeException`. + * Calling `IList.RemoveAt` or `IList<>.RemoveAt` on an uninitialized list mapped as `lazy="extra"` + with a number that is equal or higher that the current collection size will now throw an + `ArgumentOutOfRangeException`. + * Calling `IList.Insert` or `IList<>.Insert` on an uninitialized list with a negative number will + now throw an `ArgumentOutOfRangeException`. + * Calling `IList.Insert` or `IList<>.Insert` on an uninitialized list mapped as `lazy="extra"` + with a number that is higher that the current collection size will now throw an + `ArgumentOutOfRangeException`. + * Getting or setting a value with `IList.this[int index]` or `IList<>.this[int index]` on an uninitialized + list with a negative number will now throw an `ArgumentOutOfRangeException`. + * Setting a value with `IList.this[int index]` or `IList<>.this[int index]` on an uninitialized list + mapped as `lazy="extra"` with a number that is equal or higher that the current collection size will now + throw an `ArgumentOutOfRangeException`. + * Calling `IDictionary<,>.Add` or `ICollection<>.Add` on an uninitialized map mapped as `lazy="extra"` with + a key that already exists will now throw an `ArgumentException`. + * Calling `IDictionary<,>.Remove` or `ICollection<>.Remove` on an uninitialized map mapped as `lazy="extra"` + with a key that does not exist will now return false. + * Map dirtiness is now evaluated by `EqualityComparer.Default` when setting an existing key value + with `IDictionary<,>.this[]` on an initialized map. + * Calling `ISet<>.Add` on an uninitialized set mapped as `lazy="extra"` with a transient element that + already exists in the set will now return false. + * Calling `ISet<>.Add` or `ICollection<>.Add` on an uninitialized set mapped as `lazy="true"` with a + transient element that does not override `Equals` method will not initialize the collection. + * Linq custom generators deriving from `BaseHqlGeneratorForMethod` should override the + `TryGetCollectionParameter` method if they have to support parameter lists. + +** Bug + + * #2425 NRE with nullable subselect value in Linq + * #2421 Chapter 26: Best Practices, error about identifier recommendations + * #2410 Second level cache failures with CoreMemoryCaches + * #2380 OData NotSupportedException MemberInit on base class member + * #2365 Add Linq parameter type detection + * #2346 Fix SQLite typing + * #2336 Intermittent null reference exception on CloseConnection + * #2324 Update IIsEntityDecider to use ExpressionsHelper.TryGetMappedType + * #2319 Upgrade AsyncGenerator to 0.18.1 + * #2299 Proper query plan caching for DML LINQ queries + * #2286 Wrong sql if used joined-subclass with filters for key columns + * #2278 IInterceptor.OnPrepareStatement results not used in insert/update commands + * #2266 Fix comment for Restrictions.IsEmpty + * #2255 Fix a flaky test + * #2245 Add sqlite.binaryguid to configuration schema + * #2244 SelectMany Linq extension does not work correctly - subsequent FetchMany fails + * #2233 Fix possible issue with async code for delayed entity inserts + * #2231 Invalid alias name used in Linq Joins + * #2222 NHibernate query plan for Linq Dml is not cached + * #2219 Fix BuildTool output path + * #2215 Fix ShowBuildMenu.sh + * #2181 Skip null entities when bag is populated + * #2164 Do not call GC.SuppressFinalize from finalizer thread + * #2158 Proper support for IN clause for composite values in Criteria + * #2147 Improve async locking + * #2144 AdoTransaction memory leak (5.2.5) + * #2137 NullReferenceException in EntityEntry.GetLoadedValue on an update of a never loaded detached entity + * #2099 "Composite Index" not working with inheritance + * #2088 Fix cacheable CreateSQLQuery throws on query with AddJoin + * #2085 Duplicated methods generated in proxies + * #2067 Wrong proxy built for base class with interfaced sub-classes + * #2064 One-to-one properties not appearing in Select() projection result set + * #2053 Dml Style Update fails with static where sql in mapping + * #2038 Fix a typo on the memcached distributed cache description in the docs + * #2029 Incorrect SQL for cast inside an aggregate (MS SQL) + * #2019 Update symbol package format and add Sourcelink + * #2000 Fixed Equals method for transformers + * #1997 Fix criteria collection ordering + * #1994 Extra Select for every "outfiltered" Element + * #1993 InvalidCastException when merging a collection with a lazy property + * #1985 DateTime.xxxx are not supported in SelectGroup + * #1965 Fix code sample in docs, section 10.4.2 + * #1956 Fix lazy property caching + * #1921 DML insert fails when a filter is enabled + * #1738 Refresh of locally removed collection item crashes with "instance was not in a valid state" + * #1480 Fix cache build for honoring mapped concurrency + * #1368 NH-3778 - Crash when performing a Linq query on a one-to-one mapped reference + * #1341 NH-3848 - Child collection fetched using left outer join with on clause or where clause restrictions on fetched collection shouldn’t be stored in second level cache. + * #1319 NH-3549 - BasicFormatter throws exceptions for certain types of data containing "signal words" + * #1312 NH-3493 - Cannot use alias between more than 1 level of nested queries + * #1310 NH-3478 - StatefulPersistenceContext.RemoveEntity KeyNotFoundException on Evict + * #1309 NH-3469 - Impossible to load one-to-one association with LINQ for composite-id + * #1274 NH-3117 - Query on one-to-one property returns incorrect results + * #1263 NH-2991 - Criteria withClause doesn't work in case of many to many collections + * #1228 NH-2648 - HQL with joins in sub-select creates wrong SQL + * #1206 NH-1761 - Criteria query inserts an extra order by expression when using JoinType.LeftOuterJoin and Projections + * #1158 NH-3492 - SqlClientBatchingBatcher incorrectly ignoring per-SessionFactory Settings properties + * #1128 NH-3210 - NHibernate Linq Provider does cross join or left outer join and not inner join (even if outer-join=false on many-to-one mapping) + * #1124 NH-3155 - Linq subquery with group is not supported + * #1125 NH-3178 - Exception when using one-to-one properties in a criteria projections + * #1117 NH-3079 - Cannot use a sql custom loader with a composite ID + * #1107 NH-2983 - Coalesce in projection doesn't work if there is more than 1 Coalesce + * #1103 NH-2926 - CriteriaQuery - Unable to sort by composite-id + * #1100 NH-2892 - The columns containing reserved words are not quoted + * #1059 NH-1001 - Select statement issued for each not-found=ignore + * #1047 NH-3865 - Swallowed ArgumentNullException with dynamic composite id + * #1015 NH-2951 - Missing alias in hql update (select) statement with joined subclasses + * #1006 NH-2714 - Properties mapped inside a group are not set when retrieving object + +** New Feature + + * #2411 Add an option to register a custom pre-transformer for a Linq query + * #2392 Add locate support for SQLite + * #2362 Add support for lt, gt, le, ge oData operators on strings + * #2349 Add support for Oracle binary floating point types + * #2347 Support fetching individual lazy properties for Criteria EntityProjection + * #2327 Add cross join support for Hql and Linq query provider + * #2313 Add overloads to ISession.Get taking both an entityName and a lockMode + * #2259 Schema auto-update should throw errors + * #2221 Support MemberInit expression in group by + * #2216 Add a driver to support Microsoft.Data.SqlClient provider + * #2209 IN clause support in hql for composite keys on databases without row value constructor support + * #2156 Support basic arithmetic operations (+, -, *, /) in QueryOver + * #2135 Support OData GroupBy/Aggregate + * #2116 Ability to replace ConfigurationManager with a custom config provider + * #2108 Multi-Tenancy: Implement tenant per Database strategy + * #2107 Port Hibernate's Aggregate functions for subqueries + * #2106 Port Hibernate's support subqueries in HQL as CASE statement alternatives + * #2100 Allow to override default types with length or precision parameters + * #2097 Add support for fetching an individual lazy property with Criteria + * #2090 Add support for caching fetched relations with Criteria + * #2080 Add ability to set custom collection type as a string in mapping by code + * #2049 Fix property-ref ignoring not-found="exception" mapping + * #1949 Port Hibernate's lazy attribute fetch groups + * #1922 Add support for fetching an individual lazy property with hql and linq provider + * #1861 Lazy loading and Eager initialization for Component + * #1376 Composite id is incorrectly expanded in SQL + * #1195 NH-4078 - LINQ fetched collections aren't cached + * #981 NH-3873 - Explicit joins on unrelated classes + * #959 NH-4048 - Support non-deterministic/db-side-only methods in Linq + * #896 NH-1432 - Expression.Sql should support aliases other than {alias} + +** Improvement + + * #2404 Allow overriding default CastFunction + * #2401 Optimize JoinWalker.WhereString method + * #2399 Optimize PersistentGenericBag.EqualsSnapshot + * #2394 Optimize PersistentGenericSet snapshot + * #2352 Improve performance of ReflectHelper.GetMethod/Definition + * #2350 Optimize LINQ batch item processing for queries with overridden result type + * #2316 Add multiple arguments support for ISQLFunction + * #2315 Add SetFlushMode for QueryOver and Linq + * #2295 Optimize filter applying logic + * #2287 Allow customizing 'alias to bean' property not found behavior + * #2284 Make persistent collection classes implement the IReadOnly* interfaces + * #2270 IQueryOver is lacking some options + * #2254 Add dev build version suffix + * #2249 Improve handling of SqlCeParameter.SqlDbType + * #2248 Remove most RemoveAsAliasesFromSql usages + * #2241 Avoid duplicating parameters in LINQ query + * #2238 Call generic query.List from Linq queries + * #2235 Configure log4net from embedded resource log4net.xml in tests + * #2232 Use SqlStringBuilder for batching Future/QueryBatch queries + * #2226 Use DateTime.UtcNow for timestamps + * #2225 Avoid unnecessary locking via MethodImplOptions.Synchronized + * #2223 Short-Circuit SessionFactoryImpl.Close() when already closed + * #2214 Allow configuring auto-join transaction globally + * #2213 Add a shortcut to reduce Transaction.Current reads + * #2211 Port SupportsRowValueConstructorSyntaxInInList values + * #2182 Upgrade AsyncGenerator to 0.17.1 + * #2166 Optimize usages of SqlString.Append + * #2163 Add virtual DefaultQueryProvider.CreateWithOptions + * #2162 Use collection types for private members + * #2161 Optimize ToArray conversions + * #2159 Unify handling of composite values in hql and Criteria + * #2153 Use generic parameters in ActionQueue + * #2139 Add ability to set fetch for mapping in mapping by code + * #2131 Create Stopwatch only if stats is enabled + * #2126 Upgrade AsyncGenerator to 0.14.0 + * #2125 Skip logger default initialization logic when logger provided by user + * #2123 Use Assert.Throws instead of try-catch in tests + * #2119 Obsolete interfaces for Loquacios configuration and use config classes directly + * #2117 Replace array concatenation with hand written append + * #2115 Statefull Session commit performance issue when nothing changed and second level cache with query cache enabled + * #2091 Obsolete StringHelper.Join + * #2084 Improve one-to-one handling in queries + * #2082 Use entities prepared by Loader in hql select projections + * #2078 Avoid unnecessary join for entity comparisons in with clause + * #2071 Support subclass mapping with EntityName based base class mapping + * #2061 Reduce cast usage for COUNT aggregate and add support for Mssql count_big + * #2058 DB2 dialect enhancements + * #2056 Optimize GetOrphans and remove wrong checks from IsNotTransientSlow + * #2041 Hql entity join fixes + * #2039 Use generic CollectingNodeVisitor in hql parser + * #2036 Reduce cast usage for aggregate functions + * #2032 Allow using ON instead of WITH in hql + * #2024 Refactor to simplify netfx retargeting + * #2022 Make CancellationToken optional for async Linq DML queries + * #2010 Add new collection operation queue mechanism + * #2009 Add support for IDictionary to IQuery.SetProperties + * #2007 Dispose session in cascade tests + * #2006 Skip Topological sorting if not required + * #2003 Avoid some cases of Type -> string -> Type conversion in Mapping By Code + * #2002 Refactor DependentAlias handling logic in JoinWalker + * #1999 Optimize DistinctRootEntityResultTransformer + * #1989 Optimize ProxyCacheEntry equality for the same instance + * #1988 Improve exception on user types lacking some interfaces + * #1984 Reduce SessionIdLoggingContext creation + * #1981 Remove AbstractLazyInitializer unused field + * #1979 Refactor sequential select + * #1977 Obsolete IDeserializationCallback from EntityKey + * #1972 Port Hibernate's EntityKey optimization + * #1968 Optimize StaticProxyFactory GetProxy and GetFieldInterceptionProxy methods + * #1955 Optimize batchable cache calls for cached queries + * #1947 Partially port Hibernate's current field interceptor mechanism + * #1946 Port Hibernate's BytecodeEnhancementMetadata + * #1944 Extend IAccessOptimizer to support getting/setting single property value + * #1943 Skip initialization of lazy properties when setting one + * #1923 Obsolete StringHelper.Replace + * #1860 LINQ "==" operator generates OR with IS NULL + * #1754 Delay entity insert on Persist until session is flushed + * #1627 Refactored session List method for Criteria + * #913 NH-3704 - Allow Setting Dynamic Component Templates From Dictionary + * #864 NH-2379 - Add support of Left Joins to Linq Provider + * #803 NH-2521 - Session.EnableFilter method should work for HQL-DML statement + * #780 NH-1200 - Exception occurs when using criteria exist queries + * #767 NH-3892 - Add ability to coalesce using a property instead of a constant + * #722 NH-1953 - Support Future for collection filters + * #476 Eliminated double Persister resolution in Loader.InstanceNotYetLoaded flow + +** Task + + * #2433 Improve slightly mapping documentation + * #2432 Document the caches configuration providers + * #2430 Document cache.serializer setting of CoreDistributedCache + * #2397 Update GitReleaseManager + * #2391 Use latest Firebird for AppVeyor and Travis + * #2388 Release 5.3 + * #2382 Refactor debug logging in AbstractBatcher + * #2381 Use optimized Dictionary.Remove(key, out value) in .NET Core + * #2379 Simplify swap items logic in LINQ Visitors + * #2377 Use dotnet to push packages to nuget + * #2376 Add MyGet gallery link to readme + * #2368 Replace SafetyEnumerable with OfType where applicable + * #2363 Upgrade AsyncGenerator to 0.18.2 + * #2356 Obsolete IdentitySet class + * #2354 Do not require Mono to build on not Windows + * #2353 Update Microsoft.SourceLink.GitHub to 1.0.0 + * #2351 Get rid of JoinedEnumerable and SingletonEnumerable + * #2348 Use static ReferenceComparer for reference comparisons + * #2308 Merge 5.2.7 + * #2294 Add StackExchangeRedis cache provider documentation + * #2293 Update RtMemoryCache framework dependency + * #2265 Fix code style issues + * #2251 Publish development nightly builds on nuget + * #2205 Merge 5.2.6 + * #2171 Upgrade NUnit + * #2122 Update AsyncGenerator to 0.13.3 + * #2016 Avoid recursive calls in BatchFetchQueue + * #2014 Obsolete Environment.Properties + * #1973 Investigate licenseUrl deprecation + * #1971 Add SourceLink to allow NuGet package debugging + * #1940 Allow to provide dev specific properties in NHibernate.dev.props + * #1936 Upgrade AsyncGenerator to 0.13.1 + +** Tests + + * #2384 Tests to verify NH-2329 is obsolete + * #2360 Add OData test for single property $expand + * #2089 Bidirectional list fails if session only knows about child + * #2066 Tests for proxy interface handling + * #1966 Test duplicated join on some Linq queries + +Build 5.2.7 +============================= + +Release notes - NHibernate - Version 5.2.7 + +4 issues were resolved in this release. + +** Bug + + * #2302 Backport sqlite.binaryguid to configuration schema + * #2298 Dml Linq Update Produce Wrong Sql + * #2296 Missing Row Count in Debug Log for Future queries + +** Task + + * #2303 Release 5.2.7 + +Build 5.2.6 +============================= + +Release notes - NHibernate - Version 5.2.6 + +11 issues were resolved in this release. + +** Bug + + * #2190 Cannot instantiate a SessionFactory using Prevalence cache + * #2177 New Fetch() method in QueryOver returns IQueryOver<> instead of QueryOver<> + * #2172 Using DependentTransaction fails + * #2175 Subcriteria on component collection generates incorrect join alias + * #2173 Futures not batching correctly in NH 5.2.x + * #2141 Undefined call to Equals object in collection during flush just before commit + * #2127 StackExchangeRedisCache with PreferMultipleGet = true calls GetMany multiple times + * #2110 Wrong GUID to string conversion with SQLite BinaryGuid=False + +** Task + + * #2200 Release 5.2.6 + * #2199 Upgrade AsyncGenerator to 0.8.2.12 + +** Tests + + * #2132 Add GetMany for ReadWriteCache tests + Build 5.2.5 ============================= @@ -342,6 +798,22 @@ Release notes - NHibernate - Version 5.2.0 As part of releasing 5.2.0, a misnamed setting in 5.0.0 release notes has been fixed: transaction.use_connection_on_system_events correct name is transaction.use_connection_on_system_prepare +Build 5.1.7 +============================= + +Release notes - NHibernate - Version 5.1.7 + +** Bug + * #2298 Dml Linq Update Produce Wrong Sql + +Build 5.1.6 +============================= + +Release notes - NHibernate - Version 5.1.6 + +** Bug + * #2172 Using DependentTransaction fails + Build 5.1.5 ============================= @@ -605,6 +1077,14 @@ Release notes - NHibernate - Version 5.1.0 As part of releasing 5.1.0, a missing 5.0.0 possible breaking change has been added about inequality semantic in LINQ queries. See 5.0.0 possible breaking changes. +Build 5.0.8 +============================= + +Release notes - NHibernate - Version 5.0.8 + +** Bug + * #2172 Using DependentTransaction fails + Build 5.0.7 ============================= diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index 881e7096216..a8b58b1da6a 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -1,10 +1,16 @@ projects: - filePath: NHibernate/NHibernate.csproj - targetFramework: net461 + targetFramework: netcoreapp2.0 concurrentRun: true applyChanges: true + suppressDiagnosticFailures: + - pattern: ^.*(Hql\.g).*$ analyzation: methodConversion: +#TODO 6.0: Remove ignore rule for IQueryBatchItem.ProcessResults + - conversion: Ignore + name: ProcessResults + containingTypeName: IQueryBatchItem - conversion: Ignore name: PostProcessInsert containingTypeName: HqlSqlWalker @@ -104,6 +110,10 @@ - conversion: Ignore name: GetEnumerator containingTypeName: IFutureEnumerable +# TODO 6.0: Consider if ComputeFlattenedParameters should remain ignored or not + - conversion: Ignore + name: ComputeFlattenedParameters + containingTypeName: SqlQueryImpl - conversion: ToAsync name: ExecuteReader containingTypeName: IBatcher @@ -115,7 +125,7 @@ - conversion: ToAsync rule: Cache - conversion: ToAsync - rule: TransactionCompletion + rule: TransactionCompletion typeConversion: - conversion: Ignore name: EnumerableImpl @@ -150,9 +160,6 @@ transformation: configureAwaitArgument: false localFunctions: true - asyncLock: - type: NHibernate.Util.AsyncLock - methodName: LockAsync documentationComments: addOrReplaceMethodSummary: - name: Commit @@ -166,45 +173,41 @@ - type: AsyncGenerator.Core.Plugins.EmptyRegionRemover assemblyName: AsyncGenerator.Core - filePath: NHibernate.DomainModel/NHibernate.DomainModel.csproj - targetFramework: net461 + targetFramework: netcoreapp2.0 concurrentRun: true applyChanges: true + suppressDiagnosticFailures: + - pattern: ^.*(Hql\.g).*$ analyzation: scanMethodBody: true scanForMissingAsyncMembers: - all: true - filePath: NHibernate.Test/NHibernate.Test.csproj - targetFramework: net461 + targetFramework: netcoreapp2.0 concurrentRun: true applyChanges: true + suppressDiagnosticFailures: + - pattern: ^.*(Hql\.g).*$ analyzation: methodConversion: + - conversion: Ignore + name: CanUseDependentTransaction + containingTypeName: DistributedSystemTransactionFixture + - conversion: Ignore + name: CanUseSessionWithManyDependentTransaction + containingTypeName: DistributedSystemTransactionFixture + - conversion: Ignore + name: CanUseDependentTransaction + containingTypeName: SystemTransactionFixture + - conversion: Ignore + name: CanUseSessionWithManyDependentTransaction + containingTypeName: SystemTransactionFixture - conversion: Copy name: AfterTransactionCompletionProcess_EvictsFromCache - - conversion: Copy - hasAttributeName: OneTimeSetUpAttribute - - conversion: Copy - hasAttributeName: OneTimeTearDownAttribute - - conversion: Copy - hasAttributeName: SetUpAttribute - - conversion: Copy - hasAttributeName: TearDownAttribute - - conversion: Smart - hasAttributeName: TestAttribute - - conversion: Smart - hasAttributeName: TheoryAttribute asyncExtensionMethods: projectFiles: - fileName: LinqExtensionMethods.cs projectName: NHibernate - preserveReturnType: - - hasAttributeName: TestAttribute - - hasAttributeName: TheoryAttribute - alwaysAwait: - - hasAttributeName: TestAttribute - - hasAttributeName: TheoryAttribute - - hasAttributeName: SetUpAttribute - - hasAttributeName: TearDownAttribute typeConversion: - conversion: Ignore name: ObjectAssert @@ -214,32 +217,25 @@ name: MultiThreadRunner - conversion: Ignore name: PeVerifier - - conversion: Ignore - hasAttributeName: IgnoreAttribute - - conversion: NewType - hasAttributeName: TestFixtureAttribute - - conversion: NewType - anyBaseTypeRule: HasTestFixtureAttribute - conversion: Ignore rule: IsTestCase - conversion: Ignore anyBaseTypeRule: IsTestCase - ignoreSearchForMethodReferences: - - hasAttributeName: TheoryAttribute - - hasAttributeName: TestAttribute + executionPhase: PostProviders ignoreDocuments: - filePathEndsWith: Linq/MathTests.cs - filePathEndsWith: Linq/ExpressionSessionLeakTest.cs - filePathEndsWith: Linq/NorthwindDbCreator.cs cancellationTokens: - withoutCancellationToken: - - hasAttributeName: TestAttribute - - hasAttributeName: TheoryAttribute + enabled: true scanMethodBody: true scanForMissingAsyncMembers: - all: true registerPlugin: - - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder + - type: AsyncGenerator.Core.Plugins.NUnitPlugin + parameters: + - name: createNewTypes + value: true assemblyName: AsyncGenerator.Core - type: AsyncGenerator.Core.Plugins.TransactionScopeAsyncFlowAdder assemblyName: AsyncGenerator.Core @@ -281,7 +277,7 @@ methodRules: - containingType: NHibernate.Action.IAfterTransactionCompletionProcess - containingType: NHibernate.Action.IBeforeTransactionCompletionProcess - containingType: NHibernate.Action.EntityAction - name: BeforeTransactionCompletionProcessImpl + name: BeforeTransactionCompletionProcessImpl name: TransactionCompletion - filters: - containingNamespace: NHibernate @@ -291,6 +287,7 @@ methodRules: - containingType: NHibernate.Linq.DmlExtensionMethods - containingType: NHibernate.Linq.InsertBuilder - containingType: NHibernate.Linq.UpdateBuilder + - containingType: NHibernate.Multi.IQueryBatch name: PubliclyExposedType - filters: - hasAttributeName: ObsoleteAttribute @@ -302,6 +299,3 @@ typeRules: - filters: - name: TestCase name: IsTestCase -- filters: - - hasAttributeName: TestFixtureAttribute - name: HasTestFixtureAttribute diff --git a/src/NHibernate.DomainModel/Bar.cs b/src/NHibernate.DomainModel/Bar.cs index dfbb9dde84b..262d9d512e3 100644 --- a/src/NHibernate.DomainModel/Bar.cs +++ b/src/NHibernate.DomainModel/Bar.cs @@ -12,7 +12,6 @@ public class Bar : Abstract, BarProxy, INamed private string _name = "bar"; private object _object; - /// /// Gets or sets the X /// @@ -31,7 +30,6 @@ public string BarString set { _barString = value; } } - /// /// Gets or sets the _barComponent /// @@ -41,7 +39,6 @@ public FooComponent BarComponent set { _barComponent = value; } } - /// /// Gets or sets the _baz /// @@ -51,7 +48,6 @@ public Baz Baz set { _baz = value; } } - /// /// Gets or sets the _name /// @@ -61,7 +57,6 @@ public string Name set { _name = value; } } - /// /// Gets or sets the _object /// @@ -71,4 +66,4 @@ public object Object set { _object = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Baz.cs b/src/NHibernate.DomainModel/Baz.cs index acb2c846dcd..8df1fa87dcc 100644 --- a/src/NHibernate.DomainModel/Baz.cs +++ b/src/NHibernate.DomainModel/Baz.cs @@ -260,7 +260,6 @@ public string[] StringArray set { _stringArray = value; } } - /// /// Gets or sets the fooArray /// @@ -270,7 +269,6 @@ public FooProxy[] FooArray set { _fooArray = value; } } - /// /// Get/set for fooSet /// @@ -453,4 +451,4 @@ public int CompareTo(object obj) #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Circular.cs b/src/NHibernate.DomainModel/Circular.cs index 1684403c025..6c8386ae004 100644 --- a/src/NHibernate.DomainModel/Circular.cs +++ b/src/NHibernate.DomainModel/Circular.cs @@ -18,7 +18,6 @@ public string Id set { _id = value; } } - public System.Type Clazz { get { return _clazz; } @@ -37,4 +36,4 @@ public object AnyEntity set { _anyEntity = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/ComponentCollection.cs b/src/NHibernate.DomainModel/ComponentCollection.cs index 037a04f0057..3465b5489e3 100644 --- a/src/NHibernate.DomainModel/ComponentCollection.cs +++ b/src/NHibernate.DomainModel/ComponentCollection.cs @@ -20,7 +20,6 @@ public IList Foos set { _foos = value; } } - /// /// Holds the _str /// @@ -49,4 +48,4 @@ public IList Floats set { _floats = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Container.cs b/src/NHibernate.DomainModel/Container.cs index 9c397eb7f51..c1ead671661 100644 --- a/src/NHibernate.DomainModel/Container.cs +++ b/src/NHibernate.DomainModel/Container.cs @@ -80,7 +80,6 @@ public Glarch Glarch } } - private IList _oneToMany; private IList _components; private IList _manyToMany; @@ -94,7 +93,6 @@ public Glarch Glarch // mapping private ISet _ternarySet; - public virtual IList OneToMany { get { return _oneToMany; } @@ -155,4 +153,4 @@ public virtual ISet TernarySet set { _ternarySet = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Detail.cs b/src/NHibernate.DomainModel/Detail.cs index f5ae324c51a..6cd9242c8c2 100644 --- a/src/NHibernate.DomainModel/Detail.cs +++ b/src/NHibernate.DomainModel/Detail.cs @@ -26,7 +26,6 @@ public int I set { _i = value; } } - public ISet SubDetails { get { return _details; } @@ -39,4 +38,4 @@ public int X set { _x = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/DoubleStringType.cs b/src/NHibernate.DomainModel/DoubleStringType.cs index 4c794cb6f3d..b2b6d2fd8c8 100644 --- a/src/NHibernate.DomainModel/DoubleStringType.cs +++ b/src/NHibernate.DomainModel/DoubleStringType.cs @@ -57,7 +57,6 @@ public Object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor s return (first == null && second == null) ? null : new string[] {first, second}; } - public void NullSafeSet(DbCommand st, Object value, int index, bool[] settable, ISessionImplementor session) { string[] strings = (value == null) ? new string[2] : (string[]) value; @@ -107,4 +106,4 @@ public object Replace(object original, object target, ISessionImplementor sessio return DeepCopy(original); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Foo.cs b/src/NHibernate.DomainModel/Foo.cs index b64c5c736d3..7edbc288126 100644 --- a/src/NHibernate.DomainModel/Foo.cs +++ b/src/NHibernate.DomainModel/Foo.cs @@ -347,8 +347,8 @@ public LifecycleVeto OnSave(ISession s) _long = 696969696969696969L - count++; _short = 42; _float = 6666.66f; - //_double = new Double( 1.33e-69 ); // this double is too big for the sap db jdbc driver -// _double = 1.12e-36; + //_double = new Double( 1.33e-69 ); // this double is too big for the sap db jdbc driver + //_double = 1.12e-36; _boolean = true; _byte = 127; _int = 2; @@ -397,7 +397,6 @@ public bool EqualsFoo(Foo other) } } - return (_bool == other.Bool) && ((_boolean == other.Boolean) || (_boolean.Equals(other.Boolean))) && ((_byte == other.Byte) || (_byte.Equals(other.Byte))) @@ -429,7 +428,6 @@ public bool EqualsFoo(Foo other) // return key.GetHashCode() - _string.GetHashCode(); // } - private static int count = 0; } } diff --git a/src/NHibernate.DomainModel/FooComponent.cs b/src/NHibernate.DomainModel/FooComponent.cs index 4bd536eed96..e1c88c7b449 100644 --- a/src/NHibernate.DomainModel/FooComponent.cs +++ b/src/NHibernate.DomainModel/FooComponent.cs @@ -92,6 +92,8 @@ public Int32 Count set { _count = value; } } + public int NotMapped { get; set; } + public DateTime[] ImportantDates { get { return _importantDates; } diff --git a/src/NHibernate.DomainModel/Fum.cs b/src/NHibernate.DomainModel/Fum.cs index ddc1d7e1ee4..73c9aa85879 100644 --- a/src/NHibernate.DomainModel/Fum.cs +++ b/src/NHibernate.DomainModel/Fum.cs @@ -99,7 +99,6 @@ public ISet Friends set { this._friends = value; } } - public LifecycleVeto OnDelete(ISession s) { if (_friends == null) return LifecycleVeto.NoVeto; @@ -117,12 +116,10 @@ public LifecycleVeto OnDelete(ISession s) return LifecycleVeto.NoVeto; } - public void OnLoad(ISession s, object id) { } - public LifecycleVeto OnSave(ISession s) { if (_friends == null) return LifecycleVeto.NoVeto; @@ -140,7 +137,6 @@ public LifecycleVeto OnSave(ISession s) return LifecycleVeto.NoVeto; } - public LifecycleVeto OnUpdate(ISession s) { return LifecycleVeto.NoVeto; @@ -158,4 +154,4 @@ public MapComponent MapComponent set { _mapComponent = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Glarch.cs b/src/NHibernate.DomainModel/Glarch.cs index 5a3036817c0..83d23397e47 100644 --- a/src/NHibernate.DomainModel/Glarch.cs +++ b/src/NHibernate.DomainModel/Glarch.cs @@ -38,7 +38,6 @@ public int Version set { this._version = value; } } - /// /// Gets or sets the _next /// @@ -48,7 +47,6 @@ public GlarchProxy Next set { _next = value; } } - /// /// Gets or sets the _order /// @@ -58,7 +56,6 @@ public short Order set { _order = value; } } - /// /// Gets or sets the _strings /// @@ -68,7 +65,6 @@ public IList Strings set { _strings = value; } } - /// /// Gets or sets the _stringSets /// @@ -79,7 +75,6 @@ public ISet StringSets set { _stringSets = value; } } - /// /// Gets or sets the _fooComponents /// @@ -89,7 +84,6 @@ public IList FooComponents set { _fooComponents = value; } } - /// /// Gets or sets the _proxyArray /// @@ -99,7 +93,6 @@ public GlarchProxy[] ProxyArray set { _proxyArray = value; } } - /// /// Gets or sets the _proxySet /// @@ -188,4 +181,4 @@ public Multiplicity Multiple set { _name = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Location.cs b/src/NHibernate.DomainModel/Location.cs index 6da9c7a0332..4559856996e 100644 --- a/src/NHibernate.DomainModel/Location.cs +++ b/src/NHibernate.DomainModel/Location.cs @@ -40,7 +40,6 @@ public string CountryCode set { _countryCode = value; } } - public string Description { get { return _description; } @@ -76,4 +75,4 @@ public override int GetHashCode() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/MoreStuff.cs b/src/NHibernate.DomainModel/MoreStuff.cs index 238de291718..4e9238c4bc3 100644 --- a/src/NHibernate.DomainModel/MoreStuff.cs +++ b/src/NHibernate.DomainModel/MoreStuff.cs @@ -15,7 +15,6 @@ public class MoreStuff private IList _stuffs; private string _name; - public string StringId { get { return _stringId; } @@ -52,7 +51,6 @@ public override bool Equals(object obj) return (rhs.IntId == this.IntId && rhs.StringId.Equals(this.StringId)); } - public override int GetHashCode() { return _stringId.GetHashCode(); @@ -60,4 +58,4 @@ public override int GetHashCode() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/NHSpecific/Child.cs b/src/NHibernate.DomainModel/NHSpecific/Child.cs index 9c439a9237c..5eb941631c9 100644 --- a/src/NHibernate.DomainModel/NHSpecific/Child.cs +++ b/src/NHibernate.DomainModel/NHSpecific/Child.cs @@ -54,7 +54,6 @@ public Child FirstSibling set { Siblings.Insert(0, value); } } - public Child SecondSibling { get { return (Child) Siblings[1]; } @@ -115,11 +114,10 @@ public Child[] Friends set { _friends = value; } } - public DateTime FavoriteDate { get { return _favoriteDate; } set { _favoriteDate = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/NHSpecific/NullableInt32.cs b/src/NHibernate.DomainModel/NHSpecific/NullableInt32.cs index 89d599183bf..95abf028f98 100644 --- a/src/NHibernate.DomainModel/NHSpecific/NullableInt32.cs +++ b/src/NHibernate.DomainModel/NHSpecific/NullableInt32.cs @@ -7,7 +7,7 @@ namespace NHibernate.DomainModel.NHSpecific /// A nullable type that wraps an value. /// [TypeConverter(typeof(NullableInt32Converter)), Serializable()] - public struct NullableInt32 : IFormattable, IComparable + public struct NullableInt32 : IFormattable, IComparable, IConvertible { public static readonly NullableInt32 Default = new NullableInt32(); @@ -234,5 +234,94 @@ public static NullableInt32 Parse(string s) // TODO: implement the rest of the Parse overloads found in Int32 #endregion + + #region IConvertible + + public TypeCode GetTypeCode() + { + return _value.GetTypeCode(); + } + + public bool ToBoolean(IFormatProvider provider) + { + return ((IConvertible) _value).ToBoolean(provider); + } + + public char ToChar(IFormatProvider provider) + { + return ((IConvertible) _value).ToChar(provider); + } + + public sbyte ToSByte(IFormatProvider provider) + { + return ((IConvertible) _value).ToSByte(provider); + } + + public byte ToByte(IFormatProvider provider) + { + return ((IConvertible) _value).ToByte(provider); + } + + public short ToInt16(IFormatProvider provider) + { + return ((IConvertible) _value).ToInt16(provider); + } + + public ushort ToUInt16(IFormatProvider provider) + { + return ((IConvertible) _value).ToUInt16(provider); + } + + public int ToInt32(IFormatProvider provider) + { + return ((IConvertible) _value).ToInt32(provider); + } + + public uint ToUInt32(IFormatProvider provider) + { + return ((IConvertible) _value).ToUInt32(provider); + } + + public long ToInt64(IFormatProvider provider) + { + return ((IConvertible) _value).ToInt64(provider); + } + + public ulong ToUInt64(IFormatProvider provider) + { + return ((IConvertible) _value).ToUInt64(provider); + } + + public float ToSingle(IFormatProvider provider) + { + return ((IConvertible) _value).ToSingle(provider); + } + + public double ToDouble(IFormatProvider provider) + { + return ((IConvertible) _value).ToDouble(provider); + } + + public decimal ToDecimal(IFormatProvider provider) + { + return ((IConvertible) _value).ToDecimal(provider); + } + + public DateTime ToDateTime(IFormatProvider provider) + { + return ((IConvertible) _value).ToDateTime(provider); + } + + public string ToString(IFormatProvider provider) + { + return _value.ToString(provider); + } + + public object ToType(System.Type conversionType, IFormatProvider provider) + { + return ((IConvertible) _value).ToType(conversionType, provider); + } + + #endregion } } diff --git a/src/NHibernate.DomainModel/NHSpecific/Parent.cs b/src/NHibernate.DomainModel/NHSpecific/Parent.cs index 2e3cab577bd..0bb468d37b7 100644 --- a/src/NHibernate.DomainModel/NHSpecific/Parent.cs +++ b/src/NHibernate.DomainModel/NHSpecific/Parent.cs @@ -13,7 +13,6 @@ public class Parent private ISet _children; private ISet _adultFriends; - public Parent() { _adultFriends = new SortedSet(new ParentComparer()); @@ -48,4 +47,4 @@ public void AddFriend(Parent friend) _adultFriends.Add(friend); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Address.cs b/src/NHibernate.DomainModel/Northwind/Entities/Address.cs index d224bc50cf7..d2d56fd6823 100755 --- a/src/NHibernate.DomainModel/Northwind/Entities/Address.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Address.cs @@ -61,6 +61,8 @@ public string Fax get { return _fax; } } + public int NotMapped => 1; + public static bool operator ==(Address address1, Address address2) { if (!ReferenceEquals(address1, null) && @@ -114,4 +116,4 @@ public override int GetHashCode() (_fax ?? string.Empty).GetHashCode(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs b/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs index 03c76d428af..21fac97d4ae 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs @@ -12,7 +12,9 @@ public class Animal public virtual Animal Father { get; set; } public virtual IList Children { get; set; } public virtual string SerialNumber { get; set; } - } + + public virtual Animal FatherOrMother => Father ?? Mother; + } public abstract class Reptile : Animal { @@ -30,4 +32,4 @@ public abstract class Mammal : Animal public class Dog : Mammal { } public class Cat : Mammal { } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntityRequired.cs b/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntityRequired.cs new file mode 100644 index 00000000000..80bd33cb9a1 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/AnotherEntityRequired.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public class AnotherEntityRequired + { + public virtual int Id { get; set; } + + public virtual string Output { get; set; } + + public virtual string Input { get; set; } + + public virtual Address Address { get; set; } + + public virtual AnotherEntityNullability InputNullability { get; set; } + + public virtual string NullableOutput { get; set; } + + public virtual AnotherEntityRequired NullableAnotherEntityRequired { get; set; } + + public virtual int? NullableAnotherEntityRequiredId { get; set; } + + public virtual ISet RelatedItems { get; set; } = new HashSet(); + + public virtual ISet RequiredRelatedItems { get; set; } = new HashSet(); + + public virtual bool? NullableBool { get; set; } + } + + public enum AnotherEntityNullability + { + False = 0, + True = 1 + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/DynamicUser.cs b/src/NHibernate.DomainModel/Northwind/Entities/DynamicUser.cs new file mode 100644 index 00000000000..c8c9fb3cf03 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/DynamicUser.cs @@ -0,0 +1,18 @@ +using System.Collections; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public class DynamicUser : IEnumerable + { + public virtual int Id { get; set; } + + public virtual dynamic Properties { get; set; } + + public virtual IDictionary Settings { get; set; } + + public virtual IEnumerator GetEnumerator() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/IEntity.cs b/src/NHibernate.DomainModel/Northwind/Entities/IEntity.cs new file mode 100644 index 00000000000..52c661c9ec1 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/IEntity.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public interface IEntity + { + TId Id { get; set; } + } + + public interface IEntity : IEntity + { + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs index 5da2e35cadb..670b94b5bc5 100755 --- a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs @@ -69,6 +69,16 @@ public IQueryable Users get { return _session.Query(); } } + public IQueryable NumericEntities + { + get { return _session.Query(); } + } + + public IQueryable DynamicUsers + { + get { return _session.Query(); } + } + public IQueryable PatientRecords { get { return _session.Query(); } @@ -94,9 +104,9 @@ public IQueryable Role get { return _session.Query(); } } - public IEnumerable IUsers + public IQueryable IUsers { get { return _session.Query(); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs b/src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs new file mode 100644 index 00000000000..0910e58ce99 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/NorthwindQueryOver.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Linq; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public class NorthwindQueryOver + { + private readonly ISession _session; + + public NorthwindQueryOver(ISession session) + { + _session = session; + } + + public IQueryOver Customers + { + get { return _session.QueryOver(); } + } + + public IQueryOver Products + { + get { return _session.QueryOver(); } + } + + public IQueryOver Shippers + { + get { return _session.QueryOver(); } + } + + public IQueryOver Orders + { + get { return _session.QueryOver(); } + } + + public IQueryOver OrderLines + { + get { return _session.QueryOver(); } + } + + public IQueryOver Employees + { + get { return _session.QueryOver(); } + } + + public IQueryOver Categories + { + get { return _session.QueryOver(); } + } + + public IQueryOver Timesheets + { + get { return _session.QueryOver(); } + } + + public IQueryOver Animals + { + get { return _session.QueryOver(); } + } + + public IQueryOver Mammals + { + get { return _session.QueryOver(); } + } + + public IQueryOver Users + { + get { return _session.QueryOver(); } + } + + public IQueryOver PatientRecords + { + get { return _session.QueryOver(); } + } + + public IQueryOver States + { + get { return _session.QueryOver(); } + } + + public IQueryOver Patients + { + get { return _session.QueryOver(); } + } + + public IQueryOver Physicians + { + get { return _session.QueryOver(); } + } + + public IQueryOver Role + { + get { return _session.QueryOver(); } + } + + public IQueryOver IUsers + { + get { return _session.QueryOver(); } + } + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/NumericEntity.cs b/src/NHibernate.DomainModel/Northwind/Entities/NumericEntity.cs new file mode 100644 index 00000000000..f38641303b1 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/NumericEntity.cs @@ -0,0 +1,29 @@ +namespace NHibernate.DomainModel.Northwind.Entities +{ + public class NumericEntity + { + public virtual short Short { get; set; } + + public virtual short? NullableShort { get; set; } + + public virtual int Integer { get; set; } + + public virtual int? NullableInteger { get; set; } + + public virtual long Long { get; set; } + + public virtual long? NullableLong { get; set; } + + public virtual decimal Decimal { get; set; } + + public virtual decimal? NullableDecimal { get; set; } + + public virtual float Single { get; set; } + + public virtual float? NullableSingle { get; set; } + + public virtual double Double { get; set; } + + public virtual double? NullableDouble { get; set; } + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Product.cs b/src/NHibernate.DomainModel/Northwind/Entities/Product.cs index 5af739cc0d2..8c72b3895d0 100755 --- a/src/NHibernate.DomainModel/Northwind/Entities/Product.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Product.cs @@ -103,9 +103,11 @@ public virtual float ShippingWeight set { _shippingWeight = value; } } + public virtual int NotMapped => 1; + public virtual ReadOnlyCollection OrderLines { get { return new ReadOnlyCollection(_orderLines); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Role.cs b/src/NHibernate.DomainModel/Northwind/Entities/Role.cs index 362eaf0f5f8..2643c03d285 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/Role.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Role.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.DomainModel.Northwind.Entities +namespace NHibernate.DomainModel.Northwind.Entities { public class Role { diff --git a/src/NHibernate.DomainModel/Northwind/Entities/User.cs b/src/NHibernate.DomainModel/Northwind/Entities/User.cs index 35a88e2f7b1..14096dac912 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/User.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/User.cs @@ -26,9 +26,11 @@ public interface IUser Role Role { get; set; } EnumStoredAsString Enum1 { get; set; } EnumStoredAsInt32 Enum2 { get; set; } + IUser CreatedBy { get; set; } + IUser ModifiedBy { get; set; } } - public class User : IUser + public class User : IUser, IEntity { public virtual int Id { get; set; } @@ -46,10 +48,24 @@ public class User : IUser public virtual FeatureSet Features { get; set; } + public virtual User NotMappedUser => this; + public virtual EnumStoredAsString Enum1 { get; set; } + public virtual EnumStoredAsString? NullableEnum1 { get; set; } + public virtual EnumStoredAsInt32 Enum2 { get; set; } + public virtual EnumStoredAsInt32? NullableEnum2 { get; set; } + + public virtual IUser CreatedBy { get; set; } + + public virtual IUser ModifiedBy { get; set; } + + public virtual int NotMapped { get; set; } + + public virtual Role NotMappedRole { get; set; } + public User() { } public User(string name, DateTime registeredAt) @@ -59,10 +75,6 @@ public User(string name, DateTime registeredAt) } } - - - - public enum EnumStoredAsString { Unspecified, Small, Medium, Large } public enum EnumStoredAsInt32 { Unspecified, High, Low } diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntityRequired.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntityRequired.hbm.xml new file mode 100644 index 00000000000..755f83c7c05 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Mappings/AnotherEntityRequired.hbm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/DynamicUser.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/DynamicUser.hbm.xml new file mode 100644 index 00000000000..1b6775b29c6 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Mappings/DynamicUser.hbm.xml @@ -0,0 +1,30 @@ + + + + + select * from Users + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/NumericEntity.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/NumericEntity.hbm.xml new file mode 100644 index 00000000000..2eb696e1acd --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Mappings/NumericEntity.hbm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/User.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/User.hbm.xml index f59dc2956c6..f249de9574e 100644 --- a/src/NHibernate.DomainModel/Northwind/Mappings/User.hbm.xml +++ b/src/NHibernate.DomainModel/Northwind/Mappings/User.hbm.xml @@ -7,17 +7,31 @@ - + + + + + + + + + + + + + + diff --git a/src/NHibernate.DomainModel/OuterKey.cs b/src/NHibernate.DomainModel/OuterKey.cs index 52781386478..9b6d085569a 100644 --- a/src/NHibernate.DomainModel/OuterKey.cs +++ b/src/NHibernate.DomainModel/OuterKey.cs @@ -38,7 +38,6 @@ public override bool Equals(object obj) return true; } - public override int GetHashCode() { unchecked @@ -55,4 +54,4 @@ public override int GetHashCode() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Po.cs b/src/NHibernate.DomainModel/Po.cs index f25b24eb913..d46d910d0fc 100644 --- a/src/NHibernate.DomainModel/Po.cs +++ b/src/NHibernate.DomainModel/Po.cs @@ -13,7 +13,6 @@ public class Po private Top _top; private Lower _lower; - public long Id { get { return _id; } @@ -56,4 +55,4 @@ public Lower Lower set { _lower = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/StringComparator.cs b/src/NHibernate.DomainModel/StringComparator.cs index 69b0893734e..1611397c6c1 100644 --- a/src/NHibernate.DomainModel/StringComparator.cs +++ b/src/NHibernate.DomainModel/StringComparator.cs @@ -20,10 +20,9 @@ public int Compare(string x, string y) return -1; } - return ((String) x).ToLower().CompareTo(((String) y).ToLower()); } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj b/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj index 90eb6a22eb8..9fca761b567 100644 --- a/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj +++ b/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj @@ -32,10 +32,13 @@ + + + - + diff --git a/src/NHibernate.Test/Ado/BatcherFixture.cs b/src/NHibernate.Test/Ado/BatcherFixture.cs index e9f80c182b7..df7a68d452c 100644 --- a/src/NHibernate.Test/Ado/BatcherFixture.cs +++ b/src/NHibernate.Test/Ado/BatcherFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NHibernate.AdoNet; using NHibernate.Cfg; using NUnit.Framework; @@ -44,11 +43,11 @@ public void OneRoundTripInserts() private void Cleanup() { using (ISession s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from VerySimple").ExecuteUpdate(); s.CreateQuery("delete from AlmostSimple").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } @@ -90,13 +89,14 @@ public void OneRoundTripUpdate() Cleanup(); } -#if NETFX - [Test, Ignore("Not fixed yet.")] + [Test, Ignore("Not fixed yet."), NetFxOnly] [Description("SqlClient: The batcher should run all different INSERT queries in only one roundtrip.")] public void SqlClientOneRoundTripForUpdateAndInsert() { +#if NETFX if (Sfi.Settings.BatcherFactory is SqlClientBatchingBatcherFactory == false) Assert.Ignore("This test is for SqlClientBatchingBatcher only"); +#endif FillDb(); @@ -129,12 +129,14 @@ public void SqlClientOneRoundTripForUpdateAndInsert() Cleanup(); } - [Test] + [Test, NetFxOnly] [Description("SqlClient: The batcher log output should be formatted")] public void BatchedoutputShouldBeFormatted() { +#if NETFX if (Sfi.Settings.BatcherFactory is SqlClientBatchingBatcherFactory == false) Assert.Ignore("This test is for SqlClientBatchingBatcher only"); +#endif using (var sqlLog = new SqlLogSpy()) { @@ -145,7 +147,6 @@ public void BatchedoutputShouldBeFormatted() Cleanup(); } -#endif [Test] [Description("The batcher should run all DELETE queries in only one roundtrip.")] diff --git a/src/NHibernate.Test/Ado/GenericBatchingBatcherFixture.cs b/src/NHibernate.Test/Ado/GenericBatchingBatcherFixture.cs index b6ce4cd96ba..144110a612d 100644 --- a/src/NHibernate.Test/Ado/GenericBatchingBatcherFixture.cs +++ b/src/NHibernate.Test/Ado/GenericBatchingBatcherFixture.cs @@ -1,10 +1,11 @@ using System; -using System.Collections; using System.Diagnostics; using System.Linq; using NHibernate.AdoNet; using NHibernate.Cfg; using NHibernate.Dialect; +using NHibernate.Linq; +using NHibernate.SqlCommand; using NUnit.Framework; using Environment = NHibernate.Cfg.Environment; @@ -102,6 +103,62 @@ public void MassivePerformanceTest(bool batched) } } + [Test] + public void InterceptorOnPrepareStatementTest() + { + var interceptor = new DatabaseInterceptor(); + using (var sqlLog = new SqlLogSpy()) + using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.SetBatchSize(5); + for (var i = 0; i < 20; i++) + { + s.Save(new VerySimple { Id = 1 + i, Name = $"Fabio{i}", Weight = 1.45 + i }); + } + + tx.Commit(); + + Assert.That(interceptor.TotalCalls, Is.EqualTo(1)); + var log = sqlLog.GetWholeLog(); + Assert.That(FindAllOccurrences(log, "/* TEST */"), Is.EqualTo(20)); + } + + interceptor = new DatabaseInterceptor(); + using (var sqlLog = new SqlLogSpy()) + using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = s.BeginTransaction()) + { + var future = s.Query().ToFuture(); + s.Query().Where(o => o.Weight > 0).ToFuture(); + + using (var enumerator = future.GetEnumerable().GetEnumerator()) + { + while (enumerator.MoveNext()) { } + } + + tx.Commit(); + + var totalCalls = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries ? 1 : 2; + Assert.That(interceptor.TotalCalls, Is.EqualTo(totalCalls)); + var log = sqlLog.GetWholeLog(); + Assert.That(FindAllOccurrences(log, "/* TEST */"), Is.EqualTo(totalCalls)); + } + + Cleanup(); + } + + private class DatabaseInterceptor : EmptyInterceptor + { + public int TotalCalls { get; private set; } + + public override SqlString OnPrepareStatement(SqlString sql) + { + TotalCalls++; + return sql.Append("/* TEST */"); + } + } + private void BatchInsert(int totalRecords) { Sfi.Statistics.Clear(); @@ -171,10 +228,10 @@ private void DbShoudBeEmpty() private void Cleanup() { using (var s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from VerySimple").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } diff --git a/src/NHibernate.Test/Any/AnyTypeTest.cs b/src/NHibernate.Test/Any/AnyTypeTest.cs index b61d2636ef7..a32aa53ebf7 100644 --- a/src/NHibernate.Test/Any/AnyTypeTest.cs +++ b/src/NHibernate.Test/Any/AnyTypeTest.cs @@ -1,4 +1,3 @@ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Any @@ -24,30 +23,36 @@ protected override string CacheConcurrencyStrategy [Test] public void FlushProcessing() { + var person = new Person(); + var address = new Address(); //http://opensource.atlassian.com/projects/hibernate/browse/HHH-1663 - ISession session = OpenSession(); - session.BeginTransaction(); - Person person = new Person(); - Address address = new Address(); - person.Data = address; - session.SaveOrUpdate(person); - session.SaveOrUpdate(address); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + person.Data = address; + session.SaveOrUpdate(person); + session.SaveOrUpdate(address); + tran.Commit(); + session.Close(); + } - session = OpenSession(); - session.BeginTransaction(); - person = (Person) session.Load(typeof (Person), person.Id); - person.Name = "makingpersondirty"; - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + person = (Person) session.Load(typeof(Person), person.Id); + person.Name = "makingpersondirty"; + tran.Commit(); + session.Close(); + } - session = OpenSession(); - session.BeginTransaction(); - session.Delete(person); - session.Delete(address); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Delete(person); + session.Delete(address); + tran.Commit(); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/App.config b/src/NHibernate.Test/App.config index d3965012af5..40cf748a495 100644 --- a/src/NHibernate.Test/App.config +++ b/src/NHibernate.Test/App.config @@ -3,7 +3,6 @@
-
@@ -52,50 +51,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/NHibernate.Test/Async/Ado/BatcherFixture.cs b/src/NHibernate.Test/Async/Ado/BatcherFixture.cs index 89b61255596..17dfbcbeaa9 100644 --- a/src/NHibernate.Test/Async/Ado/BatcherFixture.cs +++ b/src/NHibernate.Test/Async/Ado/BatcherFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NHibernate.AdoNet; using NHibernate.Cfg; using NUnit.Framework; @@ -56,11 +55,11 @@ public async Task OneRoundTripInsertsAsync() private async Task CleanupAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (ISession s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from VerySimple").ExecuteUpdateAsync(cancellationToken)); await (s.CreateQuery("delete from AlmostSimple").ExecuteUpdateAsync(cancellationToken)); - await (s.Transaction.CommitAsync(cancellationToken)); + await (t.CommitAsync(cancellationToken)); } } @@ -102,51 +101,14 @@ public async Task OneRoundTripUpdateAsync() await (CleanupAsync()); } -#if NETFX - [Test, Ignore("Not fixed yet.")] - [Description("SqlClient: The batcher should run all different INSERT queries in only one roundtrip.")] - public async Task SqlClientOneRoundTripForUpdateAndInsertAsync() - { - if (Sfi.Settings.BatcherFactory is SqlClientBatchingBatcherFactory == false) - Assert.Ignore("This test is for SqlClientBatchingBatcher only"); - - await (FillDbAsync()); - - using (var sqlLog = new SqlLogSpy()) - using (ISession s = Sfi.OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - await (s.SaveAsync(new VerySimple - { - Name = "test441", - Weight = 894 - })); - - await (s.SaveAsync(new AlmostSimple - { - Name = "test441", - Weight = 894 - })); - - await (tx.CommitAsync()); - - var log = sqlLog.GetWholeLog(); - //log should only contain NHibernate.SQL once, because that means - //that we ony generated a single batch (NHibernate.SQL log will output - //once per batch) - Assert.AreEqual(0, log.IndexOf("NHibernate.SQL"), "log should start with NHibernate.SQL"); - Assert.AreEqual(-1, log.IndexOf("NHibernate.SQL", "NHibernate.SQL".Length), "NHibernate.SQL should only appear once in the log"); - } - - await (CleanupAsync()); - } - - [Test] + [Test, NetFxOnly] [Description("SqlClient: The batcher log output should be formatted")] public async Task BatchedoutputShouldBeFormattedAsync() { +#if NETFX if (Sfi.Settings.BatcherFactory is SqlClientBatchingBatcherFactory == false) Assert.Ignore("This test is for SqlClientBatchingBatcher only"); +#endif using (var sqlLog = new SqlLogSpy()) { @@ -157,7 +119,6 @@ public async Task BatchedoutputShouldBeFormattedAsync() await (CleanupAsync()); } -#endif [Test] [Description("The batcher should run all DELETE queries in only one roundtrip.")] diff --git a/src/NHibernate.Test/Async/Ado/GenericBatchingBatcherFixture.cs b/src/NHibernate.Test/Async/Ado/GenericBatchingBatcherFixture.cs index 99bbb4098b3..81e5b8ae118 100644 --- a/src/NHibernate.Test/Async/Ado/GenericBatchingBatcherFixture.cs +++ b/src/NHibernate.Test/Async/Ado/GenericBatchingBatcherFixture.cs @@ -9,15 +9,15 @@ using System; -using System.Collections; using System.Diagnostics; using System.Linq; using NHibernate.AdoNet; using NHibernate.Cfg; using NHibernate.Dialect; +using NHibernate.Linq; +using NHibernate.SqlCommand; using NUnit.Framework; using Environment = NHibernate.Cfg.Environment; -using NHibernate.Linq; namespace NHibernate.Test.Ado { @@ -115,6 +115,62 @@ public async Task MassivePerformanceTestAsync(bool batched) } } + [Test] + public async Task InterceptorOnPrepareStatementTestAsync() + { + var interceptor = new DatabaseInterceptor(); + using (var sqlLog = new SqlLogSpy()) + using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.SetBatchSize(5); + for (var i = 0; i < 20; i++) + { + await (s.SaveAsync(new VerySimple { Id = 1 + i, Name = $"Fabio{i}", Weight = 1.45 + i })); + } + + await (tx.CommitAsync()); + + Assert.That(interceptor.TotalCalls, Is.EqualTo(1)); + var log = sqlLog.GetWholeLog(); + Assert.That(FindAllOccurrences(log, "/* TEST */"), Is.EqualTo(20)); + } + + interceptor = new DatabaseInterceptor(); + using (var sqlLog = new SqlLogSpy()) + using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = s.BeginTransaction()) + { + var future = s.Query().ToFuture(); + s.Query().Where(o => o.Weight > 0).ToFuture(); + + using (var enumerator = (await (future.GetEnumerableAsync())).GetEnumerator()) + { + while (enumerator.MoveNext()) { } + } + + await (tx.CommitAsync()); + + var totalCalls = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries ? 1 : 2; + Assert.That(interceptor.TotalCalls, Is.EqualTo(totalCalls)); + var log = sqlLog.GetWholeLog(); + Assert.That(FindAllOccurrences(log, "/* TEST */"), Is.EqualTo(totalCalls)); + } + + await (CleanupAsync()); + } + + private class DatabaseInterceptor : EmptyInterceptor + { + public int TotalCalls { get; private set; } + + public override SqlString OnPrepareStatement(SqlString sql) + { + TotalCalls++; + return sql.Append("/* TEST */"); + } + } + private async Task BatchInsertAsync(int totalRecords, CancellationToken cancellationToken = default(CancellationToken)) { Sfi.Statistics.Clear(); @@ -184,10 +240,10 @@ public async Task MassivePerformanceTestAsync(bool batched) private async Task CleanupAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from VerySimple").ExecuteUpdateAsync(cancellationToken)); - await (s.Transaction.CommitAsync(cancellationToken)); + await (t.CommitAsync(cancellationToken)); } } diff --git a/src/NHibernate.Test/Async/Any/AnyTypeTest.cs b/src/NHibernate.Test/Async/Any/AnyTypeTest.cs index d2999894197..336e70f3c1c 100644 --- a/src/NHibernate.Test/Async/Any/AnyTypeTest.cs +++ b/src/NHibernate.Test/Async/Any/AnyTypeTest.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Any @@ -35,30 +34,36 @@ protected override string CacheConcurrencyStrategy [Test] public async Task FlushProcessingAsync() { + var person = new Person(); + var address = new Address(); //http://opensource.atlassian.com/projects/hibernate/browse/HHH-1663 - ISession session = OpenSession(); - session.BeginTransaction(); - Person person = new Person(); - Address address = new Address(); - person.Data = address; - await (session.SaveOrUpdateAsync(person)); - await (session.SaveOrUpdateAsync(address)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + person.Data = address; + await (session.SaveOrUpdateAsync(person)); + await (session.SaveOrUpdateAsync(address)); + await (tran.CommitAsync()); + session.Close(); + } - session = OpenSession(); - session.BeginTransaction(); - person = (Person) await (session.LoadAsync(typeof (Person), person.Id)); - person.Name = "makingpersondirty"; - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + person = (Person) await (session.LoadAsync(typeof(Person), person.Id)); + person.Name = "makingpersondirty"; + await (tran.CommitAsync()); + session.Close(); + } - session = OpenSession(); - session.BeginTransaction(); - await (session.DeleteAsync(person)); - await (session.DeleteAsync(address)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + await (session.DeleteAsync(person)); + await (session.DeleteAsync(address)); + await (tran.CommitAsync()); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/BulkManipulation/NativeSQLBulkOperationsWithCache.cs b/src/NHibernate.Test/Async/BulkManipulation/NativeSQLBulkOperationsWithCache.cs index 1d517305df7..9e0501e351a 100644 --- a/src/NHibernate.Test/Async/BulkManipulation/NativeSQLBulkOperationsWithCache.cs +++ b/src/NHibernate.Test/Async/BulkManipulation/NativeSQLBulkOperationsWithCache.cs @@ -50,7 +50,6 @@ public async Task SimpleNativeSQLInsert_DoesNotEvictEntireCacheWhenQuerySpacesAr using (var t = s.BeginTransaction()) { - await (s.CreateSQLQuery(ssql).ExecuteUpdateAsync()); await (t.CommitAsync()); diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs index 741992bf116..a3e191f6d2d 100644 --- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs @@ -214,6 +214,351 @@ public async Task MultipleGetReadOnlyCollectionTestAsync() } } + [Test] + public async Task GetManyReadWriteTestAsync() + { + var persister = Sfi.GetEntityPersister(typeof(ReadWrite).FullName); + Assert.That(persister.Cache.Cache, Is.Not.Null); + Assert.That(persister.Cache.Cache, Is.TypeOf()); + int[] getIds; + int[] loadIds; + + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var items = await (s.Query().ToListAsync()); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); + await (tx.CommitAsync()); + } + + // Batch size 3 + var parentTestCases = new List>> + { + // When the cache is empty, GetMany will be called three times. First time in type + // DefaultLoadEventListener, the second time in BatchingEntityLoader and third in ReadWriteCache. + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {1, 2, 3}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 1, 2} // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 1, 2}, + null + ), + // When there are not enough uninitialized entities after the demanded one to fill the batch, + // the nearest before the demanded entity are added. + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3}, + new[] {5, 3, 2}, + new[] {4, 5, 3}, + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3}, + new[] {4, 3, 2}, + new[] {5, 4, 3}, + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds, + 1, + new[] + { + new[] {1, 2, 3}, // 2 gets assembled inside LoadFromSecondLevelCache + new[] {3, 4, 5}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3}, // 4 gets assembled inside LoadFromSecondLevelCache + new[] {3, 2, 1}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2}, // 1 gets assembled inside LoadFromSecondLevelCache + new[] {2, 3, 4}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3}, // 5 and 3 get assembled inside LoadFromSecondLevelCache + new[] {2, 1, 0}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {3, 4, 5}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 4, 5}, // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 4).ToArray(), + 4, + new[] + { + new[] {4, 5, 3}, + new[] {5, 3, 2}, + new[] {3, 4, 5} + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4}, // 4 gets assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ) + }; + + foreach (var tuple in parentTestCases) + { + await (AssertMultipleCacheCallsAsync(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5)); + } + } + + [Test] + public async Task GetManyReadWriteItemTestAsync() + { + var persister = Sfi.GetEntityPersister(typeof(ReadWriteItem).FullName); + Assert.That(persister.Cache.Cache, Is.Not.Null); + Assert.That(persister.Cache.Cache, Is.TypeOf()); + int[] getIds; + int[] loadIds; + + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var items = await (s.Query().Take(6).ToListAsync()); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); + await (tx.CommitAsync()); + } + // Batch size 4 + var parentTestCases = new List>> + { + // When the cache is empty, GetMany will be called three times. First time in type + // DefaultLoadEventListener, the second time in BatchingEntityLoader and third in ReadWriteCache. + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2, 3}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {1, 2, 3, 4}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 1, 2, 3} // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 1, 2, 3}, + null + ), + // When there are not enough uninitialized entities after the demanded one to fill the batch, + // the nearest before the demanded entity are added. + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3, 2}, + new[] {5, 3, 2, 1}, + new[] {4, 5, 3, 2} + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3, 2}, + new[] {4, 3, 2, 1}, + new[] {5, 4, 3, 2} + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2, 3} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds, + 1, + new[] + { + new[] {1, 2, 3, 4}, // 2 and 4 get assembled inside LoadFromSecondLevelCache + new[] {3, 5, 0}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3, 2}, // 4 and 2 get assembled inside LoadFromSecondLevelCache + new[] {3, 1, 0}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2, 3}, // 1 and 3 get assembled inside LoadFromSecondLevelCache + new[] {2, 4, 5}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3, 2}, // 5 and 3 get assembled inside LoadFromSecondLevelCache + new[] {2, 1, 0}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {5, 4, 3, 2}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 5, 4, 3}, // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 5, 4, 3}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 5).ToArray(), + 5, + new[] + { + new[] {5, 4, 3, 2}, + new[] {4, 3, 2, 1}, + new[] {2, 3, 4, 5} + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4, 3}, // 4 get assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2, 0}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + }; + + foreach (var tuple in parentTestCases) + { + await (AssertMultipleCacheCallsAsync(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5)); + } + } + [Test] public async Task MultipleGetReadOnlyTestAsync() { @@ -767,7 +1112,7 @@ public async Task QueryCacheTestAsync() using (var t = s.BeginTransaction()) { - await (queries.ExecuteAsync(CancellationToken.None)); + await (queries.ExecuteAsync()); await (t.CommitAsync()); } @@ -782,24 +1127,24 @@ public async Task QueryCacheTestAsync() // Run a second time, to test the query cache using (var t = s.BeginTransaction()) { - await (queries.ExecuteAsync(CancellationToken.None)); + await (queries.ExecuteAsync()); await (t.CommitAsync()); } Assert.That( - await (queries.GetResultAsync(0, CancellationToken.None)), + await (queries.GetResultAsync(0)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1"); Assert.That( - await (queries.GetResultAsync(1, CancellationToken.None)), + await (queries.GetResultAsync(1)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2"); Assert.That( - await (queries.GetResultAsync(2, CancellationToken.None)), + await (queries.GetResultAsync(2)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name3), "q3"); Assert.That( - await (queries.GetResultAsync(3, CancellationToken.None)), + await (queries.GetResultAsync(3)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4"); Assert.That( - await (queries.GetResultAsync(4, CancellationToken.None)), + await (queries.GetResultAsync(4)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5"); Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(2), "cache GetMany secondExecution"); @@ -829,24 +1174,24 @@ public async Task QueryCacheTestAsync() // Run a third time, to re-test the query cache using (var t = s.BeginTransaction()) { - await (queries.ExecuteAsync(CancellationToken.None)); + await (queries.ExecuteAsync()); await (t.CommitAsync()); } Assert.That( - await (queries.GetResultAsync(0, CancellationToken.None)), + await (queries.GetResultAsync(0)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1 after update"); Assert.That( - await (queries.GetResultAsync(1, CancellationToken.None)), + await (queries.GetResultAsync(1)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2 after update"); Assert.That( - await (queries.GetResultAsync(2, CancellationToken.None)), + await (queries.GetResultAsync(2)), Has.Count.EqualTo(0), "q3 after update"); Assert.That( - await (queries.GetResultAsync(3, CancellationToken.None)), + await (queries.GetResultAsync(3)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4 after update"); Assert.That( - await (queries.GetResultAsync(4, CancellationToken.None)), + await (queries.GetResultAsync(4)), Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5 after update"); Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(3), "cache GetMany thirdExecution"); @@ -866,14 +1211,14 @@ public async Task QueryCacheTestAsync() [TestCase(true)] [TestCase(false)] - public async Task QueryEntityBatchCacheTestAsync(bool clearEntityCacheAfterQuery, CancellationToken cancellationToken = default(CancellationToken)) + public async Task QueryEntityBatchCacheTestAsync(bool clearEntityCacheAfterQuery) { var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); var cache = (BatchableCache) persister.Cache.Cache; var queryCache = GetDefaultQueryCache(); Sfi.Statistics.Clear(); - await (Sfi.EvictQueriesAsync(cancellationToken)); + await (Sfi.EvictQueriesAsync()); cache.ClearStatistics(); queryCache.ClearStatistics(); @@ -884,9 +1229,9 @@ public async Task QueryCacheTestAsync() { items = await (s.Query() .WithOptions(o => o.SetCacheable(true)) - .ToListAsync(cancellationToken)); + .ToListAsync()); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(queryCache.GetCalls, Has.Count.EqualTo(1), "Unexpected query cache GetCalls"); @@ -903,7 +1248,7 @@ public async Task QueryCacheTestAsync() if (clearEntityCacheAfterQuery) { - await (cache.ClearAsync(cancellationToken)); + await (cache.ClearAsync(CancellationToken.None)); } Sfi.Statistics.Clear(); @@ -913,9 +1258,9 @@ public async Task QueryCacheTestAsync() { items = await (s.Query() .WithOptions(o => o.SetCacheable(true)) - .ToListAsync(cancellationToken)); + .ToListAsync()); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(queryCache.GetCalls, Has.Count.EqualTo(1), "Unexpected query cache GetCalls"); @@ -935,7 +1280,7 @@ public async Task QueryCacheTestAsync() [TestCase(false, false)] [TestCase(true, true)] [TestCase(false, true)] - public async Task QueryFetchCollectionBatchCacheTestAsync(bool clearEntityCacheAfterQuery, bool future, CancellationToken cancellationToken = default(CancellationToken)) + public async Task QueryFetchCollectionBatchCacheTestAsync(bool clearEntityCacheAfterQuery, bool future) { if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) { @@ -954,19 +1299,19 @@ public async Task QueryCacheTestAsync() using (var s = OpenSession()) { - var ids = await (s.Query().Select(o => o.Id).OrderBy(o => o).ToListAsync(cancellationToken)); + var ids = await (s.Query().Select(o => o.Id).OrderBy(o => o).ToListAsync()); middleId = ids[2]; } Sfi.Statistics.Clear(); - await (Sfi.EvictQueriesAsync(cancellationToken)); + await (Sfi.EvictQueriesAsync()); queryCache.ClearStatistics(); cache.ClearStatistics(); - await (cache.ClearAsync(cancellationToken)); + await (cache.ClearAsync(CancellationToken.None)); itemCache.ClearStatistics(); - await (itemCache.ClearAsync(cancellationToken)); + await (itemCache.ClearAsync(CancellationToken.None)); collectionCache.ClearStatistics(); - await (collectionCache.ClearAsync(cancellationToken)); + await (collectionCache.ClearAsync(CancellationToken.None)); List items; using (var s = OpenSession()) @@ -992,10 +1337,10 @@ public async Task QueryCacheTestAsync() items = await (s.Query() .WithOptions(o => o.SetCacheable(true)) .FetchMany(o => o.Items) - .ToListAsync(cancellationToken)); + .ToListAsync()); } - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(queryCache.GetCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache GetCalls"); @@ -1018,9 +1363,9 @@ public async Task QueryCacheTestAsync() if (clearEntityCacheAfterQuery) { - await (cache.ClearAsync(cancellationToken)); - await (collectionCache.ClearAsync(cancellationToken)); - await (itemCache.ClearAsync(cancellationToken)); + await (cache.ClearAsync(CancellationToken.None)); + await (collectionCache.ClearAsync(CancellationToken.None)); + await (itemCache.ClearAsync(CancellationToken.None)); } Sfi.Statistics.Clear(); @@ -1048,10 +1393,10 @@ public async Task QueryCacheTestAsync() items = await (s.Query() .WithOptions(o => o.SetCacheable(true)) .FetchMany(o => o.Items) - .ToListAsync(cancellationToken)); + .ToListAsync()); } - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(queryCache.GetCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache GetCalls"); @@ -1080,7 +1425,7 @@ public async Task QueryCacheTestAsync() [TestCase(false, false)] [TestCase(true, true)] [TestCase(false, true)] - public async Task QueryFetchEntityBatchCacheTestAsync(bool clearEntityCacheAfterQuery, bool future, CancellationToken cancellationToken = default(CancellationToken)) + public async Task QueryFetchEntityBatchCacheTestAsync(bool clearEntityCacheAfterQuery, bool future) { if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) { @@ -1097,17 +1442,17 @@ public async Task QueryCacheTestAsync() using (var s = OpenSession()) { - var ids = await (s.Query().Select(o => o.Id).OrderBy(o => o).ToListAsync(cancellationToken)); + var ids = await (s.Query().Select(o => o.Id).OrderBy(o => o).ToListAsync()); middleId = ids[17]; } Sfi.Statistics.Clear(); - await (Sfi.EvictQueriesAsync(cancellationToken)); + await (Sfi.EvictQueriesAsync()); queryCache.ClearStatistics(); cache.ClearStatistics(); - await (cache.ClearAsync(cancellationToken)); + await (cache.ClearAsync(CancellationToken.None)); parentCache.ClearStatistics(); - await (parentCache.ClearAsync(cancellationToken)); + await (parentCache.ClearAsync(CancellationToken.None)); List items; using (var s = OpenSession()) @@ -1133,10 +1478,10 @@ public async Task QueryCacheTestAsync() items = await (s.Query() .WithOptions(o => o.SetCacheable(true)) .Fetch(o => o.Parent) - .ToListAsync(cancellationToken)); + .ToListAsync()); } - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(queryCache.GetCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache GetCalls"); @@ -1158,8 +1503,8 @@ public async Task QueryCacheTestAsync() if (clearEntityCacheAfterQuery) { - await (cache.ClearAsync(cancellationToken)); - await (parentCache.ClearAsync(cancellationToken)); + await (cache.ClearAsync(CancellationToken.None)); + await (parentCache.ClearAsync(CancellationToken.None)); } Sfi.Statistics.Clear(); @@ -1187,10 +1532,10 @@ public async Task QueryCacheTestAsync() items = await (s.Query() .WithOptions(o => o.SetCacheable(true)) .Fetch(o => o.Parent) - .ToListAsync(cancellationToken)); + .ToListAsync()); } - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(queryCache.GetCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache GetCalls"); @@ -1350,6 +1695,5 @@ private BatchableCache GetDefaultQueryCache() return cache; } - } } diff --git a/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs index ad8af003134..5d12747ec68 100644 --- a/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/CacheFixture.cs @@ -29,7 +29,7 @@ public async Task TestSimpleCacheAsync() private CacheKey CreateCacheKey(string text) { - return new CacheKey(text, NHibernateUtil.String, "Foo", null); + return new CacheKey(text, NHibernateUtil.String, "Foo", null, null); } public async Task DoTestCacheAsync(ICacheProvider cacheProvider, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/NHibernate.Test/Async/CacheTest/Caches/SerializingCache.cs b/src/NHibernate.Test/Async/CacheTest/Caches/SerializingCache.cs new file mode 100644 index 00000000000..82db3ead946 --- /dev/null +++ b/src/NHibernate.Test/Async/CacheTest/Caches/SerializingCache.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections; +using NHibernate.Cache; + +namespace NHibernate.Test.CacheTest.Caches +{ + using System.Threading.Tasks; + using System.Threading; + public partial class SerializingCache : CacheBase + { + + public override Task GetAsync(object key, CancellationToken cancellationToken) + { + try + { + return Task.FromResult(_hashtable[key]); + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public override Task PutAsync(object key, object value, CancellationToken cancellationToken) + { + try + { + _hashtable[key] = value; + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public override Task RemoveAsync(object key, CancellationToken cancellationToken) + { + try + { + _hashtable.Remove(key); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public override Task ClearAsync(CancellationToken cancellationToken) + { + try + { + _hashtable.Clear(); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public override Task LockAsync(object key, CancellationToken cancellationToken) + { + // local cache, no need to actually lock. + return Task.FromResult(null); + } + + public override Task UnlockAsync(object key, object lockValue, CancellationToken cancellationToken) + { + return Task.CompletedTask; + // local cache, no need to actually lock. + } + } +} diff --git a/src/NHibernate.Test/Async/CacheTest/SerializationFixture.cs b/src/NHibernate.Test/Async/CacheTest/SerializationFixture.cs index 85d822288c9..f2683f7f26c 100644 --- a/src/NHibernate.Test/Async/CacheTest/SerializationFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/SerializationFixture.cs @@ -119,7 +119,6 @@ public async Task TestAnyTypeObjectTypeCacheEntrySerializationAsync() CheckObjectTypeCacheEntry(item, copy); } - [Serializable] public class MyEntity { diff --git a/src/NHibernate.Test/Async/CacheTest/SerializingCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/SerializingCacheFixture.cs new file mode 100644 index 00000000000..d18ccb13f35 --- /dev/null +++ b/src/NHibernate.Test/Async/CacheTest/SerializingCacheFixture.cs @@ -0,0 +1,93 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Linq; +using NHibernate.Test.CacheTest.Caches; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Test.CacheTest +{ + using System.Threading.Tasks; + [TestFixture] + public class SerializingCacheFixtureAsync : TestCase + { + protected override string[] Mappings => new[] + { + "CacheTest.ReadOnly.hbm.xml" + }; + + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override string CacheConcurrencyStrategy => null; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.UseSecondLevelCache, "true"); + configuration.SetProperty(Environment.UseQueryCache, "true"); + configuration.SetProperty(Environment.CacheProvider, typeof(SerializingCacheProvider).AssemblyQualifiedName); + configuration.SetProperty(Environment.SessionFactoryName, "SerializingCacheFactory"); + } + + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var totalItems = 6; + for (var i = 1; i <= totalItems; i++) + { + var parent = new ReadOnly + { + Name = $"Name{i}" + }; + for (var j = 1; j <= totalItems; j++) + { + var child = new ReadOnlyItem + { + Parent = parent + }; + parent.Items.Add(child); + } + s.Save(parent); + } + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.CreateQuery("delete from ReadOnlyItem").ExecuteUpdate(); + s.CreateQuery("delete from ReadOnly").ExecuteUpdate(); + tx.Commit(); + } + } + + [Test] + public async Task CachedQueryTestAsync() + { + // Put things in cache + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var items = await (s.Query().WithOptions(o => o.SetCacheable(true)).ToListAsync()); + Assert.That(items, Has.Count.GreaterThan(0)); + await (tx.CommitAsync()); + } + + RebuildSessionFactory(); + } + } +} diff --git a/src/NHibernate.Test/Async/Cascade/Circle/MultiPathCircleCascadeTest.cs b/src/NHibernate.Test/Async/Cascade/Circle/MultiPathCircleCascadeTest.cs index 61a15a50132..7d7a4a491d1 100644 --- a/src/NHibernate.Test/Async/Cascade/Circle/MultiPathCircleCascadeTest.cs +++ b/src/NHibernate.Test/Async/Cascade/Circle/MultiPathCircleCascadeTest.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.Cascade.Circle { using System.Threading.Tasks; using System.Threading; - /** * The test case uses the following model: * @@ -167,21 +166,21 @@ public async Task MergeRouteAsync() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.MergeAsync(route)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertInsertCount(4); AssertUpdateCount(1); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = await (s.GetAsync(route.RouteId)); CheckResults(route, true); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -193,23 +192,23 @@ public async Task MergePickupNodeAsync() ClearCounts(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Node pickupNode = route.Nodes.First(n => n.Name == "pickupNodeB"); pickupNode = (Node) await (s.MergeAsync(pickupNode)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertInsertCount(4); AssertUpdateCount(0); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = await (s.GetAsync(route.RouteId)); CheckResults(route, false); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -221,22 +220,22 @@ public async Task MergeDeliveryNodeAsync() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Node deliveryNode = route.Nodes.First(n => n.Name == "deliveryNodeB"); deliveryNode = (Node) await (s.MergeAsync(deliveryNode)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertInsertCount(4); AssertUpdateCount(0); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = await (s.GetAsync(route.RouteId)); CheckResults(route, false); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -248,21 +247,21 @@ public async Task MergeTourAsync() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Tour tour = await (s.MergeAsync(route.Nodes.First().Tour)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertInsertCount(4); AssertUpdateCount(0); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = await (s.GetAsync(route.RouteId)); CheckResults(route, false); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -274,7 +273,7 @@ public async Task MergeTransportAsync() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Node node = route.Nodes.First(); Transport transport = null; @@ -286,18 +285,18 @@ public async Task MergeTransportAsync() transport = (Transport) await (s.MergeAsync(transport)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertInsertCount(4); AssertUpdateCount(0); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = await (s.GetAsync(route.RouteId)); CheckResults(route, false); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -305,12 +304,12 @@ public async Task MergeTransportAsync() { Route route = new Route(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route.Name = "routeA"; await (s.SaveAsync(route, cancellationToken)); - await (s.Transaction.CommitAsync(cancellationToken)); + await (t.CommitAsync(cancellationToken)); } route.Name = "new routeA"; route.TransientField = "sfnaouisrbn"; diff --git a/src/NHibernate.Test/Async/Cascade/MultiPathCascadeTest.cs b/src/NHibernate.Test/Async/Cascade/MultiPathCascadeTest.cs index 25fa311a43b..05fa006502d 100644 --- a/src/NHibernate.Test/Async/Cascade/MultiPathCascadeTest.cs +++ b/src/NHibernate.Test/Async/Cascade/MultiPathCascadeTest.cs @@ -36,21 +36,20 @@ public async Task MultiPathMergeModifiedDetachedAsync() A a = new A(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { - a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = await (s.MergeAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); @@ -62,22 +61,22 @@ public async Task MultiPathMergeModifiedDetachedIntoProxyAsync() // persist a simple A in the database A a = new A(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { A aLoaded = await (s.LoadAsync(a.Id)); Assert.That(aLoaded, Is.InstanceOf()); Assert.That(await (s.MergeAsync(a)), Is.SameAs(aLoaded)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); @@ -90,21 +89,21 @@ public async Task MultiPathUpdateModifiedDetachedAsync() A a = new A(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // modify detached entity this.ModifyEntity(a); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.UpdateAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); } @@ -116,20 +115,20 @@ public async Task MultiPathGetAndModifyAsync() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { // retrieve the previously saved instance from the database, and update it a = await (s.GetAsync(a.Id)); this.ModifyEntity(a); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); } @@ -141,20 +140,20 @@ public async Task MultiPathMergeNonCascadedTransientEntityInCollectionAsync() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = await (s.MergeAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); @@ -168,7 +167,7 @@ public async Task MultiPathMergeNonCascadedTransientEntityInCollectionAsync() h.Gs.Add(gNew); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { try { @@ -190,20 +189,20 @@ public async Task MultiPathMergeNonCascadedTransientEntityInOneToOneAsync() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = (A) await (s.MergeAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); @@ -239,20 +238,20 @@ public async Task MultiPathMergeNonCascadedTransientEntityInManyToOneAsync() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; await (s.SaveAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = (A) await (s.MergeAsync(a)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } await (this.VerifyModificationsAsync(a.Id)); @@ -318,7 +317,7 @@ private void ModifyEntity(A a) private async Task VerifyModificationsAsync(long aId, CancellationToken cancellationToken = default(CancellationToken)) { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { // retrieve the A object and check it A a = await (s.GetAsync(aId, cancellationToken)); @@ -345,7 +344,7 @@ private void ModifyEntity(A a) Assert.That(hFromA.Gs.Count, Is.EqualTo(1)); Assert.That(hFromA.Gs.First(), Is.SameAs(gFromA)); - await (s.Transaction.CommitAsync(cancellationToken)); + await (t.CommitAsync(cancellationToken)); } } } diff --git a/src/NHibernate.Test/Async/Cascade/RefreshFixture.cs b/src/NHibernate.Test/Async/Cascade/RefreshFixture.cs index 3e45819fbe1..d414a222866 100644 --- a/src/NHibernate.Test/Async/Cascade/RefreshFixture.cs +++ b/src/NHibernate.Test/Async/Cascade/RefreshFixture.cs @@ -9,9 +9,7 @@ using System; -using System.Collections; using System.Data; -using System.Data.Common; using NUnit.Framework; namespace NHibernate.Test.Cascade @@ -70,7 +68,7 @@ public async Task RefreshCascadeAsync() var cmd = conn.CreateCommand(); cmd.CommandText = "UPDATE T_JOB SET JOB_STATUS = 1"; cmd.CommandType = CommandType.Text; - session.Transaction.Enlist(cmd); + session.GetSessionImplementation().ConnectionManager.EnlistInTransaction(cmd); return cmd.ExecuteNonQueryAsync(cancellationToken); } catch (Exception ex) @@ -102,7 +100,6 @@ public async Task RefreshIgnoringTransientInCollectionAsync() { using (ITransaction txn = session.BeginTransaction()) { - var batch = new JobBatch(DateTime.Now); batch.CreateJob().ProcessingInstructions = "Just do it!"; await (session.PersistAsync(batch)); @@ -135,6 +132,5 @@ public async Task RefreshNotIgnoringTransientByUnsavedValueAsync() session.Close(); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/CfgTest/ConfigurationFixture.cs b/src/NHibernate.Test/Async/CfgTest/ConfigurationFixture.cs index 210befc7a9b..51a82d0aff9 100644 --- a/src/NHibernate.Test/Async/CfgTest/ConfigurationFixture.cs +++ b/src/NHibernate.Test/Async/CfgTest/ConfigurationFixture.cs @@ -11,6 +11,7 @@ using System; using System.Collections; using System.IO; +using System.Linq; using System.Xml; using NHibernate.Cfg; using NHibernate.DomainModel; @@ -105,9 +106,16 @@ public class SampleQueryProvider : DefaultQueryProvider { public SampleQueryProvider(ISessionImplementor session) : base(session) { + } + protected SampleQueryProvider(ISessionImplementor session, object collection, NhQueryableOptions options) : base(session, collection, options) + { } - } + protected override IQueryProvider CreateWithOptions(NhQueryableOptions options) + { + return new SampleQueryProvider(Session, Collection, options); + } + } } } diff --git a/src/NHibernate.Test/Async/Classic/LifecycleFixture.cs b/src/NHibernate.Test/Async/Classic/LifecycleFixture.cs index d346845518a..aecfdfbd818 100644 --- a/src/NHibernate.Test/Async/Classic/LifecycleFixture.cs +++ b/src/NHibernate.Test/Async/Classic/LifecycleFixture.cs @@ -109,7 +109,6 @@ public async Task MergeAsync() Assert.That(Sfi.Statistics.EntityInsertCount, Is.EqualTo(0)); Assert.That(Sfi.Statistics.EntityUpdateCount, Is.EqualTo(0)); - // cleanup using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) @@ -142,4 +141,4 @@ public async Task DeleteAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Classic/ValidatableFixture.cs b/src/NHibernate.Test/Async/Classic/ValidatableFixture.cs index 7f08613bca3..d8b3b514ddc 100644 --- a/src/NHibernate.Test/Async/Classic/ValidatableFixture.cs +++ b/src/NHibernate.Test/Async/Classic/ValidatableFixture.cs @@ -144,7 +144,6 @@ public async Task MergeAsync() // Ok } - // cleanup using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) @@ -167,6 +166,5 @@ public async Task DeleteAsync() await (s.FlushAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/Component/Basic/ComponentTest.cs b/src/NHibernate.Test/Async/Component/Basic/ComponentTest.cs index 62346be8e41..a3a058a3def 100644 --- a/src/NHibernate.Test/Async/Component/Basic/ComponentTest.cs +++ b/src/NHibernate.Test/Async/Component/Basic/ComponentTest.cs @@ -167,78 +167,6 @@ public async Task TestComponentStateChangeAndDirtinessAsync() } } - [Test] - [Ignore("Ported from Hibernate. Read properties not supported in NH yet.")] - public async Task TestCustomColumnReadAndWriteAsync() - { - const double HEIGHT_INCHES = 73; - const double HEIGHT_CENTIMETERS = HEIGHT_INCHES * 2.54d; - - using (ISession s = Sfi.OpenSession()) - using (ITransaction t = s.BeginTransaction()) - { - User u = new User("steve", "hibernater", new Person( "Steve Ebersole", new DateTime(1999, 12, 31), "Main St")); - u.Person.HeightInches = HEIGHT_INCHES; - await (s.PersistAsync(u)); - await (s.FlushAsync()); - - // Test value conversion during insert - double heightViaSql = (double)await (s.CreateSQLQuery("select height_centimeters from t_user where t_user.username='steve'").UniqueResultAsync()); - Assert.That(heightViaSql, Is.EqualTo(HEIGHT_CENTIMETERS).Within(0.01d)); - - // Test projection - double heightViaHql = (double)await (s.CreateQuery("select u.Person.HeightInches from User u where u.Id = 'steve'").UniqueResultAsync()); - Assert.That(heightViaHql, Is.EqualTo(HEIGHT_INCHES).Within(0.01d)); - - // Test restriction and entity load via criteria - u = (User)await (s.CreateCriteria(typeof(User)) - .Add(Restrictions.Between("Person.HeightInches", HEIGHT_INCHES - 0.01d, HEIGHT_INCHES + 0.01d)) - .UniqueResultAsync()); - Assert.That(u.Person.HeightInches, Is.EqualTo(HEIGHT_INCHES).Within(0.01d)); - - // Test predicate and entity load via HQL - u = (User)await (s.CreateQuery("from User u where u.Person.HeightInches between ? and ?") - .SetDouble(0, HEIGHT_INCHES - 0.01d) - .SetDouble(1, HEIGHT_INCHES + 0.01d) - .UniqueResultAsync()); - - Assert.That(u.Person.HeightInches, Is.EqualTo(HEIGHT_INCHES).Within(0.01d)); - - // Test update - u.Person.HeightInches = 1; - await (s.FlushAsync()); - heightViaSql = (double)await (s.CreateSQLQuery("select height_centimeters from t_user where t_user.username='steve'").UniqueResultAsync()); - Assert.That(heightViaSql, Is.EqualTo(2.54d).Within(0.01d)); - await (s.DeleteAsync(u)); - await (t.CommitAsync()); - s.Close(); - } - } - - [Test] - [Ignore("Ported from Hibernate - failing in NH")] - public async Task TestComponentQueriesAsync() - { - using (ISession s = Sfi.OpenSession()) - using (ITransaction t = s.BeginTransaction()) - { - Employee emp = new Employee(); - emp.HireDate = new DateTime(1999, 12, 31); - emp.Person = new Person(); - emp.Person.Name = "steve"; - emp.Person.Dob = new DateTime(1999, 12, 31); - await (s.SaveAsync(emp)); - - await (s.CreateQuery("from Employee e where e.Person = :p and 1=1 and 2=2").SetParameter("p", emp.Person).ListAsync()); - await (s.CreateQuery("from Employee e where :p = e.Person").SetParameter("p", emp.Person).ListAsync()); - await (s.CreateQuery("from Employee e where e.Person = ('steve', current_timestamp)").ListAsync()); - - await (s.DeleteAsync( emp )); - await (t.CommitAsync()); - s.Close(); - } - } - [Test] public async Task TestComponentFormulaQueryAsync() { diff --git a/src/NHibernate.Test/Async/CompositeId/ClassWithCompositeIdFixture.cs b/src/NHibernate.Test/Async/CompositeId/ClassWithCompositeIdFixture.cs index 18382995a77..92288ae1108 100644 --- a/src/NHibernate.Test/Async/CompositeId/ClassWithCompositeIdFixture.cs +++ b/src/NHibernate.Test/Async/CompositeId/ClassWithCompositeIdFixture.cs @@ -10,6 +10,7 @@ using System; using System.Collections; +using System.Linq; using NHibernate.Criterion; using NUnit.Framework; @@ -37,11 +38,6 @@ protected override string[] Mappings get { return new string[] {"CompositeId.ClassWithCompositeId.hbm.xml"}; } } - protected override bool AppliesTo(Dialect.Dialect dialect) - { - return !(dialect is Dialect.FirebirdDialect); // Firebird has no CommandTimeout, and locks up during the tear-down of this fixture - } - protected override void OnSetUp() { id = new Id("stringKey", 3, firstDateTime); @@ -51,13 +47,14 @@ protected override void OnSetUp() protected override void OnTearDown() { using (ISession s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) { s.Delete("from ClassWithCompositeId"); s.Flush(); + t.Commit(); } } - /// /// Test the basic CRUD operations for a class with a Composite Identifier /// @@ -239,5 +236,214 @@ public async Task HqlAsync() Assert.AreEqual(1, results.Count); } } + + [Test] + public async Task HqlInClauseAsync() + { + var id1 = id; + var id2 = secondId; + var id3 = new Id(id.KeyString, id.GetKeyShort(), id2.KeyDateTime); + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(new ClassWithCompositeId(id1) {OneProperty = 5})); + await (s.SaveAsync(new ClassWithCompositeId(id2) {OneProperty = 10})); + await (s.SaveAsync(new ClassWithCompositeId(id3))); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + var results1 = await (s.CreateQuery("from ClassWithCompositeId x where x.Id in (:id1, :id2)") + .SetParameter("id1", id1) + .SetParameter("id2", id2) + .ListAsync()); + var results2 = await (s.CreateQuery("from ClassWithCompositeId x where x.Id in (:id1)") + .SetParameter("id1", id1) + .ListAsync()); + var results3 = await (s.CreateQuery("from ClassWithCompositeId x where x.Id not in (:id1)") + .SetParameter("id1", id1) + .ListAsync()); + var results4 = await (s.CreateQuery("from ClassWithCompositeId x where x.Id not in (:id1, :id2)") + .SetParameter("id1", id1) + .SetParameter("id2", id2) + .ListAsync()); + + Assert.Multiple( + () => + { + Assert.That(results1.Count, Is.EqualTo(2), "in multiple ids"); + Assert.That(results1.Select(x => x.Id), Is.EquivalentTo(new[] {id1, id2}), "in multiple ids"); + Assert.That(results2.Count, Is.EqualTo(1), "in single id"); + Assert.That(results2.Single().Id, Is.EqualTo(id1), "in single id"); + Assert.That(results3.Count, Is.EqualTo(2), "not in single id"); + Assert.That(results3.Select(x => x.Id), Is.EquivalentTo(new[] {id2, id3}), "not in single id"); + Assert.That(results4.Count, Is.EqualTo(1), "not in multiple ids"); + Assert.That(results4.Single().Id, Is.EqualTo(id3), "not in multiple ids"); + }); + } + } + + [Test] + public async Task QueryOverInClauseSubqueryAsync() + { + if (!TestDialect.SupportsRowValueConstructorSyntax) + { + Assert.Ignore(); + } + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(new ClassWithCompositeId(id) {OneProperty = 5})); + await (s.SaveAsync(new ClassWithCompositeId(secondId) {OneProperty = 10})); + await (s.SaveAsync(new ClassWithCompositeId(new Id(id.KeyString, id.GetKeyShort(), secondId.KeyDateTime)))); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + var results = await (s.QueryOver().WithSubquery.WhereProperty(p => p.Id).In(QueryOver.Of().Where(p => p.Id.KeyString == id.KeyString).Select(p => p.Id)).ListAsync()); + Assert.That(results.Count, Is.EqualTo(2)); + } + } + + [Test] + public async Task HqlInClauseSubqueryAsync() + { + if (!TestDialect.SupportsRowValueConstructorSyntax) + Assert.Ignore(); + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(new ClassWithCompositeId(id) {OneProperty = 5})); + await (s.SaveAsync(new ClassWithCompositeId(secondId) {OneProperty = 10})); + await (s.SaveAsync(new ClassWithCompositeId(new Id(id.KeyString, id.GetKeyShort(), secondId.KeyDateTime)))); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + var results = await (s.CreateQuery("from ClassWithCompositeId x where x.Id in (select s.Id from ClassWithCompositeId s where s.Id.KeyString = :keyString)") + .SetParameter("keyString", id.KeyString).ListAsync()); + Assert.That(results.Count, Is.EqualTo(2)); + } + } + + //GH-1376 + [Test] + public async Task HqlInClauseSubquery_ForEntityAsync() + { + if (!TestDialect.SupportsRowValueConstructorSyntax) + Assert.Ignore(); + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(new ClassWithCompositeId(id) {OneProperty = 5})); + await (s.SaveAsync(new ClassWithCompositeId(secondId) {OneProperty = 10})); + await (s.SaveAsync(new ClassWithCompositeId(new Id(id.KeyString, id.GetKeyShort(), secondId.KeyDateTime)))); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + var results = await (s.CreateQuery("from ClassWithCompositeId x where x in (select s from ClassWithCompositeId s where s.Id.KeyString = :keyString)") + .SetParameter("keyString", id.KeyString).ListAsync()); + Assert.That(results.Count, Is.EqualTo(2)); + } + } + + //NH-2926 (GH-1103) + [Test] + public async Task QueryOverOrderByAndWhereWithIdProjectionDoesntThrowAsync() + { + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + ClassWithCompositeId theClass = new ClassWithCompositeId(id); + theClass.OneProperty = 5; + + ClassWithCompositeId theSecondClass = new ClassWithCompositeId(secondId); + theSecondClass.OneProperty = 10; + + await (s.SaveAsync(theClass)); + await (s.SaveAsync(theSecondClass)); + + await (t.CommitAsync()); + } + + using (ISession s = OpenSession()) + { + var results = await (s.QueryOver() + .Select(Projections.Id()) + .Where(Restrictions.Eq(Projections.Id(), id)) + .OrderBy(Projections.Id()).Desc.ListAsync()); + Assert.That(results.Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task CriteriaGroupProjectionAsync() + { + using (ISession s = OpenSession()) + { + await (s.CreateCriteria() + .SetProjection(Projections.GroupProperty(Projections.Id())) + .Add(Restrictions.Eq(Projections.Id(), id)) + .ListAsync()); + } + } + + [Test] + public async Task QueryOverInClauseAsync() + { + // insert the new objects + var id1 = id; + var id2 = secondId; + var id3 = new Id(id1.KeyString, id1.GetKeyShort(), id2.KeyDateTime); + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(new ClassWithCompositeId(id1) {OneProperty = 5})); + await (s.SaveAsync(new ClassWithCompositeId(id2) {OneProperty = 10})); + await (s.SaveAsync(new ClassWithCompositeId(id3))); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + var results1 = await (s.QueryOver().WhereRestrictionOn(p => p.Id).IsIn(new[] {id1, id2}).ListAsync()); + var results2 = await (s.QueryOver().WhereRestrictionOn(p => p.Id).IsIn(new[] {id1}).ListAsync()); + var results3 = await (s.QueryOver().WhereRestrictionOn(p => p.Id).Not.IsIn(new[] {id1, id2}).ListAsync()); + var results4 = await (s.QueryOver().WhereRestrictionOn(p => p.Id).Not.IsIn(new[] {id1}).ListAsync()); + + Assert.Multiple( + () => + { + Assert.That(results1.Count, Is.EqualTo(2), "in multiple ids"); + Assert.That(results1.Select(r => r.Id), Is.EquivalentTo(new[] {id1, id2}), "in multiple ids"); + Assert.That(results2.Count, Is.EqualTo(1), "in single id"); + Assert.That(results2.Select(r => r.Id), Is.EquivalentTo(new[] {id1}), "in single id"); + Assert.That(results3.Count, Is.EqualTo(1), "not in multiple ids"); + Assert.That(results3.Select(r => r.Id), Is.EquivalentTo(new[] {id3}), "not in multiple ids"); + Assert.That(results4.Count, Is.EqualTo(2), "not in single id"); + Assert.That(results4.Select(r => r.Id), Is.EquivalentTo(new[] {id2, id3}), "not in single id"); + }); + } + } } } diff --git a/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs b/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs index 569abe99b0f..5bc228546f8 100644 --- a/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs +++ b/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs @@ -138,7 +138,6 @@ public async Task CompositeIdsAsync() await (t.CommitAsync()); } - using (s = OpenSession()) { t = s.BeginTransaction(); @@ -298,4 +297,4 @@ public async Task QueryAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs b/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs index 8bfc1ae19cf..48a945bba1c 100644 --- a/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs +++ b/src/NHibernate.Test/Async/ConnectionStringTest/NamedConnectionStringFixture.cs @@ -22,16 +22,14 @@ namespace NHibernate.Test.ConnectionStringTest public partial class MockConnectionProvider : ConnectionProvider { - + /// /// Get an open . /// - /// A cancellation token that can be used to cancel the work /// An open . - public override Task GetConnectionAsync(CancellationToken cancellationToken) + public override Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { throw new NotImplementedException(); } } - } diff --git a/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs b/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs index 24dca7f8a36..a0171cba778 100644 --- a/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs +++ b/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs @@ -230,30 +230,31 @@ public async Task ConnectionMaintanenceDuringFlushAsync() { Prepare(); ISession s = GetSessionUnderTest(); - s.BeginTransaction(); - - IList entities = new List(); - for (int i = 0; i < 10; i++) + using (var t = s.BeginTransaction()) { - Other other = new Other("other-" + i); - Silly silly = new Silly("silly-" + i, other); - entities.Add(silly); - await (s.SaveAsync(silly)); + IList entities = new List(); + for (int i = 0; i < 10; i++) + { + Other other = new Other("other-" + i); + Silly silly = new Silly("silly-" + i, other); + entities.Add(silly); + await (s.SaveAsync(silly)); + } + await (s.FlushAsync()); + + foreach (Silly silly in entities) + { + silly.Name = "new-" + silly.Name; + silly.Other.Name = "new-" + silly.Other.Name; + } + // long initialCount = sessions.Statistics.getConnectCount(); + await (s.FlushAsync()); + //Assert.AreEqual(initialCount + 1, sessions.Statistics.getConnectCount(), "connection not maintained through Flush"); + + await (s.DeleteAsync("from Silly")); + await (s.DeleteAsync("from Other")); + await (t.CommitAsync()); } - await (s.FlushAsync()); - - foreach (Silly silly in entities) - { - silly.Name = "new-" + silly.Name; - silly.Other.Name = "new-" + silly.Other.Name; - } -// long initialCount = sessions.Statistics.getConnectCount(); - await (s.FlushAsync()); -// Assert.AreEqual(initialCount + 1, sessions.Statistics.getConnectCount(), "connection not maintained through Flush"); - - await (s.DeleteAsync("from Silly")); - await (s.DeleteAsync("from Other")); - await (s.Transaction.CommitAsync()); Release(s); Done(); } diff --git a/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs b/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs index cba892a06aa..ef2bbf8dae0 100644 --- a/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs +++ b/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs @@ -11,8 +11,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Dialect; using NHibernate.Criterion; +using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; using NHibernate.Type; @@ -97,29 +99,6 @@ public async Task EscapeCharacterAsync() } } - [Test, Ignore("ScrollableResults not implemented")] - public async Task ScrollCriteriaAsync() - { - ISession session = OpenSession(); - ITransaction t = session.BeginTransaction(); - - Course course = new Course(); - course.CourseCode = "HIB"; - course.Description = "Hibernate Training"; - await (session.SaveAsync(course)); - await (session.FlushAsync()); - session.Clear(); - //IScrollableResults sr = session.CreateCriteria(typeof(Course)).Scroll(); - //Assert.IsTrue( sr.Next() ); - //course = (Course) sr[0]; - Assert.IsNotNull(course); - //sr.Close(); - await (session.DeleteAsync(course)); - - await (t.CommitAsync()); - session.Close(); - } - [Test] public async Task AllowToSetLimitOnSubqueriesAsync() { @@ -311,122 +290,56 @@ public async Task SubselectWithComponentAsync() await (t.CommitAsync()); } - if (TestDialect.SupportsOperatorAll) + //Note: It might require separate test dialect flag like SupportsRowValueConstructorWithOperatorAll + if (TestDialect.SupportsOperatorAll && TestDialect.SupportsRowValueConstructorSyntax) { using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) { - try - { - await (session.CreateCriteria() + await (session.CreateCriteria() .Add(Subqueries.PropertyEqAll("CityState", dc)) .ListAsync()); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (QueryException) - { - // expected - } - await (t.RollbackAsync()); } - } - if (TestDialect.SupportsOperatorAll) - { using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) { - try - { - await (session.CreateCriteria() + await (session.CreateCriteria() .Add(Property.ForName("CityState").EqAll(dc)) .ListAsync()); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (QueryException) - { - // expected - } - finally - { - await (t.RollbackAsync()); - } } } - using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) + if (TestDialect.SupportsRowValueConstructorSyntax) { - try + using (ISession session = OpenSession()) { await (session.CreateCriteria() - .Add(Subqueries.In(odessaWa, dc)) - .ListAsync()); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (NHibernate.Exceptions.GenericADOException) - { - // expected - } - finally - { - await (t.RollbackAsync()); + .Add(Subqueries.In(odessaWa, dc)) + .ListAsync()); } - } - - using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) - { - DetachedCriteria dc2 = DetachedCriteria.For("st1") - .Add(Property.ForName("st1.CityState").EqProperty("st2.CityState")) - .SetProjection(Property.ForName("CityState")); - - try + + using (ISession session = OpenSession()) { + DetachedCriteria dc2 = DetachedCriteria.For("st1") + .Add(Property.ForName("st1.CityState").EqProperty("st2.CityState")) + .SetProjection(Property.ForName("CityState")); await (session.CreateCriteria("st2") - .Add( Subqueries.Eq(odessaWa, dc2)) - .ListAsync()); - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (NHibernate.Exceptions.GenericADOException) - { - // expected - } - finally - { - await (t.RollbackAsync()); + .Add( Subqueries.Eq(odessaWa, dc2)) + .ListAsync()); } - } - - using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) - { - DetachedCriteria dc3 = DetachedCriteria.For("st") - .CreateCriteria("Enrolments") - .CreateCriteria("Course") - .Add(Property.ForName("Description").Eq("Hibernate Training")) - .SetProjection(Property.ForName("st.CityState")); - try + + using (ISession session = OpenSession()) { + DetachedCriteria dc3 = DetachedCriteria.For("st") + .CreateCriteria("Enrolments") + .CreateCriteria("Course") + .Add(Property.ForName("Description").Eq("Hibernate Training")) + .SetProjection(Property.ForName("st.CityState")); await (session.CreateCriteria("e") - .Add(Subqueries.Eq(odessaWa, dc3)) - .ListAsync()); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (NHibernate.Exceptions.GenericADOException) - { - // expected - } - finally - { - await (t.RollbackAsync()); + .Add(Subqueries.Eq(odessaWa, dc3)) + .ListAsync()); } } - + using (ISession session = OpenSession()) using (ITransaction t = session.BeginTransaction()) { @@ -466,7 +379,6 @@ public async Task CloningCriteria_AddCount_RemoveOrderingAsync() .AddOrder(Order.Asc("a.bodyWeight")); ICriteria cloned = CriteriaTransformer.TransformToRowCount(c); - await (cloned.ListAsync()); await (t.RollbackAsync()); s.Close(); @@ -825,93 +737,6 @@ public async Task ProjectionCacheAsync() s.Close(); } - [Test, Ignore("Not supported.")] - public async Task NH_1155_ShouldNotLoadAllChildrenInPagedSubSelectAsync() - { - if (this.Dialect.GetType().Equals((typeof(MsSql2000Dialect)))) - Assert.Ignore("This is not fixed for SQL 2000 Dialect"); - - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - Course course = new Course(); - course.CourseCode = "HIB"; - course.Description = "Hibernate Training"; - await (s.SaveAsync(course)); - - - Student gavin = new Student(); - gavin.Name = "Gavin King"; - gavin.StudentNumber = 667; - await (s.SaveAsync(gavin)); - - Student ayende = new Student(); - ayende.Name = "Ayende Rahien"; - ayende.StudentNumber = 1337; - await (s.SaveAsync(ayende)); - - - Student xam = new Student(); - xam.Name = "Max Rydahl Andersen"; - xam.StudentNumber = 101; - await (s.SaveAsync(xam)); - - Enrolment enrolment = new Enrolment(); - enrolment.Course = course; - enrolment.CourseCode = course.CourseCode; - enrolment.Semester = 1; - enrolment.Year = 1999; - enrolment.Student = xam; - enrolment.StudentNumber = xam.StudentNumber; - xam.Enrolments.Add(enrolment); - await (s.SaveAsync(enrolment)); - - enrolment = new Enrolment(); - enrolment.Course = course; - enrolment.CourseCode = course.CourseCode; - enrolment.Semester = 3; - enrolment.Year = 1998; - enrolment.Student = ayende; - enrolment.StudentNumber = ayende.StudentNumber; - ayende.Enrolments.Add(enrolment); - await (s.SaveAsync(enrolment)); - await (tx.CommitAsync()); - } - - using (ISession s = OpenSession()) - { - IList list = await (s.CreateCriteria(typeof(Student)) - .SetFirstResult(1) - .SetMaxResults(10) - .AddOrder(Order.Asc("StudentNumber")) - .ListAsync()); - foreach (Student student in list) - { - foreach (Enrolment enrolment in student.Enrolments) - { - await (NHibernateUtil.InitializeAsync(enrolment)); - } - } - - Enrolment key = new Enrolment(); - key.CourseCode = "HIB"; - key.StudentNumber = 101;// xam - //since we didn't load xam's entrollments before (skipped by orderring) - //it should not be already loaded - Enrolment shouldNotBeLoaded = (Enrolment)await (s.LoadAsync(typeof(Enrolment), key)); - Assert.IsFalse(NHibernateUtil.IsInitialized(shouldNotBeLoaded)); - } - - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - await (s.DeleteAsync("from Enrolment")); - await (s.DeleteAsync("from Student")); - await (s.DeleteAsync("from Course")); - await (tx.CommitAsync()); - } - } - [Test] public async Task ProjectionsTestAsync() { @@ -978,7 +803,6 @@ public async Task ProjectionsTestAsync() Assert.AreEqual(101L, result[2]); Assert.AreEqual(384.0D, (Double)result[3], 0.01D); - IList resultWithMaps = await (s.CreateCriteria(typeof(Enrolment)) .SetProjection(Projections.Distinct(Projections.ProjectionList() .Add(Projections.Property("StudentNumber"), "stNumber") @@ -1009,7 +833,6 @@ public async Task ProjectionsTestAsync() Assert.AreEqual(101L, m1["stNumber"]); Assert.AreEqual(667L, m0["stNumber"]); - IList resultWithAliasedBean = await (s.CreateCriteria(typeof(Enrolment)) .CreateAlias("Student", "st") .CreateAlias("Course", "co") @@ -1066,7 +889,6 @@ public async Task ProjectionsTestAsync() ProjectionList pp1 = Projections.ProjectionList().Add(Projections.RowCountInt64()); - object r = await (s.CreateCriteria(typeof(Enrolment)) .SetProjection(pp1) .UniqueResultAsync()); @@ -1102,6 +924,176 @@ public async Task ProjectionsTestAsync() s.Close(); } + [Test] + public async Task TestSQLProjectionWithAliasesAsync() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + await (s.SaveAsync(course)); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + await (s.SaveAsync(gavin)); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + await (s.SaveAsync(xam)); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + Student studentSubquery = null; + var subquery = QueryOver.Of(() => studentSubquery) + .And( + Expression.Sql("{e}.studentId = 667 and {studentSubquery}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = await (s.CreateCriteria(typeof(Student)) + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResultAsync()); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.GetCurrentTransaction().CommitAsync()); + } + } + + [Test] + public async Task TestSQLProjectionWithDuplicateAliasesAsync() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + await (s.SaveAsync(course)); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + await (s.SaveAsync(gavin)); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + await (s.SaveAsync(xam)); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + Student student = null; + var subquery = QueryOver.Of(() => student) + .And( + Expression.Sql("{e}.studentId = 667 and {student}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = await (s.CreateCriteria(typeof(Student), "student") + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResultAsync()); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.GetCurrentTransaction().CommitAsync()); + } + } + [Test] public async Task CloningProjectionsTestAsync() { @@ -1170,7 +1162,6 @@ public async Task CloningProjectionsTestAsync() Assert.AreEqual(101L, result[2]); Assert.AreEqual(384.0D, (Double)result[3], 0.01D); - ICriteria criteriaToClone2 = s.CreateCriteria(typeof(Enrolment)) .SetProjection(Projections.Distinct(Projections.ProjectionList() .Add(Projections.Property("StudentNumber"), "stNumber") @@ -1203,7 +1194,6 @@ public async Task CloningProjectionsTestAsync() Assert.AreEqual(101L, m1["stNumber"]); Assert.AreEqual(667L, m0["stNumber"]); - ICriteria criteriaToClone3 = s.CreateCriteria(typeof(Enrolment)) .CreateAlias("Student", "st") .CreateAlias("Course", "co") @@ -1954,7 +1944,6 @@ public async Task CloningProjectionsUsingPropertyAsync() Assert.AreEqual(101L, result[2]); Assert.AreEqual(384.0D, (double)result[3], 0.01D); - await (CriteriaTransformer.Clone( s.CreateCriteria(typeof(Enrolment)) .Add(Property.ForName("StudentNumber").Gt(665L)) @@ -1998,7 +1987,6 @@ public async Task CloningProjectionsUsingPropertyAsync() Assert.AreEqual(101L, m1["stNumber"]); Assert.AreEqual(667L, m0["stNumber"]); - IList resultWithAliasedBean = await (CriteriaTransformer.Clone(s.CreateCriteria(typeof(Enrolment)) .CreateAlias("Student", "st") .CreateAlias("Course", "co") @@ -2098,6 +2086,64 @@ public async Task RestrictionOnSubclassCollectionAsync() s.Close(); } + //NH-2239 (GH-1075) - Wrong OrderBy in generated SQL + [Test] + public async Task OrderByAndOrderedCollectionAsync() + { + var reptile1 = new Reptile { SerialNumber = "9" }; + var reptile2 = new Reptile { SerialNumber = "8" }; + var reptile3 = new Reptile { SerialNumber = "7" }; + var reptile4 = new Reptile { SerialNumber = "6" }; + var reptile5 = new Reptile { SerialNumber = "5" }; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.SaveAsync(reptile5)); + await (s.SaveAsync(reptile1)); + await (s.SaveAsync(reptile2)); + await (s.SaveAsync(reptile3)); + await (s.SaveAsync(reptile4)); + + reptile1.AddOffspring(reptile4); + reptile1.AddOffspring(reptile2); + reptile4.Father = reptile3; + reptile2.Father = reptile4; + reptile1.Father = reptile5; + reptile3.AddOffspring(reptile1); + + await (t.CommitAsync()); + } + + using(var s = OpenSession()) + { + var list = await (s.CreateCriteria(typeof(Reptile)) + .Fetch(SelectMode.Fetch, "offspring") + .AddOrder(Order.Asc("serialNumber")) + .SetResultTransformer(Transformers.DistinctRootEntity) + .ListAsync()); + + var expectedList = list.OrderBy(x => x.SerialNumber).ToList(); + + Assert.That(list.Count, Is.EqualTo(5)); + CollectionAssert.AreEqual(expectedList, list); + + var mother = expectedList.Last(); + Assert.That(NHibernateUtil.IsInitialized(mother.Offspring), Is.True); + + var expectedAssociationList = mother.Offspring.OrderBy(r => r.Father.Id).ToList(); + Assert.That(expectedAssociationList.Count, Is.EqualTo(2)); + CollectionAssert.AreEqual(expectedAssociationList, mother.Offspring); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from Reptile")); + await (t.CommitAsync()); + } + } + [Test] public async Task ClassPropertyAsync() { @@ -2454,6 +2500,22 @@ public async Task SubcriteriaJoinTypesAsync() session.Close(); } + public class NotMappedEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + [Test] + public void CriteriaOnNotMappedEntityAsync() + { + using (ISession session = OpenSession()) + { + Assert.ThrowsAsync( + () => session.CreateCriteria(typeof(NotMappedEntity)).ListAsync()); + } + } + [Test] public void TypeMismatchAsync() { @@ -2520,7 +2582,6 @@ public async Task SameColumnAndAliasNamesAsync() .Add(Projections.Property("StudentNumber"), "StudentNumber") .Add(Projections.Property("Name"), "Name")); - ISession session = OpenSession(); ITransaction t = session.BeginTransaction(); @@ -2557,7 +2618,6 @@ public async Task SameColumnAndAliasNamesResultTransformerAsync() .Add(Property.ForName("Name").Eq("Gavin King")) .AddOrder(Order.Asc("StudentNumber")); - ISession session = OpenSession(); ITransaction t = session.BeginTransaction(); @@ -2689,7 +2749,6 @@ public async Task TransformToRowCountTestAsync() ICriteria subCriterium = crit.CreateCriteria("PreferredCourse"); subCriterium.Add(Property.ForName("CourseCode").Eq("PREFFERED_CODE")); - ICriteria countCriteria = CriteriaTransformer.TransformToRowCount(crit); await (countCriteria.ListAsync()); @@ -2725,7 +2784,6 @@ public async Task OrderProjectionAliasedTestAsync() using (ISession session = OpenSession()) using (ITransaction t = session.BeginTransaction()) { - Course courseA = new Course(); courseA.CourseCode = "HIB-A"; courseA.Description = "Hibernate Training A"; diff --git a/src/NHibernate.Test/Async/Criteria/DetachedCriteriaSerializable.cs b/src/NHibernate.Test/Async/Criteria/DetachedCriteriaSerializable.cs index eac01bb46aa..acaabbf375b 100644 --- a/src/NHibernate.Test/Async/Criteria/DetachedCriteriaSerializable.cs +++ b/src/NHibernate.Test/Async/Criteria/DetachedCriteriaSerializable.cs @@ -137,13 +137,16 @@ public async Task ExecutableCriteriaAsync() await (SerializeAndListAsync(dc)); // Subquery - dc = DetachedCriteria.For(typeof(Student)) - .Add(Property.ForName("StudentNumber").Eq(232L)) - .SetProjection(Property.ForName("Name")); + if (TestDialect.SupportsOperatorAll) + { + dc = DetachedCriteria.For(typeof(Student)) + .Add(Property.ForName("StudentNumber").Eq(232L)) + .SetProjection(Property.ForName("Name")); - DetachedCriteria dcs = DetachedCriteria.For(typeof(Student)) - .Add(Subqueries.PropertyEqAll("Name", dc)); - await (SerializeAndListAsync(dc)); + DetachedCriteria dcs = DetachedCriteria.For(typeof(Student)) + .Add(Subqueries.PropertyEqAll("Name", dc)); + await (SerializeAndListAsync(dcs)); + } // SQLCriterion dc = DetachedCriteria.For(typeof(Student)) diff --git a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs index 215c0fff9b2..ff3b63d13e4 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityJoinCriteriaTest.cs @@ -208,7 +208,6 @@ public async Task MixOfJoinsForAssociatedAndNotAssociatedEntitiesAsync() using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - EntityComplex root = null; EntityComplex ejLevel1 = null; EntitySimpleChild customChildForEjLevel1 = null; @@ -293,7 +292,6 @@ public async Task NullLeftEntityJoinWithEntityProjectionAsync() [Test] public async Task EntityJoinForCustomEntityNameAsync() { - using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { @@ -314,7 +312,6 @@ public async Task EntityJoinForCustomEntityNameAsync() [Test] public async Task EntityJoinForCustomEntityName_ExpressionAsync() { - using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { @@ -415,7 +412,6 @@ protected override HbmMapping GetMappings() m.Inverse(true); }, a => a.OneToMany()); - }); mapper.Class( @@ -465,7 +461,6 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Composite1Key1); rc.Property(e => e.Composite1Key2); rc.Property(e => e.CustomEntityNameId); - }); mapper.Class( diff --git a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs index 5167de586a1..9429c309985 100644 --- a/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs @@ -47,7 +47,17 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); - rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + rc.Property(ep => ep.LazyProp, m => + { + m.Lazy(true); + m.FetchGroup("LazyProp1"); + }); + + rc.Property(ep => ep.LazyProp2, m => + { + m.Lazy(true); + m.FetchGroup("LazyProp2"); + }); rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); @@ -61,7 +71,6 @@ protected override HbmMapping GetMappings() m.Inverse(true); }, a => a.OneToMany()); - }); mapper.Class( @@ -192,7 +201,6 @@ public async Task RootEntityProjectionLazyAsync() .Select(Projections.RootEntity().SetLazy(true)) .Take(1).SingleOrDefaultAsync()); - Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.False, "Object must be lazy loaded."); } } @@ -325,7 +333,6 @@ public async Task MultipleLazyEntityProjectionsAsync() //make sure objects are populated from different aliases for the same types Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected for root and sameTypeChild."); Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected for child1 and child2."); - } } @@ -343,6 +350,26 @@ public async Task EntityProjectionWithLazyPropertiesFetchedAsync() Assert.That(entityRoot, Is.Not.Null); Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized"); Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.True, "Lazy property must be initialized"); + } + } + + [Test] + public async Task EntityProjectionWithLazyPropertiesSinglePropertyFetchAsync() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot; + entityRoot = await (session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity().SetFetchLazyPropertyGroups(nameof(entityRoot.LazyProp))) + .Take(1).SingleOrDefaultAsync()); + + Assert.That(entityRoot, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.False, "Property must be lazy"); } } @@ -455,7 +482,6 @@ public async Task MultipleEntitiesProjectionsToResultTransformerAsync() } } - [Test] public async Task ReadOnlyProjectionAsync() { @@ -487,7 +513,6 @@ public async Task EntityProjectionForCompositeKeyInitializedAsync() Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key) && Equals(x.Name, y.Name)) ? 0 : 1)); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } - } [Test] diff --git a/src/NHibernate.Test/Async/Criteria/Lambda/FunctionsIntegrationFixture.cs b/src/NHibernate.Test/Async/Criteria/Lambda/FunctionsIntegrationFixture.cs index 59731c3ed08..e21a2e3f6b2 100644 --- a/src/NHibernate.Test/Async/Criteria/Lambda/FunctionsIntegrationFixture.cs +++ b/src/NHibernate.Test/Async/Criteria/Lambda/FunctionsIntegrationFixture.cs @@ -62,7 +62,6 @@ public async Task SqrtSingleOrDefaultAsync() } } - [Test] public async Task RoundDoubleWithOneArgumentAsync() { diff --git a/src/NHibernate.Test/Async/Criteria/Lambda/IntegrationFixture.cs b/src/NHibernate.Test/Async/Criteria/Lambda/IntegrationFixture.cs index 017634dc4f5..df178490384 100644 --- a/src/NHibernate.Test/Async/Criteria/Lambda/IntegrationFixture.cs +++ b/src/NHibernate.Test/Async/Criteria/Lambda/IntegrationFixture.cs @@ -428,8 +428,8 @@ public async Task MultiQueryAsync() .Add("page", query) .Add("count", query.ToRowCountQuery()); - var pageResults = await (multiQuery.GetResultAsync("page", CancellationToken.None)); - var countResults = await (multiQuery.GetResultAsync("count", CancellationToken.None)); + var pageResults = await (multiQuery.GetResultAsync("page")); + var countResults = await (multiQuery.GetResultAsync("count")); Assert.That(pageResults.Count, Is.EqualTo(1)); Assert.That(pageResults[0].Name, Is.EqualTo("Name 3")); @@ -451,8 +451,8 @@ public async Task MultiQueryAsync() .Add("page", query) .Add("count", query.ToRowCountQuery()); - var pageResults = await (multiCriteria.GetResultAsync("page", CancellationToken.None)); - var countResults = await (multiCriteria.GetResultAsync("count", CancellationToken.None)); + var pageResults = await (multiCriteria.GetResultAsync("page")); + var countResults = await (multiCriteria.GetResultAsync("count")); Assert.That(pageResults.Count, Is.EqualTo(1)); Assert.That(pageResults[0].Name, Is.EqualTo("Name 3")); @@ -510,5 +510,40 @@ public async Task StatelessSessionAsync() Assert.That(statelessPerson2.Id, Is.EqualTo(personId)); } } + + [Test] + public async Task QueryOverArithmeticAsync() + { + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(new Person() {Name = "test person 1", Age = 20})); + await (s.SaveAsync(new Person() {Name = "test person 2", Age = 50})); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + var persons1 = await (s.QueryOver().Where(p => ((p.Age * 2) / 2) + 20 - 20 == 20).ListAsync()); + var persons2 = await (s.QueryOver().Where(p => (-(-p.Age)) > 20).ListAsync()); + var persons3 = await (s.QueryOver().WhereRestrictionOn(p => ((p.Age * 2) / 2) + 20 - 20).IsBetween(19).And(21).ListAsync()); + var persons4 = await (s.QueryOver().WhereRestrictionOn(p => -(-p.Age)).IsBetween(19).And(21).ListAsync()); + var persons5 = await (s.QueryOver().WhereRestrictionOn(p => ((p.Age * 2) / 2) + 20 - 20).IsBetween(19).And(51).ListAsync()); + var persons6 = await (s.QueryOver().Where(p => ((p.Age * 2) / 2) + 20 - 20 == p.Age - p.Age + 20).ListAsync()); +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + var persons7 = await (s.QueryOver().Where(p => ((p.Age * 2) / 2) + 20 - 20 == null || p.Age * 2 == 20 * 1).ListAsync()); +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + var val1 = await (s.QueryOver().Select(p => p.Age * 2).Where(p => p.Age == 20).SingleOrDefaultAsync()); + + Assert.That(persons1.Count, Is.EqualTo(1)); + Assert.That(persons2.Count, Is.EqualTo(1)); + Assert.That(persons3.Count, Is.EqualTo(1)); + Assert.That(persons4.Count, Is.EqualTo(1)); + Assert.That(persons5.Count, Is.EqualTo(2)); + Assert.That(persons6.Count, Is.EqualTo(1)); + Assert.That(persons7.Count, Is.EqualTo(0)); + Assert.That(val1, Is.EqualTo(40)); + } + } } } diff --git a/src/NHibernate.Test/Async/Criteria/Lambda/ProjectIntegrationFixture.cs b/src/NHibernate.Test/Async/Criteria/Lambda/ProjectIntegrationFixture.cs index 446a0b1833a..e1c2cd5c089 100644 --- a/src/NHibernate.Test/Async/Criteria/Lambda/ProjectIntegrationFixture.cs +++ b/src/NHibernate.Test/Async/Criteria/Lambda/ProjectIntegrationFixture.cs @@ -134,5 +134,43 @@ var actual Assert.That((int) actual[1], Is.EqualTo(2), "distinct count by name"); } } + + [Test] + public async Task ProjectionCanCoalesceInSelectAsync() + { + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var actual + = (await (s.QueryOver() + .Select(x => x.Age.Coalesce(0)) + .Where(x => x.Age == 20) + .ListAsync())).FirstOrDefault(); + + Assert.That(actual, Is.EqualTo(20)); + } + } + + //NH-2983 + [Test] + public async Task ProjectionSelectSumOnCoalesceAsync() + { + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var actual + = (await (s.QueryOver() + .SelectList( + l => + l + .SelectSum(xx => xx.Age.Coalesce(0)) + .SelectSum(xx => xx.Age.Coalesce(1))) + .Where(x => x.Age == 20) + .ListAsync())).FirstOrDefault(); + + Assert.That(actual[0], Is.EqualTo(20)); + Assert.That(actual[1], Is.EqualTo(20)); + } + } } } diff --git a/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs index c885d804f96..aca0e5d3660 100644 --- a/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs @@ -197,7 +197,6 @@ public async Task UsingConditionalsAsync() .UniqueResultAsync()); Assert.AreEqual("yes", result); - result = await (session.CreateCriteria(typeof(Student)) .SetProjection( Projections.Conditional( @@ -222,7 +221,6 @@ public async Task UseInWithProjectionAsync() } } - [Test] public async Task UseLikeWithProjectionAsync() { @@ -271,7 +269,6 @@ public async Task UseEqWithProjectionAsync() } } - [Test] public async Task UseGtWithProjectionAsync() { diff --git a/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs new file mode 100644 index 00000000000..b3f6e51b233 --- /dev/null +++ b/src/NHibernate.Test/Async/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -0,0 +1,290 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.SqlCommand; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + using System.Threading.Tasks; + [TestFixture] + public class QueryOverCacheableTestsAsync : CriteriaNorthwindReadonlyTestCase + { + //Just for discoverability + private class CriteriaCacheableTest{} + + protected override void Configure(Configuration config) + { + config.SetProperty(Environment.UseQueryCache, "true"); + config.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(config); + } + + [Test] + public async Task QueryIsCacheableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + await (db.Customers.Cacheable().Take(1).ListAsync()); + await (db.Customers.Cacheable().Take(1).ListAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task QueryIsCacheable2Async() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + await (db.Customers.Cacheable().Take(1).ListAsync()); + await (db.Customers.Take(1).ListAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "Unexpected cache hit count"); + } + + [Test] + public async Task QueryIsCacheableWithRegionAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + await (Sfi.EvictQueriesAsync("test")); + await (Sfi.EvictQueriesAsync("other")); + + await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); + await (db.Customers.Cacheable().Take(1).CacheRegion("test").ListAsync()); + await (db.Customers.Cacheable().Take(1).CacheRegion("other").ListAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task CanBeCombinedWithFetchAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + await (db.Customers + .Cacheable() + .ListAsync()); + + await (db.Orders + .Cacheable() + .Take(1) + .ListAsync()); + + await (db.Customers + .Fetch(SelectMode.Fetch, x => x.Orders) + .Cacheable() + .Take(1) + .ListAsync()); + + await (db.Orders + .Fetch(SelectMode.Fetch, x => x.OrderLines) + .Cacheable() + .Take(1) + .ListAsync()); + + await (db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefaultAsync()); + + var customer = await (db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(customer.Address), Is.True, "Expected the fetched Address to be initialized"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(5), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task FetchIsCacheableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + var order = (await (db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + Session.Clear(); + + order = (await (db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task FetchIsCacheableForJoinAliasAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + Customer customer = null; + OrderLine orderLines = null; + Product product = null; + OrderLine prOrderLines = null; + + var order = (await (db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + Session.Clear(); + + order = (await (db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .ListAsync())) + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task FutureFetchIsCacheableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + var order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(multiQueries ? 1 : 2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(2), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + Session.Clear(); + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); + } + + private static void AssertFetchedOrder(Order order) + { + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items"); + var orderLine = order.OrderLines.First(); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + } + } +} diff --git a/src/NHibernate.Test/Async/Criteria/SelectModeTest/SelectModeTest.cs b/src/NHibernate.Test/Async/Criteria/SelectModeTest/SelectModeTest.cs index b9d0531be4f..c9446e1dd6d 100644 --- a/src/NHibernate.Test/Async/Criteria/SelectModeTest/SelectModeTest.cs +++ b/src/NHibernate.Test/Async/Criteria/SelectModeTest/SelectModeTest.cs @@ -54,6 +54,27 @@ public async Task SelectModeJoinOnlyAsync() } } + [Test] + public async Task SelectModeDetachedQueryOverAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex root = null; + root = await (QueryOver.Of(() => root) + .Where(x => x.Id == _parentEntityComplexId) + .Fetch(SelectMode.Fetch, r => r.Child1) + .GetExecutableQueryOver(session) + .SingleOrDefaultAsync()); + + Assert.That(root, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(root.Child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root.Child1), Is.True, "Joined ManyToOne Child1 should not be fetched."); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + [Test] public async Task SelectModeFetchAsync() { @@ -153,6 +174,53 @@ public async Task SelectModeFetchLazyPropertiesAsync() Assert.That(NHibernateUtil.IsInitialized(root), Is.True); Assert.That(root.LazyProp, Is.Not.Null); Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched."); + + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task SelectModeFetchKeepLazyPropertiesUninitializedAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var root = await (session.QueryOver() + .Fetch(SelectMode.Fetch, ec => ec) + .Where(ec => ec.LazyProp != null) + .Take(1) + .SingleOrDefaultAsync()); + + Assert.That(root, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.False, "Property must be lazy."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.False, "Property must be lazy."); + + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task SelectModeFetchLazyPropertiesFetchGroupAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var root = await (session.QueryOver() + .Fetch(SelectMode.FetchLazyPropertyGroup, ec => ec.LazyProp, ec => ec.LazyProp2, ec => ec.SameTypeChild.LazyProp2) + .Where(ec => ec.Id == _parentEntityComplexId) + .SingleOrDefaultAsync()); + + Assert.That(root, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(root.LazyProp, Is.Not.Null); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsInitialized(root.SameTypeChild), Is.True, "Object must be initialized."); + Assert.That(root.SameTypeChild, Is.Not.Null); + Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp)), Is.False, "Property must be lazy."); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } @@ -412,6 +480,72 @@ public async Task OrderedInnerJoinFetchAsync() } } + //GH-2440 + [Test] + public async Task FetchWithAliasedJoinFutureAsync() + { + using (var session = OpenSession()) + { + EntityComplex alias = null; + EntitySimpleChild child1 = null; + var list = (await (session.QueryOver(() => alias) + .Where(ec => ec.Id == _parentEntityComplexId) + .JoinQueryOver(() => alias.Child1, () => child1) + .Fetch(SelectMode.Fetch, () => alias.ChildrenList) + .TransformUsing(Transformers.DistinctRootEntity) + .Future() + .GetEnumerableAsync())) + .ToList(); + + var childList = list[0].ChildrenList; + Assert.That(list[0].ChildrenList.Count, Is.GreaterThan(1)); + Assert.That(list[0].ChildrenList, Is.EqualTo(list[0].ChildrenList.OrderByDescending(c => c.OrderIdx)), "wrong order"); + } + } + + //GH-2440 + [Test] + public async Task CacheableFetchWithAliasedJoinFutureAsync() + { + using (var session = OpenSession()) + { + EntityComplex alias = null; + EntitySimpleChild child1 = null; + var list = (await (session.QueryOver(() => alias) + .Where(ec => ec.Id == _parentEntityComplexId) + .JoinQueryOver(() => alias.Child1, () => child1) + .Fetch(SelectMode.Fetch, () => alias.ChildrenList) + .TransformUsing(Transformers.DistinctRootEntity) + .Cacheable() + .Future() + .GetEnumerableAsync())) + .ToList(); + EntityComplex value = null; + Assert.DoesNotThrow(() => value = list[0]); + Assert.That(value, Is.Not.Null); + } + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex alias = null; + EntitySimpleChild child1 = null; + var list = session.QueryOver(() => alias) + .Where(ec => ec.Id == _parentEntityComplexId) + .JoinQueryOver(() => alias.Child1, () => child1) + .Fetch(SelectMode.Fetch, () => alias.ChildrenList) + .TransformUsing(Transformers.DistinctRootEntity) + .Cacheable() + .Future() + .ToList(); + EntityComplex value = null; + Assert.DoesNotThrow(() => value = list[0]); + Assert.That(value, Is.Not.Null); + + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(0), "Query is expected to be retrieved from cache"); + } + } + [Test, Obsolete] public async Task FetchModeEagerForLazyAsync() { @@ -558,7 +692,16 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); - rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + rc.Property(ep => ep.LazyProp, m => + { + m.Lazy(true); + m.FetchGroup("LazyGroup"); + }); + rc.Property(ep => ep.LazyProp2, m => + { + m.Lazy(true); + m.FetchGroup("LazyGroup2"); + }); rc.ManyToOne( ep => ep.Child1, @@ -634,7 +777,6 @@ private static void MapList(IClassMapper rc, Express ckm.Name("ParentId"); }); km.ForeignKey("none"); - }); m.Cascade(Mapping.ByCode.Cascade.All); if (fetchMode != null) @@ -731,9 +873,12 @@ protected override void OnSetUp() Child1 = child1, Child2 = child2, LazyProp = "SomeBigValue", + LazyProp2 = "SomeBigValue2", SameTypeChild = new EntityComplex() { - Name = "ComplexEntityChild" + Name = "ComplexEntityChild", + LazyProp = "LazyProp1", + LazyProp2 = "LazyProp2", }, ChildrenList = new List {child3, child1, child4 }, ChildrenListEmpty = new List { }, diff --git a/src/NHibernate.Test/Async/DebugSessionFactory.cs b/src/NHibernate.Test/Async/DebugSessionFactory.cs index 54b9e7ac6ad..5c24cd724c5 100644 --- a/src/NHibernate.Test/Async/DebugSessionFactory.cs +++ b/src/NHibernate.Test/Async/DebugSessionFactory.cs @@ -26,6 +26,7 @@ using NHibernate.Id; using NHibernate.Impl; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; diff --git a/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs b/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs index 2fd3d53d33a..7cd23188f84 100644 --- a/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs +++ b/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs @@ -41,7 +41,6 @@ public class DialectFixtureAsync // be. protected string[] tableThatNeedsToBeQuoted; - [SetUp] public virtual void SetUp() { diff --git a/src/NHibernate.Test/Async/DriverTest/Sql2008DateTime2Test.cs b/src/NHibernate.Test/Async/DriverTest/Sql2008DateTime2Test.cs index 091bd489524..cf496863aab 100644 --- a/src/NHibernate.Test/Async/DriverTest/Sql2008DateTime2Test.cs +++ b/src/NHibernate.Test/Async/DriverTest/Sql2008DateTime2Test.cs @@ -74,6 +74,5 @@ public async Task CrudAsync() await (t.CommitAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs b/src/NHibernate.Test/Async/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs index 6bef5e1ccd1..60f8a1042d1 100644 --- a/src/NHibernate.Test/Async/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs +++ b/src/NHibernate.Test/Async/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs @@ -9,7 +9,6 @@ using System; -using System.Collections; using NHibernate.Cfg; using NUnit.Framework; @@ -38,68 +37,78 @@ protected override void Configure(Configuration configuration) [Test] public async Task ItAsync() { + var company = ProxyHelper.NewCompanyProxy(); + var customer = ProxyHelper.NewCustomerProxy(); // Test saving these dyna-proxies - ISession session = OpenSession(); - session.BeginTransaction(); - Company company = ProxyHelper.NewCompanyProxy(); - company.Name = "acme"; - await (session.SaveAsync(company)); - Customer customer = ProxyHelper.NewCustomerProxy(); - customer.Name = "Steve"; - customer.Company = company; - await (session.SaveAsync(customer)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + company.Name = "acme"; + await (session.SaveAsync(company)); + customer.Name = "Steve"; + customer.Company = company; + await (session.SaveAsync(customer)); + await (tran.CommitAsync()); + session.Close(); + } Assert.IsNotNull(company.Id, "company id not assigned"); Assert.IsNotNull(customer.Id, "customer id not assigned"); // Test loading these dyna-proxies, along with flush processing - session = OpenSession(); - session.BeginTransaction(); - customer = await (session.LoadAsync(customer.Id)); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + customer = await (session.LoadAsync(customer.Id)); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); - customer.Name = "other"; - await (session.FlushAsync()); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); + customer.Name = "other"; + await (session.FlushAsync()); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); - await (session.RefreshAsync(customer)); - Assert.AreEqual("other", customer.Name, "name not updated"); - Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); + await (session.RefreshAsync(customer)); + Assert.AreEqual("other", customer.Name, "name not updated"); + Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); - await (session.Transaction.CommitAsync()); - session.Close(); + await (tran.CommitAsync()); + session.Close(); + } // Test detached entity re-attachment with these dyna-proxies customer.Name = "Steve"; - session = OpenSession(); - session.BeginTransaction(); - await (session.UpdateAsync(customer)); - await (session.FlushAsync()); - await (session.RefreshAsync(customer)); - Assert.AreEqual("Steve", customer.Name, "name not updated"); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + await (session.UpdateAsync(customer)); + await (session.FlushAsync()); + await (session.RefreshAsync(customer)); + Assert.AreEqual("Steve", customer.Name, "name not updated"); + await (tran.CommitAsync()); + session.Close(); + } // Test querying - session = OpenSession(); - session.BeginTransaction(); - int count = (await (session.CreateQuery("from Customer").ListAsync())).Count; - Assert.AreEqual(1, count, "querying dynamic entity"); - session.Clear(); - count = (await (session.CreateQuery("from Person").ListAsync())).Count; - Assert.AreEqual(1, count, "querying dynamic entity"); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + int count = (await (session.CreateQuery("from Customer").ListAsync())).Count; + Assert.AreEqual(1, count, "querying dynamic entity"); + session.Clear(); + count = (await (session.CreateQuery("from Person").ListAsync())).Count; + Assert.AreEqual(1, count, "querying dynamic entity"); + await (tran.CommitAsync()); + session.Close(); + } // test deleteing - session = OpenSession(); - session.BeginTransaction(); - await (session.DeleteAsync(company)); - await (session.DeleteAsync(customer)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + await (session.DeleteAsync(company)); + await (session.DeleteAsync(customer)); + await (tran.CommitAsync()); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/Async/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs b/src/NHibernate.Test/Async/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs index d6a8504ab00..758a0494fe4 100644 --- a/src/NHibernate.Test/Async/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs +++ b/src/NHibernate.Test/Async/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs @@ -9,7 +9,6 @@ using System; -using System.Collections; using System.Collections.Generic; using NHibernate.Cfg; using NUnit.Framework; @@ -39,30 +38,33 @@ protected override void Configure(Configuration configuration) [Test] public async Task ItAsync() { + var company = ProxyHelper.NewCompanyProxy(); + var customer = ProxyHelper.NewCustomerProxy(); + var address = ProxyHelper.NewAddressProxy(); + var son = ProxyHelper.NewPersonProxy(); + var wife = ProxyHelper.NewPersonProxy(); + // Test saving these dyna-proxies - ISession session = OpenSession(); - session.BeginTransaction(); - Company company = ProxyHelper.NewCompanyProxy(); - company.Name = "acme"; - await (session.SaveAsync(company)); - Customer customer = ProxyHelper.NewCustomerProxy(); - customer.Name = "Steve"; - customer.Company = company; - Address address = ProxyHelper.NewAddressProxy(); - address.Street = "somewhere over the rainbow"; - address.City = "lawerence, kansas"; - address.PostalCode = "toto"; - customer.Address = address; - customer.Family = new HashSet(); - Person son = ProxyHelper.NewPersonProxy(); - son.Name = "son"; - customer.Family.Add(son); - Person wife = ProxyHelper.NewPersonProxy(); - wife.Name = "wife"; - customer.Family.Add(wife); - await (session.SaveAsync(customer)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + company.Name = "acme"; + await (session.SaveAsync(company)); + customer.Name = "Steve"; + customer.Company = company; + address.Street = "somewhere over the rainbow"; + address.City = "lawerence, kansas"; + address.PostalCode = "toto"; + customer.Address = address; + customer.Family = new HashSet(); + son.Name = "son"; + customer.Family.Add(son); + wife.Name = "wife"; + customer.Family.Add(wife); + await (session.SaveAsync(customer)); + await (tran.CommitAsync()); + session.Close(); + } Assert.IsNotNull(company.Id, "company id not assigned"); Assert.IsNotNull(customer.Id, "customer id not assigned"); @@ -71,51 +73,59 @@ public async Task ItAsync() Assert.IsNotNull(wife.Id, "wife:Person id not assigned"); // Test loading these dyna-proxies, along with flush processing - session = OpenSession(); - session.BeginTransaction(); - customer = await (session.LoadAsync(customer.Id)); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + customer = await (session.LoadAsync(customer.Id)); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); - customer.Name = "other"; - await (session.FlushAsync()); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); + customer.Name = "other"; + await (session.FlushAsync()); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); - await (session.RefreshAsync(customer)); - Assert.AreEqual("other", customer.Name, "name not updated"); - Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); + await (session.RefreshAsync(customer)); + Assert.AreEqual("other", customer.Name, "name not updated"); + Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); - await (session.Transaction.CommitAsync()); - session.Close(); + await (tran.CommitAsync()); + session.Close(); + } // Test detached entity re-attachment with these dyna-proxies customer.Name = "Steve"; - session = OpenSession(); - session.BeginTransaction(); - await (session.UpdateAsync(customer)); - await (session.FlushAsync()); - await (session.RefreshAsync(customer)); - Assert.AreEqual("Steve", customer.Name, "name not updated"); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + await (session.UpdateAsync(customer)); + await (session.FlushAsync()); + await (session.RefreshAsync(customer)); + Assert.AreEqual("Steve", customer.Name, "name not updated"); + await (tran.CommitAsync()); + session.Close(); + } // Test querying - session = OpenSession(); - session.BeginTransaction(); - int count = (await (session.CreateQuery("from Customer").ListAsync())).Count; - Assert.AreEqual(1, count, "querying dynamic entity"); - session.Clear(); - count = (await (session.CreateQuery("from Person").ListAsync())).Count; - Assert.AreEqual(3, count, "querying dynamic entity"); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + int count = (await (session.CreateQuery("from Customer").ListAsync())).Count; + Assert.AreEqual(1, count, "querying dynamic entity"); + session.Clear(); + count = (await (session.CreateQuery("from Person").ListAsync())).Count; + Assert.AreEqual(3, count, "querying dynamic entity"); + await (tran.CommitAsync()); + session.Close(); + } // test deleteing - session = OpenSession(); - session.BeginTransaction(); - await (session.DeleteAsync(company)); - await (session.DeleteAsync(customer)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + await (session.DeleteAsync(company)); + await (session.DeleteAsync(customer)); + await (tran.CommitAsync()); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/Async/Events/Collections/AbstractCollectionEventFixture.cs b/src/NHibernate.Test/Async/Events/Collections/AbstractCollectionEventFixture.cs index 9f6e691d52c..04be202d8a3 100644 --- a/src/NHibernate.Test/Async/Events/Collections/AbstractCollectionEventFixture.cs +++ b/src/NHibernate.Test/Async/Events/Collections/AbstractCollectionEventFixture.cs @@ -8,6 +8,7 @@ //------------------------------------------------------------------------------ +using System; using System.Collections; using System.Collections.Generic; using NHibernate.Collection; @@ -38,30 +39,12 @@ protected override string MappingsAssembly protected override void OnTearDown() { - IParentWithCollection dummyParent = CreateParent("dummyParent"); - dummyParent.NewChildren(CreateCollection()); - IChild dummyChild = dummyParent.AddChild("dummyChild"); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) { - using (ITransaction tx = s.BeginTransaction()) - { - IList children = s.CreateCriteria(dummyChild.GetType()).List(); - IList parents = s.CreateCriteria(dummyParent.GetType()).List(); - foreach (IParentWithCollection parent in parents) - { - parent.ClearChildren(); - s.Delete(parent); - } - foreach (IChild child in children) - { - s.Delete(child); - } - - tx.Commit(); - } + s.Delete("from System.Object"); + tx.Commit(); } - base.OnTearDown(); } [Test] diff --git a/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs b/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs index 0ad016e2c01..6167138396e 100644 --- a/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs +++ b/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs @@ -9,7 +9,6 @@ using System; -using System.Collections; using System.Data; using System.Data.Common; using NHibernate.Dialect; @@ -80,65 +79,68 @@ protected override void Configure(Cfg.Configuration configuration) [Test] public async Task IntegrityViolationAsync() { - //ISQLExceptionConverter converter = Dialect.BuildSQLExceptionConverter(); ISQLExceptionConverter converter = Sfi.Settings.SqlExceptionConverter; - ISession session = OpenSession(); - session.BeginTransaction(); - var connection = session.Connection; - - // Attempt to insert some bad values into the T_MEMBERSHIP table that should - // result in a constraint violation - DbCommand ps = null; - try + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - ps = connection.CreateCommand(); - ps.CommandType = CommandType.Text; - ps.CommandText = "INSERT INTO T_MEMBERSHIP (user_id, group_id) VALUES (@p1, @p2)"; - var pr = ps.CreateParameter(); - pr.ParameterName = "p1"; - pr.DbType = DbType.Int64; - pr.Value = 52134241L; // Non-existent user_id - ps.Parameters.Add(pr); - - pr = ps.CreateParameter(); - pr.ParameterName = "p2"; - pr.DbType = DbType.Int64; - pr.Value = 5342L; // Non-existent group_id - ps.Parameters.Add(pr); - - session.Transaction.Enlist(ps); - await (ps.ExecuteNonQueryAsync()); + var connection = session.Connection; - Assert.Fail("INSERT should have failed"); - } - catch (Exception sqle) - { - ADOExceptionReporter.LogExceptions(sqle, "Just output!!!!"); - Exception adoException = converter.Convert(new AdoExceptionContextInfo{SqlException = sqle}); - Assert.AreEqual(typeof(ConstraintViolationException), adoException.GetType(), - "Bad conversion [" + sqle.Message + "]"); - ConstraintViolationException ex = (ConstraintViolationException)adoException; - Console.WriteLine("Violated constraint name: " + ex.ConstraintName); - } - finally - { - if (ps != null) + // Attempt to insert some bad values into the T_MEMBERSHIP table that should + // result in a constraint violation + DbCommand ps = null; + try { - try - { - ps.Dispose(); - } - catch (Exception) + ps = connection.CreateCommand(); + ps.CommandType = CommandType.Text; + ps.CommandText = "INSERT INTO T_MEMBERSHIP (user_id, group_id) VALUES (@p1, @p2)"; + var pr = ps.CreateParameter(); + pr.ParameterName = "p1"; + pr.DbType = DbType.Int64; + pr.Value = 52134241L; // Non-existent user_id + ps.Parameters.Add(pr); + + pr = ps.CreateParameter(); + pr.ParameterName = "p2"; + pr.DbType = DbType.Int64; + pr.Value = 5342L; // Non-existent group_id + ps.Parameters.Add(pr); + + tran.Enlist(ps); + await (ps.ExecuteNonQueryAsync()); + + Assert.Fail("INSERT should have failed"); + } + catch (Exception sqle) + { + ADOExceptionReporter.LogExceptions(sqle, "Just output!!!!"); + Exception adoException = converter.Convert(new AdoExceptionContextInfo { SqlException = sqle }); + Assert.AreEqual( + typeof(ConstraintViolationException), + adoException.GetType(), + "Bad conversion [" + sqle.Message + "]"); + ConstraintViolationException ex = (ConstraintViolationException) adoException; + Console.WriteLine("Violated constraint name: " + ex.ConstraintName); + } + finally + { + if (ps != null) { - // ignore... + try + { + ps.Dispose(); + } + catch (Exception) + { + // ignore... + } } } - } - await (session.Transaction.RollbackAsync()); - session.Close(); + await (tran.RollbackAsync()); + session.Close(); + } } [Test] diff --git a/src/NHibernate.Test/Async/ExpressionTest/Projection/ProjectionSqlFixture.cs b/src/NHibernate.Test/Async/ExpressionTest/Projection/ProjectionSqlFixture.cs index 50ec21d53b2..86b59bbe491 100644 --- a/src/NHibernate.Test/Async/ExpressionTest/Projection/ProjectionSqlFixture.cs +++ b/src/NHibernate.Test/Async/ExpressionTest/Projection/ProjectionSqlFixture.cs @@ -92,7 +92,7 @@ public async Task QueryTest1Async() .Add(Projections.Max("Pay")) .Add(Projections.Min("Pay"))) ; - IList result = await (c.ListAsync());// c.UniqueResult(); + IList result = await (c.ListAsync()); // c.UniqueResult(); Assert.IsTrue(result.Count == 1, "More than one record was found, while just one was expected"); Assert.IsTrue(result[0] is object[], "expected object[] as result, but found " + result[0].GetType().Name); @@ -116,7 +116,7 @@ public async Task SelectSqlProjectionTestAsync() new string[] { "MyPay" }, new IType[] { NHibernateUtil.Double }))); - IList result = await (c.ListAsync());// c.UniqueResult(); + IList result = await (c.ListAsync()); // c.UniqueResult(); Assert.IsTrue(result.Count == 1); object results = result[0]; Assert.AreEqual(results, 2.5); diff --git a/src/NHibernate.Test/Async/ExpressionTest/QueryByExampleTest.cs b/src/NHibernate.Test/Async/ExpressionTest/QueryByExampleTest.cs index 5d6204f408d..4c734ef7bd9 100644 --- a/src/NHibernate.Test/Async/ExpressionTest/QueryByExampleTest.cs +++ b/src/NHibernate.Test/Async/ExpressionTest/QueryByExampleTest.cs @@ -110,7 +110,6 @@ public async Task TestJunctionNotExpressionQBEAsync() ICriteria crit = s.CreateCriteria(typeof(Componentizable)); Example ex = Example.Create(master).EnableLike(); - crit.Add(Expression.Or(Expression.Not(ex), ex)); IList result = await (crit.ListAsync()); @@ -202,4 +201,4 @@ private Componentizable GetMaster(String name, String subName, String subName1) return master; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs b/src/NHibernate.Test/Async/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs index b5e7c9b38a5..f8ccfdb53f4 100644 --- a/src/NHibernate.Test/Async/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs +++ b/src/NHibernate.Test/Async/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs @@ -56,7 +56,6 @@ protected override void OnSetUp() comment.IndexInPost = 0; post1.Comments.Add(comment); - session.Save(category); session.Save(author); session.Save(commenter); @@ -116,4 +115,4 @@ public async Task ComplexSubQuery_QueryingByGrandChildrenAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Extralazy/ExtraLazyFixture.cs b/src/NHibernate.Test/Async/Extralazy/ExtraLazyFixture.cs index 4dd7e85fced..4543d6e8a6f 100644 --- a/src/NHibernate.Test/Async/Extralazy/ExtraLazyFixture.cs +++ b/src/NHibernate.Test/Async/Extralazy/ExtraLazyFixture.cs @@ -8,10 +8,13 @@ //------------------------------------------------------------------------------ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using NHibernate.Cfg; using NUnit.Framework; +using NUnit.Framework.Constraints; namespace NHibernate.Test.Extralazy { @@ -34,13 +37,2258 @@ protected override string CacheConcurrencyStrategy get { return null; } } + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Cfg.Environment.GenerateStatistics, "true"); + } + protected override void OnTearDown() { using (var s = OpenSession()) using (var t = s.BeginTransaction()) { - s.Delete("from System.Object"); - t.Commit(); + s.Delete("from System.Object"); + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListAddAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Gavin's companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after companies count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after count"); + + // Test adding companies with ICollection interface + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Test adding companies with IList interface + Sfi.Statistics.Clear(); + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + // Returned value is not valid with lazy list, no check for it. + ((IList) gavin.Companies).Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after adding through IList"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through IList"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through IList"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding through IList"); + + // Check existence of added companies + Sfi.Statistics.Clear(); + // Have to skip unloaded (non-queued indeed) elements to avoid triggering existence queries on them. + foreach (var item in addedItems.Skip(5)) + { + Assert.That(gavin.Companies.Contains(item), Is.True, "Company '{0}' existence", item.Name); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of non-flushed"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after checking existence of non-flushed"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after checking existence of non-flushed"); + + // Check existence of not loaded companies + Assert.That(gavin.Companies.Contains(addedItems[0]), Is.True, "First company existence"); + Assert.That(gavin.Companies.Contains(addedItems[1]), Is.True, "Second company existence"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of unloaded"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking existence of unloaded"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after checking existence of unloaded"); + + // Check existence of not existing companies + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Contains(new Company("test1", 15, gavin)), Is.False, "First non-existent test"); + Assert.That(gavin.Companies.Contains(new Company("test2", 16, gavin)), Is.False, "Second non-existent test"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking non-existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking non-existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after checking non-existence"); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [Explicit] + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public async Task ListAddDuplicatedAsync(bool initialize, bool flush) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count before flush"); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statement count after count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after get"); + + // Re-add items + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + gavin.Companies.Add(addedItems[i]); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statement count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after re-adding"); + + if (flush) + { + await (s.FlushAsync()); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after second flush"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after second flush"); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumeration"); + Assert.That(gavin.Companies.Count, Is.EqualTo(flush ? 5 : 10), "Companies count after enumeration"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after loading Gavin again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading Gavin again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListInsertAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after get"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after get"); + + // Test inserting companies at the start + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(0, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after insert"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after insert"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after insert"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after insert"); + + // Test inserting companies at the end + Sfi.Statistics.Clear(); + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after tail insert"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after tail insert"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after tail insert"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after tail insert"); + + // Try insert invalid indexes + Assert.Throws( + () => gavin.Companies.Insert(-1, new Company("c-1", -1, gavin)), "inserting at -1"); + Assert.Throws( + () => gavin.Companies.Insert(20, new Company("c20", 20, gavin)), "inserting too far"); + + // Check existence of added companies + Sfi.Statistics.Clear(); + // Have to skip unloaded (non-queued indeed) elements to avoid triggering existence queries on them. + foreach (var item in addedItems.Skip(5)) + { + Assert.That(gavin.Companies.Contains(item), Is.True, "Company '{0}' existence", item.Name); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after existence check"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after existence check"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after existence check"); + + // Check existence of not loaded companies + Assert.That(gavin.Companies.Contains(addedItems[0]), Is.True, "First company existence"); + Assert.That(gavin.Companies.Contains(addedItems[1]), Is.True, "Second company existence"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after unloaded existence check"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after unloaded existence check"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after unloaded existence check"); + + // Check existence of not existing companies + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Contains(new Company("test1", 15, gavin)), Is.False, "First non-existence test"); + Assert.That(gavin.Companies.Contains(new Company("test2", 16, gavin)), Is.False, "Second non-existence test"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after non-existence check"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after non-existence check"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after non-existence check"); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumeration"); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after enumeration"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [Explicit] + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public async Task ListInsertDuplicatedAsync(bool initialize, bool flush) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count before flush"); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after count"); + + // Re-add items + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + gavin.Companies.Insert(4 - i, addedItems[i]); + } + + Assert.That(gavin.Companies[0].ListIndex, Is.EqualTo(4), "Company at 0"); + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after re-insert"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-insert"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after re-insert"); + + if (flush) + { + await (s.FlushAsync()); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after flush"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after flush"); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumeration"); + Assert.That(gavin.Companies.Count, Is.EqualTo(flush ? 5 : 10), "Companies count after enumeration"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListRemoveAtAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + var finalIndexOrder = new List {0, 1, 2, 6, 8, 9}; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + // Add transient companies + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Remove transient companies + Sfi.Statistics.Clear(); + gavin.Companies.RemoveAt(5); + gavin.Companies.RemoveAt(6); + + Assert.That(gavin.Companies.Count, Is.EqualTo(8), "Gavin's companies count after removing 2 transient companies"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing transient companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing transient companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing transient companies"); + + // Remove persisted companies + Sfi.Statistics.Clear(); + gavin.Companies.RemoveAt(3); + gavin.Companies.RemoveAt(3); + + Assert.That(gavin.Companies.Count, Is.EqualTo(6), "Gavin's companies count after removing 2 persisted companies"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing persisted companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after removing persisted companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing persisted companies"); + + // Try remove invalid indexes + Assert.Throws(() => gavin.Companies.RemoveAt(-1), "Removing at -1"); + Assert.Throws(() => gavin.Companies.RemoveAt(8), "Removing too far"); + + // Check existence of companies + Sfi.Statistics.Clear(); + var removedIndexes = new HashSet {3, 4, 5, 7}; + for (var i = 0; i < addedItems.Count; i++) + { + Assert.That( + gavin.Companies.Contains(addedItems[i]), + removedIndexes.Contains(i) ? Is.False : (IResolveConstraint) Is.True, + $"Element at index {i} was {(removedIndexes.Contains(i) ? "not " : "")}removed"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Gavin's companies count after checking existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Flushes count after checking existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization after checking existence"); + + // Check existence of not existing companies + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Contains(new Company("test1", 15, gavin)), Is.False, "Checking existence of non-existence"); + Assert.That(gavin.Companies.Contains(new Company("test2", 16, gavin)), Is.False, "Checking existence of non-existence"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Gavin's companies count after checking non-existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Flushes count after checking non-existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization after checking non-existence"); + + gavin.UpdateCompaniesIndexes(); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(6), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(6), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListGetSetAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + var finalIndexOrder = new List {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + // Add transient companies + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Compare all items + Sfi.Statistics.Clear(); + for (var i = 0; i < 10; i++) + { + Assert.That(gavin.Companies[i], Is.EqualTo(addedItems[i]), "Comparing added company at index {0}", i); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding comparing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding comparing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after comparing"); + + // Try get invalid indexes + Assert.Throws(() => + { + var item = gavin.Companies[10]; + }, "Get too far"); + Assert.Throws(() => + { + var item = gavin.Companies[-1]; + }, "Get at -1"); + + // Try set invalid indexes + Assert.Throws(() => gavin.Companies[10] = addedItems[0], "Set too far"); + Assert.Throws(() => gavin.Companies[-1] = addedItems[0], "Set at -1"); + + // Swap transient and persisted indexes + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var hiIndex = 9 - i; + var tmp = gavin.Companies[i]; + gavin.Companies[i] = gavin.Companies[hiIndex]; + gavin.Companies[hiIndex] = tmp; + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after swapping"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after swapping"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(10), "Statements count after adding swapping"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after swapping"); + + // Check indexes + Sfi.Statistics.Clear(); + for (var i = 0; i < 10; i++) + { + Assert.That(gavin.Companies[i].ListIndex, Is.EqualTo(finalIndexOrder[i]), "Comparing company ListIndex at index {0}", i); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after comparing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after comparing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after comparing"); + + gavin.UpdateCompaniesIndexes(); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListFlushAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + var finalIndexOrder = Enumerable.Range(0, 13).ToList(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + // Add transient companies with Add + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Add transient companies with Insert + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "INSERT \n INTO"), Is.EqualTo(5), "Statements count after inserting"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after inserting 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after inserting"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after inserting"); + + // Add transient companies with Add + Sfi.Statistics.Clear(); + for (var i = 15; i < 20; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(20), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Remove last 5 transient companies + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + for (var i = 15; i < 20; i++) + { + Assert.That(gavin.Companies.Remove(addedItems[i]), Is.True, "Removing transient company at index {0}", i); + } + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "INSERT \n INTO"), Is.EqualTo(10), "Statements count after removing"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after removing 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing"); + + // Remove last 5 transient companies + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + for (var i = 10; i < 15; i++) + { + gavin.Companies.RemoveAt(10); + } + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "DELETE \n FROM"), Is.EqualTo(5), "Statements count after second removing"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after second removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after second removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(7), "Statements count after second removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after second removing"); + + // Add transient companies with Add + Sfi.Statistics.Clear(); + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems[i] = item; + // NOTE: the returned index is currently invalid due to extra-lazy avoiding to query the count or initializing the collection + ((IList) gavin.Companies).Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after adding through IList"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through IList"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through IList"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding through IList"); + + // Remove last transient company + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + Assert.That(gavin.Companies.Remove(addedItems[14]), Is.EqualTo(true), "Removing last transient company"); + var log = sqlLog.GetWholeLog(); + Assert.That(FindAllOccurrences(log, "DELETE \n FROM"), Is.EqualTo(5), "Delete statements count after removing last transient company"); + Assert.That(FindAllOccurrences(log, "INSERT \n INTO"), Is.EqualTo(5), "Insert statements count after removing last transient company"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(14), "Gavin's companies count after adding removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing"); + + // Test index getter + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies[0], Is.EqualTo(addedItems[0]), "Comparing first item with index getter"); + + Assert.That(gavin.Companies.Count, Is.EqualTo(14), "Gavin's companies count after adding comparing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after comparing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Statements count after comparing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after comparing"); + + // Remove last transient company + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Remove(addedItems[13]), Is.EqualTo(true), "Removing last transient company"); + + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Gavin's companies count after adding repeated removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after repeated removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after repeated removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after repeated removing"); + + // Test index setter + Sfi.Statistics.Clear(); + gavin.Companies[0] = addedItems[0]; + + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Gavin's companies count after setting first item"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after setting first item"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Statements count after setting first item"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after setting first item"); + + // Test manual flush after remove + Sfi.Statistics.Clear(); + gavin.Companies.RemoveAt(12); + using (var sqlLog = new SqlLogSpy()) + { + await (s.FlushAsync()); + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "DELETE \n FROM"), Is.EqualTo(1), "Delete statements count after removing at 12 index"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(12), "Gavin's companies count after removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing"); + + // Test manual flush after insert + Sfi.Statistics.Clear(); + gavin.Companies.Add(new Company("c12", 12, gavin)); + using (var sqlLog = new SqlLogSpy()) + { + await (s.FlushAsync()); + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "INSERT \n INTO"), Is.EqualTo(1), "Insert statements count after flushing"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Gavin's companies count after flushing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after flushing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after flushing"); + + for (var i = 0; i < gavin.Companies.Count; i++) + { + Assert.That(gavin.Companies[i].ListIndex, Is.EqualTo(i), "Comparing company ListIndex at index {0}", i); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.ListIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.ListIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListClearAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new CreditCard($"c{i}", i, gavin); + addedItems.Add(item); + gavin.CreditCards.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = FlushMode.Commit; + + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + var collection = gavin.CreditCards; + + // Add transient credit cards + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new CreditCard($"c{i}", i, gavin); + addedItems.Add(item); + collection.Insert(i, item); + } + + Assert.That(collection.Count, Is.EqualTo(10), "Gavin's credit cards count after inserting 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after inserting"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after inserting"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after inserting"); + + Sfi.Statistics.Clear(); + collection.Clear(); + + Assert.That(collection.Count, Is.EqualTo(0), "Gavin's credit cards count after clearing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after clearing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after clearing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after clearing"); + + // Re-add two not loaded and two transient credit cards + collection.Add(addedItems[0]); + collection.Add(addedItems[1]); + collection.Add(addedItems[5]); + collection.Add(addedItems[6]); + + Assert.That(collection.Count, Is.EqualTo(4), "Gavin's credit cards count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after re-adding"); + + // Remove one not loaded and one transient credit cards + Assert.That(collection.Remove(addedItems[1]), Is.True, "Removing not loaded credit card"); + Assert.That(collection.Remove(addedItems[6]), Is.True, "Removing transient credit card"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's credit cards count after removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after removing"); + + // Remove not existing items + Assert.That(collection.Remove(addedItems[1]), Is.False, "Removing not-existing credit card"); + Assert.That(collection.Remove(addedItems[6]), Is.False, "Removing not-existing credit card"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's credit cards count after not-existing removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after not-existing removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after not-existing removing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after not-existing removing"); + + if (initialize) + { + using (var e = collection.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.True, "Credit cards initialization status after enumerating"); + Assert.That(collection.Count, Is.EqualTo(2), "Credit cards count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + var collection = gavin.CreditCards; + // As the cascade option is set to all, the clear operation will only work on + // transient credit cards + Assert.That(collection.Count, Is.EqualTo(6), "Credit cards count after loading again Gavin"); + for (var i = 0; i < 10; i++) + { + Assert.That(collection.Contains(addedItems[i]), i < 6 ? Is.True : (IResolveConstraint) Is.False, "Checking existence for item at {0}", i); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task ListIndexOperationsAsync(bool initialize) + { + User gavin; + var finalIndexOrder = new List {6, 0, 4}; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + gavin.Companies.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Current tracker state: + // Indexes: 0,1,2,3,4 + // Queue: / + // RemoveDbIndexes: / + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Gavin's companies count"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status"); + + Sfi.Statistics.Clear(); + gavin.Companies.Insert(1, new Company("c5", 5, gavin)); + // Current tracker state: + // Indexes: 0,5,1,2,3,4 + // Queue: {1, 5} + // RemoveDbIndexes: / + + gavin.Companies.Insert(0, new Company("c6", 6, gavin)); + // Current tracker state: + // Indexes: 6,0,5,1,2,3,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: / + + gavin.Companies.RemoveAt(4); + // Current tracker state: + // Indexes: 6,0,5,1,3,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: 2 + + gavin.Companies.RemoveAt(3); + // Current tracker state: + // Indexes: 6,0,5,3,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: 1,2 + + gavin.Companies.RemoveAt(3); + // Current tracker state: + // Indexes: 6,0,5,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: 1,2,3 + + gavin.Companies.RemoveAt(2); + // Current tracker state: + // Indexes: 6,0,4 + // Queue: {0, 6} + // RemoveDbIndexes: 1,2,3 + + Assert.That(gavin.Companies.Count, Is.EqualTo(3), "Gavin's companies count after remove/insert operations"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after remove/insert operations"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Statements count after remove/insert operations"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after remove/insert operations"); + + gavin.UpdateCompaniesIndexes(); + + for (var i = 0; i < gavin.Companies.Count; i++) + { + Assert.That(gavin.Companies[i].OriginalIndex, Is.EqualTo(finalIndexOrder[i]), "Comparing company index at {0}", i); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(3), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Companies.Count, Is.EqualTo(3), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task SetAddAsync(bool initialize) + { + User gavin; + Document hia; + Document hia2; + var addedDocuments = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + hia = new Document("HiA", "blah blah blah", gavin); + hia2 = new Document("HiA2", "blah blah blah blah", gavin); + gavin.Documents.Add(hia); + gavin.Documents.Add(hia2); + await (s.PersistAsync(gavin)); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Count, Is.EqualTo(2), "Gavin's documents count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding"); + + // Test adding documents with ISet interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"document{i}", $"content{i}", gavin); + addedDocuments.Add(document); + Assert.That(gavin.Documents.Add(document), Is.True, "Adding document through ISet"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(7), "Gavin's documents count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding"); + + // Test adding documents with ICollection interface + Sfi.Statistics.Clear(); + var documents = (ICollection) gavin.Documents; + for (var i = 0; i < 5; i++) + { + var document = new Document($"document2{i}", $"content{i}", gavin); + addedDocuments.Add(document); + documents.Add(document); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after adding through ICollection<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through ICollection<>"); + // In this case we cannot determine whether the entities are transient or not so + // we are forced to check the database + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding through ICollection<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding through ICollection<>"); + + // Test re-adding documents with ISet interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Add(addedDocuments[i]), Is.False, "Re-add document through ISet<>"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after re-adding"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after re-adding"); + + // Test re-adding documents with ICollection interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + documents.Add(addedDocuments[i]); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after re-adding through ICollection<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding through ICollection<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding through ICollection<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after re-adding through ICollection<>"); + + // Check existence of added documents + Sfi.Statistics.Clear(); + foreach (var document in addedDocuments) + { + Assert.That(gavin.Documents.Contains(document), Is.True, "Checking existence of an existing document"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after checking existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after checking existence"); + + // Check existence of not loaded documents + Assert.That(gavin.Documents.Contains(hia), Is.True, "Checking existence of not loaded document"); + Assert.That(gavin.Documents.Contains(hia2), Is.True, "Checking existence of not loaded document"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after checking existence"); + + // Check existence of not existing documents + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Contains(new Document("test1", "content", gavin)), Is.False, "Checking existence of not-existing document"); + Assert.That(gavin.Documents.Contains(new Document("test2", "content", gavin)), Is.False, "Checking existence of not-existing document"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking non-existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking non-existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after checking non-existence"); + + // Test adding not loaded documents + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Add(hia), Is.False, "Adding not loaded element"); + documents.Add(hia); + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after adding not loaded element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding not loaded element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after adding not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding not loaded element"); + + if (initialize) + { + using (var e = gavin.Documents.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.True, "Documents initialization status after enumerating"); + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Documents count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Documents count after loading again Gavin"); + Assert.That(gavin.Documents.Contains(hia2), Is.True, "Checking not loaded element"); + Assert.That(gavin.Documents.Contains(hia), Is.True, "Checking not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [Test] + public async Task SetAddWithOverrideEqualsAsync() + { + User gavin; + User robert; + User tom; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + robert = new User("robert", "secret"); + tom = new User("tom", "secret"); + await (s.PersistAsync(gavin)); + await (s.PersistAsync(robert)); + await (s.PersistAsync(tom)); + + gavin.Followers.Add(new UserFollower(gavin, robert)); + gavin.Followers.Add(new UserFollower(gavin, tom)); + robert.Followers.Add(new UserFollower(robert, tom)); + + Assert.That(gavin.Followers.Count, Is.EqualTo(2), "Gavin's documents count after adding 2"); + Assert.That(robert.Followers.Count, Is.EqualTo(1), "Robert's followers count after adding one"); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + robert = await (s.GetAsync("robert")); + tom = await (s.GetAsync("tom")); + + // Re-add + Assert.That(gavin.Followers.Add(new UserFollower(gavin, robert)), Is.False, "Re-adding element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after re-adding"); + Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Gavin's followers count after re-adding"); + + // Add new + Assert.That(robert.Followers.Add(new UserFollower(robert, gavin)), Is.True, "Adding element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after adding"); + Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Robert's followers count after re-adding"); + } + } + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public async Task SetAddDuplicatedAsync(bool initialize, bool flush) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new Document($"d{i}", $"c{i}", gavin); + addedItems.Add(item); + gavin.Documents.Add(item); + gavin.Documents.Add(item); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after adding 5"); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Title)); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after reload"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after reload"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after reload"); + + // Re-add items + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Add(addedItems[i]), Is.False, "Re-adding element"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after re-adding"); + + if (flush) + { + await (s.FlushAsync()); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after flushing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after flushing"); + } + + if (initialize) + { + using (var e = gavin.Documents.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.True, "Documents initialization status after enumerating"); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Documents count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Documents count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task SetAddTransientAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p{i}", gavin); + addedItems.Add(item); + gavin.Permissions.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = FlushMode.Commit; + + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Permissions.Count, Is.EqualTo(5), "Gavin's permissions count after adding 5"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after adding"); + + // Test adding permissions with ICollection interface + Sfi.Statistics.Clear(); + var items = (ICollection) gavin.Permissions; + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p2{i}", gavin); + addedItems.Add(item); + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after adding through ICollection<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through ICollection<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through ICollection<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after adding through ICollection<>"); + + // Test re-adding permissions with ICollection interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Skip(5)) + { + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after re-adding"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding"); + + // Test adding not loaded permissions with ICollection interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Take(5)) + { + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after re-adding not loaded elements"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding not loaded elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after re-adding not loaded elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding not loaded elements"); + + // Test adding loaded permissions with ICollection interface + Sfi.Statistics.Clear(); + foreach (var item in s.Query()) + { + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after re-adding loaded elements"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding loaded elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(6), "Statements count after re-adding loaded elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding loaded elements"); + + // Test adding permissions with ISet interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p3{i}", gavin); + addedItems.Add(item); + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after adding through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after adding through ISet<>"); + + // Test re-adding permissions with ISet interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Skip(10)) + { + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after re-adding through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding through ISet<>"); + + // Test adding not loaded permissions with ISet interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Take(5)) + { + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after re-adding not loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding not loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after re-adding not loaded permissions through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding not loaded permissions through ISet<>"); + + // Test adding loaded permissions with ISet interface + Sfi.Statistics.Clear(); + foreach (var item in s.Query()) + { + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after re-adding loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(6), "Statements count after re-adding loaded permissions through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding loaded permissions through ISet<>"); + + if (initialize) + { + using (var e = gavin.Permissions.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.True, "Permissions initialization status after enumerating"); + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Permissions count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Permissions count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task SetRemoveAsync(bool initialize) + { + User gavin; + var addedDocuments = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + for (var i = 0; i < 5; i++) + { + var document = new Document($"document{i}", $"content{i}", gavin); + addedDocuments.Add(document); + gavin.Documents.Add(document); + } + + await (s.PersistAsync(gavin)); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedDocuments[i] = await (s.GetAsync(addedDocuments[i].Title)); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after refresh"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after refresh"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after refresh"); + + // Add new documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"document2{i}", $"content{i}", gavin); + addedDocuments.Add(document); + ((ICollection)gavin.Documents).Add(document); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(10), "Gavin's documents count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding"); + + // Test removing existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.True, "Removing existing document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing"); + + // Test removing removed existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Contains(addedDocuments[i]), Is.False, "Checking existence of a removed document"); + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.False, "Removing removed document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after removing removed documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing removed documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing removed documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing removed documents"); + + // Test removing not existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"test{i}", "content", gavin); + Assert.That(gavin.Documents.Remove(document), Is.False, "Removing not existing document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after removing not existing documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing not existing documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after removing not existing documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing not existing documents"); + + // Test removing newly added documents + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + Assert.That(gavin.Documents.Contains(addedDocuments[i]), Is.True, "Checking existence of an added document"); + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.True, "Removing added document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Gavin's documents count after removing added documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing added documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing added documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing added documents"); + + // Test removing removed newly added documents + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + Assert.That(gavin.Documents.Contains(addedDocuments[i]), Is.False, "Checking existence of a removed document"); + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.False, "Removing removed document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Gavin's documents count after removing removed documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing removed documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing removed documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing removed documents"); + + // Test removing not existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"test{i}", "content", gavin); + Assert.That(gavin.Documents.Remove(document), Is.False, "Removing not existing document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Gavin's documents count after removing not existing documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing not existing documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after removing not existing documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing not existing documents"); + + if (initialize) + { + using (var e = gavin.Documents.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.True, "Documents initialization status after enumerating"); + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Documents count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Documents count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task SetClearAsync(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p{i}", gavin); + addedItems.Add(item); + gavin.Permissions.Add(item); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = FlushMode.Commit; + + gavin = await (s.GetAsync("gavin")); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = await (s.GetAsync(addedItems[i].Id)); + } + + var collection = gavin.Permissions; + + Sfi.Statistics.Clear(); + Assert.That(collection.Count, Is.EqualTo(5), "Gavin's permissions count after refresh"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after refresh"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after refresh"); + + // Add transient permissions + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p2{i}", gavin); + addedItems.Add(item); + collection.Add(item); + } + + Assert.That(collection.Count, Is.EqualTo(10), "Gavin's permissions count after adding 5"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after adding"); + + Sfi.Statistics.Clear(); + collection.Clear(); + + Assert.That(collection.Count, Is.EqualTo(0), "Gavin's permissions count after flushing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after flushing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after flushing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after flushing"); + + // Re-add two not loaded and two transient permissions + Assert.That(collection.Add(addedItems[0]), Is.True, "Re-adding not loaded element"); + Assert.That(collection.Add(addedItems[1]), Is.True, "Re-adding not loaded element"); + Assert.That(collection.Add(addedItems[5]), Is.True, "Re-adding transient element"); + Assert.That(collection.Add(addedItems[6]), Is.True, "Re-adding transient element"); + + Assert.That(collection.Count, Is.EqualTo(4), "Gavin's permissions count after re-adding"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after re-adding"); + + // Remove one not loaded and one transient permissions + Assert.That(collection.Remove(addedItems[1]), Is.True, "Removing not loaded element"); + Assert.That(collection.Remove(addedItems[6]), Is.True, "Removing transient element"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's permissions count after removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after removing"); + + // Remove not existing items + Assert.That(collection.Remove(addedItems[1]), Is.False, "Removing removed element"); + Assert.That(collection.Remove(addedItems[6]), Is.False, "Removing removed element"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's permissions count after removing removed elements"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing removed elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing removed elements"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after removing removed elements"); + + if (initialize) + { + using (var e = collection.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.True, "Permissions initialization status after enumerating"); + Assert.That(collection.Count, Is.EqualTo(2), "Permissions count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + var collection = gavin.Permissions; + // As the cascade option is set to all, the clear operation will only work on + // transient permissions + Assert.That(collection.Count, Is.EqualTo(6), "Permissions count after loading again Gavin"); + for (var i = 0; i < 10; i++) + { + Assert.That(collection.Contains(addedItems[i]), i < 6 ? Is.True : (IResolveConstraint) Is.False, + "Checking existence of added element at {0}", i); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task MapAddAsync(bool initialize) + { + User gavin; + UserSetting setting; + var addedSettings = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s{i}", $"data{i}", gavin); + addedSettings.Add(setting); + gavin.Settings.Add(setting.Name, setting); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Count, Is.EqualTo(5), "Gavin's user settings count after load"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after load"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after load"); + + // Test adding settings with Add method + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + addedSettings.Add(setting); + gavin.Settings.Add(setting.Name, setting); + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding"); + + // Test adding settings with [] + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s3{i}", $"data{i}", gavin); + addedSettings.Add(setting); + + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(15), "Gavin's user settings count after adding 5 through indexer"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through indexer"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding through indexer"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding through indexer"); + + // Check existence of added settings + Sfi.Statistics.Clear(); + foreach (var item in addedSettings.Skip(5)) + { + Assert.That(gavin.Settings.ContainsKey(item.Name), Is.True, "Checking existence of added element"); + Assert.That(gavin.Settings.Contains(new KeyValuePair(item.Name, item)), Is.True, "Checking existence of added element using KeyValuePair<,>"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of added elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after checking existence of added elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after checking existence of added elements"); + + // Check existence of not loaded settings + foreach (var item in addedSettings.Take(5)) + { + Assert.That(gavin.Settings.ContainsKey(item.Name), Is.True, "Checking key existence of not loaded elements"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of not loaded elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after checking existence of not loaded elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after checking existence of not loaded elements"); + + // Check existence of not existing settings + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.ContainsKey("test"), Is.False, "Checking existence of not existing element"); + Assert.That(gavin.Settings.ContainsKey("test2"), Is.False, "Checking existence of not existing element"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of not existing elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking existence of not existing elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after checking existence of not existing elements"); + + // Try to add an existing setting + Assert.Throws(() => gavin.Settings.Add("s0", new UserSetting("s0", "data", gavin)), "Adding an existing key"); + Assert.Throws(() => gavin.Settings.Add("s20", new UserSetting("s20", "data", gavin)), "Adding an existing key"); + Assert.Throws(() => gavin.Settings.Add("s30", new UserSetting("s30", "data", gavin)), "Adding an existing key"); + + // Get values of not loaded keys + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.TryGetValue("s0", out setting), Is.True, "Getting value of not loaded key"); + Assert.That(setting.Id, Is.EqualTo(addedSettings[0].Id), "Comparing retrieved element id"); + Assert.That(gavin.Settings["s0"].Id, Is.EqualTo(addedSettings[0].Id), "Comparing retrieved element id by indexer"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after reading elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after reading elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after reading elements"); + + // Get values of newly added keys + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.TryGetValue("s20", out setting), Is.True, "Getting value of a newly added key"); + Assert.That(setting, Is.EqualTo(addedSettings[5]), "Comparing retrieved element"); + Assert.That(gavin.Settings["s20"], Is.EqualTo(addedSettings[5]), "Comparing retrieved element by indexer"); + Assert.That(gavin.Settings.TryGetValue("s30", out setting), Is.True, "Getting value of a newly added key"); + Assert.That(setting, Is.EqualTo(addedSettings[10]), "Comparing retrieved element"); + Assert.That(gavin.Settings["s30"], Is.EqualTo(addedSettings[10]), "Getting value of a newly added key"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after reading elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after reading elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after reading elements"); + + // Try to get a non existing setting + Assert.That(gavin.Settings.TryGetValue("test", out setting), Is.False, "Try to get a not existing key"); + Assert.That(gavin.Settings.TryGetValue("test2", out setting), Is.False, "Try to get a not existing key"); + Assert.Throws(() => + { + setting = gavin.Settings["test"]; + }, "Getting a not existing key"); + Assert.Throws(() => + { + setting = gavin.Settings["test2"]; + }, "Getting a not existing key"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after reading not existing elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(4), "Statements count after reading not existing elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after reading not existing elements"); + + if (initialize) + { + using (var e = gavin.Settings.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.True, "User settings initialization status after enumerating"); + Assert.That(gavin.Settings.Count, Is.EqualTo(15), "User settings count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Settings.Count, Is.EqualTo(15), "User settings count after loading again Gavin"); + Assert.That(gavin.Settings.ContainsKey(addedSettings[0].Name), Is.True, "Checking key existence"); + Assert.That(gavin.Settings.ContainsKey(addedSettings[5].Name), Is.True, "Checking key existence"); + Assert.That(gavin.Settings.ContainsKey(addedSettings[10].Name), Is.True, "Checking key existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task MapSetAsync(bool initialize) + { + User gavin; + UserSetting setting; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s{i}", $"data{i}", gavin); + gavin.Settings.Add(setting.Name, setting); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Count, Is.EqualTo(5), "Gavin's user settings count after load"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after load"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after load"); + + // Set a key that does not exist in db and is not in the queue + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding"); + + // Set a key that does not exist in db and it is in the queue + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after re-adding existing keys"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding existing keys"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding existing keys"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after re-adding existing keys"); + + // Set a key that exists in db and is not in the queue + Sfi.Statistics.Clear(); + gavin.Settings["s0"] = new UserSetting("s0", "s0", gavin); + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after re-adding a not loaded key"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding a not loaded key"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after re-adding a not loaded key"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after re-adding a not loaded key"); + + // Set a key that exists in db and it is in the queue + Sfi.Statistics.Clear(); + gavin.Settings["s0"] = new UserSetting("s0", "s0", gavin); + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after re-adding a loaded key"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding a loaded key"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding a loaded key"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after re-adding a loaded key"); + + // Set a key that exists in db and it is in the removal queue + Assert.That(gavin.Settings.Remove("s1"), Is.True, "Removing an existing key"); + Sfi.Statistics.Clear(); + gavin.Settings["s1"] = new UserSetting("s1", "s1", gavin); + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after removing an existing key"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing an existing key"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing an existing key"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing an existing key"); + + if (initialize) + { + using (var e = gavin.Settings.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.True, "User settings initialization status after enumerating"); + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "User settings count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "User settings count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading again"); + + await (t.CommitAsync()); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task MapRemoveAsync(bool initialize) + { + User gavin; + UserSetting setting; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + await (s.PersistAsync(gavin)); + + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s{i}", $"data{i}", gavin); + gavin.Settings.Add(setting.Name, setting); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Count, Is.EqualTo(5), "Gavin's user settings count after loading"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after loading"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading"); + + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding"); + + // Remove a key that exists in db and is not in the queue and removal queue + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s0"), Is.True, "Removing an existing element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(9), "Gavin's user settings count after removing a not loaded element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing a not loaded element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after removing a not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing a not loaded element"); + + // Remove a key that exists in db and it is in the queue + var item = gavin.Settings["s1"]; + Assert.That(gavin.Settings.Remove("s1"), Is.True, "Removing an existing element"); + gavin.Settings.Add(item.Name, item); + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s1"), Is.True, "Removing a re-added element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(8), "Gavin's user settings count after removing a re-added element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing a re-added element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing a re-added element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing a re-added element"); + + // Remove a key that does not exist in db and is not in the queue + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("test"), Is.False, "Removing not existing element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(8), "Gavin's user settings count after removing not existing element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing not existing element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after removing not existing element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing not existing element"); + + // Remove a key that does not exist in db and it is in the queue + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s20"), Is.True); + + Assert.That(gavin.Settings.Count, Is.EqualTo(7), "Gavin's user settings count after removing an existing element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing an existing element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing an existing element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing an existing element"); + + // Remove a key that exists in db and it is in the removal queue + Assert.That(gavin.Settings.Remove("s2"), Is.True, "Removing not loaded element"); + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s2"), Is.False, "Removing removed element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(6), "Gavin's user settings count after removing a not loaded element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing a not loaded element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing a not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing a not loaded element"); + + if (initialize) + { + using (var e = gavin.Settings.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.True, "User settings initialization status after enumerating"); + Assert.That(gavin.Settings.Count, Is.EqualTo(6), "User settings count after enumerating"); + } + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = await (s.GetAsync("gavin")); + Assert.That(gavin.Settings.Count, Is.EqualTo(6), "User settings count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading again"); + + await (t.CommitAsync()); } } @@ -395,5 +2643,20 @@ public async Task AddToUninitializedSetWithLaterLazyLoadAsync() await (t.CommitAsync()); } } + + private int FindAllOccurrences(string source, string substring) + { + if (source == null) + { + return 0; + } + int n = 0, count = 0; + while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1) + { + n += substring.Length; + ++count; + } + return count; + } } } diff --git a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs index d6a85b966c0..e95e9c6fb3b 100644 --- a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -9,18 +9,19 @@ using System; +using System.Collections.Generic; using System.Linq; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Hql.Ast.ANTLR; using NHibernate.Linq; using NUnit.Framework; +using NUnit.Framework.Constraints; using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.FetchLazyProperties { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FetchLazyPropertiesFixtureAsync : TestCase { @@ -196,21 +197,20 @@ private static void AssertFetchProperty(Person person) #endregion - #region FetchComponentAndFormulaTwoQueryReadOnly [TestCase(true)] [TestCase(false)] - public async Task TestHqlFetchComponentAndFormulaTwoQueryReadOnlyAsync(bool readOnly, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestHqlFetchComponentAndFormulaTwoQueryReadOnlyAsync(bool readOnly) { Person person; using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - person = await (s.CreateQuery("from Person fetch Address where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); - person = await (s.CreateQuery("from Person fetch Formula where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); + person = await (s.CreateQuery("from Person fetch Address where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); + person = await (s.CreateQuery("from Person fetch Formula where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } AssertFetchComponentAndFormulaTwoQuery(person); @@ -218,16 +218,16 @@ private static void AssertFetchProperty(Person person) [TestCase(true)] [TestCase(false)] - public async Task TestLinqFetchComponentAndFormulaTwoQueryAsync(bool readOnly, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestLinqFetchComponentAndFormulaTwoQueryAsync(bool readOnly) { Person person; using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - person = await (s.Query().Fetch(o => o.Address).WithOptions(o => o.SetReadOnly(readOnly)).FirstOrDefaultAsync(o => o.Id == 1, cancellationToken)); - person = await (s.Query().Fetch(o => o.Formula).WithOptions(o => o.SetReadOnly(readOnly)).FirstOrDefaultAsync(o => o.Id == 1, cancellationToken)); + person = await (s.Query().Fetch(o => o.Address).WithOptions(o => o.SetReadOnly(readOnly)).FirstOrDefaultAsync(o => o.Id == 1)); + person = await (s.Query().Fetch(o => o.Formula).WithOptions(o => o.SetReadOnly(readOnly)).FirstOrDefaultAsync(o => o.Id == 1)); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } AssertFetchComponentAndFormulaTwoQuery(person); @@ -343,7 +343,6 @@ public async Task TestLinqFetchFormulaAndManyToOneComponentAsync() .Fetch(o => o.Formula) .Fetch(o => o.BestFriend).ThenFetch(o => o.Address) .FirstOrDefaultAsync(o => o.Id == 1)); - } AssertFetchFormulaAndManyToOneComponent(person); @@ -373,13 +372,13 @@ private static void AssertFetchFormulaAndManyToOneComponent(Person person) [TestCase(true)] [TestCase(false)] - public async Task TestHqlFetchFormulaAndManyToOneComponentListAsync(bool descending, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestHqlFetchFormulaAndManyToOneComponentListAsync(bool descending) { Person person; using (var s = OpenSession()) { person = (await (s.CreateQuery("from Person p fetch p.Formula left join fetch p.BestFriend bf fetch bf.Address order by p.Id" + (descending ? " desc" : "")) - .ListAsync(cancellationToken))).FirstOrDefault(o => o.Id == 1); + .ListAsync())).FirstOrDefault(o => o.Id == 1); } AssertFetchFormulaAndManyToOneComponentList(person); @@ -387,7 +386,7 @@ private static void AssertFetchFormulaAndManyToOneComponent(Person person) [TestCase(true)] [TestCase(false)] - public async Task TestLinqFetchFormulaAndManyToOneComponentListAsync(bool descending, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestLinqFetchFormulaAndManyToOneComponentListAsync(bool descending) { Person person; using (var s = OpenSession()) @@ -397,7 +396,7 @@ private static void AssertFetchFormulaAndManyToOneComponent(Person person) .Fetch(o => o.BestFriend).ThenFetch(o => o.Address); query = descending ? query.OrderByDescending(o => o.Id) : query.OrderBy(o => o.Id); person = (await (query - .ToListAsync(cancellationToken))) + .ToListAsync())) .FirstOrDefault(o => o.Id == 1); } @@ -667,6 +666,40 @@ private static void AssertFetchComponentManyToOne(Person person) #endregion + #region TestHqlFetchManyToOneAndComponentManyToOne + + [Test] + public async Task TestHqlFetchManyToOneAndComponentManyToOneAsync() + { + Person person; + using (var s = OpenSession()) + { + person = await (s.CreateQuery("from Person p fetch p.Address left join fetch p.Address.Continent left join fetch p.BestFriend where p.Id = 1").UniqueResultAsync()); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + [Test] + public async Task TestLinqFetchManyToOneAndComponentManyToOneAsync() + { + Person person; + using (var s = OpenSession()) + { + person = await (s.Query().Fetch(o => o.BestFriend).Fetch(o => o.Address).ThenFetch(o => o.Continent).FirstOrDefaultAsync(o => o.Id == 1)); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + private static void AssertFetchManyToOneAndComponentManyToOne(Person person) + { + AssertFetchComponentManyToOne(person); + Assert.That(NHibernateUtil.IsInitialized(person.BestFriend), Is.True); + } + + #endregion + #region FetchSubClassFormula [Test] @@ -838,17 +871,17 @@ public async Task TestHqlFetchComponentAliasAsync() [TestCase(true)] [TestCase(false)] - public async Task TestFetchComponentAndFormulaTwoQueryCacheAsync(bool readOnly, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestFetchComponentAndFormulaTwoQueryCacheAsync(bool readOnly) { - await (TestLinqFetchComponentAndFormulaTwoQueryAsync(readOnly, cancellationToken)); + await (TestLinqFetchComponentAndFormulaTwoQueryAsync(readOnly)); Person person; using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - person = await (s.GetAsync(1, cancellationToken)); + person = await (s.GetAsync(1)); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } AssertFetchComponentAndFormulaTwoQuery(person); @@ -893,17 +926,17 @@ public async Task TestFetchComponentCacheAsync() [TestCase(true)] [TestCase(false)] - public async Task TestFetchAfterPropertyIsInitializedAsync(bool readOnly, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestFetchAfterPropertyIsInitializedAsync(bool readOnly) { Person person; using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - person = await (s.CreateQuery("from Person fetch Address where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); + person = await (s.CreateQuery("from Person fetch Address where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); person.Image = new byte[10]; - person = await (s.CreateQuery("from Person fetch Image where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); + person = await (s.CreateQuery("from Person fetch Image where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True); @@ -915,10 +948,10 @@ public async Task TestFetchComponentCacheAsync() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - person = await (s.CreateQuery("from Person where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); + person = await (s.CreateQuery("from Person where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); person.Image = new byte[1]; - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True); @@ -928,17 +961,37 @@ public async Task TestFetchComponentCacheAsync() [TestCase(true)] [TestCase(false)] - public async Task TestFetchAfterEntityIsInitializedAsync(bool readOnly, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestFetchAfterEntityIsInitializedAsync(bool readOnly) { Person person; using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - person = await (s.CreateQuery("from Person where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); + person = await (s.CreateQuery("from Person where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); + var image = person.Image; + person = await (s.CreateQuery("from Person fetch Image where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); + + await (tx.CommitAsync()); + } + + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Formula"), Is.True); + } + + [TestCase(true)] + [TestCase(false)] + public async Task TestFetchAllPropertiesAfterEntityIsInitializedAsync(bool readOnly) + { + Person person; + using(var s = OpenSession()) + using(var tx = s.BeginTransaction()) + { + person = await (s.CreateQuery("from Person where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); var image = person.Image; - person = await (s.CreateQuery("from Person fetch Image where Id = 1").SetReadOnly(readOnly).UniqueResultAsync(cancellationToken)); + person = await (s.CreateQuery("from Person fetch all properties where Id = 1").SetReadOnly(readOnly).UniqueResultAsync()); - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True); @@ -946,6 +999,42 @@ public async Task TestFetchComponentCacheAsync() Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Formula"), Is.True); } + [Test] + public async Task TestHqlCrossJoinFetchFormulaAsync() + { + if (!Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + var persons = new List(); + var bestFriends = new List(); + using (var sqlSpy = new SqlLogSpy()) + using (var s = OpenSession()) + { + var list = await (s.CreateQuery("select p, bf from Person p cross join Person bf fetch bf.Formula where bf.Id = p.BestFriend.Id").ListAsync()); + foreach (var arr in list) + { + persons.Add((Person) arr[0]); + bestFriends.Add((Person) arr[1]); + } + } + + AssertPersons(persons, false); + AssertPersons(bestFriends, true); + + void AssertPersons(List results, bool fetched) + { + foreach (var person in results) + { + Assert.That(person, Is.Not.Null); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Formula"), fetched ? Is.True : (IResolveConstraint) Is.False); + } + } + } + private static Person GeneratePerson(int i, Person bestFriend) { return new Person diff --git a/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs b/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs index 85853191c30..9dd89c37955 100644 --- a/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs +++ b/src/NHibernate.Test/Async/FilterTest/DynamicFilterTest.cs @@ -45,7 +45,7 @@ public async Task SecondLevelCachedCollectionsFilteringAsync() var persister = Sfi .GetCollectionPersister(typeof(Salesperson).FullName + ".Orders"); var cacheKey = - new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi); + new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi, null); CollectionCacheEntry cachedData; using (var session = OpenSession()) @@ -199,7 +199,6 @@ public async Task CriteriaQueryFiltersAsync() } } - [Test] public async Task CriteriaControlAsync() { @@ -294,7 +293,6 @@ public async Task CriteriaSubqueryWithFiltersAsync() Assert.That(orders.Count, Is.EqualTo(1), "Incorrect orders count"); - log.Info("query against Order with a subquery for line items with a subquery line items where the product name is Acme Hair Gel and the quantity is greater than 1 in a given region and the product is effective as of 4 months ago"); session.EnableFilter("region").SetParameter("region", "APAC"); session.EnableFilter("effectiveDate").SetParameter("asOfDate", testData.fourMonthsAgo.Date); @@ -310,7 +308,6 @@ public async Task CriteriaSubqueryWithFiltersAsync() } } - [Test] public async Task GetFiltersAsync() { @@ -826,7 +823,6 @@ public void Prepare() using (var session = outer.OpenSession()) using (var transaction = session.BeginTransaction()) { - foreach (var obj in entitiesToCleanUp) { await (session.DeleteAsync(obj, cancellationToken)); @@ -841,7 +837,6 @@ public void Release() using (var session = outer.OpenSession()) using (var transaction = session.BeginTransaction()) { - foreach (var obj in entitiesToCleanUp) { session.Delete(obj); diff --git a/src/NHibernate.Test/Async/Futures/FutureCriteriaFixture.cs b/src/NHibernate.Test/Async/Futures/FutureCriteriaFixture.cs index 2582546c369..db61d3313fa 100644 --- a/src/NHibernate.Test/Async/Futures/FutureCriteriaFixture.cs +++ b/src/NHibernate.Test/Async/Futures/FutureCriteriaFixture.cs @@ -50,12 +50,10 @@ public async Task CanUseFutureCriteriaAsync() { foreach (var person in await (persons5.GetEnumerableAsync())) { - } foreach (var person in await (persons10.GetEnumerableAsync())) { - } var events = logSpy.Appender.GetEvents(); @@ -66,30 +64,30 @@ public async Task CanUseFutureCriteriaAsync() [Test] public async Task TwoFuturesRunInTwoRoundTripsAsync() - { - using (var s = Sfi.OpenSession()) - { - IgnoreThisTestIfMultipleQueriesArentSupportedByDriver(); - - using (var logSpy = new SqlLogSpy()) - { - var persons10 = s.CreateCriteria(typeof(Person)) + { + using (var s = Sfi.OpenSession()) + { + IgnoreThisTestIfMultipleQueriesArentSupportedByDriver(); + + using (var logSpy = new SqlLogSpy()) + { + var persons10 = s.CreateCriteria(typeof(Person)) .SetMaxResults(10) .Future(); - foreach (var person in await (persons10.GetEnumerableAsync())) { } // fire first future round-trip + foreach (var person in await (persons10.GetEnumerableAsync())) { } // fire first future round-trip - var persons5 = s.CreateCriteria(typeof(Person)) + var persons5 = s.CreateCriteria(typeof(Person)) .SetMaxResults(5) .Future(); - foreach (var person in await (persons5.GetEnumerableAsync())) { } // fire second future round-trip + foreach (var person in await (persons5.GetEnumerableAsync())) { } // fire second future round-trip - var events = logSpy.Appender.GetEvents(); - Assert.AreEqual(2, events.Length); - } - } - } + var events = logSpy.Appender.GetEvents(); + Assert.AreEqual(2, events.Length); + } + } + } [Test] public async Task CanCombineSingleFutureValueWithEnumerableFuturesAsync() @@ -112,7 +110,6 @@ public async Task CanCombineSingleFutureValueWithEnumerableFuturesAsync() foreach (var person in await (persons.GetEnumerableAsync())) { - } var events = logSpy.Appender.GetEvents(); diff --git a/src/NHibernate.Test/Async/Futures/FutureQueryFixture.cs b/src/NHibernate.Test/Async/Futures/FutureQueryFixture.cs index 47b3cf8e2c3..0a9a2ba4be4 100644 --- a/src/NHibernate.Test/Async/Futures/FutureQueryFixture.cs +++ b/src/NHibernate.Test/Async/Futures/FutureQueryFixture.cs @@ -63,12 +63,10 @@ public async Task CanUseFutureQueryAsync() { foreach (var person in await (persons5.GetEnumerableAsync())) { - } foreach (var person in await (persons10.GetEnumerableAsync())) { - } var events = logSpy.Appender.GetEvents(); diff --git a/src/NHibernate.Test/Async/Futures/FutureQueryOverFixture.cs b/src/NHibernate.Test/Async/Futures/FutureQueryOverFixture.cs index 12acab5f3d1..2646203cf33 100644 --- a/src/NHibernate.Test/Async/Futures/FutureQueryOverFixture.cs +++ b/src/NHibernate.Test/Async/Futures/FutureQueryOverFixture.cs @@ -17,7 +17,6 @@ namespace NHibernate.Test.Futures [TestFixture] public class FutureQueryOverFixtureAsync : FutureFixture { - protected override void OnSetUp() { base.OnSetUp(); @@ -141,7 +140,6 @@ public async Task CanCombineSingleFutureValueWithEnumerableFuturesAsync() foreach (var person in await (persons.GetEnumerableAsync())) { - } var events = logSpy.Appender.GetEvents(); diff --git a/src/NHibernate.Test/Async/Futures/LinqFutureFixture.cs b/src/NHibernate.Test/Async/Futures/LinqFutureFixture.cs index bab0ca692ca..57ade75dcb2 100644 --- a/src/NHibernate.Test/Async/Futures/LinqFutureFixture.cs +++ b/src/NHibernate.Test/Async/Futures/LinqFutureFixture.cs @@ -260,7 +260,6 @@ public async Task CanUseFutureFetchQueryAsync() using (var logSpy = new SqlLogSpy()) { - Assert.That((await (persons.GetEnumerableAsync())).Any(x => x.Children.Any()), "No children found"); Assert.That((await (persons10.GetEnumerableAsync())).Any(x => x.Children.Any()), "No children found"); diff --git a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs index 2fe7614ce42..e8000f407e8 100644 --- a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs @@ -22,7 +22,6 @@ namespace NHibernate.Test.Futures { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class QueryBatchFixtureAsync : TestCaseMappingByCode { @@ -81,10 +80,10 @@ public async Task CanCombineCriteriaAndHqlInBatchAsync() using (var sqlLog = new SqlLogSpy()) { - await (batch.GetResultAsync(0, CancellationToken.None)); - await (batch.GetResultAsync("queryOver", CancellationToken.None)); - await (batch.GetResultAsync(2, CancellationToken.None)); - await (batch.GetResultAsync("sql", CancellationToken.None)); + await (batch.GetResultAsync(0)); + await (batch.GetResultAsync("queryOver")); + await (batch.GetResultAsync(2)); + await (batch.GetResultAsync("sql")); if (SupportsMultipleQueries) Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1)); } @@ -142,7 +141,7 @@ public async Task CanFetchCollectionInBatchAsync() batch.Add(q1); batch.Add(session.Query().Fetch(c => c.ChildrenList)); - await (batch.ExecuteAsync(CancellationToken.None)); + await (batch.ExecuteAsync()); var parent = await (session.LoadAsync(_parentId)); Assert.That(NHibernateUtil.IsInitialized(parent), Is.True); @@ -162,7 +161,7 @@ public async Task AfterLoadCallbackAsync() int count = 0; batch.Add(session.Query().WithOptions(o => o.SetCacheable(true)), r => results = r); batch.Add(session.Query().WithOptions(o => o.SetCacheable(true)), ec => ec.Count(), r => count = r); - await (batch.ExecuteAsync(CancellationToken.None)); + await (batch.ExecuteAsync()); Assert.That(results, Is.Not.Null); Assert.That(count, Is.GreaterThan(0)); @@ -177,7 +176,7 @@ public async Task AfterLoadCallbackAsync() batch.Add(session.Query().WithOptions(o => o.SetCacheable(true)), r => results = r); batch.Add(session.Query().WithOptions(o => o.SetCacheable(true)), ec => ec.Count(), r => count = r); - await (batch.ExecuteAsync(CancellationToken.None)); + await (batch.ExecuteAsync()); Assert.That(results, Is.Not.Null); Assert.That(count, Is.GreaterThan(0)); @@ -205,7 +204,6 @@ public async Task SameCollectionFetchesAsync() Assert.That(NHibernateUtil.IsInitialized(parent), Is.True); Assert.That(NHibernateUtil.IsInitialized(parent.ChildrenList), Is.True); Assert.That(parent.ChildrenList.Count, Is.EqualTo(2)); - } } @@ -498,6 +496,35 @@ public async Task CacheModeWorksWithFutureAsync() } } + //GH-2173 + [Test] + public async Task CanFetchNonLazyEntitiesInSubsequentQueryAsync() + { + Sfi.Statistics.IsStatisticsEnabled = true; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.SaveAsync( + new EntityEager + { + Name = "EagerManyToOneAssociation", + EagerEntity = new EntityEagerChild {Name = "association"} + })); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + Sfi.Statistics.Clear(); + //EntityEager.EagerEntity is lazy initialized instead of being loaded by the second query + s.QueryOver().Fetch(SelectMode.Skip, x => x.EagerEntity).Future(); + await (s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().GetEnumerableAsync()); + + if(SupportsMultipleQueries) + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -543,6 +570,10 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); + rc.ManyToOne(x => x.EagerEntity, m => + { + m.Cascade(Mapping.ByCode.Cascade.Persist); + }); rc.Bag(ep => ep.ChildrenListSubselect, m => { @@ -560,6 +591,14 @@ protected override HbmMapping GetMappings() }, a => a.OneToMany()); }); + mapper.Class( + rc => + { + rc.Lazy(false); + + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); mapper.Class( rc => { diff --git a/src/NHibernate.Test/Async/GeneratedTest/PartiallyGeneratedComponentTest.cs b/src/NHibernate.Test/Async/GeneratedTest/PartiallyGeneratedComponentTest.cs index dff6d7408e3..f301c0a1cd1 100644 --- a/src/NHibernate.Test/Async/GeneratedTest/PartiallyGeneratedComponentTest.cs +++ b/src/NHibernate.Test/Async/GeneratedTest/PartiallyGeneratedComponentTest.cs @@ -8,10 +8,7 @@ //------------------------------------------------------------------------------ -using System; - using NHibernate.Dialect; - using NUnit.Framework; namespace NHibernate.Test.GeneratedTest @@ -27,7 +24,7 @@ protected override string MappingsAssembly protected override string[] Mappings { - get { return new string[] { "GeneratedTest.ComponentOwner.hbm.xml" }; } + get { return new [] { "GeneratedTest.ComponentOwner.hbm.xml" }; } } protected override bool AppliesTo(Dialect.Dialect dialect) @@ -39,34 +36,40 @@ protected override bool AppliesTo(Dialect.Dialect dialect) public async Task PartialComponentGenerationAsync() { ComponentOwner owner = new ComponentOwner("initial"); - ISession s = OpenSession(); - s.BeginTransaction(); - await (s.SaveAsync(owner)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.SaveAsync(owner)); + await (t.CommitAsync()); + s.Close(); + } Assert.IsNotNull(owner.Component, "expecting insert value generation"); int previousValue = owner.Component.Generated; Assert.AreNotEqual(0, previousValue, "expecting insert value generation"); - s = OpenSession(); - s.BeginTransaction(); - owner = (ComponentOwner) await (s.GetAsync(typeof(ComponentOwner), owner.Id)); - Assert.AreEqual(previousValue, owner.Component.Generated, "expecting insert value generation"); - owner.Name = "subsequent"; - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + owner = (ComponentOwner) await (s.GetAsync(typeof(ComponentOwner), owner.Id)); + Assert.AreEqual(previousValue, owner.Component.Generated, "expecting insert value generation"); + owner.Name = "subsequent"; + await (t.CommitAsync()); + s.Close(); + } Assert.IsNotNull(owner.Component); previousValue = owner.Component.Generated; - s = OpenSession(); - s.BeginTransaction(); - owner = (ComponentOwner) await (s.GetAsync(typeof(ComponentOwner), owner.Id)); - Assert.AreEqual(previousValue, owner.Component.Generated, "expecting update value generation"); - await (s.DeleteAsync(owner)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + owner = (ComponentOwner) await (s.GetAsync(typeof(ComponentOwner), owner.Id)); + Assert.AreEqual(previousValue, owner.Component.Generated, "expecting update value generation"); + await (s.DeleteAsync(owner)); + await (t.CommitAsync()); + s.Close(); + } } } } diff --git a/src/NHibernate.Test/Async/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs b/src/NHibernate.Test/Async/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs index 310aaf86d60..dc3b311db84 100644 --- a/src/NHibernate.Test/Async/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs +++ b/src/NHibernate.Test/Async/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.ByTrigger @@ -35,18 +34,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public async Task GetGeneratedKeysSupportAsync() { - ISession session = OpenSession(); - session.BeginTransaction(); - - var e = new MyEntity { Name = "entity-1" }; - await (session.SaveAsync(e)); - - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - await (session.DeleteAsync(e)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntity { Name = "entity-1" }; + await (session.SaveAsync(e)); + + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); + + await (session.DeleteAsync(e)); + await (tran.CommitAsync()); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs b/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs index 5fc2a393c04..b927eeb17b9 100644 --- a/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs +++ b/src/NHibernate.Test/Async/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NHibernate.Cfg; using NUnit.Framework; @@ -42,137 +41,17 @@ protected override void Configure(Configuration configuration) [Test] public async Task IdentityColumnGeneratedIdsAsync() { - ISession s = OpenSession(); - s.BeginTransaction(); - MyEntity myEntity = new MyEntity("test"); - long id = (long)await (s.SaveAsync(myEntity)); - Assert.IsNotNull(id,"identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity.Id); - await (s.DeleteAsync(myEntity)); - await (s.Transaction.CommitAsync()); - s.Close(); - } - - [Test, Ignore("Not supported yet.")] - public async Task PersistOutsideTransactionAsync() - { - ISession s = OpenSession(); - - // first test save() which should force an immediate insert... - MyEntity myEntity1 = new MyEntity("test-save"); - long id = (long)await (s.SaveAsync(myEntity1)); - Assert.IsNotNull(id, "identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity1.Id); - - // next test persist() which should cause a delayed insert... - long initialInsertCount = Sfi.Statistics.EntityInsertCount; - MyEntity myEntity2 = new MyEntity("test-persist"); - await (s.PersistAsync(myEntity2)); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0,myEntity2.Id); - - // an explicit flush should cause execution of the delayed insertion - await (s.FlushAsync()); - Assert.AreEqual(initialInsertCount + 1, Sfi.Statistics.EntityInsertCount, "delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(myEntity1)); - await (s.DeleteAsync(myEntity2)); - await (s.Transaction.CommitAsync()); - s.Close(); - } - - [Test, Ignore("Not supported yet.")] - public async Task PersistOutsideTransactionCascadedToNonInverseCollectionAsync() - { - long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity = new MyEntity("test-persist"); - myEntity.NonInverseChildren.Add(new MyChild("test-child-persist-non-inverse")); - await (s.PersistAsync(myEntity)); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0, myEntity.Id); - await (s.FlushAsync()); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount,"delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from MyChild")); - await (s.DeleteAsync("from MyEntity")); - await (s.Transaction.CommitAsync()); - s.Close(); - } - - [Test, Ignore("Not supported yet.")] - public async Task PersistOutsideTransactionCascadedToInverseCollectionAsync() - { - long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity2 = new MyEntity("test-persist-2"); - MyChild child = new MyChild("test-child-persist-inverse"); - myEntity2.InverseChildren.Add(child); - child.InverseParent= myEntity2; - await (s.PersistAsync(myEntity2)); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount,"persist on identity column not delayed"); - Assert.AreEqual(0, myEntity2.Id); - await (s.FlushAsync()); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount,"delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from MyChild")); - await (s.DeleteAsync("from MyEntity")); - await (s.Transaction.CommitAsync()); - s.Close(); - } - - [Test, Ignore("Not supported yet.")] - public async Task PersistOutsideTransactionCascadedToManyToOneAsync() - { - long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity = new MyEntity("test-persist"); - myEntity.Sibling=new MySibling("test-persist-sibling-out"); - await (s.PersistAsync(myEntity)); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0, myEntity.Id); - await (s.FlushAsync()); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount, "delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from MyEntity")); - await (s.DeleteAsync("from MySibling")); - await (s.Transaction.CommitAsync()); - s.Close(); - } - - [Test, Ignore("Not supported yet.")] - public async Task PersistOutsideTransactionCascadedFromManyToOneAsync() - { - long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity2 = new MyEntity("test-persist-2"); - MySibling sibling = new MySibling("test-persist-sibling-in"); - sibling.Entity= myEntity2; - await (s.PersistAsync(sibling)); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0, myEntity2.Id); - await (s.FlushAsync()); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount, "delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from MySibling")); - await (s.DeleteAsync("from MyEntity")); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + MyEntity myEntity = new MyEntity("test"); + long id = (long) await (s.SaveAsync(myEntity)); + Assert.IsNotNull(id, "identity column did not force immediate insert"); + Assert.AreEqual(id, myEntity.Id); + await (s.DeleteAsync(myEntity)); + await (t.CommitAsync()); + s.Close(); + } } } } diff --git a/src/NHibernate.Test/Async/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs b/src/NHibernate.Test/Async/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs index aa5eae5b76d..195f865f3ba 100644 --- a/src/NHibernate.Test/Async/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs +++ b/src/NHibernate.Test/Async/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Identity @@ -33,18 +32,19 @@ protected override string MappingsAssembly [Test] public async Task SequenceIdentityGeneratorAsync() { - ISession session = OpenSession(); - session.BeginTransaction(); - - var e = new MyEntityIdentity { Name = "entity-1" }; - await (session.SaveAsync(e)); - - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - await (session.DeleteAsync(e)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntityIdentity { Name = "entity-1" }; + await (session.SaveAsync(e)); + + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); + + await (session.DeleteAsync(e)); + await (tran.CommitAsync()); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Generatedkeys/Select/SelectGeneratorTest.cs b/src/NHibernate.Test/Async/Generatedkeys/Select/SelectGeneratorTest.cs index c7eb168df5c..12fc1aec40b 100644 --- a/src/NHibernate.Test/Async/Generatedkeys/Select/SelectGeneratorTest.cs +++ b/src/NHibernate.Test/Async/Generatedkeys/Select/SelectGeneratorTest.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Select @@ -35,19 +34,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public async Task GetGeneratedKeysSupportAsync() { - ISession session = OpenSession(); - session.BeginTransaction(); - - MyEntity e = new MyEntity("entity-1"); - await (session.SaveAsync(e)); - - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - await (session.DeleteAsync(e)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + MyEntity e = new MyEntity("entity-1"); + await (session.SaveAsync(e)); + + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); + + await (session.DeleteAsync(e)); + await (tran.CommitAsync()); + session.Close(); + } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs b/src/NHibernate.Test/Async/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs index 215ff2dfba0..95945328d91 100644 --- a/src/NHibernate.Test/Async/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs +++ b/src/NHibernate.Test/Async/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Seqidentity @@ -41,18 +40,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public async Task SequenceIdentityGeneratorAsync() { - ISession session = OpenSession(); - session.BeginTransaction(); - - var e = new MyEntity{Name="entity-1"}; - await (session.SaveAsync(e)); - - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - await (session.DeleteAsync(e)); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntity { Name = "entity-1" }; + await (session.SaveAsync(e)); + + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); + + await (session.DeleteAsync(e)); + await (tran.CommitAsync()); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/Async/GenericTest/BagGeneric/BagGenericFixture.cs b/src/NHibernate.Test/Async/GenericTest/BagGeneric/BagGenericFixture.cs index 22dcf9ca779..74542bbc995 100644 --- a/src/NHibernate.Test/Async/GenericTest/BagGeneric/BagGenericFixture.cs +++ b/src/NHibernate.Test/Async/GenericTest/BagGeneric/BagGenericFixture.cs @@ -8,18 +8,19 @@ //------------------------------------------------------------------------------ -using System; using System.Collections.Generic; -using System.Text; +using System.Linq; +using NHibernate.Collection; +using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.GenericTest.BagGeneric { using System.Threading.Tasks; + using System.Threading; [TestFixture] public class BagGenericFixtureAsync : TestCase { - protected override string[] Mappings { get { return new string[] { "GenericTest.BagGeneric.BagGenericFixture.hbm.xml" }; } @@ -75,6 +76,51 @@ public async Task SimpleAsync() s.Close(); } + [Test] + public async Task EqualsSnapshotAsync() + { + var a = new A {Name = "first generic type"}; + var i0 = new B {Name = "1"}; + var i4 = new B {Name = "4"}; + a.Items = new List + { + i0, + i0, + new B {Name = "2"}, + new B {Name = "3"}, + i4, + i4, + }; + var lastIdx = a.Items.Count - 1; + using (var s = OpenSession()) + { + await (s.SaveAsync(a)); + await (s.FlushAsync()); + var collection = (IPersistentCollection) a.Items; + var collectionPersister = Sfi.GetCollectionPersister(collection.Role); + + a.Items[0] = i4; + Assert.Multiple( + async () => + { + Assert.That(await (collection.EqualsSnapshotAsync(collectionPersister, CancellationToken.None)), Is.False, "modify first collection element"); + + a.Items[lastIdx] = i0; + Assert.That(await (collection.EqualsSnapshotAsync(collectionPersister, CancellationToken.None)), Is.True, "swap elements in collection"); + + a.Items[0] = i0; + a.Items[lastIdx] = i0; + Assert.That(await (collection.EqualsSnapshotAsync(collectionPersister, CancellationToken.None)), Is.False, "modify last collection element"); + + a.Items[lastIdx] = i4; + var reversed = a.Items.Reverse().ToArray(); + a.Items.Clear(); + ArrayHelper.AddAll(a.Items, reversed); + Assert.That(await (collection.EqualsSnapshotAsync(collectionPersister, CancellationToken.None)), Is.True, "reverse collection elements"); + }); + } + } + [Test] public async Task CopyAsync() { diff --git a/src/NHibernate.Test/Async/GenericTest/ListGeneric/ListGenericFixture.cs b/src/NHibernate.Test/Async/GenericTest/ListGeneric/ListGenericFixture.cs index c633d6b18d5..5440614c613 100644 --- a/src/NHibernate.Test/Async/GenericTest/ListGeneric/ListGenericFixture.cs +++ b/src/NHibernate.Test/Async/GenericTest/ListGeneric/ListGenericFixture.cs @@ -20,7 +20,6 @@ namespace NHibernate.Test.GenericTest.ListGeneric [TestFixture] public class ListGenericFixtureAsync : TestCase { - protected override string[] Mappings { get { return new string[] { "GenericTest.ListGeneric.ListGenericFixture.hbm.xml" }; } diff --git a/src/NHibernate.Test/Async/GenericTest/Overall/Fixture.cs b/src/NHibernate.Test/Async/GenericTest/Overall/Fixture.cs index 3b345242e9d..1160fe8aef2 100644 --- a/src/NHibernate.Test/Async/GenericTest/Overall/Fixture.cs +++ b/src/NHibernate.Test/Async/GenericTest/Overall/Fixture.cs @@ -79,5 +79,4 @@ public async Task CRUDABAsync() } } } - } diff --git a/src/NHibernate.Test/Async/GenericTest/SetGeneric/SetGenericFixture.cs b/src/NHibernate.Test/Async/GenericTest/SetGeneric/SetGenericFixture.cs index 684ecbdfe34..e04a5476262 100644 --- a/src/NHibernate.Test/Async/GenericTest/SetGeneric/SetGenericFixture.cs +++ b/src/NHibernate.Test/Async/GenericTest/SetGeneric/SetGenericFixture.cs @@ -20,7 +20,6 @@ namespace NHibernate.Test.GenericTest.SetGeneric [TestFixture] public class SetGenericFixtureAsync : TestCase { - protected override string[] Mappings { get { return new string[] { "GenericTest.SetGeneric.SetGenericFixture.hbm.xml" }; } diff --git a/src/NHibernate.Test/Async/GhostProperty/GhostPropertyFixture.cs b/src/NHibernate.Test/Async/GhostProperty/GhostPropertyFixture.cs index 5a0aac53159..12ab309a529 100644 --- a/src/NHibernate.Test/Async/GhostProperty/GhostPropertyFixture.cs +++ b/src/NHibernate.Test/Async/GhostProperty/GhostPropertyFixture.cs @@ -59,7 +59,6 @@ protected override void OnSetUp() }); tx.Commit(); } - } protected override void OnTearDown() @@ -180,18 +179,6 @@ public async Task GhostPropertyMaintainIdentityMapAsync() } } - [Test, Ignore("This shows an expected edge case")] - public async Task GhostPropertyMaintainIdentityMapUsingGetAsync() - { - using (ISession s = OpenSession()) - { - var payment = await (s.LoadAsync(1)); - var order = await (s.GetAsync(1)); - - Assert.AreSame(order.Payment, payment); - } - } - [Test] public async Task WillLoadGhostAssociationOnAccessAsync() { diff --git a/src/NHibernate.Test/Async/Hql/AggregateFunctionsWithSubSelectTest.cs b/src/NHibernate.Test/Async/Hql/AggregateFunctionsWithSubSelectTest.cs new file mode 100644 index 00000000000..2ffa203a637 --- /dev/null +++ b/src/NHibernate.Test/Async/Hql/AggregateFunctionsWithSubSelectTest.cs @@ -0,0 +1,120 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.Hql +{ + using System.Threading.Tasks; + [TestFixture] + public class AggregateFunctionsWithSubSelectTestAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.Name); + rc.Map(x => x.Localized, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.Element()); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.Name); + rc.Map(x => x.Contacts, cm => cm.Key(k => k.Column("position")), x => x.OneToMany()); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var document = new Document(); + var p1 = new Person(); + var p2 = new Person(); + + p1.Localized.Add(1, "p1.1"); + p1.Localized.Add(2, "p1.2"); + p2.Localized.Add(1, "p2.1"); + p2.Localized.Add(2, "p2.2"); + + document.Contacts.Add(1, p1); + document.Contacts.Add(2, p2); + + session.Persist(p1); + session.Persist(p2); + session.Persist(document); + + transaction.Commit(); + } + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return TestDialect.SupportsAggregateInSubSelect; + } + + [TestCase("SUM", 4)] + [TestCase("MIN", 2)] + [TestCase("MAX", 2)] + [TestCase("AVG", 2d)] + public async Task TestAggregateFunctionAsync(string functionName, object result) + { + var query = "SELECT " + + " d.Id, " + + $" {functionName}(" + + " (" + + " SELECT COUNT(localized) " + + " FROM Person p " + + " LEFT JOIN p.Localized localized " + + " WHERE p.Id = c.Id" + + " )" + + " ) AS LocalizedCount " + + "FROM Document d " + + "LEFT JOIN d.Contacts c " + + "GROUP BY d.Id"; + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var results = await (session.CreateQuery(query).ListAsync()); + + Assert.That(results, Has.Count.EqualTo(1)); + var tuple = results[0] as object[]; + Assert.That(tuple, Is.Not.Null); + Assert.That(tuple, Has.Length.EqualTo(2)); + Assert.That(tuple[1], Is.EqualTo(result)); + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/Hql/Ast/BulkManipulation.cs b/src/NHibernate.Test/Async/Hql/Ast/BulkManipulation.cs index 15d04cf3bac..dbd820ee55f 100644 --- a/src/NHibernate.Test/Async/Hql/Ast/BulkManipulation.cs +++ b/src/NHibernate.Test/Async/Hql/Ast/BulkManipulation.cs @@ -11,7 +11,6 @@ using System; using System.Collections; using System.Threading; -using NHibernate.Dialect; using NHibernate.Hql.Ast.ANTLR; using NHibernate.Id; using NHibernate.Persister.Entity; @@ -103,7 +102,6 @@ public async Task SimpleInsertFromAggregateAsync() await (data.CleanupAsync()); } - [Test] public async Task InsertWithManyToOneAsync() { @@ -225,7 +223,8 @@ public async Task InsertAcrossMappedJoinFailsAsync() await (data.CleanupAsync()); } - public async Task InsertWithGeneratedIdAsync(CancellationToken cancellationToken = default(CancellationToken)) + [Test] + public async Task InsertWithGeneratedIdAsync() { // Make sure the env supports bulk inserts with generated ids... IEntityPersister persister = Sfi.GetEntityPersister(typeof (PettingZoo).FullName); @@ -240,21 +239,21 @@ public async Task InsertAcrossMappedJoinFailsAsync() ISession s = OpenSession(); ITransaction t = s.BeginTransaction(); - await (s.SaveAsync(zoo, cancellationToken)); - await (t.CommitAsync(cancellationToken)); + await (s.SaveAsync(zoo)); + await (t.CommitAsync()); s.Close(); s = OpenSession(); t = s.BeginTransaction(); - int count = await (s.CreateQuery("insert into PettingZoo (name) select name from Zoo").ExecuteUpdateAsync(cancellationToken)); - await (t.CommitAsync(cancellationToken)); + int count = await (s.CreateQuery("insert into PettingZoo (name) select name from Zoo").ExecuteUpdateAsync()); + await (t.CommitAsync()); s.Close(); Assert.That(count, Is.EqualTo(1), "unexpected insertion count"); s = OpenSession(); t = s.BeginTransaction(); - var pz = (PettingZoo) await (s.CreateQuery("from PettingZoo").UniqueResultAsync(cancellationToken)); - await (t.CommitAsync(cancellationToken)); + var pz = (PettingZoo) await (s.CreateQuery("from PettingZoo").UniqueResultAsync()); + await (t.CommitAsync()); s.Close(); Assert.That(zoo.Name, Is.EqualTo(pz.Name)); @@ -262,8 +261,8 @@ public async Task InsertAcrossMappedJoinFailsAsync() s = OpenSession(); t = s.BeginTransaction(); - await (s.CreateQuery("delete Zoo").ExecuteUpdateAsync(cancellationToken)); - await (t.CommitAsync(cancellationToken)); + await (s.CreateQuery("delete Zoo").ExecuteUpdateAsync()); + await (t.CommitAsync()); s.Close(); } @@ -374,14 +373,16 @@ public async Task InsertWithGeneratedTimestampVersionAsync() public async Task InsertWithSelectListUsingJoinsAsync() { // this is just checking parsing and syntax... - ISession s = OpenSession(); - s.BeginTransaction(); - await (s.CreateQuery( - "insert into Animal (description, bodyWeight) select h.description, h.bodyWeight from Human h where h.mother.mother is not null") - .ExecuteUpdateAsync()); - await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.CreateQuery( + "insert into Animal (description, bodyWeight) select h.description, h.bodyWeight from Human h where h.mother.mother is not null") + .ExecuteUpdateAsync()); + await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); + await (t.CommitAsync()); + s.Close(); + } } #endregion @@ -826,6 +827,31 @@ public async Task UpdateSetNullOnJoinedSubclassAsync() await (data.CleanupAsync()); } + [Test] + public async Task UpdateMultitableWithWhereExistsSubqueryAsync() + { + if (!Dialect.SupportsTemporaryTables) + Assert.Ignore("Cannot perform multi-table updates using dialect not supporting temp tables."); + + if(!Dialect.SupportsScalarSubSelects) + Assert.Ignore("Dialect does not support scalar sub-select"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + String updateQryString = "update Human h " + + "set h.description = 'updated' " + + "where exists (" + + " select f.id " + + " from h.friends f " + + " where f.name.last = 'Public' " + + ")"; + await (s.CreateQuery(updateQryString).ExecuteUpdateAsync()); + + await (t.CommitAsync()); + } + } + #endregion #region DELETES @@ -834,41 +860,23 @@ public async Task UpdateSetNullOnJoinedSubclassAsync() public async Task DeleteWithSubqueryAsync() { // setup the test data... - ISession s = OpenSession(); - s.BeginTransaction(); - var owner = new SimpleEntityWithAssociation {Name = "myEntity-1"}; - owner.AddAssociation("assoc-1"); - owner.AddAssociation("assoc-2"); - owner.AddAssociation("assoc-3"); - await (s.SaveAsync(owner)); - var owner2 = new SimpleEntityWithAssociation {Name = "myEntity-2"}; - owner2.AddAssociation("assoc-1"); - owner2.AddAssociation("assoc-2"); - owner2.AddAssociation("assoc-3"); - owner2.AddAssociation("assoc-4"); - await (s.SaveAsync(owner2)); - var owner3 = new SimpleEntityWithAssociation {Name = "myEntity-3"}; - await (s.SaveAsync(owner3)); - await (s.Transaction.CommitAsync()); - s.Close(); + await (CreateSimpleEntityWithAssociationDataAsync()); // now try the bulk delete - s = OpenSession(); - s.BeginTransaction(); - int count = - await (s.CreateQuery("delete SimpleEntityWithAssociation e where size(e.AssociatedEntities ) = 0 and e.Name like '%'"). - ExecuteUpdateAsync()); - Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + int count = + await (s.CreateQuery( + "delete SimpleEntityWithAssociation e where size(e.AssociatedEntities ) = 0 and e.Name like '%'") + .ExecuteUpdateAsync()); + Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); + await (t.CommitAsync()); + s.Close(); + } // finally, clean up - s = OpenSession(); - s.BeginTransaction(); - await (s.CreateQuery("delete SimpleAssociatedEntity").ExecuteUpdateAsync()); - await (s.CreateQuery("delete SimpleEntityWithAssociation").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); + await (CleanSimpleEntityWithAssociationDataAsync()); } [Test] @@ -1072,6 +1080,56 @@ public async Task DeleteSyntaxWithCompositeIdAsync() s.Close(); } + [Test] + public async Task DeleteSubQueryReferencingTargetPropertAsync() + { + await (CreateSimpleEntityWithAssociationDataAsync()); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var count = await (s.CreateQuery("delete from SimpleEntityWithAssociation m where not exists (select d from SimpleAssociatedEntity d where d.Owner = m) and m.Name like 'myEntity%'").ExecuteUpdateAsync()); + Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); + await (t.CommitAsync()); + } + + await (CleanSimpleEntityWithAssociationDataAsync()); + } + + private async Task CreateSimpleEntityWithAssociationDataAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var owner = new SimpleEntityWithAssociation {Name = "myEntity-1"}; + owner.AddAssociation("assoc-1"); + owner.AddAssociation("assoc-2"); + owner.AddAssociation("assoc-3"); + await (s.SaveAsync(owner, cancellationToken)); + var owner2 = new SimpleEntityWithAssociation {Name = "myEntity-2"}; + owner2.AddAssociation("assoc-1"); + owner2.AddAssociation("assoc-2"); + owner2.AddAssociation("assoc-3"); + owner2.AddAssociation("assoc-4"); + await (s.SaveAsync(owner2, cancellationToken)); + var owner3 = new SimpleEntityWithAssociation {Name = "myEntity-3"}; + await (s.SaveAsync(owner3, cancellationToken)); + await (t.CommitAsync(cancellationToken)); + s.Close(); + } + } + + private async Task CleanSimpleEntityWithAssociationDataAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.CreateQuery("delete SimpleAssociatedEntity").ExecuteUpdateAsync(cancellationToken)); + await (s.CreateQuery("delete SimpleEntityWithAssociation").ExecuteUpdateAsync(cancellationToken)); + await (t.CommitAsync(cancellationToken)); + s.Close(); + } + } #endregion private class TestData diff --git a/src/NHibernate.Test/Async/Hql/Ast/HqlFixture.cs b/src/NHibernate.Test/Async/Hql/Ast/HqlFixture.cs index 3a0c9a92244..eb285f5b3ce 100644 --- a/src/NHibernate.Test/Async/Hql/Ast/HqlFixture.cs +++ b/src/NHibernate.Test/Async/Hql/Ast/HqlFixture.cs @@ -82,10 +82,10 @@ public async Task CaseClauseInSelectAsync() { // NH-322 using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.SaveAsync(new Animal {BodyWeight = 12, Description = "Polliwog"})); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } using (ISession s = OpenSession()) @@ -101,10 +101,10 @@ public async Task CaseClauseInSelectAsync() } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -112,10 +112,10 @@ public async Task CaseClauseInSelectAsync() public async Task MultipleParametersInCaseStatementAsync() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.SaveAsync(new Animal { BodyWeight = 12, Description = "Polliwog" })); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } try @@ -133,10 +133,10 @@ public async Task MultipleParametersInCaseStatementAsync() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } } @@ -145,10 +145,10 @@ public async Task MultipleParametersInCaseStatementAsync() public async Task ParameterInCaseThenClauseAsync() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.SaveAsync(new Animal { BodyWeight = 12, Description = "Polliwog" })); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } try @@ -164,10 +164,10 @@ public async Task ParameterInCaseThenClauseAsync() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } } @@ -176,10 +176,10 @@ public async Task ParameterInCaseThenClauseAsync() public async Task ParameterInCaseThenAndElseClausesWithCastAsync() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.SaveAsync(new Animal { BodyWeight = 12, Description = "Polliwog" })); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } try @@ -196,10 +196,10 @@ public async Task ParameterInCaseThenAndElseClausesWithCastAsync() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } } @@ -208,10 +208,10 @@ public async Task ParameterInCaseThenAndElseClausesWithCastAsync() public async Task SubselectAdditionAsync() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.SaveAsync(new Animal { BodyWeight = 12, Description = "Polliwog" })); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } try @@ -226,39 +226,14 @@ public async Task SubselectAdditionAsync() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } } - [Test, Ignore("Not fixed yet.")] - public async Task SumShouldReturnDoubleAsync() - { - // NH-1734 - using (ISession s = OpenSession()) - using (s.BeginTransaction()) - { - await (s.SaveAsync(new Human{ IntValue = 11, BodyWeight = 12.5f, Description = "Polliwog" })); - await (s.Transaction.CommitAsync()); - } - - using (ISession s = OpenSession()) - { - var l = await (s.CreateQuery("select sum(a.intValue * a.bodyWeight) from Animal a group by a.id").ListAsync()); - Assert.That(l[0], Is.InstanceOf()); - } - - using (ISession s = OpenSession()) - using (s.BeginTransaction()) - { - await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); - } - } - [Test] public async Task CanParseMaxLongAsync() { @@ -292,29 +267,26 @@ public void InvalidJoinOnPropertyAsync() public async Task InsertIntoFromSelect_WithSelectClauseParametersAsync() { using (ISession s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { - // arrange - await (s.SaveAsync(new Animal() {Description = "cat1", BodyWeight = 2.1f})); - await (s.SaveAsync(new Animal() {Description = "cat2", BodyWeight = 2.5f})); - await (s.SaveAsync(new Animal() {Description = "cat3", BodyWeight = 2.7f})); + // arrange + await (s.SaveAsync(new Animal() {Description = "cat1", BodyWeight = 2.1f})); + await (s.SaveAsync(new Animal() {Description = "cat2", BodyWeight = 2.5f})); + await (s.SaveAsync(new Animal() {Description = "cat3", BodyWeight = 2.7f})); - // act - await (s.CreateQuery("insert into Animal (description, bodyWeight) select a.description, :weight from Animal a where a.bodyWeight < :weight") - .SetParameter("weight", 5.7f).ExecuteUpdateAsync()); + // act + await (s.CreateQuery("insert into Animal (description, bodyWeight) select a.description, :weight from Animal a where a.bodyWeight < :weight") + .SetParameter("weight", 5.7f).ExecuteUpdateAsync()); - // assert - Assert.AreEqual(3, await (s.CreateCriteria().SetProjection(Projections.RowCount()) - .Add(Restrictions.Gt("bodyWeight", 5.5f)).UniqueResultAsync())); + // assert + Assert.AreEqual(3, await (s.CreateCriteria().SetProjection(Projections.RowCount()) + .Add(Restrictions.Gt("bodyWeight", 5.5f)).UniqueResultAsync())); - await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); - } + await (s.CreateQuery("delete from Animal").ExecuteUpdateAsync()); + await (t.CommitAsync()); } } - [Test] public async Task UnaryMinusBeforeParenthesesHandledCorrectlyAsync() { @@ -334,5 +306,14 @@ public async Task UnaryMinusBeforeParenthesesHandledCorrectlyAsync() Assert.That(actualWorkaround, Is.EqualTo(-2)); } } + + //NH-3249 (GH-1285) + [Test] + public async Task CountDistinctOnFunctionAsync() + { + var hql = @"SELECT COUNT(DISTINCT DATE(m.birthdate)) FROM Mammal m"; + using(var s = OpenSession()) + await (s.CreateQuery(hql).ListAsync()); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Hql/Ast/OrderByTest.cs b/src/NHibernate.Test/Async/Hql/Ast/OrderByTest.cs index 49704e951ba..5bff80a1d9f 100644 --- a/src/NHibernate.Test/Async/Hql/Ast/OrderByTest.cs +++ b/src/NHibernate.Test/Async/Hql/Ast/OrderByTest.cs @@ -367,7 +367,6 @@ private async Task CheckTestOrderByResultAsync(object result, Zoo zooExpected, HashSet zoosUnordered, CancellationToken cancellationToken = default(CancellationToken)) { - Assert.IsInstanceOf(result); var resultArray = (object[]) result; Assert.AreEqual(2, resultArray.Length); @@ -388,7 +387,6 @@ private async Task CheckTestOrderByResultAsync(object result, } } - private class TestData { private readonly OrderByTestAsync tc; @@ -651,4 +649,4 @@ public void Cleanup() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Hql/Ast/WithClauseFixture.cs b/src/NHibernate.Test/Async/Hql/Ast/WithClauseFixture.cs index fadd86df8c0..f14008a2ec0 100644 --- a/src/NHibernate.Test/Async/Hql/Ast/WithClauseFixture.cs +++ b/src/NHibernate.Test/Async/Hql/Ast/WithClauseFixture.cs @@ -8,9 +8,7 @@ //------------------------------------------------------------------------------ -using System; using System.Collections; -using NHibernate.Exceptions; using NHibernate.Hql.Ast.ANTLR; using NUnit.Framework; @@ -54,40 +52,21 @@ public async Task WithClauseFailsWithFetchAsync() } [Test] - public async Task ValidWithSemanticsAsync() + public async Task WithClauseOnSubclassesAsync() { using (var s = OpenSession()) { await (s.CreateQuery( "from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1").ListAsync()); - } - } - [Test] - public async Task InvalidWithSemanticsAsync() - { - using (ISession s = OpenSession()) - { - // PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f' - // alias relates to the Human.friends collection which the aonther Human entity. The issue - // here is the way JoinSequence and Joinable (the persister) interact to generate the - // joins relating to the sublcass/superclass tables - Assert.ThrowsAsync( - () => - s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).ListAsync()); - - //The query below is no longer throw InvalidWithClauseException but generates "invalid" SQL to better support complex with join clauses. - //Invalid SQL means that additional joins for "o.mother.father" are currently added after "offspring" join. Some DBs can process such query and some can't. - try - { - await (s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin") - .SetInt32("cousin", 123) - .ListAsync()); - } - catch (GenericADOException) - { - //Apparently SQLite can process queries with wrong join orders - } + // f.bodyWeight is a reference to a column on the Animal table; however, the 'f' + // alias relates to the Human.friends collection which the aonther Human entity. + // Group join allows us to use such constructs + await (s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).ListAsync()); + + await (s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin") + .SetInt32("cousin", 123) + .ListAsync()); } } diff --git a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs index e6041310ee1..ca151b49690 100644 --- a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs @@ -153,6 +153,124 @@ public async Task EntityJoinFoSubqueryAsync() } } + [Test] + public async Task EntityJoinWithNullableOneToOneEntityComparisonInWithClausShouldAddJoinAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "left join OneToOneEntity st with st = ex.OneToOne " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(2)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task NullableOneToOneWhereEntityIsNotNullAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.OneToOne is not null " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task NullableOneToOneWhereIdIsNotNullAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.OneToOne.Id is not null " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task NullablePropRefWhereIdEntityNotNullShouldAddJoinAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.PropRef is not null " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "PropRefEntity").Count, Is.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task NullableOneToOneFetchQueryIsNotAffectedAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex left join fetch ex.OneToOne o " + + "where o is null " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task NullableOneToOneFetchQueryIsNotAffected2Async() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex left join fetch ex.OneToOne o " + + "where o.Id is null " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + } + } + [Test] public async Task EntityJoinWithEntityComparisonInWithClausShouldNotAddJoinAsync() { @@ -213,7 +331,6 @@ public async Task EntityJoinWithEntityAssociationComparison2ShouldAddJoinAsync() } } - [Test] public async Task WithClauseOnOtherAssociationAsync() { @@ -275,19 +392,48 @@ public async Task EntityJoinWithFetchesAsync() } } - [Test, Ignore("Failing for unrelated reasons")] - public async Task CrossJoinAndWithClauseAsync() + [Test] + public async Task WithImpliedJoinOnAssociationAsync() + { + using (var session = OpenSession()) + { + var result = await (session.CreateQuery( + "SELECT s " + + "FROM EntityComplex s left join s.SameTypeChild q on q.SameTypeChild.SameTypeChild.Name = s.Name" + ).ListAsync()); + } + } + + [Test] + public async Task WithImpliedEntityJoinAsync() + { + using (var session = OpenSession()) + { + var result = await (session.CreateQuery( + "SELECT s " + + "FROM EntityComplex s left join EntityComplex q on q.SameTypeChild.SameTypeChild.Name = s.Name" + ).ListAsync()); + } + } + + [Test] + public async Task CrossJoinAndWhereClauseAsync() { - //This is about complex theta style join fix that was implemented in hibernate along with entity join functionality - //https://hibernate.atlassian.net/browse/HHH-7321 using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - await (session.CreateQuery( - "SELECT s " + - "FROM EntityComplex s, EntityComplex q " + - "LEFT JOIN s.SameTypeChild AS sa WITH sa.SameTypeChild.Id = q.SameTypeChild.Id" + var result = await (session.CreateQuery( + "SELECT s " + + "FROM EntityComplex s cross join EntityComplex q " + + "where s.SameTypeChild.Id = q.SameTypeChild.Id" ).ListAsync()); + + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + if (Dialect.SupportsCrossJoin) + { + Assert.That(sqlLog.GetWholeLog(), Does.Contain("cross join"), "A cross join is expected in the SQL select"); + } } } @@ -310,10 +456,8 @@ protected override HbmMapping GetMappings() rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); rc.ManyToOne(ep => ep.SameTypeChild2, m => m.Column("SameTypeChild2Id")); - }); - mapper.Class( rc => { @@ -327,7 +471,7 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Name); }); - + mapper.Class( rc => { @@ -354,7 +498,6 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Composite1Key1); rc.Property(e => e.Composite1Key2); rc.Property(e => e.CustomEntityNameId); - }); mapper.Class( @@ -366,6 +509,39 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Name); }); + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + rc.Property(e => e.PropertyRef, m => m.Column("EntityPropertyRef")); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + rc.OneToOne(e => e.OneToOne, m => m.Constrained(false)); + rc.ManyToOne( + e => e.PropRef, + m => + { + m.Column("OwnerPropertyRef"); + m.PropertyRef(nameof(PropRefEntity.PropertyRef)); + m.ForeignKey("none"); + m.NotFound(NotFoundMode.Ignore); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); } @@ -398,7 +574,6 @@ protected override void OnSetUp() { Name = "ComplexEntityChild2" } - }; _entityWithCompositeId = new EntityWithCompositeId @@ -431,7 +606,6 @@ protected override void OnSetUp() Composite1Key2 = _entityWithCompositeId.Key.Id2, CustomEntityNameId = _entityWithCustomEntityName.Id }; - session.Save(_noAssociation); session.Flush(); diff --git a/src/NHibernate.Test/Async/Hql/HQLFunctions.cs b/src/NHibernate.Test/Async/Hql/HQLFunctions.cs index a67a8ad518a..a9c304aaa66 100644 --- a/src/NHibernate.Test/Async/Hql/HQLFunctions.cs +++ b/src/NHibernate.Test/Async/Hql/HQLFunctions.cs @@ -215,8 +215,7 @@ public void AggregatesAndMathNH959Async() Assert.DoesNotThrowAsync(() => s.CreateQuery("select a.Id, sum(BodyWeight)/avg(BodyWeight) from Animal a group by a.Id having sum(BodyWeight)>0").ListAsync()); } } - - + [Test] public async Task SubStringTwoParametersAsync() { @@ -257,8 +256,7 @@ public async Task SubStringTwoParametersAsync() Assert.AreEqual("abcdef", result.Description); } } - - + [Test] public async Task SubStringAsync() { @@ -283,7 +281,6 @@ public async Task SubStringAsync() .UniqueResultAsync()); Assert.AreEqual("abcdef", result.Description); - // Following tests verify that parameters can be used. hql = "from Animal a where substring(a.Description, 2, ?) = 'bcd'"; @@ -292,7 +289,6 @@ public async Task SubStringAsync() .UniqueResultAsync()); Assert.AreEqual("abcdef", result.Description); - hql = "from Animal a where substring(a.Description, ?, ?) = ?"; result = (Animal)await (s.CreateQuery(hql) .SetParameter(0, 2) @@ -314,7 +310,6 @@ public async Task SubStringAsync() [Test] public async Task LocateAsync() { - AssumeFunctionSupported("locate"); using (ISession s = OpenSession()) { Animal a1 = new Animal("abcdef", 20); @@ -1257,40 +1252,6 @@ public async Task NH1725Async() } } - [Test, Ignore("Not supported yet!")] - public async Task ParameterLikeArgumentAsync() - { - using (ISession s = OpenSession()) - { - Animal a1 = new Animal("abcdef", 1.3f); - await (s.SaveAsync(a1)); - await (s.FlushAsync()); - } - - using (ISession s = OpenSession()) - { - string hql; - IList l; - Animal result; - - // Render in WHERE - hql = "from Animal a where cast(:aParam as Double)>0"; - result = (Animal)await (s.CreateQuery(hql).SetDouble("aParam", 2D).UniqueResultAsync()); - Assert.IsNotNull(result); - - // Render in WHERE with math operation - hql = "from Animal a where cast(:aParam+a.BodyWeight as Double)>3"; - result = (Animal) await (s.CreateQuery(hql).SetDouble("aParam", 2D).UniqueResultAsync()); - Assert.IsNotNull(result); - - // Render in all clauses - hql = - "select cast(:aParam+a.BodyWeight as int) from Animal a group by cast(:aParam+a.BodyWeight as int) having cast(:aParam+a.BodyWeight as Double)>0"; - l = await (s.CreateQuery(hql).SetInt32("aParam", 10).ListAsync()); - Assert.AreEqual(1, l.Count); - } - } - [Test] public async Task BitwiseAndAsync() { diff --git a/src/NHibernate.Test/Async/Hql/SubQueryTest.cs b/src/NHibernate.Test/Async/Hql/SubQueryTest.cs new file mode 100644 index 00000000000..f88c5ed3629 --- /dev/null +++ b/src/NHibernate.Test/Async/Hql/SubQueryTest.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.Hql +{ + using System.Threading.Tasks; + [TestFixture] + public class SubQueryTestAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.RootName); + rc.ManyToOne(x => x.Branch); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.BranchName); + rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany()); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.LeafName); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + [TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")] + [TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")] + public async Task TestSubQueryAsync(string query) + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // Simple syntax check + await (session.CreateQuery(query).ListAsync()); + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/IdGen/Enhanced/OptimizerTests.cs b/src/NHibernate.Test/Async/IdGen/Enhanced/OptimizerTests.cs index 49d72dc7ac4..e7e5c23fad0 100644 --- a/src/NHibernate.Test/Async/IdGen/Enhanced/OptimizerTests.cs +++ b/src/NHibernate.Test/Async/IdGen/Enhanced/OptimizerTests.cs @@ -40,7 +40,7 @@ public async Task TestBasicNoOptimizerUsageAsync() long next = (long)await (optimizer.GenerateAsync(sequence, CancellationToken.None)); Assert.That(next, Is.EqualTo(i)); } - Assert.That(sequence.TimesCalled, Is.EqualTo(11)); // an extra time to get to 1 initially + Assert.That(sequence.TimesCalled, Is.EqualTo(11)); // an extra time to get to 1 initially Assert.That(sequence.CurrentValue, Is.EqualTo(10)); } @@ -67,7 +67,6 @@ public async Task TestBasicHiLoOptimizerUsageAsync() Assert.That(sequence.TimesCalled, Is.EqualTo(2)); Assert.That(sequence.CurrentValue, Is.EqualTo(2)); - // test historic table behavior, where the initial values started at 0 (we now force 1 to be the first used id value) sequence = new SourceMock(0); optimizer = OptimizerFactory.BuildOptimizer(OptimizerFactory.HiLo, typeof(long), increment, -1); @@ -251,4 +250,4 @@ public async Task TestRecoveredPooledLoOptimizerUsageAsync() Assert.That(sequence.CurrentValue, Is.EqualTo(4)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/BasicTableTest.cs b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/BasicTableTest.cs index 960228e6528..3ca65376d2d 100644 --- a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/BasicTableTest.cs +++ b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/BasicTableTest.cs @@ -28,7 +28,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - [Test] public async Task TestNormalBoundaryAsync() { @@ -52,12 +51,10 @@ public async Task TestNormalBoundaryAsync() Assert.That(entities[i].Id, Is.EqualTo(expectedId)); Assert.That(generator.TableAccessCount, Is.EqualTo(expectedId)); Assert.That(generator.Optimizer.LastSourceValue, Is.EqualTo(expectedId)); - } await (transaction.CommitAsync()); } - using (ITransaction transaction = s.BeginTransaction()) { for (int i = 0; i < count; i++) diff --git a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/HiLoTableTest.cs b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/HiLoTableTest.cs index 048aba891c8..962460f90e0 100644 --- a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/HiLoTableTest.cs +++ b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/HiLoTableTest.cs @@ -65,7 +65,6 @@ public async Task TestNormalBoundaryAsync() await (transaction.CommitAsync()); } - using (ITransaction transaction = s.BeginTransaction()) { for (int i = 0; i < entities.Length; i++) diff --git a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledLoTableTest.cs b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledLoTableTest.cs index 5be6374f66f..d721443bdd6 100644 --- a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledLoTableTest.cs +++ b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledLoTableTest.cs @@ -28,7 +28,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - [Test] public async Task TestNormalBoundaryAsync() { @@ -51,7 +50,7 @@ public async Task TestNormalBoundaryAsync() entities[i] = new Entity("" + (i + 1)); await (s.SaveAsync(entities[i])); Assert.That(generator.TableAccessCount, Is.EqualTo(1)); // initialization - Assert.That(optimizer.LastSourceValue, Is.EqualTo(1)); // initialization + Assert.That(optimizer.LastSourceValue, Is.EqualTo(1)); // initialization Assert.That(optimizer.LastValue, Is.EqualTo(i + 1)); } diff --git a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledTableTest.cs b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledTableTest.cs index 83e5bd20315..00c921da834 100644 --- a/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledTableTest.cs +++ b/src/NHibernate.Test/Async/IdGen/Enhanced/Table/PooledTableTest.cs @@ -28,7 +28,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - [Test] public async Task TestNormalBoundaryAsync() { @@ -51,7 +50,7 @@ public async Task TestNormalBoundaryAsync() entities[i] = new Entity("" + (i + 1)); await (s.SaveAsync(entities[i])); Assert.That(generator.TableAccessCount, Is.EqualTo(2)); // initialization calls seq twice - Assert.That(optimizer.LastSourceValue, Is.EqualTo(increment + 1)); // initialization calls seq twice + Assert.That(optimizer.LastSourceValue, Is.EqualTo(increment + 1)); // initialization calls seq twice Assert.That(optimizer.LastValue, Is.EqualTo(i + 1)); } diff --git a/src/NHibernate.Test/Async/IdTest/AssignedFixture.cs b/src/NHibernate.Test/Async/IdTest/AssignedFixture.cs index 42a417f7851..06fc3e6ec09 100644 --- a/src/NHibernate.Test/Async/IdTest/AssignedFixture.cs +++ b/src/NHibernate.Test/Async/IdTest/AssignedFixture.cs @@ -17,11 +17,9 @@ namespace NHibernate.Test.IdTest { using System.Threading.Tasks; - [TestFixture] public class AssignedFixtureAsync : IdFixtureBase { - private string[] GetAssignedIdentifierWarnings(LogSpy ls) { List warnings = new List(); @@ -260,7 +258,5 @@ public async Task InsertCascadeNoWarningAsync() Assert.That(warnings.Length, Is.EqualTo(0)); } } - } - } diff --git a/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs b/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs index 44b2fb38c30..a5e9222fd42 100644 --- a/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs +++ b/src/NHibernate.Test/Async/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs @@ -496,7 +496,7 @@ public async Task CreateWithNonEmptyManyToManyCollectionMergeWithNewElementAsync s.Close(); AssertInsertCount(1); - AssertUpdateCount(isContractVersioned && isPlanVersioned ? 1 : 0); // NH-specific: Hibernate issues a separate UPDATE for the version number + AssertUpdateCount(isContractVersioned && isPlanVersioned ? 1 : 0); // NH-specific: Hibernate issues a separate UPDATE for the version number ClearCounts(); s = OpenSession(); diff --git a/src/NHibernate.Test/Async/Immutable/ImmutableTest.cs b/src/NHibernate.Test/Async/Immutable/ImmutableTest.cs index e1294c54f7b..b75063bd44e 100644 --- a/src/NHibernate.Test/Async/Immutable/ImmutableTest.cs +++ b/src/NHibernate.Test/Async/Immutable/ImmutableTest.cs @@ -1050,7 +1050,6 @@ public async Task ImmutableParentEntityWithMergeAsync() AssertUpdateCount(0); AssertDeleteCount(3); - } [Test] diff --git a/src/NHibernate.Test/Async/Insertordering/AnimalModel/Fixture.cs b/src/NHibernate.Test/Async/Insertordering/AnimalModel/Fixture.cs index 42c94087569..15aa084e180 100644 --- a/src/NHibernate.Test/Async/Insertordering/AnimalModel/Fixture.cs +++ b/src/NHibernate.Test/Async/Insertordering/AnimalModel/Fixture.cs @@ -7,7 +7,6 @@ // //------------------------------------------------------------------------------ - using System.Collections; using NHibernate.Cfg; using NUnit.Framework; @@ -163,5 +162,29 @@ public async System.Threading.Tasks.Task InsertShouldNotInitializeOneToManyProxy // instead.) } } + + // #2141 + [Test] + public async System.Threading.Tasks.Task InsertShouldNotDependOnEntityEqualsImplementationAsync() + { + var person = new PersonWithWrongEquals { Name = "AnimalOwner" }; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.SaveAsync(person)); + await (t.CommitAsync()); + } + await (Sfi.EvictAsync(typeof(PersonWithWrongEquals))); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var personProxy = await (s.GetAsync(person.Id)); + + await (s.SaveAsync(new Cat { Name = "Felix", Owner = personProxy })); + await (s.SaveAsync(new Cat { Name = "Loustic", Owner = personProxy })); + Assert.DoesNotThrowAsync(() => { return t.CommitAsync(); }); + } + } } } diff --git a/src/NHibernate.Test/Async/Insertordering/FamilyModel/Fixture.cs b/src/NHibernate.Test/Async/Insertordering/FamilyModel/Fixture.cs index c60b2cfe24b..1f109a259df 100644 --- a/src/NHibernate.Test/Async/Insertordering/FamilyModel/Fixture.cs +++ b/src/NHibernate.Test/Async/Insertordering/FamilyModel/Fixture.cs @@ -7,7 +7,6 @@ // //------------------------------------------------------------------------------ - using System.Collections; using System.Linq; using NHibernate.Cfg; diff --git a/src/NHibernate.Test/Async/Insertordering/InsertOrderingFixture.cs b/src/NHibernate.Test/Async/Insertordering/InsertOrderingFixture.cs index b41af58320a..0a4892ffe1a 100644 --- a/src/NHibernate.Test/Async/Insertordering/InsertOrderingFixture.cs +++ b/src/NHibernate.Test/Async/Insertordering/InsertOrderingFixture.cs @@ -7,7 +7,6 @@ // //------------------------------------------------------------------------------ - using System; using System.Collections; using System.Collections.Generic; @@ -88,8 +87,8 @@ protected override void OnTearDown() [Test] public async System.Threading.Tasks.Task BatchOrderingAsync() { - using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { for (int i = 0; i < instancesPerEach; i++) { @@ -100,7 +99,7 @@ public async System.Threading.Tasks.Task BatchOrderingAsync() user.AddMembership(group); } StatsBatcher.Reset(); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } int expectedBatchesPerEntity = (instancesPerEach / batchSize) + ((instancesPerEach % batchSize) == 0 ? 0 : 1); @@ -676,25 +675,16 @@ public IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor #endregion } + public partial class InsertOrderingFixture : TestCase { - - - - - - - - - - - - - public partial class StatsBatcher : SqlClientBatchingBatcher { - - public override async System.Threading.Tasks.Task PrepareBatchCommandAsync(CommandType type, SqlString sql, SqlType[] parameterTypes, CancellationToken cancellationToken) + public override async System.Threading.Tasks.Task PrepareBatchCommandAsync( + CommandType type, + SqlString sql, + SqlType[] parameterTypes, + CancellationToken cancellationToken) { var result = await (base.PrepareBatchCommandAsync(type, sql, parameterTypes, cancellationToken)); @@ -703,7 +693,9 @@ public override async System.Threading.Tasks.Task PrepareBatchCommand return result; } - public override System.Threading.Tasks.Task AddToBatchAsync(IExpectation expectation, CancellationToken cancellationToken) + public override System.Threading.Tasks.Task AddToBatchAsync( + IExpectation expectation, + CancellationToken cancellationToken) { try { @@ -716,7 +708,9 @@ public override System.Threading.Tasks.Task AddToBatchAsync(IExpectation expecta } } - protected override System.Threading.Tasks.Task DoExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken) + protected override System.Threading.Tasks.Task DoExecuteBatchAsync( + DbCommand ps, + CancellationToken cancellationToken) { try { @@ -730,6 +724,5 @@ protected override System.Threading.Tasks.Task DoExecuteBatchAsync(DbCommand ps, } } - - } + } } diff --git a/src/NHibernate.Test/Async/Join/JoinTest.cs b/src/NHibernate.Test/Async/Join/JoinTest.cs index 9a9ee0185fe..855b74f3968 100644 --- a/src/NHibernate.Test/Async/Join/JoinTest.cs +++ b/src/NHibernate.Test/Async/Join/JoinTest.cs @@ -442,7 +442,6 @@ private Employee CreateEmployee(string name, string title) return result; } - [Test] public async Task TestSimpleInsertAndRetrieveEmployeeAsync() { @@ -585,4 +584,4 @@ public async Task PolymorphicGetByTypeofSuperclassAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/LazyGroup/LazyGroupFixture.cs b/src/NHibernate.Test/Async/LazyGroup/LazyGroupFixture.cs index 149e7b001f7..4c680979d39 100644 --- a/src/NHibernate.Test/Async/LazyGroup/LazyGroupFixture.cs +++ b/src/NHibernate.Test/Async/LazyGroup/LazyGroupFixture.cs @@ -99,14 +99,14 @@ public async Task TestGroupsAsync() [TestCase(true)] [TestCase(false)] - public async Task TestUpdateAsync(bool fetchBeforeUpdate, CancellationToken cancellationToken = default(CancellationToken)) + public async Task TestUpdateAsync(bool fetchBeforeUpdate) { Sfi.Statistics.Clear(); using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - var person = await (s.GetAsync(1, cancellationToken)); + var person = await (s.GetAsync(1)); if (fetchBeforeUpdate) { var nickName = person.NickName; @@ -114,7 +114,7 @@ public async Task TestGroupsAsync() person.NickName = "test"; - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(Sfi.Statistics.EntityUpdateCount, Is.EqualTo(1)); @@ -122,12 +122,12 @@ public async Task TestGroupsAsync() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - var person = await (s.GetAsync(1, cancellationToken)); + var person = await (s.GetAsync(1)); Assert.That(person.NickName, Is.EqualTo("test")); person.NickName = "NickName1"; // reset name - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } } @@ -224,7 +224,6 @@ private static Person GeneratePerson(int i) Image = new byte[i], NickName = $"NickName{i}" }; - } } } diff --git a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs index cb8dc0298de..e0252e2647b 100644 --- a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs @@ -21,7 +21,6 @@ namespace NHibernate.Test.LazyProperty { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class LazyPropertyFixtureAsync : TestCase { @@ -74,7 +73,6 @@ protected override void OnSetUp() }); tx.Commit(); } - } protected override void OnTearDown() @@ -152,25 +150,25 @@ public async Task CanSetValueForLazyPropertyAsync() [TestCase(false)] [TestCase(true)] - public async Task CanUpdateValueForLazyPropertyAsync(bool initializeAfterSet, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CanUpdateValueForLazyPropertyAsync(bool initializeAfterSet) { Book book; using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - book = await (s.GetAsync(1, cancellationToken)); + book = await (s.GetAsync(1)); book.ALotOfText = "update-text"; if (initializeAfterSet) { var image = book.Image; } - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } using (var s = OpenSession()) { - book = await (s.GetAsync(1, cancellationToken)); + book = await (s.GetAsync(1)); var text = book.ALotOfText; } @@ -182,14 +180,14 @@ public async Task CanSetValueForLazyPropertyAsync() [TestCase(false)] [TestCase(true)] - public async Task UpdateValueForLazyPropertyToSameValueAsync(bool initializeAfterSet, CancellationToken cancellationToken = default(CancellationToken)) + public async Task UpdateValueForLazyPropertyToSameValueAsync(bool initializeAfterSet) { Book book; string text; using (var s = OpenSession()) { - book = await (s.GetAsync(1, cancellationToken)); + book = await (s.GetAsync(1)); text = book.ALotOfText; } @@ -198,14 +196,14 @@ public async Task CanSetValueForLazyPropertyAsync() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - book = await (s.GetAsync(1, cancellationToken)); + book = await (s.GetAsync(1)); book.ALotOfText = text; if (initializeAfterSet) { var image = book.Image; } - await (tx.CommitAsync(cancellationToken)); + await (tx.CommitAsync()); } Assert.That(Sfi.Statistics.EntityUpdateCount, Is.EqualTo(initializeAfterSet ? 0 : 1)); @@ -215,7 +213,7 @@ public async Task CanSetValueForLazyPropertyAsync() using (var s = OpenSession()) { - book = await (s.GetAsync(1, cancellationToken)); + book = await (s.GetAsync(1)); text = book.ALotOfText; } @@ -326,7 +324,6 @@ public async Task CacheShouldNotContainLazyPropertiesAsync() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - book = await (s.CreateQuery("from Book b fetch all properties where b.Id = :id") .SetParameter("id", 1) .UniqueResultAsync()); @@ -339,7 +336,6 @@ public async Task CacheShouldNotContainLazyPropertiesAsync() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - book = await (s.GetAsync(1)); await (tx.CommitAsync()); } @@ -373,7 +369,6 @@ public async Task CanMergeTransientWithLazyPropertyInCollectionAsync() Assert.That(book, Is.Not.Null); Assert.That(book.Name, Is.EqualTo("some name two")); Assert.That(book.ALotOfText, Is.EqualTo("a lot of text two...")); - } using (var s = OpenSession()) using (var tx = s.BeginTransaction()) diff --git a/src/NHibernate.Test/Async/Legacy/ABCProxyTest.cs b/src/NHibernate.Test/Async/Legacy/ABCProxyTest.cs index 135f8876840..3804e300925 100644 --- a/src/NHibernate.Test/Async/Legacy/ABCProxyTest.cs +++ b/src/NHibernate.Test/Async/Legacy/ABCProxyTest.cs @@ -275,39 +275,6 @@ public async Task SubclassMapAsync() s.Close(); } - [Test, Ignore("ANTLR parser : Not supported ")] - public async Task OnoToOneComparingAsync() - { - A a = new A(); - E d1 = new E(); - C1 c = new C1(); - E d2 = new E(); - a.Forward = d1; - d1.Reverse = a; - c.Forward = d2; - d2.Reverse = c; - - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) - { - await (s.SaveAsync(a)); - await (s.SaveAsync(d2)); - await (t.CommitAsync()); - } - using (ISession s = OpenSession()) - { - IList l = await (s.CreateQuery("from E e, A a where e.Reverse = a.Forward and a = ?").SetEntity(0, a).ListAsync()); - Assert.AreEqual(1, l.Count); - } - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) - { - await (s.DeleteAsync("from A")); - await (s.DeleteAsync("from E")); - await (t.CommitAsync()); - } - } - [Test] public async Task OneToOneAsync() { diff --git a/src/NHibernate.Test/Async/Legacy/CriteriaTest.cs b/src/NHibernate.Test/Async/Legacy/CriteriaTest.cs index e5b79d45bda..db7b2370a4c 100644 --- a/src/NHibernate.Test/Async/Legacy/CriteriaTest.cs +++ b/src/NHibernate.Test/Async/Legacy/CriteriaTest.cs @@ -179,6 +179,5 @@ public async Task CriteriaLeftOuterJoinAsync() await (s.FlushAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/Legacy/FooBarTest.cs b/src/NHibernate.Test/Async/Legacy/FooBarTest.cs index 0b1211a7068..f7f7f1a2b8f 100644 --- a/src/NHibernate.Test/Async/Legacy/FooBarTest.cs +++ b/src/NHibernate.Test/Async/Legacy/FooBarTest.cs @@ -452,7 +452,6 @@ public async Task QueryAsync() list = await (s.CreateQuery("select foo, bar from Foo foo left outer join foo.TheFoo bar where foo = ?") .SetEntity(0, foo).ListAsync()); - object[] row1 = (object[]) list[0]; Assert.IsTrue(row1[0] == foo && row1[1] == foo2); @@ -611,7 +610,6 @@ public async Task QueryAsync() enumerable = await (s.CreateQuery( "select foo.Component.Name, elements(foo.Component.ImportantDates) from foo in class Foo where foo.TheFoo.id=?"). SetString(0, foo.TheFoo.Key).EnumerableAsync()); - int i = 0; foreach (object[] row in enumerable) @@ -1017,7 +1015,6 @@ public async Task NonlazyCollectionsAsync() } } - [Test] public async Task ReuseDeletedCollectionAsync() { @@ -1266,7 +1263,6 @@ public async Task FetchInitializedCollectionAsync() s.Close(); } - [Test] public async Task LateCollectionAddAsync() { @@ -1329,7 +1325,6 @@ public async Task UpdateAsync() s.Close(); } - [Test] public async Task ListRemoveAsync() { @@ -1475,7 +1470,6 @@ public async Task SortablesAsync() s.Close(); } - [Test] public async Task FetchListAsync() { @@ -1503,7 +1497,6 @@ public async Task FetchListAsync() s.Close(); } - [Test] public async Task BagOneToManyAsync() { @@ -1527,7 +1520,6 @@ public async Task BagOneToManyAsync() s.Close(); } - [Test] public async Task QueryLockModeAsync() { @@ -1602,7 +1594,6 @@ public async Task QueryLockModeAsync() s.Close(); } - [Test] public async Task ManyToManyBagAsync() { @@ -1629,7 +1620,6 @@ public async Task ManyToManyBagAsync() s.Close(); } - [Test] public async Task IdBagAsync() { @@ -1689,7 +1679,6 @@ public async Task IdBagAsync() s.Close(); } - [Test] public async Task ForceOuterJoinAsync() { @@ -1726,7 +1715,6 @@ public async Task ForceOuterJoinAsync() s.Close(); } - [Test] public async Task EmptyCollectionAsync() { @@ -1747,7 +1735,6 @@ public async Task EmptyCollectionAsync() s.Close(); } - [Test] public async Task OneToOneGeneratorAsync() { @@ -1768,7 +1755,6 @@ public async Task OneToOneGeneratorAsync() Assert.AreEqual(x.Id, y.Id); - s = OpenSession(); x = new X(); y = new Y(); @@ -1784,7 +1770,6 @@ public async Task OneToOneGeneratorAsync() Assert.AreEqual(x.Id, y.Id); - s = OpenSession(); x = new X(); y = new Y(); @@ -1816,7 +1801,6 @@ public async Task OneToOneGeneratorAsync() s.Close(); } - [Test] public async Task LimitAsync() { @@ -1846,7 +1830,6 @@ public async Task LimitAsync() s.Close(); } - [Test] public async Task CustomAsync() { @@ -1879,7 +1862,6 @@ public async Task CustomAsync() s.Close(); } - [Test] public async Task SaveAddDeleteAsync() { @@ -1895,7 +1877,6 @@ public async Task SaveAddDeleteAsync() s.Close(); } - [Test] public async Task NamedParamsAsync() { @@ -2089,7 +2070,6 @@ public async Task FindByCriteriaAsync() Assert.IsTrue(list.Count == 1 && list[0] == f); - list = await (s.CreateCriteria(typeof(Foo)) .SetMaxResults(5) .AddOrder(Order.Asc("Date")) @@ -2137,7 +2117,6 @@ public async Task FindByCriteriaAsync() s.Close(); } - [Test] public async Task AfterDeleteAsync() { @@ -2152,7 +2131,6 @@ public async Task AfterDeleteAsync() s.Close(); } - [Test] public async Task CollectionWhereAsync() { @@ -2191,7 +2169,6 @@ public async Task CollectionWhereAsync() s.Close(); } - [Test] public async Task ComponentParentAsync() { @@ -2223,7 +2200,6 @@ public async Task ComponentParentAsync() s.Close(); } - [Test] public async Task CollectionCacheAsync() { @@ -2246,7 +2222,6 @@ public async Task CollectionCacheAsync() s.Close(); } - [Test] //[Ignore("TimeZone Portions commented out - http://nhibernate.jira.com/browse/NH-88")] public async Task AssociationIdAsync() @@ -2292,7 +2267,6 @@ public async Task AssociationIdAsync() NHibernateUtil.String }; - //IList results = s.List( hqlString, values, types ); IQuery q = s.CreateQuery(hqlString); for (int i = 0; i < values.Length; i++) @@ -2319,7 +2293,6 @@ public async Task AssociationIdAsync() results = await (q.ListAsync()); Assert.AreEqual(1, results.Count); - hqlString = "from s in class Stuff where s.Foo.String is not null"; await (s.CreateQuery(hqlString).ListAsync()); @@ -2362,7 +2335,6 @@ public async Task AssociationIdAsync() } } - [Test] public async Task CascadeSaveAsync() { @@ -2389,7 +2361,6 @@ public async Task CascadeSaveAsync() s.Close(); } - [Test] public async Task CompositeKeyPathExpressionsAsync() { @@ -2420,7 +2391,6 @@ public async Task CompositeKeyPathExpressionsAsync() s.Close(); } - [Test] public async Task CollectionsInSelectAsync() { @@ -2457,7 +2427,6 @@ public async Task CollectionsInSelectAsync() Assert.AreEqual(1, r.Count); Assert.AreEqual(foos[1].Long, r.Amount); - list = await (s.CreateQuery( "select new Result( baz.Name, max(foo.Long), count(foo) ) from Baz baz join baz.FooArray foo group by baz.Name"). @@ -2557,7 +2526,6 @@ public async Task CollectionsInSelectAsync() s.Close(); } - [Test] public async Task NewFlushingAsync() { @@ -2640,7 +2608,6 @@ public async Task NewFlushingAsync() s.Close(); } - [Test] public async Task PersistCollectionsAsync() { @@ -2688,7 +2655,6 @@ public async Task PersistCollectionsAsync() list = await (s.CreateQuery( "select foo from foo in class NHibernate.DomainModel.Foo, baz in class NHibernate.DomainModel.Baz where foo in elements(baz.FooArray) and 3 = some elements(baz.IntArray) and 4 > all indices(baz.IntArray)") .ListAsync()); - Assert.AreEqual(2, list.Count, "collection.elements find"); } @@ -2871,7 +2837,7 @@ public async Task PersistCollectionsAsync() await (s.DeleteAsync(baz.TopGlarchez['H'])); var cmd = s.Connection.CreateCommand(); - s.Transaction.Enlist(cmd); + txn.Enlist(cmd); cmd.CommandText = "update " + Dialect.QuoteForTableName("glarchez") + " set baz_map_id=null where baz_map_index='a'"; int rows = await (cmd.ExecuteNonQueryAsync()); Assert.AreEqual(1, rows); @@ -2916,7 +2882,6 @@ public async Task PersistCollectionsAsync() s.Close(); } - [Test] public async Task SaveFlushAsync() { @@ -2936,7 +2901,6 @@ public async Task SaveFlushAsync() s.Close(); } - [Test] public async Task CreateUpdateAsync() { @@ -2970,7 +2934,6 @@ public async Task CreateUpdateAsync() s.Close(); } - [Test] public async Task UpdateCollectionsAsync() { @@ -3038,7 +3001,6 @@ public async Task UpdateCollectionsAsync() s.Close(); } - [Test] public async Task LoadAsync() { @@ -3067,7 +3029,6 @@ public async Task LoadAsync() s.Close(); } - [Test] public async Task CreateAsync() { @@ -3087,7 +3048,6 @@ public async Task CreateAsync() s.Close(); } - [Test] public async Task CallbackAsync() { @@ -3125,7 +3085,6 @@ public async Task CallbackAsync() s.Close(); } - [Test] public async Task PolymorphismAsync() { @@ -3145,7 +3104,6 @@ public async Task PolymorphismAsync() s.Close(); } - [Test] public async Task RemoveContainsAsync() { @@ -3168,7 +3126,6 @@ public async Task RemoveContainsAsync() s.Close(); } - [Test] public async Task CollectionOfSelfAsync() { @@ -3209,7 +3166,6 @@ public async Task CollectionOfSelfAsync() s.Close(); } - [Test] public async Task FindAsync() { @@ -3284,8 +3240,7 @@ public async Task FindAsync() await (txn.CommitAsync()); s.Close(); } - - + [Test] public async Task DeleteRecursiveAsync() { @@ -3302,8 +3257,7 @@ public async Task DeleteRecursiveAsync() await (s.FlushAsync()); s.Close(); } - - + [Test] public async Task ReachabilityAsync() { @@ -3426,8 +3380,7 @@ public async Task ReachabilityAsync() await (s.FlushAsync()); s.Close(); } - - + [Test] public async Task PersistentLifecycleAsync() { @@ -3453,8 +3406,7 @@ public async Task PersistentLifecycleAsync() await (s.FlushAsync()); s.Close(); } - - + [Test] public async Task EnumerableAsync() { @@ -3791,7 +3743,6 @@ public async Task VersionedCollectionsAsync() s.Close(); } - [Test] public async Task RecursiveLoadAsync() { @@ -3994,7 +3945,6 @@ public async Task MultiColumnQueriesAsync() s.Close(); } - [Test] public async Task DeleteTransientAsync() { @@ -4219,7 +4169,6 @@ public async Task UpdateFromTransientAsync() s.Close(); } - [Test] public async Task ArraysOfTimesAsync() { @@ -4297,7 +4246,6 @@ public async Task ComponentsAsync() s.Close(); } - [Test] public async Task EnumAsync() { @@ -4363,8 +4311,7 @@ public async Task EnumAsync() await (s.FlushAsync()); s.Close(); } - - + [Test] public async Task NoForeignKeyViolationsAsync() { @@ -4933,7 +4880,6 @@ public async Task AutoFlushCollectionsAsync() e = (await (s.CreateQuery("select elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").EnumerableAsync())). GetEnumerator(); - bool found = false; while (e.MoveNext()) @@ -5139,7 +5085,6 @@ public async Task LoadAfterDeleteAsync() s.Close(); } - [Test] public async Task ObjectTypeAsync() { @@ -5166,7 +5111,6 @@ public async Task ObjectTypeAsync() } } - [Test] public async Task AnyAsync() { @@ -5477,6 +5421,20 @@ public async Task PSCacheAsync() } } + // NH-2329 (GH-1218) + [Test] + public async Task JoinOverJoinAsync() + { + using (var s = OpenSession()) + { + await (s.CreateCriteria(typeof(Stuff), "s1") + .CreateAlias("s1.MoreStuff", "m1") + .CreateAlias("m1.Stuffs", "s2") + .Add(Expression.Eq("s2.Id", 2L)) + .ListAsync()); + } + } + #region NHibernate specific tests [Test] diff --git a/src/NHibernate.Test/Async/Legacy/FumTest.cs b/src/NHibernate.Test/Async/Legacy/FumTest.cs index a63039aed4f..0c234e2b96f 100644 --- a/src/NHibernate.Test/Async/Legacy/FumTest.cs +++ b/src/NHibernate.Test/Async/Legacy/FumTest.cs @@ -547,7 +547,6 @@ public async Task CompositeIDsAsync() s.Close(); } - [Test] public async Task KeyManyToOneAsync() { @@ -599,7 +598,6 @@ public async Task KeyManyToOneAsync() s.Close(); } - [Test] public async Task CompositeKeyPathExpressionsAsync() { diff --git a/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs b/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs index d8b599db94a..f61cbeb88ff 100644 --- a/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs +++ b/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs @@ -40,7 +40,6 @@ public async Task FetchManyToOneAsync() s.Close(); } - [Test] public async Task JoinsAsync() { diff --git a/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs b/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs index 44a9c7d1556..fe3c7128450 100644 --- a/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs +++ b/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs @@ -11,6 +11,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using log4net; using NHibernate.Dialect; using NHibernate.Dialect.Function; @@ -115,19 +116,30 @@ public async Task DialectSQLFunctionsAsync() s.Close(); } + //NH-3893 (GH-1349) left and right functions are broken [Test] - [Ignore("NH-3893 is not fixed")] public async Task LeftAndRightAsync() { - // As of NH-3893, left and right functions are broken. Seemed confused with join keyword, and not - // supported on Hibernate side. + //left or right functions are supported by most dialects but not registered. + AssumeFunctionSupported("left"); + AssumeFunctionSupported("right"); + using (var s = OpenSession()) using (var t = s.BeginTransaction()) { + Simple simple = new Simple(); + simple.Name = "Simple Dialect Function Test"; + simple.Address = "Simple Address"; + simple.Pay = 45.8f; + simple.Count = 2; + await (s.SaveAsync(simple, 10L)); + var rset = await (s.CreateQuery("select left('abc', 2), right('abc', 2) from s in class Simple").ListAsync()); var row = rset[0]; Assert.AreEqual("ab", row[0], "Left function is broken."); Assert.AreEqual("bc", row[1], "Right function is broken."); + + await (s.DeleteAsync(simple)); await (t.CommitAsync()); } } @@ -148,6 +160,112 @@ public async Task SetPropertiesAsync() s.Close(); } + [Test] + public async Task SetParametersWithDictionaryAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + await (s.SaveAsync(simple, 10L)); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and s.Count = :Count"); + var parameters = new Dictionary + { + { nameof(simple.Name), simple.Name }, + { nameof(simple.Count), simple.Count }, + }; + q.SetProperties(parameters); + var results = await (q.ListAsync()); + Assert.That(results, Has.One.EqualTo(simple)); + await (s.DeleteAsync(simple)); + await (t.CommitAsync()); + } + } + + [Test] + public async Task SetParametersWithHashtableAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + await (s.SaveAsync(simple, 10L)); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and (s.Address = :Address or :Address is null and s.Address is null)"); + var parameters = new Hashtable + { + { nameof(simple.Name), simple.Name }, + { nameof(simple.Address), simple.Address }, + }; + q.SetProperties(parameters); + var results = await (q.ListAsync()); + Assert.That(results, Has.One.EqualTo(simple)); + await (s.DeleteAsync(simple)); + await (t.CommitAsync()); + } + } + + [Test] + public async Task SetParametersWithDynamicAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + await (s.SaveAsync(simple, 10L)); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and s.Count = :Count"); + dynamic parameters = new ExpandoObject(); + parameters.Name = simple.Name; + parameters.Count = simple.Count; + q.SetProperties(parameters); + var results = await (q.ListAsync()); + Assert.That(results, Has.One.EqualTo(simple)); + await (s.DeleteAsync(simple)); + await (t.CommitAsync()); + } + } + + [Test] + public async Task SetNullParameterWithDictionaryAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + await (s.SaveAsync(simple, 10L)); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and (s.Address = :Address or :Address is null and s.Address is null)"); + var parameters = new Dictionary + { + { nameof(simple.Name), simple.Name }, + { nameof(simple.Address), null }, + }; + q.SetProperties(parameters); + var results = await (q.ListAsync()); + Assert.That(results, Has.One.EqualTo(simple)); + await (s.DeleteAsync(simple)); + await (t.CommitAsync()); + } + } + + [Test] + public async Task SetParameterListWithDictionaryAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + await (s.SaveAsync(simple, 10L)); + var q = s.CreateQuery("from s in class Simple where s.Name in (:Name)"); + var parameters = new Dictionary + { + { nameof(simple.Name), new [] {simple.Name} } + }; + q.SetProperties(parameters); + var results = await (q.ListAsync()); + Assert.That(results, Has.One.EqualTo(simple)); + await (s.DeleteAsync(simple)); + await (t.CommitAsync()); + } + } [Test] public async Task BrokenAsync() @@ -211,7 +329,6 @@ public async Task NothingToUpdateAsync() s.Close(); } - [Test] public async Task CachedQueryAsync() { diff --git a/src/NHibernate.Test/Async/Legacy/SQLLoaderTest.cs b/src/NHibernate.Test/Async/Legacy/SQLLoaderTest.cs index ac6cef6520a..3d9119be986 100644 --- a/src/NHibernate.Test/Async/Legacy/SQLLoaderTest.cs +++ b/src/NHibernate.Test/Async/Legacy/SQLLoaderTest.cs @@ -11,6 +11,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using NHibernate.Dialect; using NHibernate.DomainModel; using NUnit.Framework; @@ -152,6 +153,64 @@ public async Task FindBySQLPropertiesAsync() session.Close(); } + [Test] + public async Task FindBySQLDictionaryAsync() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var s = new Category { Name = nextLong.ToString() }; + nextLong++; + await (session.SaveAsync(s)); + + s = new Category { Name = "WannaBeFound" }; + await (session.FlushAsync()); + + var query = + session.CreateSQLQuery("select {category.*} from Category {category} where {category}.Name = :Name") + .AddEntity("category", typeof(Category)); + var parameters = new Dictionary + { + { nameof(s.Name), s.Name } + }; + query.SetProperties(parameters); + var results = await (query.ListAsync()); + Assert.That(results, Is.Empty); + + await (session.DeleteAsync("from Category")); + await (tran.CommitAsync()); + } + } + + [Test] + public async Task FindBySQLDynamicAsync() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var s = new Category { Name = nextLong.ToString() }; + nextLong++; + await (session.SaveAsync(s)); + + s = new Category { Name = "WannaBeFound" }; + await (session.FlushAsync()); + + var query = + session.CreateSQLQuery("select {category.*} from Category {category} where {category}.Name = :Name") + .AddEntity("category", typeof(Category)); + dynamic parameters = new ExpandoObject(); + parameters.Name = s.Name; + // dynamic does not work on inherited interface method calls. https://stackoverflow.com/q/3071634 + IQuery q = query; + q.SetProperties(parameters); + var results = await (query.ListAsync()); + Assert.That(results, Is.Empty); + + await (session.DeleteAsync("from Category")); + await (tran.CommitAsync()); + } + } + [Test] public async Task FindBySQLAssociatedObjectAsync() { @@ -291,57 +350,6 @@ public async Task FindBySQLParametersAsync() s.Close(); } - [Test] - [Ignore("Escaping not implemented. Need to test with ODBC/OLEDB when implemented.")] - public async Task EscapedODBCAsync() - { - if (Dialect is MySQLDialect || Dialect is PostgreSQLDialect) return; - - ISession session = OpenSession(); - - A savedA = new A(); - savedA.Name = "Max"; - await (session.SaveAsync(savedA)); - - B savedB = new B(); - await (session.SaveAsync(savedB)); - await (session.FlushAsync()); - - session.Close(); - - session = OpenSession(); - - IQuery query; - - query = - session.CreateSQLQuery( - "select identifier_column as {a.id}, clazz_discriminata as {a.class}, count_ as {a.Count}, name as {a.Name} from A where {fn ucase(Name)} like {fn ucase('max')}") - .AddEntity("a", typeof(A)); - - // NH: Replaced the whole if by the line above - /* - if( dialect is Dialect.TimesTenDialect) - { - // TimesTen does not permit general expressions (like UPPER) in the second part of a LIKE expression, - // so we execute a similar test - query = session.CreateSQLQuery("select identifier_column as {a.id}, clazz_discriminata as {a.class}, count_ as {a.Count}, name as {a.Name} from A where {fn ucase(name)} like 'MAX'", "a", typeof( A )); - } - else - { - query = session.CreateSQLQuery("select identifier_column as {a.id}, clazz_discriminata as {a.class}, count_ as {a.Count}, name as {a.Name} from A where {fn ucase(name)} like {fn ucase('max')}", "a", typeof( A )); - } - */ - - IList list = await (query.ListAsync()); - - Assert.IsNotNull(list); - Assert.AreEqual(1, list.Count); - - await (session.DeleteAsync("from A")); - await (session.FlushAsync()); - session.Close(); - } - [Test] public async Task DoubleAliasingAsync() { @@ -421,7 +429,6 @@ public async Task EmbeddedCompositePropertiesAsync() Assert.IsTrue(list.Count == 1); - session.Clear(); query = diff --git a/src/NHibernate.Test/Async/Legacy/SimpleTest.cs b/src/NHibernate.Test/Async/Legacy/SimpleTest.cs index 2d1bc94c3a0..a810dbadf31 100644 --- a/src/NHibernate.Test/Async/Legacy/SimpleTest.cs +++ b/src/NHibernate.Test/Async/Legacy/SimpleTest.cs @@ -111,7 +111,6 @@ public async Task TestCRUDAsync() s4.Close(); } - [Test] public async Task SetPropertiesOnQueryAsync() { @@ -149,4 +148,4 @@ public async Task SetPropertiesOnQueryAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Linq/BooleanMethodExtensionExample.cs b/src/NHibernate.Test/Async/Linq/BooleanMethodExtensionExample.cs deleted file mode 100644 index f08ca9aebe7..00000000000 --- a/src/NHibernate.Test/Async/Linq/BooleanMethodExtensionExample.cs +++ /dev/null @@ -1,55 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by AsyncGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using NHibernate.Cfg; -using NHibernate.Hql.Ast; -using NHibernate.Linq.Functions; -using NHibernate.Linq.Visitors; -using NHibernate.DomainModel.Northwind.Entities; -using NUnit.Framework; -using NHibernate.Util; -using NHibernate.Linq; - -namespace NHibernate.Test.Linq -{ - using System.Threading.Tasks; - - [TestFixture] - public class BooleanMethodExtensionExampleAsync : LinqTestCase - { - public class MyLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry - { - public MyLinqToHqlGeneratorsRegistry() - { - RegisterGenerator(ReflectHelper.GetMethodDefinition(() => BooleanLinqExtensions.FreeText(null, null)), - new FreetextGenerator()); - } - } - - protected override void Configure(Configuration configuration) - { - configuration.LinqToHqlGeneratorsRegistry(); - } - - [Test, Ignore("It work only with full-text indexes enabled.")] - public async Task CanUseMyCustomExtensionAsync() - { - List contacts = await ((from c in db.Customers where c.ContactName.FreeText("Thomas") select c).ToListAsync()); - Assert.That(contacts.Count, Is.GreaterThan(0)); - Assert.That(contacts.Select(c => c.ContactName).All(c => c.Contains("Thomas")), Is.True); - } - } -} \ No newline at end of file diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs index 7e9eaa14526..14e4fdf91ef 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/AverageTests.cs @@ -22,32 +22,10 @@ public class AverageTestsAsync : LinqTestCase [Test] public async Task CanGetAverageOfIntegersAsDoubleAsync() { - if (Dialect is Oracle8iDialect) - { - // The point of this test is to verify that LINQ's Average over an - // integer columns yields a non-integer result, even on databases - // where the corresponding avg() will yield a return type equal to - // the input type. This means the LINQ provider must generate HQL - // that cast the input to double inside the call to avg(). This works - // fine on most databases, but Oracle causes trouble. - // - // The dialect maps double to "DOUBLE PRECISION" on Oracle, which - // on Oracle has larger precision than C#'s double. When such - // values are returned, ODP.NET will convert it to .Net decimal, which - // has lower precision and thus causes an overflow exception. - // - // Some argue that this is a flaw in ODP.NET, others have created - // local dialects that use e.g. BINARY_DOUBLE instead, which more - // closely matches C#'s IEEE 745 double, see e.g. HHH-1961 and - // serveral blogs. - - Assert.Ignore("Not supported on Oracle due to casting/overflow issues."); - } - //NH-2429 var average = await (db.Products.AverageAsync(x => x.UnitsOnOrder)); Assert.AreEqual(average, 10.129870d, 0.000001d); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/CastTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/CastTests.cs index 94f958ee645..0ddc287bd9d 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/CastTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/CastTests.cs @@ -57,14 +57,5 @@ public void OrderByAfterCastAsync() var query = session.Query().Cast().OrderBy(a=> a.BodyWeight); Assert.That(() => query.ToListAsync(), Throws.Nothing); } - - [Test, Ignore("Not fixed yet. The method OfType does not work as expected.")] - public void CastDowncastUsingOfTypeAsync() - { - var query = session.Query().OfType().Cast(); - // the list contains at least one Cat then should Throws - // Do not use bare Throws.Exception due to https://github.com/nunit/nunit/issues/1899 - Assert.That(() => query.ToListAsync(), Throws.InstanceOf()); - } } } diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/CountTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/CountTests.cs index 8dfdc304aae..2120bd127ad 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/CountTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/CountTests.cs @@ -11,6 +11,7 @@ using System; using System.Linq; using NHibernate.Cfg; +using NHibernate.Dialect; using NUnit.Framework; using NHibernate.Linq; @@ -39,6 +40,21 @@ public async Task CountDistinctProperty_ReturnsNumberOfDistinctEntriesForThatPro Assert.That(result, Is.EqualTo(387)); } + //NH-3249 (GH-1285) + [Test] + public async Task CountDistinctFunc_ReturnsNumberOfDistinctEntriesForThatFuncAsync() + { + if (!TestDialect.SupportsCountDistinct) + Assert.Ignore("Dialect does not support count distinct"); + + var result = await (db.Orders + .Select(x => x.ShippingDate.Value.Date) + .Distinct() + .CountAsync()); + + Assert.That(result, Is.EqualTo(387)); + } + [Test] public async Task CountProperty_ReturnsNumberOfNonNullEntriesForThatPropertyAsync() { @@ -110,5 +126,50 @@ into temp Assert.That(result.Count, Is.EqualTo(77)); } + + [Test] + public async Task CheckSqlFunctionNameLongCountAsync() + { + var name = Dialect is MsSql2000Dialect ? "count_big" : "count"; + using (var sqlLog = new SqlLogSpy()) + { + var result = await (db.Orders.LongCountAsync()); + Assert.That(result, Is.EqualTo(830)); + + var log = sqlLog.GetWholeLog(); + Assert.That(log, Does.Contain($"{name}(")); + } + } + + [Test] + public async Task CheckSqlFunctionNameForCountAsync() + { + using (var sqlLog = new SqlLogSpy()) + { + var result = await (db.Orders.CountAsync()); + Assert.That(result, Is.EqualTo(830)); + + var log = sqlLog.GetWholeLog(); + Assert.That(log, Does.Contain("count(")); + } + } + + [Test] + public async Task CheckMssqlCountCastAsync() + { + if (!(Dialect is MsSql2000Dialect)) + { + Assert.Ignore(); + } + + using (var sqlLog = new SqlLogSpy()) + { + var result = await (db.Orders.CountAsync()); + Assert.That(result, Is.EqualTo(830)); + + var log = sqlLog.GetWholeLog(); + Assert.That(log, Does.Not.Contain("cast(")); + } + } } } diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/GroupByTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/GroupByTests.cs index f405fda0f7c..a672dd18381 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/GroupByTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/GroupByTests.cs @@ -139,7 +139,6 @@ public async Task SingleKeyPropertyGroupAndOrderByProjectedCountAsync() AssertOrderedBy.Descending(orderCounts, oc => oc.OrderCount); } - [Test] public async Task SingleKeyPropertyGroupAndOrderByCountBeforeProjectionAsync() { @@ -902,7 +901,6 @@ public override int GetHashCode() } } - [Test(Description = "NH-3446"), KnownBug("NH-3446", "NHibernate.HibernateException")] public async Task GroupByOrderByKeySelectToClassAsync() { @@ -912,6 +910,48 @@ public async Task GroupByOrderByKeySelectToClassAsync() .ToListAsync()); } + [Test] + public async Task SelectArrayIndexBeforeGroupByAsync() + { + var result = db.Orders + .SelectMany(o => o.OrderLines.Select(c => c.Id).DefaultIfEmpty().Select(c => new object[] {c, o})) + .GroupBy(g => g[0], g => (Order) g[1]) + .Select(g => new[] {g.Key, g.Count(), g.Max(x => x.OrderDate)}); + + Assert.True(await (result.AnyAsync())); + } + + [Test] + public async Task SelectMemberInitBeforeGroupByAsync() + { + var result = await (db.Orders + .Select(o => new OrderGroup {OrderId = o.OrderId, OrderDate = o.OrderDate}) + .GroupBy(o => o.OrderId) + .Select(g => new OrderGroup {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)}) + .ToListAsync()); + + Assert.True(result.Any()); + } + + [Test] + public async Task SelectNewBeforeGroupByAsync() + { + var result = await (db.Orders + .Select(o => new {o.OrderId, o.OrderDate}) + .GroupBy(o => o.OrderId) + .Select(g => new {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)}) + .ToListAsync()); + + Assert.True(result.Any()); + } + + private class OrderGroup + { + public int OrderId { get; set; } + + public DateTime? OrderDate { get; set; } + } + private class GroupInfo { public object Key { get; set; } diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs new file mode 100644 index 00000000000..9fda2e873c9 --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/ByMethod/JoinTests.cs @@ -0,0 +1,160 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Engine.Query; +using NHibernate.Linq; +using NHibernate.Util; +using NSubstitute; +using NUnit.Framework; + +namespace NHibernate.Test.Linq.ByMethod +{ + using System.Threading.Tasks; + [TestFixture] + public class JoinTestsAsync : LinqTestCase + { + [Test] + public async Task MultipleLinqJoinsWithSameProjectionNamesAsync() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = await (db.Orders + .Join(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 }) + .Select(x => new { First = x.order, Second = x.order1 }) + .Join(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 }) + .Select(x => new { FirstId = x.order.First.OrderId, SecondId = x.order.Second.OrderId, ThirdId = x.order1.OrderId }) + .ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(828)); + Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + } + } + + [Test] + public async Task MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinAsync() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = await (db.Orders + .GroupJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 }) + .SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new { First = x.order, Second = order1 }) + .GroupJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 }) + .SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new + { + FirstId = x.order.First.OrderId, + SecondId = (int?) x.order.Second.OrderId, + ThirdId = (int?) order1.OrderId + }) + .ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(830)); + Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue) + .All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public async Task MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinExtensionMethodAsync() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = await (db.Orders + .LeftJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 }) + .Select(x => new { First = x.order, Second = x.order1 }) + .LeftJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 }) + .Select(x => new + { + FirstId = x.order.First.OrderId, + SecondId = (int?) x.order.Second.OrderId, + ThirdId = (int?) x.order1.OrderId + }) + .ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(830)); + Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue) + .All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public async Task LeftJoinExtensionMethodWithMultipleKeyPropertiesAsync() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = await (db.Orders + .LeftJoin( + db.Orders, + x => new {x.OrderId, x.Customer.CustomerId}, + x => new {x.OrderId, x.Customer.CustomerId}, + (order, order1) => new {order, order1}) + .Select(x => new {FirstId = x.order.OrderId, SecondId = x.order1.OrderId}) + .ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(830)); + Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task CrossJoinWithPredicateInWhereStatementAsync(bool useCrossJoin) + { + if (useCrossJoin && !Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + using (var substitute = SubstituteDialect()) + using (var sqlSpy = new SqlLogSpy()) + { + ClearQueryPlanCache(); + substitute.Value.SupportsCrossJoin.Returns(useCrossJoin); + + var result = await ((from o in db.Orders + from o2 in db.Orders.Where(x => x.Freight > 50) + where (o.OrderId == o2.OrderId + 1) || (o.OrderId == o2.OrderId - 1) + select new { o.OrderId, OrderId2 = o2.OrderId }).ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(result.Count, Is.EqualTo(720)); + Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join")); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(useCrossJoin ? 0 : 1)); + } + } + + [Test] + public async Task CanJoinOnEntityWithSubclassesAsync() + { + var result = await ((from o in db.Animals + from o2 in db.Animals.Where(x => x.BodyWeight > 50) + select new {o, o2}).Take(1).ToListAsync()); + } + + [Test(Description = "GH-2580")] + public async Task CanInnerJoinOnSubclassWithBaseTableReferenceInOnClauseAsync() + { + var result = await ((from o in db.Animals + join o2 in db.Mammals on o.BodyWeight equals o2.BodyWeight + select new { o, o2 }).Take(1).ToListAsync()); + } + } +} diff --git a/src/NHibernate.Test/Async/Linq/ConstantTest.cs b/src/NHibernate.Test/Async/Linq/ConstantTest.cs index 718ac40fdc2..9c2558a6902 100644 --- a/src/NHibernate.Test/Async/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Async/Linq/ConstantTest.cs @@ -11,33 +11,22 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using NHibernate.Criterion; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Engine.Query; +using NHibernate.Linq; using NHibernate.Linq.Visitors; using NHibernate.Util; using NUnit.Framework; -using NHibernate.Linq; namespace NHibernate.Test.Linq { using System.Threading.Tasks; + using System.Threading; // Mainly adapted from tests contributed by Nicola Tuveri on NH-2500 (NH-2500.patch file) [TestFixture] public class ConstantTestAsync : LinqTestCase { - [Test] - [Ignore("Linq query not supported yet")] - public async Task ConstantNonCachedAsync() - { - var c1 = await ((from c in db.Customers - select "customer1").FirstAsync()); - - var c2 = await ((from c in db.Customers - select "customer2").FirstAsync()); - - Assert.That(c1, Is.EqualTo("customer1")); - Assert.That(c2, Is.EqualTo("customer2")); - } [Test] public async Task ConstantNonCachedInAnonymousNewExpressionAsync() @@ -130,6 +119,29 @@ public async Task ConstantNonCachedInMemberInitExpressionAsync() Assert.That(s2, Has.All.Property("Name").EqualTo("shipper2"), "s2 Names"); } + [Test] + public async Task ConstantNonCachedInMemberInitExpressionWithConditionAsync() + { + var shipper1 = await (GetShipperAsync(1)); + var shipper2 = await (GetShipperAsync(2)); + + Assert.That(shipper1.Number, Is.EqualTo(1)); + Assert.That(shipper2.Number, Is.EqualTo(2)); + } + + private Task GetShipperAsync(int id, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + return db.Shippers.Where(o => o.ShipperId == id) + .Select(o => new ShipperDto {Number = id, CompanyName = o.CompanyName}).SingleAsync(cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + [Test] public async Task ConstantInNewArrayExpressionAsync() { @@ -250,6 +262,66 @@ public async Task PlansAreCachedAsync() } } + [Test] + public async Task DmlPlansAreCachedAsync() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + using (session.BeginTransaction()) + { + await (db.Customers.Where(c => c.CustomerId == "UNKNOWN").UpdateAsync(x => new Customer {CompanyName = "Constant1"})); + await (db.Customers.Where(c => c.CustomerId == "ALFKI").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); + await (db.Customers.Where(c => c.CustomerId == "UNKNOWN").UpdateAsync(x => new Customer {ContactName = "Constant1"})); + Assert.That( + cache, + Has.Count.EqualTo(3), + "Query plans should be cached."); + + using (var spy = new LogSpy(queryPlanCacheType)) + { + //Queries below should hit plan cache. + using (var sqlSpy = new SqlLogSpy()) + { + await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); + await (db.Customers.Where(c => c.CustomerId == "UNKNOWN").UpdateAsync(x => new Customer {CompanyName = "Constant2"})); + await (db.Customers.Where(c => c.CustomerId == "UNKNOWN").UpdateAsync(x => new Customer {ContactName = "Constant2"})); + + var sqlEvents = sqlSpy.Appender.GetEvents(); + Assert.That( + sqlEvents[0].RenderedMessage, + Does.Contain("ANATR").And.Not.Contain("UNKNOWN").And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + Assert.That( + sqlEvents[1].RenderedMessage, + Does.Contain("UNKNOWN").And.Contain("Constant2").And.Contain("CompanyName").IgnoreCase + .And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + Assert.That( + sqlEvents[2].RenderedMessage, + Does.Contain("UNKNOWN").And.Contain("Constant2").And.Contain("ContactName").IgnoreCase + .And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + } + + Assert.That(cache, Has.Count.EqualTo(3), "Additional queries should not cause a plan to be cached."); + Assert.That( + spy.GetWholeLog(), + Does + .Contain("located HQL query plan in cache") + .And.Not.Contain("unable to locate HQL query plan in cache")); + + await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {ContactName = x.ContactName})); + Assert.That(cache, Has.Count.EqualTo(4), "Query should be cached"); + } + } + } + [Test] public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync() { @@ -269,5 +341,62 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync() Has.Count.EqualTo(0), "Query plan should not be cached."); } + + [Test] + public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQueryAsync() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + var ids = new[] {"ANATR", "UNKNOWN"}.ToList(); + await (db.Customers.Where(x => ids.Contains(x.CustomerId)).Select( + c => new {c.CustomerId, c.ContactName, Constant = 1}).FirstAsync()); + + Assert.That( + cache, + Has.Count.EqualTo(0), + "Query plan should not be cached."); + } + + //GH-2298 - Different Update queries - same query cache plan + [Test] + public async Task DmlPlansForExpandedQueryAsync() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + using (session.BeginTransaction()) + { + var list = new[] {"UNKNOWN", "UNKNOWN2"}.ToList(); + await (db.Customers.Where(x => list.Contains(x.CustomerId)).UpdateAsync( + x => new Customer + { + CompanyName = "Constant1" + })); + + await (db.Customers.Where(x => list.Contains(x.CustomerId)) + .UpdateAsync( + x => new Customer + { + ContactName = "Constant1" + })); + + Assert.That( + cache.Count, + //2 original queries + 2 expanded queries are expected in cache + Is.EqualTo(0).Or.EqualTo(4), + "Query plans should either be cached separately or not cached at all."); + } + } } } diff --git a/src/NHibernate.Test/Async/Linq/CustomExpressionTransformerRegistrarTests.cs b/src/NHibernate.Test/Async/Linq/CustomExpressionTransformerRegistrarTests.cs new file mode 100644 index 00000000000..522fa988966 --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/CustomExpressionTransformerRegistrarTests.cs @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Linq; +using NHibernate.Linq.Visitors; +using NHibernate.Util; +using NUnit.Framework; +using Remotion.Linq.Parsing.ExpressionVisitors.Transformation; + +namespace NHibernate.Test.Linq +{ + using System.Threading.Tasks; + [TestFixture] + public class CustomPreTransformRegistrarTestsAsync : LinqTestCase + { + protected override void Configure(Cfg.Configuration configuration) + { + configuration.Properties[Cfg.Environment.PreTransformerRegistrar] = typeof(PreTransformerRegistrar).AssemblyQualifiedName; + } + + [Test] + public async Task RewriteLikeAsync() + { + // This example shows how to use the pre-transformer registrar to rewrite the + // query so that StartsWith, EndsWith and Contains methods will generate the same sql. + var queryPlanCache = GetQueryPlanCache(); + queryPlanCache.Clear(); + await (db.Customers.Where(o => o.ContactName.StartsWith("A")).ToListAsync()); + await (db.Customers.Where(o => o.ContactName.EndsWith("A")).ToListAsync()); + await (db.Customers.Where(o => o.ContactName.Contains("A")).ToListAsync()); + + Assert.That(queryPlanCache.Count, Is.EqualTo(1)); + } + + [Serializable] + public class PreTransformerRegistrar : IExpressionTransformerRegistrar + { + public void Register(ExpressionTransformerRegistry expressionTransformerRegistry) + { + expressionTransformerRegistry.Register(new LikeTransformer()); + } + } + + private class LikeTransformer : IExpressionTransformer + { + private static readonly MethodInfo Like = ReflectHelper.GetMethodDefinition(() => SqlMethods.Like(null, null)); + private static readonly MethodInfo EndsWith = ReflectHelper.GetMethodDefinition(x => x.EndsWith(null)); + private static readonly MethodInfo StartsWith = ReflectHelper.GetMethodDefinition(x => x.StartsWith(null)); + private static readonly MethodInfo Contains = ReflectHelper.GetMethodDefinition(x => x.Contains(null)); + private static readonly Dictionary> ValueTransformers = + new Dictionary> + { + {StartsWith, s => $"{s}%"}, + {EndsWith, s => $"%{s}"}, + {Contains, s => $"%{s}%"}, + }; + + public Expression Transform(MethodCallExpression expression) + { + if (ValueTransformers.TryGetValue(expression.Method, out var valueTransformer) && + expression.Arguments[0] is ConstantExpression constantExpression) + { + return Expression.Call( + Like, + expression.Object, + Expression.Constant(valueTransformer(constantExpression.Value)) + ); + } + + return expression; + } + + public ExpressionType[] SupportedExpressionTypes { get; } = {ExpressionType.Call}; + } + } +} diff --git a/src/NHibernate.Test/Async/Linq/EagerLoadTests.cs b/src/NHibernate.Test/Async/Linq/EagerLoadTests.cs index 47095f66ba7..36f98691671 100644 --- a/src/NHibernate.Test/Async/Linq/EagerLoadTests.cs +++ b/src/NHibernate.Test/Async/Linq/EagerLoadTests.cs @@ -34,6 +34,48 @@ public async Task CanSelectAndFetchAsync() Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].Orders)); } + [Test] + public async Task CanSelectAndFetchManyAsync() + { + var result = await (db.OrderLines + .Select(o => o.Product) + .FetchMany(o => o.OrderLines) + .ToListAsync()); + + session.Close(); + + Assert.IsNotEmpty(result); + Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].OrderLines)); + } + + [Test] + public async Task CanSelectManyAndFetchAsync() + { + var result = await (db.Orders + .SelectMany(o => o.OrderLines) + .Fetch(o => o.Product) + .ToListAsync()); + + session.Close(); + + Assert.IsNotEmpty(result); + Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].Product)); + } + + [Test] + public async Task CanSelectManyAndFetchManyAsync() + { + var result = await (db.Employees + .SelectMany(o => o.Orders) + .FetchMany(o => o.OrderLines) + .ToListAsync()); + + session.Close(); + + Assert.IsNotEmpty(result); + Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].OrderLines)); + } + [Test] public async Task CanSelectAndFetchHqlAsync() { @@ -339,5 +381,231 @@ public async Task WhereAfterFetchAndSingleOrDefaultAsync() Assert.IsTrue(NHibernateUtil.IsInitialized(order.Shipper)); } + + [Test] + public async Task WhereReuseJoinsAsync() + { + OrderLine orderLine; + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .Fetch(o => o.Order) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.False); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.OrderLines.Any(l => l.Product.Name == "Tofu")) + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + sql = sql.Substring(0, sql.IndexOf("where")); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + using (var logSpy = new SqlLogSpy()) + { + (await (db.Employees + .Where(o => o.Superior.Superior.Superior.FirstName != null) + .Fetch(o => o.Superior) + .ToListAsync())) + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, ","), Is.EqualTo(31), "Only the first level should be fetched."); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(3)); + } + + using (var logSpy = new SqlLogSpy()) + { + (await (db.Employees + .Where(o => o.Superior.FirstName != null) + .Fetch(o => o.Superior).ThenFetch(o => o.Superior).ThenFetch(o => o.Superior) + .ToListAsync())) + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public async Task OrderByReuseJoinsAsync() + { + OrderLine orderLine; + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.OrderId == 10248) + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.OrderId == 10248) + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.False); + } + + using (var logSpy = new SqlLogSpy()) + { + (await (db.Employees + .OrderBy(o => o.Superior.Superior.Superior.FirstName) + .Fetch(o => o.Superior) + .ToListAsync())) + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, ","), Is.EqualTo(31), "Only the first level should be fetched."); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(3)); + } + + using (var logSpy = new SqlLogSpy()) + { + (await (db.Employees + .OrderBy(o => o.Superior.FirstName) + .Fetch(o => o.Superior).ThenFetch(o => o.Superior).ThenFetch(o => o.Superior) + .ToListAsync())) + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(3)); + } + } + + [Test] + public async Task WhereAndOrderByReuseJoinsAsync() + { + OrderLine orderLine; + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = (await (db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order) + .ToListAsync())) + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.False); + } + + using (var logSpy = new SqlLogSpy()) + { + (await (db.Employees + .Where(o => o.Superior.Superior.Superior.FirstName != null) + .OrderBy(o => o.Superior.Superior.Superior.FirstName) + .Fetch(o => o.Superior) + .ToListAsync())) + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, ","), Is.EqualTo(31), "Only the first level should be fetched."); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(3)); + } + + using (var logSpy = new SqlLogSpy()) + { + (await (db.Employees + .Where(o => o.Superior.FirstName != null) + .OrderBy(o => o.Superior.FirstName) + .Fetch(o => o.Superior).ThenFetch(o => o.Superior).ThenFetch(o => o.Superior) + .ToListAsync())) + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public async Task FetchBeforeSelectAsync() + { + var result = await (db.Orders + .Where(o => o.OrderId == 10248) + .Fetch(x => x.Customer) + .Select(x => new {x.Customer.ContactName}) + .ToListAsync()); + + Assert.True(result.Any()); + } } } diff --git a/src/NHibernate.Test/Async/Linq/EnumTests.cs b/src/NHibernate.Test/Async/Linq/EnumTests.cs index 97a9354b36f..21f23a84c8b 100644 --- a/src/NHibernate.Test/Async/Linq/EnumTests.cs +++ b/src/NHibernate.Test/Async/Linq/EnumTests.cs @@ -8,8 +8,12 @@ //------------------------------------------------------------------------------ +using System; using System.Linq; -using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlTypes; +using NHibernate.Type; using NUnit.Framework; using NHibernate.Linq; @@ -17,52 +21,168 @@ namespace NHibernate.Test.Linq { using System.Threading.Tasks; using System.Threading; - [TestFixture] - public class EnumTestsAsync : LinqTestCase + [TestFixture(typeof(EnumType), "0")] + [TestFixture(typeof(EnumStringType), "'Unspecified'")] + [TestFixture(typeof(EnumAnsiStringType), "'Unspecified'")] + public class EnumTestsAsync : TestCaseMappingByCode { + private IType _enumType; + private string _unspecifiedValue; + + public EnumTestsAsync(System.Type enumType, string unspecifiedValue) + { + _enumType = (IType) Activator.CreateInstance(enumType); + _unspecifiedValue = unspecifiedValue; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Table("EnumEntity"); + rc.Id(x => x.Id, m => m.Generator(Generators.Guid)); + rc.Property(x => x.Name); + rc.Property(x => x.Enum1, m => m.Type(_enumType)); + rc.Property(x => x.NullableEnum1, m => + { + m.Type(_enumType); + m.Formula($"(case when Enum1 = {_unspecifiedValue} then null else Enum1 end)"); + }); + rc.ManyToOne(x => x.Other, m => m.Cascade(Mapping.ByCode.Cascade.All)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + base.OnSetUp(); + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + session.Save(new EnumEntity { Enum1 = TestEnum.Unspecified }); + session.Save(new EnumEntity { Enum1 = TestEnum.Small }); + session.Save(new EnumEntity { Enum1 = TestEnum.Small }); + session.Save(new EnumEntity { Enum1 = TestEnum.Medium }); + session.Save(new EnumEntity { Enum1 = TestEnum.Medium }); + session.Save(new EnumEntity { Enum1 = TestEnum.Medium }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + trans.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public async Task CanQueryOnEnum_Large_4Async() + { + await (CanQueryOnEnumAsync(TestEnum.Large, 4)); + } + [Test] - public async Task CanQueryOnEnumStoredAsInt32_High_1Async() + public async Task CanQueryOnEnum_Medium_3Async() { - await (CanQueryOnEnumStoredAsInt32Async(EnumStoredAsInt32.High, 1)); + await (CanQueryOnEnumAsync(TestEnum.Medium, 3)); } [Test] - public async Task CanQueryOnEnumStoredAsInt32_Unspecified_2Async() + public async Task CanQueryOnEnum_Small_2Async() { - await (CanQueryOnEnumStoredAsInt32Async(EnumStoredAsInt32.Unspecified, 2)); + await (CanQueryOnEnumAsync(TestEnum.Small, 2)); } + [Test] + public async Task CanQueryOnEnum_Unspecified_1Async() + { + await (CanQueryOnEnumAsync(TestEnum.Unspecified, 1)); + } - public async Task CanQueryOnEnumStoredAsInt32Async(EnumStoredAsInt32 type, int expectedCount, CancellationToken cancellationToken = default(CancellationToken)) + private async Task CanQueryOnEnumAsync(TestEnum type, int expectedCount, CancellationToken cancellationToken = default(CancellationToken)) { - var query = await ((from user in db.Users - where user.Enum2 == type - select user).ToListAsync(cancellationToken)); + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var query = await (session.Query().Where(x => x.Enum1 == type).ToListAsync(cancellationToken)); - Assert.AreEqual(expectedCount, query.Count); + Assert.AreEqual(expectedCount, query.Count); + } } [Test] - public async Task CanQueryOnEnumStoredAsString_Meduim_2Async() + public async Task CanQueryWithContainsOnTestEnum_Small_1Async() { - await (CanQueryOnEnumStoredAsStringAsync(EnumStoredAsString.Medium, 2)); + var values = new[] { TestEnum.Small, TestEnum.Medium }; + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var query = await (session.Query().Where(x => values.Contains(x.Enum1)).ToListAsync()); + Assert.AreEqual(5, query.Count); + } } [Test] - public async Task CanQueryOnEnumStoredAsString_Small_1Async() + public async Task ConditionalNavigationPropertyAsync() { - await (CanQueryOnEnumStoredAsStringAsync(EnumStoredAsString.Small, 1)); + TestEnum? type = null; + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var entities = session.Query(); + await (entities.Where(o => o.Enum1 == TestEnum.Large).ToListAsync()); + await (entities.Where(o => TestEnum.Large != o.Enum1).ToListAsync()); + await (entities.Where(o => (o.NullableEnum1 ?? TestEnum.Large) == TestEnum.Medium).ToListAsync()); + await (entities.Where(o => ((o.NullableEnum1 ?? type) ?? o.Enum1) == TestEnum.Medium).ToListAsync()); + await (entities.Where(o => (o.NullableEnum1.HasValue ? o.Enum1 : TestEnum.Unspecified) == TestEnum.Medium).ToListAsync()); + await (entities.Where(o => (o.Enum1 != TestEnum.Large + ? (o.NullableEnum1.HasValue ? o.Enum1 : TestEnum.Unspecified) + : TestEnum.Small) == TestEnum.Medium).ToListAsync()); + + await (entities.Where(o => (o.Enum1 == TestEnum.Large ? o.Other : o.Other).Name == "test").ToListAsync()); + } } - public async Task CanQueryOnEnumStoredAsStringAsync(EnumStoredAsString type, int expectedCount, CancellationToken cancellationToken = default(CancellationToken)) + [Test] + public async Task CanQueryComplexExpressionOnTestEnumAsync() { - var query = await ((from user in db.Users - where user.Enum1 == type - select user).ToListAsync(cancellationToken)); + //TODO: Fix issue on SQLite with type set to TestEnum.Unspecified + TestEnum? type = null; + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var entities = session.Query(); + + var query = await ((from user in entities + where (user.NullableEnum1 == TestEnum.Large + ? TestEnum.Medium + : user.NullableEnum1 ?? user.Enum1 + ) == type + select new + { + user, + simple = user.Enum1, + condition = user.Enum1 == TestEnum.Large ? TestEnum.Medium : user.Enum1, + coalesce = user.NullableEnum1 ?? TestEnum.Medium + }).ToListAsync()); - Assert.AreEqual(expectedCount, query.Count); + Assert.That(query.Count, Is.EqualTo(0)); + } } } } diff --git a/src/NHibernate.Test/Async/Linq/FunctionTests.cs b/src/NHibernate.Test/Async/Linq/FunctionTests.cs index df1606221ad..e47e3d22c37 100644 --- a/src/NHibernate.Test/Async/Linq/FunctionTests.cs +++ b/src/NHibernate.Test/Async/Linq/FunctionTests.cs @@ -13,8 +13,8 @@ using System.Text.RegularExpressions; using NHibernate.DomainModel; using NHibernate.DomainModel.Northwind.Entities; -using NHibernate.Linq; using NUnit.Framework; +using NHibernate.Linq; namespace NHibernate.Test.Linq { @@ -47,7 +47,6 @@ public async Task LikeFunctionWithEscapeCharacterAsync() await (session.SaveAsync(new Employee { FirstName = employeeName, LastName = "LastName" })); await (session.FlushAsync()); - var query = await ((from e in db.Employees where NHibernate.Linq.SqlMethods.Like(e.FirstName, employeeNameEscaped, escapeChar) select e).ToListAsync()); @@ -196,9 +195,6 @@ where e.FirstName.StartsWith("An") [Test] public async Task CharIndexFunctionAsync() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = await ((from e in db.Employees select e.FirstName).ToListAsync()); var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf('a') == 0).ToList(); @@ -215,9 +211,6 @@ where lowerName.IndexOf('a') == 0 [Test] public async Task CharIndexOffsetNegativeFunctionAsync() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = await ((from e in db.Employees select e.FirstName).ToListAsync()); var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf('a', 2) == -1).ToList(); @@ -234,9 +227,6 @@ where lowerName.IndexOf('a', 2) == -1 [Test] public async Task IndexOfFunctionExpressionAsync() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = await ((from e in db.Employees select e.FirstName).ToListAsync()); var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf("an") == 0).ToList(); @@ -253,9 +243,6 @@ where lowerName.IndexOf("an") == 0 [Test] public async Task IndexOfFunctionProjectionAsync() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = await ((from e in db.Employees select e.FirstName).ToListAsync()); var expected = raw.Select(x => x.ToLower()).Where(x => x.Contains("a")).Select(x => x.IndexOf("a", 1)).ToList(); @@ -272,9 +259,6 @@ where lowerName.Contains("a") [Test] public async Task TwoFunctionExpressionAsync() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var query = from e in db.Employees where e.FirstName.IndexOf("A") == e.BirthDate.Value.Month select e.FirstName; @@ -383,16 +367,6 @@ where item.Id.Equals(-1) await (ObjectDumper.WriteAsync(query)); } - [Test] - public async Task WhereShortEqualAsync() - { - var query = from item in session.Query() - where item.Short.Equals(-1) - select item; - - await (ObjectDumper.WriteAsync(query)); - } - [Test] public async Task WhereBoolConstantEqualAsync() { @@ -474,36 +448,6 @@ where item.BodyWeight.Equals(-1) await (ObjectDumper.WriteAsync(query)); } - - [Test] - public async Task WhereFloatEqualAsync() - { - var query = from item in session.Query() - where item.Float.Equals(-1) - select item; - - await (ObjectDumper.WriteAsync(query)); - } - - [Test] - public async Task WhereCharEqualAsync() - { - var query = from item in session.Query() - where item.Char.Equals('A') - select item; - - await (ObjectDumper.WriteAsync(query)); - } - - [Test] - public async Task WhereByteEqualAsync() - { - var query = from item in session.Query() - where item.Byte.Equals(1) - select item; - - await (ObjectDumper.WriteAsync(query)); - } [Test] public async Task WhereDecimalEqualAsync() diff --git a/src/NHibernate.Test/Async/Linq/JoinTests.cs b/src/NHibernate.Test/Async/Linq/JoinTests.cs index aeff80c56b4..4582dd7652b 100644 --- a/src/NHibernate.Test/Async/Linq/JoinTests.cs +++ b/src/NHibernate.Test/Async/Linq/JoinTests.cs @@ -285,21 +285,6 @@ public async Task OrderLinesWithSelectingCustomerIdInCaseShouldProduceOneJoinAsy } } - [Test(Description = "NH-3801"), Ignore("This is an ideal case, but not possible without better join detection")] - public async Task OrderLinesWithSelectingCustomerInCaseShouldProduceOneJoinAsync() - { - using (var spy = new SqlLogSpy()) - { - // Without nominating the conditional to the select clause (and placing it in SQL) - // [l.Order.Customer] will be selected in its entirety, creating a second join - await ((from l in db.OrderLines - select new { CustomerKnown = l.Order.Customer == null ? 0 : 1, l.Order.OrderDate }).ToListAsync()); - - var countJoins = CountJoins(spy); - Assert.That(countJoins, Is.EqualTo(1)); - } - } - [Test(Description = "NH-3801")] public async Task OrderLinesWithSelectingCustomerNameInCaseShouldProduceTwoJoinsAsync() { @@ -313,21 +298,6 @@ public async Task OrderLinesWithSelectingCustomerNameInCaseShouldProduceTwoJoins } } - [Test(Description = "NH-3801"), Ignore("This is an ideal case, but not possible without better join detection")] - public async Task OrderLinesWithSelectingCustomerNameInCaseShouldProduceTwoJoinsAlternateAsync() - { - using (var spy = new SqlLogSpy()) - { - // Without nominating the conditional to the select clause (and placing it in SQL) - // [l.Order.Customer] will be selected in its entirety, creating a second join - await ((from l in db.OrderLines - select new { CustomerKnown = l.Order.Customer == null ? "unknown" : l.Order.Customer.CompanyName, l.Order.OrderDate }).ToListAsync()); - - var countJoins = CountJoins(spy); - Assert.That(countJoins, Is.EqualTo(2)); - } - } - private static int CountJoins(LogSpy sqlLog) { return Count(sqlLog, "join"); diff --git a/src/NHibernate.Test/Async/Linq/LinqQuerySamples.cs b/src/NHibernate.Test/Async/Linq/LinqQuerySamples.cs index 736ba204739..618d7bdb1df 100644 --- a/src/NHibernate.Test/Async/Linq/LinqQuerySamples.cs +++ b/src/NHibernate.Test/Async/Linq/LinqQuerySamples.cs @@ -9,11 +9,14 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using NHibernate.DomainModel.Northwind.Entities; -using NUnit.Framework; +using NHibernate.Hql.Ast.ANTLR; using NHibernate.Linq; +using NSubstitute; +using NUnit.Framework; namespace NHibernate.Test.Linq { @@ -21,6 +24,43 @@ namespace NHibernate.Test.Linq [TestFixture] public class LinqQuerySamplesAsync : LinqTestCase { + class NotMappedEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + [Test] + public void ShouldThrowForQueryOnNotMappedEntityAsync() + { + var querySyntaxException = Assert.ThrowsAsync(() => session.Query().Select(x => x.Id).ToListAsync()); + Assert.That(querySyntaxException.Message, Does.Contain(nameof(NotMappedEntity))); + } + + [Test] + public void ShouldThrowForQueryOnNotMappedEntityNameAsync() + { + var entityName = "SomeNamespace.NotMappedEntityName"; + var querySyntaxException = Assert.ThrowsAsync(() => session.Query(entityName).ToListAsync()); + Assert.That(querySyntaxException.Message, Does.Contain(entityName)); + } + + [Test] + public void ShouldThrowForDmlQueryOnNotMappedEntityAsync() + { + Assert.Multiple( + () => + { + var querySyntaxException = Assert.ThrowsAsync(() => session.Query().DeleteAsync()); + Assert.That(querySyntaxException.Message, Does.Contain(nameof(NotMappedEntity))); + + var entityName = "SomeNamespace.NotMappedEntityName"; + querySyntaxException = Assert.ThrowsAsync(() => session.DeleteAsync($"from {entityName}")); + Assert.That(querySyntaxException.Message, Does.Contain(entityName)); + return Task.CompletedTask; + }); + } + [Test] public async Task GroupTwoQueriesAndSumAsync() { @@ -135,131 +175,6 @@ from c in db.Customers await (ObjectDumper.WriteAsync(q, 1)); } - [Category("SELECT/DISTINCT")] - [Test(Description = "This sample uses nested queries to return a sequence of " + - "all orders containing their OrderId, a subsequence of the " + - "items in the order where there is a discount, and the money " + - "saved if shipping is not included.")] - [Ignore("TODO - nested select")] - public async Task DLinq17Async() - { - using (ISession s = OpenSession()) - { - ///////////// - ///// Flattened Select - ///////////// - - //// In HQL select, get all the data that's needed - //var dbOrders = - // s.CreateQuery("select o.OrderId, od, o.Freight from Order o join o.OrderLines od").List(); - - //// Now group by the items in the parent select, grouping the items in the child select (note lookups on object[], ala SelectClauseVisitor) - //// Note the casts to get the types correct. Need to check if SelectClauseVisitor handles that, but think it does - //var a = from o in dbOrders - // group new { OrderLine = (OrderLine)o[1], Freight = (Decimal?)o[2] } by new { OrderId = (int) o[0] } - // into g - // select - // // Select the parent items, and the child items in a nested select - // new { g.Key.OrderId, DiscountedProducts = from e in g select new { e.OrderLine, FreeShippingDiscount = e.Freight } }; - - //a.ToList(); - - ///////////// - ///// Nested Select - ///////////// - //var dbOrders2 = s.CreateQuery("select o.OrderId from Order o").List(); - - //var q2 = from o in dbOrders2 - // select new - // { - // OrderId = o, - // DiscountedProducts = - // from subO in db.Orders - // where subO.OrderId == o - // from orderLine in subO.OrderLines - // select new { orderLine, FreeShippingDiscount = subO.Freight } - // }; - - //q2.ToList(); - - /////////// - ///// Batching Select - /////////// - var dbOrders3 = await (s.CreateQuery("select o.OrderId from Order o").ListAsync()); - - //var q3 = dbOrders3.SubQueryBatcher(orderId => orderId, - // ids => from subO in db.Orders.ToList() // Note that ToList is just because current group by code is incorrent in our linq provider - // where ids.Contains(subO.OrderId) - // from orderLine in subO.OrderLines - // group new {orderLine, FreeShippingDiscount = subO.Freight} - // by subO.OrderId - // into g - // select g - // ) - // .Select((input, index) => new - // { - // OrderId = input.Item, - // DiscountedProducts = input.Batcher.GetData(index) - // }); - - // This is what we want: - //var q3 = dbOrders3.SubQueryBatcher(orderId => orderId, - // ids => db.Orders - // .Where(o => ids.Contains(o.OrderId)) - // .Select(o => new {o.OrderId, o.OrderLines, o.Freight}).ToList() - // .GroupBy(k => k.OrderId, e => new { e.OrderLines, FreeShippingDiscount = e.Freight}) - // ) - // .Select((input, index) => new - // { - // OrderId = input.Item, - // DiscountedProducts = input.Batcher.GetData(index) - // }); - - // This is what we're using since our provider can't yet handle the in or the group by clauses correctly (note the ToList and the Where clause moving to get us into Linq to Objects world) - var q3 = dbOrders3.SubQueryBatcher(orderId => orderId, - ids => - (from o in db.Orders - from ol in o.OrderLines - select new { OrderLines = ol, FreeShippingDiscount = o.Freight, o.OrderId }) - .ToList() - .Where(o => ids.Contains(o.OrderId)) - .GroupBy(k => k.OrderId, e => new { e.OrderLines, e.FreeShippingDiscount }) - ) - .Select((input, index) => new - { - OrderId = input.Item, - DiscountedProducts = input.Batcher.GetData(index) - }); - - - foreach (var x in q3) - { - Console.WriteLine(x.OrderId); - - foreach (var y in x.DiscountedProducts) - { - Console.WriteLine(y.FreeShippingDiscount); - } - } - - q3.ToList(); - } - - var q = - from o in db.Orders - select new - { - o.OrderId, - DiscountedProducts = - from od in o.OrderLines -// from od in o.OrderLines.Cast() - where od.Discount > 0.0m - select od, FreeShippingDiscount = o.Freight - }; - - await (ObjectDumper.WriteAsync(q, 1)); - } - [Category("SELECT/DISTINCT")] [Test(Description = "This sample uses nested queries to return a sequence of " + "all orders containing their OrderId, a subsequence of the " + @@ -282,31 +197,6 @@ where od.Discount > 0.0m await (ObjectDumper.WriteAsync(q, 1)); } - [Category("SELECT/DISTINCT")] - [Test(Description = "This sample uses nested queries to return a sequence of " + - "all orders containing their OrderId, a subsequence of the " + - "items in the order where there is a discount, and the money " + - "saved if shipping is not included.")] - [Ignore("TODO - nested select")] - public async Task DLinq17cAsync() - { - var q = - from o in db.Orders - select new - { - o.OrderId, - DiscountedProducts = - from od in o.OrderLines -// from od in o.OrderLines.Cast() - where od.Discount > 0.0m - orderby od.Discount descending - select od, - FreeShippingDiscount = o.Freight - }; - - await (ObjectDumper.WriteAsync(q, 1)); - } - [Category("SELECT/DISTINCT")] [Test(Description = "This sample uses Distinct to select a sequence of the unique cities " + "that have Customers.")] @@ -369,64 +259,6 @@ public async Task DLinq24Async() Console.WriteLine(q); } - [Category("COUNT/SUM/MIN/MAX/AVG")] - [Test(Description = "This sample uses Min to find the Products that have the lowest unit price " + - "in each category.")] - [Ignore("TODO nested aggregating group by")] - public async Task DLinq25Async() - { - using (var session = OpenSession()) - { - var output = (await (session - .CreateQuery( - "select p.Category.CategoryId, p from Product p where p.UnitPrice = (select min(p2.UnitPrice) from Product p2 where p.Category.CategoryId = p2.Category.CategoryId)" - ) - .ListAsync())) - .GroupBy(input => input[0]) - .Select(input => new {CategoryId = (int) input.Key, CheapestProducts = from g in input select (Product) g[1]}); - } - - /* - * From g, only using g.Key, min(UnitPrice), g - * - g.Key is fine - * - min(UnitPrice) is fine - * - g is the problem. Can't just issue a single select since it's non-aggregating - * However, don't want to loose the aggregate; need that processed in the DB - * - * To get additional information over and above g.Key and any aggregates, need a where clause against the aggregate: - * - * select xxx, yyy, zzz from Product p where p.UnitPrice = (select min(p2.UnitPrice) from Product p2) - * - * the outer where comes from the inner where in the queryModel: - * - * where p2.UnitPrice == g.Min(p3 => p3.UnitPrice) - * - * also need additional constraints on the aggregate to fulfil the groupby requirements: - * - * where p.Category.CategoryId = p2.Category.CategoryId - * - * so join the inner select to the outer select using the group by criteria - * - * finally, need to do some client-side processing to get the "shape" correct - * - */ - - var categories = - from p in db.Products - group p by p.Category.CategoryId - into g - select new - { - CategoryId = g.Key, - CheapestProducts = - (IEnumerable) (from p2 in g - where p2.UnitPrice == g.Min(p3 => p3.UnitPrice) - select p2) - }; - - Console.WriteLine(await (ObjectDumper.WriteAsync(categories, 1))); - } - [Category("COUNT/SUM/MIN/MAX/AVG")] [Test(Description = "This sample uses Max to find the latest hire date of any Employee.")] public async Task DLinq26Async() @@ -443,28 +275,6 @@ public async Task DLinq27Async() Console.WriteLine(q); } - [Category("COUNT/SUM/MIN/MAX/AVG")] - [Test(Description = "This sample uses Max to find the Products that have the highest unit price " + - "in each category.")] - [Ignore("TODO nested aggregating group by")] - public async Task DLinq28Async() - { - var categories = - from p in db.Products - group p by p.Category.CategoryId - into g - select new - { - g.Key, - MostExpensiveProducts = - from p2 in g - where p2.UnitPrice == g.Max(p3 => p3.UnitPrice) - select p2 - }; - - await (ObjectDumper.WriteAsync(categories, 1)); - } - [Category("COUNT/SUM/MIN/MAX/AVG")] [Test(Description = "This sample uses Average to find the average freight of all Orders.")] public async Task DLinq29Async() @@ -481,28 +291,6 @@ public async Task DLinq30Async() Console.WriteLine(q); } - [Category("COUNT/SUM/MIN/MAX/AVG")] - [Test(Description = "This sample uses Average to find the Products that have unit price higher than " + - "the average unit price of the category for each category.")] - [Ignore("TODO nested aggregating group by")] - public async Task DLinq31Async() - { - var categories = - from p in db.Products - group p by p.Category.CategoryId - into g - select new - { - g.Key, - ExpensiveProducts = - from p2 in g - where p2.UnitPrice > g.Average(p3 => p3.UnitPrice) - select p2 - }; - - await (ObjectDumper.WriteAsync(categories, 1)); - } - [Category("ORDER BY")] [Test(Description = "This sample uses orderby to sort Employees by hire date.")] public async Task DLinq36Async() @@ -569,30 +357,6 @@ from o in db.Orders await (ObjectDumper.WriteAsync(q)); } - - [Category("ORDER BY")] - [Test(Description = "This sample uses Orderby, Max and Group By to find the Products that have " + - "the highest unit price in each category, and sorts the group by category id.")] - [Ignore("TODO nested aggregating group by")] - public async Task DLinq41Async() - { - var categories = - from p in db.Products - group p by p.Category.CategoryId - into g - orderby g.Key - select new - { - g.Key, - MostExpensiveProducts = - from p2 in g - where p2.UnitPrice == g.Max(p3 => p3.UnitPrice) - select p2 - }; - - await (ObjectDumper.WriteAsync(categories, 1)); - } - [Category("GROUP BY/HAVING")] [Test(Description = "This sample uses group by to partition Products by " + "CategoryId.")] @@ -925,94 +689,6 @@ where c.Orders.All(o => o.ShippingAddress.City == c.Address.City) } } - [Category("UNION ALL/UNION/INTERSECT")] - [Test(Description = "This sample uses Concat to return a sequence of all Customer and Employee " + - "phone/fax numbers.")] - [Ignore("TODO set operations")] - public async Task DLinq55Async() - { - IQueryable q = ( - from c in db.Customers - select c.Address.PhoneNumber - ).Concat( - from c in db.Customers - select c.Address.Fax - ).Concat( - from e in db.Employees - select e.Address.PhoneNumber - ); - - await (ObjectDumper.WriteAsync(q)); - } - - [Category("UNION ALL/UNION/INTERSECT")] - [Test(Description = "This sample uses Concat to return a sequence of all Customer and Employee " + - "name and phone number mappings.")] - [Ignore("TODO set operations")] - public async Task DLinq56Async() - { - var q = ( - from c in db.Customers - select new {Name = c.CompanyName, Phone = c.Address.PhoneNumber} - ).Concat( - from e in db.Employees - select new {Name = e.FirstName + " " + e.LastName, Phone = e.Address.PhoneNumber} - ); - - await (ObjectDumper.WriteAsync(q)); - } - - [Category("UNION ALL/UNION/INTERSECT")] - [Test(Description = "This sample uses Union to return a sequence of all countries that either " + - "Customers or Employees are in.")] - [Ignore("TODO set operations")] - public async Task DLinq57Async() - { - IQueryable q = ( - from c in db.Customers - select c.Address.Country - ).Union( - from e in db.Employees - select e.Address.Country - ); - - await (ObjectDumper.WriteAsync(q)); - } - - [Category("UNION ALL/UNION/INTERSECT")] - [Test(Description = "This sample uses Intersect to return a sequence of all countries that both " + - "Customers and Employees live in.")] - [Ignore("TODO set operations")] - public async Task DLinq58Async() - { - IQueryable q = ( - from c in db.Customers - select c.Address.Country - ).Intersect( - from e in db.Employees - select e.Address.Country - ); - - await (ObjectDumper.WriteAsync(q)); - } - - [Category("UNION ALL/UNION/INTERSECT")] - [Test(Description = "This sample uses Except to return a sequence of all countries that " + - "Customers live in but no Employees live in.")] - [Ignore("TODO set operations")] - public async Task DLinq59Async() - { - IQueryable q = ( - from c in db.Customers - select c.Address.Country - ).Except( - from e in db.Employees - select e.Address.Country - ); - - await (ObjectDumper.WriteAsync(q)); - } - [Category("WHERE")] [Test(Description = "This sample uses First to select the first Shipper in the table.")] public async Task DLinq6Async() @@ -1121,7 +797,33 @@ from o in c.Orders where c.Address.City == "London" select o; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample uses foreign key navigation in the " + + "from clause to select all orders for customers in London.")] + public async Task DLinqJoin1LeftJoinAsync() + { + IQueryable q = + from c in db.Customers + from o in c.Orders.DefaultIfEmpty() + where c.Address.City == "London" + select o; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1227,7 +929,13 @@ from p in db.Products where p.Supplier.Address.Country == "USA" && p.UnitsInStock == 0 select p; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1243,7 +951,16 @@ from et in e.Territories where e.Address.City == "Seattle" select new {e.FirstName, e.LastName, et.Region.Description}; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + // EmployeeTerritories and Territories + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + // Region + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1267,7 +984,13 @@ from e2 in e1.Subordinates e1.Address.City }; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1282,7 +1005,13 @@ from c in db.Customers join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders select new {c.ContactName, OrderCount = orders.Average(x => x.Freight)}; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(0)); + } } [Category("JOIN")] @@ -1294,7 +1023,33 @@ from c in db.Customers join o in db.Orders on c.CustomerId equals o.Customer.CustomerId select new { c.ContactName, o.OrderId }; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample explictly joins two tables and projects results from both tables.")] + public async Task DLinqJoin5aLeftJoinAsync() + { + var q = + from c in db.Customers + join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders + from o in orders.DefaultIfEmpty() + where o != null + select new { c.ContactName, o.OrderId }; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1328,12 +1083,119 @@ public async Task DLinqJoin5dAsync() { var q = from c in db.Customers - join o in db.Orders on new {c.CustomerId, HasContractTitle = c.ContactTitle != null} equals new {o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } + join o in db.Orders on + new {c.CustomerId, HasContractTitle = c.ContactTitle != null} equals + new {o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } select new { c.ContactName, o.OrderId }; + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(0)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "cross join"), Is.EqualTo(0)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")] + public async Task DLinqJoin5dLeftJoinAsync() + { + var q = + from c in db.Customers + join o in db.Orders on + new {c.CustomerId, HasContractTitle = c.ContactTitle != null} equals + new {o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null} into orders + from o in orders.DefaultIfEmpty() + select new {c.ContactName, OrderId = (int?) o.OrderId}; + await (ObjectDumper.WriteAsync(q)); } + [Category("JOIN")] + [Test(Description = "This sample joins two tables and projects results from the first table.")] + public async Task DLinqJoin5eAsync() + { + var q = + from c in db.Customers + join o in db.Orders on c.CustomerId equals o.Customer.CustomerId + where c.ContactName != null + select o; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample joins two tables and projects results from the first table.")] + public async Task DLinqJoin5eLeftJoinAsync() + { + var q = + from c in db.Customers + join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders + from o in orders.DefaultIfEmpty() + where c.ContactName != null + select o; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")] + public async Task DLinqJoin5fAsync() + { + var q = + from o in db.Orders + join c in db.Customers on + new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals + new { c.CustomerId, HasContractTitle = c.ContactTitle != null } + select new { c.ContactName, o.OrderId }; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(0)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + } + } + + [Category("JOIN")] + [TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")] + public async Task DLinqJoin5fLeftJoinAsync() + { + var q = + from o in db.Orders + join c in db.Customers on + new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals + new { c.CustomerId, HasContractTitle = c.ContactTitle != null } into customers + from c in customers.DefaultIfEmpty() + select new { c.ContactName, o.OrderId }; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(0)); + } + } + [Category("JOIN")] [Test(Description = "This sample explictly joins three tables and projects results from each of them.")] public async Task DLinqJoin6Async() @@ -1347,7 +1209,13 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into ords join e in db.Employees on c.Address.City equals e.Address.City into emps select new {c.ContactName, ords = ords.Count(), emps = emps.Count()}; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(0)); + } } [Category("JOIN")] @@ -1355,7 +1223,6 @@ join e in db.Employees on c.Address.City equals e.Address.City into emps Description = "This sample shows how to get LEFT OUTER JOIN by using DefaultIfEmpty(). The DefaultIfEmpty() method returns null when there is no Order for the Employee." )] - [Ignore("TODO left outer join")] public async Task DLinqJoin7Async() { var q = @@ -1364,7 +1231,13 @@ join o in db.Orders on e equals o.Employee into ords from o in ords.DefaultIfEmpty() select new {e.FirstName, e.LastName, Order = o}; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1378,32 +1251,99 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into ords from o in ords select new {c.ContactName, o.OrderId, z}; - await (ObjectDumper.WriteAsync(q)); + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [TestCase(true, Description = "This sample shows a group join with a composite key.")] + [TestCase(false, Description = "This sample shows a group join with a composite key.")] + public async Task DLinqJoin9Async(bool useCrossJoin) + { + if (useCrossJoin && !Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + // The expected collection can be obtained from the below Linq to Objects query. + //var expected = + // (from o in db.Orders.ToList() + // from p in db.Products.ToList() + // join d in db.OrderLines.ToList() + // on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} + // into details + // from d in details + // select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList(); + + using (var substitute = SubstituteDialect()) + using (var sqlSpy = new SqlLogSpy()) + { + ClearQueryPlanCache(); + substitute.Value.SupportsCrossJoin.Returns(useCrossJoin); + + var actual = + await ((from o in db.Orders + from p in db.Products + join d in db.OrderLines + on new { o.OrderId, p.ProductId } equals new { d.Order.OrderId, d.Product.ProductId } + into details + from d in details + select new { o.OrderId, p.ProductId, d.UnitPrice }).ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(actual.Count, Is.EqualTo(2155)); + Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join")); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(useCrossJoin ? 1 : 2)); + } } [Category("JOIN")] - [Test(Description = "This sample shows a group join with a composite key.")] - public async Task DLinqJoin9Async() - { - var expected = - (from o in db.Orders.ToList() - from p in db.Products.ToList() - join d in db.OrderLines.ToList() - on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} - into details - from d in details - select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList(); - - var actual = - await ((from o in db.Orders - from p in db.Products - join d in db.OrderLines - on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} - into details - from d in details - select new {o.OrderId, p.ProductId, d.UnitPrice}).ToListAsync()); - - Assert.AreEqual(expected.Count, actual.Count); + [TestCase(true, Description = "This sample shows a group left join with a composite key.")] + [TestCase(false, Description = "This sample shows a group left join with a composite key.")] + public async Task DLinqJoin9LeftJoinAsync(bool useCrossJoin) + { + if (useCrossJoin && !Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + // The expected collection can be obtained from the below Linq to Objects query. + //var expected = + // (from o in db.Orders.ToList() + // from p in db.Products.ToList() + // join d in db.OrderLines.ToList() + // on new { o.OrderId, p.ProductId } equals new { d.Order.OrderId, d.Product.ProductId } + // into details + // from d in details.DefaultIfEmpty() + // where d != null && d.UnitPrice > 50 + // select new { o.OrderId, p.ProductId, d.UnitPrice }).ToList(); + + using (var substitute = SubstituteDialect()) + using (var sqlSpy = new SqlLogSpy()) + { + ClearQueryPlanCache(); + substitute.Value.SupportsCrossJoin.Returns(useCrossJoin); + + var actual = + await ((from o in db.Orders + from p in db.Products + join d in db.OrderLines + on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} + into details + from d in details.DefaultIfEmpty() + where d != null && d.UnitPrice > 50 + select new {o.OrderId, p.ProductId, d.UnitPrice}).ToListAsync()); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(actual.Count, Is.EqualTo(163)); + Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join")); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1417,5 +1357,45 @@ group o by c into x await (ObjectDumper.WriteAsync(q)); } + + [Category("JOIN")] + [Test(Description = "This sample shows how to join multiple tables.")] + public async Task DLinqJoin10aAsync() + { + var q = + from e in db.Employees + join s in db.Employees on e.Superior.EmployeeId equals s.EmployeeId + join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId + select new { e.FirstName, SuperiorName = s.FirstName, Superior2Name = s2.FirstName }; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample shows how to join multiple tables using a left join.")] + public async Task DLinqJoin10aLeftJoinAsync() + { + var q = + from e in db.Employees + join s in db.Employees on e.Superior.EmployeeId equals s.EmployeeId into sup + from s in sup.DefaultIfEmpty() + join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId into sup2 + from s2 in sup2.DefaultIfEmpty() + select new { e.FirstName, SuperiorName = s.FirstName, Superior2Name = s2.FirstName }; + + using (var sqlSpy = new SqlLogSpy()) + { + await (ObjectDumper.WriteAsync(q)); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Linq/LoggingTests.cs b/src/NHibernate.Test/Async/Linq/LoggingTests.cs index c47544dab35..8b4533304ff 100644 --- a/src/NHibernate.Test/Async/Linq/LoggingTests.cs +++ b/src/NHibernate.Test/Async/Linq/LoggingTests.cs @@ -39,7 +39,6 @@ public async Task PageBetweenProjectionsAsync() } } - [Test] public async Task CanLogLinqExpressionWithoutInitializingContainedProxyAsync() { diff --git a/src/NHibernate.Test/Async/Linq/MiscellaneousTextFixture.cs b/src/NHibernate.Test/Async/Linq/MiscellaneousTextFixture.cs index 2d660623a63..883651c9da1 100644 --- a/src/NHibernate.Test/Async/Linq/MiscellaneousTextFixture.cs +++ b/src/NHibernate.Test/Async/Linq/MiscellaneousTextFixture.cs @@ -26,13 +26,14 @@ public class MiscellaneousTextFixtureAsync : LinqTestCase [Category("COUNT/SUM/MIN/MAX/AVG")] [Test(Description = "This sample uses Count to find the number of Orders placed before yesterday in the database.")] public async Task CountWithWhereClauseAsync() - { - var q = from o in db.Orders where o.OrderDate <= DateTime.Today.AddDays(-1) select o; + { + var yesterday = DateTime.Today.AddDays(-1); + var q = from o in db.Orders where o.OrderDate <= yesterday select o; - var count = await (q.CountAsync()); + var count = await (q.CountAsync()); - Console.WriteLine(count); - } + Console.WriteLine(count); + } [Category("From NHUser list")] [Test(Description = "Telerik grid example, http://www.telerik.com/community/forums/aspnet-mvc/grid/grid-and-nhibernate-linq.aspx")] @@ -136,17 +137,17 @@ orderby p.ProductId [Test] public async Task SelectFromObjectAsync() - { - using (var s = OpenSession()) - { - var hql = await (s.CreateQuery("from System.Object o").ListAsync()); + { + using (var s = OpenSession()) + { + var hql = await (s.CreateQuery("from System.Object o").ListAsync()); - var r = from o in s.Query() select o; + var r = from o in s.Query() select o; - var l = await (r.ToListAsync()); + var l = await (r.ToListAsync()); - Assert.AreEqual(hql.Count, l.Count); - } - } + Assert.AreEqual(hql.Count, l.Count); + } + } } } diff --git a/src/NHibernate.Test/Async/Linq/NestedSelectsTests.cs b/src/NHibernate.Test/Async/Linq/NestedSelectsTests.cs index 5d2da6a7fd5..87749ef9584 100644 --- a/src/NHibernate.Test/Async/Linq/NestedSelectsTests.cs +++ b/src/NHibernate.Test/Async/Linq/NestedSelectsTests.cs @@ -11,6 +11,8 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using System.Linq.Expressions; +using NHibernate.DomainModel.Northwind.Entities; using NUnit.Framework; using NHibernate.Linq; @@ -362,5 +364,100 @@ public async Task OrdersIdWithOrderLinesNestedWhereIdAsync() Assert.That(orders.Count, Is.EqualTo(830)); Assert.That(orders[0].OrderLinesIds, Is.Empty); } + + [Test] + public async Task NoNestedSelects_AnyOnGroupBySubqueryAsync() + { + var subQuery = from vms in db.Animals + group vms by vms.Father + into vmReqs + select vmReqs.Max(x => x.Id); + + var outerQuery = from vm in db.Animals + where subQuery.Any(x => vm.Id == x) + select vm; + var animals = await (outerQuery.ToListAsync()); + Assert.That(animals.Count, Is.EqualTo(2)); + } + + //NH-3155 + [Test] + public async Task NoNestedSelects_ContainsOnGroupBySubqueryAsync() + { + var subQuery = from vms in db.Animals + where vms.BodyWeight > 0 + group vms by vms.Father + into vmReqs + select vmReqs.Max(x => x.Id); + + var outerQuery = from vm in db.Animals + where subQuery.Contains(vm.Id) + select vm; + + var animals = await (outerQuery.ToListAsync()); + Assert.That(animals.Count, Is.EqualTo(2)); + } + + [Test] + public async Task CanSelectWithWhereSubQueryAsync() + { + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.Where(e => e.NumberOfHours >= 0).ToList() + }; + + var list = await (query.ToListAsync()); + + Assert.AreEqual(3, list.Count); + } + + [Test(Description = "GH-2540")] + public async Task CanSelectWithAsQueryableAndWhereSubQueryAsync() + { + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.AsQueryable().Where(e => e.NumberOfHours >= 0).ToList() + }; + + var list = await (query.ToListAsync()); + + Assert.AreEqual(3, list.Count); + } + + [Test(Description = "GH-2540")] + public async Task CanSelectWithAsQueryableAndWhereSubQueryToArrayAsync() + { + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.AsQueryable().Where(e => e.NumberOfHours >= 0).ToArray() + }; + + var list = await (query.ToListAsync()); + + Assert.AreEqual(3, list.Count); + } + + [Test(Description = "GH-2540")] + public async Task CanSelectWithAsQueryableAndWhereSubQueryWithExternalPredicateAsync() + { + Expression> predicate = e => e.NumberOfHours >= 0; + + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.AsQueryable().Where(predicate).ToList() + }; + + var list = await (query.ToListAsync()); + + Assert.AreEqual(3, list.Count); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Linq/NullComparisonTests.cs b/src/NHibernate.Test/Async/Linq/NullComparisonTests.cs index cecc6778c74..185dff330f2 100644 --- a/src/NHibernate.Test/Async/Linq/NullComparisonTests.cs +++ b/src/NHibernate.Test/Async/Linq/NullComparisonTests.cs @@ -12,13 +12,16 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.DomainModel.Northwind.Entities; using NUnit.Framework; +using NUnit.Framework.Constraints; namespace NHibernate.Test.Linq { using System.Threading.Tasks; + using System.Threading; [TestFixture] public class NullComparisonTestsAsync : LinqTestCase { @@ -28,6 +31,350 @@ public class NullComparisonTestsAsync : LinqTestCase private static readonly AnotherEntity BothNull = new AnotherEntity(); private static readonly AnotherEntity BothDifferent = new AnotherEntity {Input = "input", Output = "output"}; + [Test] + public async Task NullInequalityWithNotNullAsync() + { + var q = session.Query().Where(o => o.Input != null); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothSame, BothDifferent)); + + q = session.Query().Where(o => null != o.Input); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothSame, BothDifferent)); + + q = session.Query().Where(o => o.InputNullability != AnotherEntityNullability.True); + await (ExpectAsync(q, Does.Not.Contain("end is null").IgnoreCase, InputSet, BothSame, BothDifferent)); + + q = session.Query().Where(o => AnotherEntityNullability.True != o.InputNullability); + await (ExpectAsync(q, Does.Not.Contain("end is null").IgnoreCase, InputSet, BothSame, BothDifferent)); + + q = session.Query().Where(o => "input" != o.Input); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Input != "input"); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Input != o.Output); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent)); + + q = session.Query().Where(o => o.Output != o.Input); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent)); + + q = session.Query().Where(o => o.Input != o.NullableOutput); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull)); + + q = session.Query().Where(o => o.NullableOutput != o.Input); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Output != o.Input); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull)); + + q = session.Query().Where(o => o.Input != o.NullableAnotherEntityRequired.Output); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Input != o.Output); + await (ExpectAsync(q, Does.Contain("Input is null").IgnoreCase, BothDifferent, OutputSet, BothNull)); + + q = session.Query().Where(o => o.Output != o.NullableAnotherEntityRequired.Input); + await (ExpectAsync(q, Does.Contain("Input is null").IgnoreCase, BothDifferent, OutputSet, BothNull)); + + q = session.Query().Where(o => 3 != o.NullableOutput.Length); + await (ExpectAsync(q, Does.Contain("is null").IgnoreCase, InputSet, BothDifferent, BothNull, OutputSet)); + + q = session.Query().Where(o => o.NullableOutput.Length != 3); + await (ExpectAsync(q, Does.Contain("is null").IgnoreCase, InputSet, BothDifferent, BothNull, OutputSet)); + + q = session.Query().Where(o => 3 != o.Input.Length); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent)); + + q = session.Query().Where(o => o.Input.Length != 3); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent)); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequiredId ?? 0) != (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0) != (o.NullableAnotherEntityRequiredId ?? 0)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.GetValueOrDefault() != o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault()); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault() != o.NullableAnotherEntityRequiredId.GetValueOrDefault()); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value != o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value != o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value && o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value != 0); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value != 0 && o.NullableAnotherEntityRequiredId.HasValue); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue || o.NullableAnotherEntityRequiredId.Value != 0); + await (ExpectAllAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value != 0 || o.NullableAnotherEntityRequiredId.HasValue); + await (ExpectAllAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableOutput != null && o.NullableOutput != "test"); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet)); + + q = session.Query().Where(o => o.NullableOutput != "test" && o.NullableOutput != null); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet)); + + q = session.Query().Where(o => o.NullableOutput != null || o.NullableOutput != "test"); + await (ExpectAllAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableOutput != "test" || o.NullableOutput != null); + await (ExpectAllAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableOutput != "test" && (o.NullableAnotherEntityRequiredId > 0 && o.NullableOutput != null)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet)); + + q = session.Query().Where(o => o.NullableOutput != null && (o.NullableAnotherEntityRequiredId > 0 && o.NullableOutput != "test")); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value != o.NullableAnotherEntityRequiredId.Value); + await (ExpectAsync(q, Does.Contain("or case").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.Any(r => r.Output != o.Input)); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase.And.Contain("Output is null").IgnoreCase, BothDifferent, InputSet, BothNull)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != o.Input)); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase.And.Contain("Output is null").IgnoreCase, InputSet, OutputSet, BothDifferent, BothNull)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null && r.Output != o.Input)); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase.And.Not.Contain("Output is null").IgnoreCase, BothDifferent, OutputSet)); + + q = session.Query().Where(o => (o.NullableOutput + o.Output) != o.Output); + await (ExpectAllAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => (o.Input + o.Output) != o.Output); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame, BothDifferent)); + + q = session.Query().Where(o => o.Address.Street != o.Output); + await (ExpectAsync(q, Does.Contain("Input is null").IgnoreCase, BothDifferent, OutputSet, BothNull)); + + q = session.Query().Where(o => o.Address.City != o.Output); + await (ExpectAsync(q, Does.Contain("Output is null").IgnoreCase, InputSet, BothNull)); + + q = session.Query().Where(o => o.Address.City != null && o.Address.City != o.Output); + await (ExpectAsync(q, Does.Not.Contain("Output is null").IgnoreCase)); + + q = session.Query().Where(o => o.Address.Street != null && o.Address.Street != o.NullableOutput); + await (ExpectAsync(q, Does.Contain("Output is null").IgnoreCase, InputSet, BothDifferent)); + + await (ExpectAsync(session.Query().Where(o => o.CustomerId != null), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => null != o.CustomerId), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CustomerId != "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.CustomerId), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.CustomerId != "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.Order.Customer.CustomerId), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.CompanyName != "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.Order.Customer.CompanyName), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.CreatedBy.CreatedBy.Name != "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.CreatedBy.CreatedBy.CreatedBy.Name), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.CreatedBy.Id != 5), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => 5 != o.CreatedBy.CreatedBy.Id), Does.Not.Contain("is null").IgnoreCase)); + } + + [Test] + public async Task NullInequalityWithNotNullSubSelectAsync() + { + if (!Dialect.SupportsScalarSubSelects) + { + Assert.Ignore("Dialect does not support scalar subselects"); + } + + var q = session.Query().Where(o => o.RelatedItems.Count != 1); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.Max(r => r.Id) != 0); + await (ExpectAllAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null) != o.NullableBool); + await (ExpectAllAsync(q, Does.Not.Contain("or case").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.Where(r => r.Id == 0).Sum(r => r.Input.Length) != 5); + await (ExpectAllAsync(q, Does.Contain("or (").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null) != (o.NullableOutput.Length > 0)); + await (ExpectAsync(q, Does.Not.Contain("or case").IgnoreCase)); + } + + [Test] + public async Task NullEqualityWithNotNullAsync() + { + var q = session.Query().Where(o => o.Input == null); + await (ExpectAsync(q, Does.Contain("is null").IgnoreCase, OutputSet, BothNull)); + + q = session.Query().Where(o => null == o.Input); + await (ExpectAsync(q, Does.Contain("is null").IgnoreCase, OutputSet, BothNull)); + + q = session.Query().Where(o => o.InputNullability == AnotherEntityNullability.True); + await (ExpectAsync(q, Does.Not.Contain("end is null").IgnoreCase, BothNull, OutputSet)); + + q = session.Query().Where(o => AnotherEntityNullability.True == o.InputNullability); + await (ExpectAsync(q, Does.Not.Contain("end is null").IgnoreCase, BothNull, OutputSet)); + + q = session.Query().Where(o => "input" == o.Input); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent)); + + q = session.Query().Where(o => o.Input == "input"); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent)); + + q = session.Query().Where(o => o.Input == o.Output); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Output == o.Input); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Input == o.NullableOutput); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.NullableOutput == o.Input); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Output == o.Input); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Input == o.NullableAnotherEntityRequired.Output); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Input == o.Output); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Output == o.NullableAnotherEntityRequired.Input); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => 3 == o.Input.Length); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.Input.Length == 3); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequiredId ?? 0) == (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0)); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0) == (o.NullableAnotherEntityRequiredId ?? 0)); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.GetValueOrDefault() == o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault()); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault() == o.NullableAnotherEntityRequiredId.GetValueOrDefault()); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value == o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value == o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value && o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value == 0); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value == 0 && o.NullableAnotherEntityRequiredId.HasValue); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue || o.NullableAnotherEntityRequiredId.Value == 0); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value == 0 || o.NullableAnotherEntityRequiredId.HasValue); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableOutput == "test"); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.NullableOutput != null || o.NullableOutput == "test"); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, OutputSet, BothDifferent, BothSame)); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value == o.NullableAnotherEntityRequiredId.Value); + await (ExpectAllAsync(q, Does.Contain("Id is null").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.Any(r => r.Output == o.Input)); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase.And.Not.Contain("Output is null").IgnoreCase, BothSame)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output == o.Input)); + await (ExpectAsync(q, Does.Not.Contain("Input is null").IgnoreCase.And.Not.Contain("Output is null").IgnoreCase, BothSame, BothNull, InputSet, OutputSet)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output == o.NullableOutput)); + await (ExpectAllAsync(q, Does.Contain("Output is null").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null && o.NullableOutput != null && r.Output == o.NullableOutput)); + await (ExpectAsync(q, Does.Not.Contain("Output is null").IgnoreCase, BothSame, BothDifferent, OutputSet)); + + q = session.Query().Where(o => (o.NullableOutput + o.Output) == o.Output); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => (o.Output + o.Output) == o.Output); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => !o.Input.Equals(o.Output)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent)); + + q = session.Query().Where(o => !o.Output.Equals(o.Input)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent)); + + q = session.Query().Where(o => !o.Input.Equals(o.NullableOutput)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent)); + + q = session.Query().Where(o => !o.NullableOutput.Equals(o.Input)); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent)); + + q = session.Query().Where(o => !o.NullableOutput.Equals(o.NullableOutput)); + await (ExpectAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => !o.NullableOutput.Equals(o.NullableOutput)); + await (ExpectAsync(q, Does.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.Address.City == o.NullableOutput); + await (ExpectAllAsync(q, Does.Contain("Output is null").IgnoreCase)); + + q = session.Query().Where(o => o.Address.Street != null && o.Address.Street == o.NullableOutput); + await (ExpectAsync(q, Does.Not.Contain("Output is null").IgnoreCase, BothSame)); + + await (ExpectAsync(session.Query().Where(o => o.CustomerId == null), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => null == o.CustomerId), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CustomerId == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.CustomerId), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.CustomerId == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.Order.Customer.CustomerId), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.CompanyName == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.Order.Customer.CompanyName), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.CreatedBy.CreatedBy.Name == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.CreatedBy.CreatedBy.CreatedBy.Name), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.CreatedBy.Id == 5), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => 5 == o.CreatedBy.CreatedBy.Id), Does.Not.Contain("is null").IgnoreCase)); + } + + [Test] + public async Task NullEqualityWithNotNullSubSelectAsync() + { + if (!Dialect.SupportsScalarSubSelects) + { + Assert.Ignore("Dialect does not support scalar subselects"); + } + + var q = session.Query().Where(o => o.RelatedItems.Count == 1); + await (ExpectAllAsync(q, Does.Not.Contain("is null").IgnoreCase)); + + q = session.Query().Where(o => o.RelatedItems.Max(r => r.Id) == 0); + await (ExpectAsync(q, Does.Not.Contain("is null").IgnoreCase)); + } + [Test] public async Task NullEqualityAsync() { @@ -96,6 +443,63 @@ public async Task NullEqualityAsync() // Columns against columns q = from x in session.Query() where x.Input == x.Output select x; await (ExpectAsync(q, BothSame, BothNull)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.ContactName == null), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => null == o.Order.Customer.ContactName), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.ContactName == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.Order.Customer.ContactName), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => null == o.Component.Property1), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.Property1 == null), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => "test" == o.Component.Property1), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.Property1 == "test"), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => null == o.Component.OtherComponent.OtherProperty1), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 == null), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => "test" == o.Component.OtherComponent.OtherProperty1), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 == "test"), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.ModifiedBy.CreatedBy.Name == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.CreatedBy.ModifiedBy.CreatedBy.Name), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1 == "test"), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" == o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.ModifiedBy.CreatedBy.Id == 5), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => 5 == o.ModifiedBy.CreatedBy.Id), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.ModifiedBy.Id == 5), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => 5 == o.CreatedBy.ModifiedBy.Id), Does.Not.Contain("is null").IgnoreCase)); + + if (Sfi.Dialect is FirebirdDialect) + { + return; + } + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort == o.NullableShort), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short == o.Short), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort == o.Short), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short == o.NullableShort), WithoutIsNullAndWithoutCast())); + + short value = 3; + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort == value), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => value == o.NullableShort), WithoutIsNullAndWithoutCast())); + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort.Value == value), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => value == o.NullableShort.Value), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short == value), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => value == o.Short), WithoutIsNullAndWithoutCast())); + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort == 3L), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => 3L == o.NullableShort), WithoutIsNullAndWithoutCast())); + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort.Value == 3L), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => 3L == o.NullableShort.Value), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short == 3L), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => 3L == o.Short), WithoutIsNullAndWithoutCast())); } [Test] @@ -154,6 +558,73 @@ public async Task NullInequalityAsync() // Columns against columns q = from x in session.Query() where x.Input != x.Output select x; await (ExpectAsync(q, BothDifferent, InputSet, OutputSet)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.ContactName != null), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => null != o.Order.Customer.ContactName), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.Order.Customer.ContactName != "test"), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.Order.Customer.ContactName), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => null != o.Component.Property1), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.Property1 != null), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => "test" != o.Component.Property1), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.Property1 != "test"), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => null != o.Component.OtherComponent.OtherProperty1), Does.Not.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 != null), Does.Not.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => "test" != o.Component.OtherComponent.OtherProperty1), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 != "test"), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.ModifiedBy.CreatedBy.Name != "test"), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.CreatedBy.ModifiedBy.CreatedBy.Name), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1 != "test"), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => "test" != o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.ModifiedBy.CreatedBy.Id != 5), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => 5 != o.ModifiedBy.CreatedBy.Id), Does.Contain("is null").IgnoreCase)); + + await (ExpectAsync(session.Query().Where(o => o.CreatedBy.ModifiedBy.Id != 5), Does.Contain("is null").IgnoreCase)); + await (ExpectAsync(session.Query().Where(o => 5 != o.CreatedBy.ModifiedBy.Id), Does.Contain("is null").IgnoreCase)); + + if (Sfi.Dialect is FirebirdDialect) + { + return; + } + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort != o.NullableShort), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short != o.Short), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort != o.Short), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short != o.NullableShort), WithIsNullAndWithoutCast())); + + short value = 3; + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort != value), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => value != o.NullableShort), WithIsNullAndWithoutCast())); + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort.Value != value), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => value != o.NullableShort.Value), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short != value), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => value != o.Short), WithoutIsNullAndWithoutCast())); + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort != 3L), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => 3 != o.NullableShort), WithIsNullAndWithoutCast())); + + await (ExpectAsync(db.NumericEntities.Where(o => o.NullableShort.Value != 3L), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => 3L != o.NullableShort.Value), WithIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => o.Short != 3L), WithoutIsNullAndWithoutCast())); + await (ExpectAsync(db.NumericEntities.Where(o => 3L != o.Short), WithoutIsNullAndWithoutCast())); + } + + private IResolveConstraint WithIsNullAndWithoutCast() + { + return Does.Contain("is null").IgnoreCase.And.Not.Contain("cast").IgnoreCase; + } + + private IResolveConstraint WithoutIsNullAndWithoutCast() + { + return Does.Not.Contain("is null").IgnoreCase.And.Not.Contain("cast").IgnoreCase; } [Test] @@ -316,5 +787,54 @@ private string Key(AnotherEntity e) { return "Input=" + (e.Input ?? "NULL") + ", Output=" + (e.Output ?? "NULL"); } + + private Task ExpectAllAsync(IQueryable q, IResolveConstraint sqlConstraint) + { + return ExpectAsync(q, sqlConstraint, BothNull, BothSame, BothDifferent, InputSet, OutputSet); + } + + private async Task ExpectAsync(IQueryable q, IResolveConstraint sqlConstraint, params AnotherEntity[] entities) + { + IList results; + if (sqlConstraint == null) + { + results = await (GetResultsAsync(q)); + } + else + { + using (var sqlLog = new SqlLogSpy()) + { + results = await (GetResultsAsync(q)); + Assert.That(sqlLog.GetWholeLog(), sqlConstraint); + } + } + + IList check = entities.OrderBy(Key).ToList(); + + Assert.That(results.Count, Is.EqualTo(check.Count)); + for (var i = 0; i < check.Count; i++) + { + Assert.That(Key(results[i]), Is.EqualTo(Key(check[i]))); + } + } + + private async Task> GetResultsAsync(IQueryable q, CancellationToken cancellationToken = default(CancellationToken)) + { + return (await (q.ToListAsync(cancellationToken))).OrderBy(Key).ToList(); + } + + private static async Task ExpectAsync(IQueryable query, IResolveConstraint sqlConstraint, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var sqlLog = new SqlLogSpy()) + { + var list = await (query.ToListAsync(cancellationToken)); + Assert.That(sqlLog.GetWholeLog(), sqlConstraint); + } + } + + private static string Key(AnotherEntityRequired e) + { + return "Input=" + (e.Input ?? "NULL") + ", Output=" + (e.Output ?? "NULL"); + } } } diff --git a/src/NHibernate.Test/Async/Linq/ODataTests.cs b/src/NHibernate.Test/Async/Linq/ODataTests.cs new file mode 100644 index 00000000000..ec0cd901f11 --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/ODataTests.cs @@ -0,0 +1,223 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.AspNetCore.Http; +using Microsoft.OData.Edm; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.Linq +{ + using System.Threading.Tasks; + [TestFixture] + public class ODataTestsAsync : LinqTestCase + { + private IEdmModel _edmModel; + + protected override void OnSetUp() + { + base.OnSetUp(); + + _edmModel = CreatEdmModel(); + } + + [TestCase("$expand=Customer", 830, "Customer")] + [TestCase("$expand=OrderLines", 830, "OrderLines")] + public async Task ExpandAsync(string queryString, int expectedRows, string expandedProperty) + { + var query = ApplyFilter(session.Query(), queryString); + Assert.That(query, Is.AssignableTo>()); + + var results = await (((IQueryable) query).ToListAsync()); + Assert.That(results, Has.Count.EqualTo(expectedRows)); + + var dict = results[0].ToDictionary(); + Assert.That(dict.TryGetValue(expandedProperty, out var value), Is.True); + Assert.That(value, Is.Not.Null); + } + + [TestCase("$apply=groupby((Customer/CustomerId))", 89)] + [TestCase("$apply=groupby((Customer/CustomerId))&$orderby=Customer/CustomerId", 89)] + [TestCase("$apply=groupby((Customer/CustomerId, ShippingAddress/PostalCode), aggregate(OrderId with average as Average, Employee/EmployeeId with max as Max))", 89)] + [TestCase("$apply=groupby((Customer/CustomerId), aggregate(OrderId with sum as Total))&$skip=2", 87)] + public async Task OrderGroupByAsync(string queryString, int expectedRows) + { + var query = ApplyFilter(session.Query(), queryString); + Assert.That(query, Is.AssignableTo>()); + + var results = await (((IQueryable) query).ToListAsync()); + Assert.That(results, Has.Count.EqualTo(expectedRows)); + } + + private class CustomerVm : BaseCustomerVm + { + } + + private class BaseCustomerVm + { + public string Id { get; set; } + + public string Name { get; set; } + } + + [TestCase("$filter=Name eq 'Maria Anders'", 1)] + public async Task BasePropertyFilterAsync(string queryString, int expectedRows) + { + var query = ApplyFilter( + session.Query().Select(o => new CustomerVm {Name = o.ContactName, Id = o.CustomerId}), + queryString); + + var results = await (((IQueryable) query).ToListAsync()); + Assert.That(results, Has.Count.EqualTo(expectedRows)); + } + + //GH-2362 + [TestCase("$filter=CustomerId le 'ANATR'", 2)] + [TestCase("$filter=startswith(CustomerId, 'ANATR')", 1)] + [TestCase("$filter=endswith(CustomerId, 'ANATR')", 1)] + [TestCase("$filter=indexof(CustomerId, 'ANATR') eq 0", 1)] + public async Task StringFilterAsync(string queryString, int expectedCount) + { + Assert.That( + await (ApplyFilter(session.Query(), queryString).Cast().ToListAsync()), + Has.Count.EqualTo(expectedCount)); + } + + private IQueryable ApplyFilter(IQueryable query, string queryString) + { + var context = new ODataQueryContext(CreatEdmModel(), typeof(T), null) { }; + var dataQuerySettings = new ODataQuerySettings {HandleNullPropagation = HandleNullPropagationOption.False}; + var serviceProvider = new ODataServiceProvider( + new Dictionary() + { + {typeof(DefaultQuerySettings), new DefaultQuerySettings()}, + {typeof(ODataOptions), new ODataOptions()}, + {typeof(IEdmModel), _edmModel}, + {typeof(ODataQuerySettings), dataQuerySettings}, + }); + + HttpContext httpContext = new DefaultHttpContext(); + httpContext.ODataFeature().RequestContainer = serviceProvider; + httpContext.RequestServices = serviceProvider; + var request = httpContext.Request; + Uri requestUri = new Uri($"http://localhost/?{queryString}"); + request.Method = HttpMethods.Get; + request.Scheme = requestUri.Scheme; + request.Host = new HostString(requestUri.Host); + request.QueryString = new QueryString(requestUri.Query); + request.Path = new PathString(requestUri.AbsolutePath); + var options = new ODataQueryOptions(context, request); + + return options.ApplyTo(query, dataQuerySettings); + } + + private static IEdmModel CreatEdmModel() + { + var builder = new ODataConventionModelBuilder(); + + var addressModel = builder.ComplexType
(); + addressModel.Property(o => o.City); + addressModel.Property(o => o.Country); + addressModel.Property(o => o.Fax); + addressModel.Property(o => o.PhoneNumber); + addressModel.Property(o => o.PostalCode); + addressModel.Property(o => o.Region); + addressModel.Property(o => o.Street); + + var customerModel = builder.EntitySet(nameof(Customer)); + customerModel.EntityType.HasKey(o => o.CustomerId); + customerModel.EntityType.Property(o => o.CompanyName); + customerModel.EntityType.Property(o => o.ContactTitle); + customerModel.EntityType.ComplexProperty(o => o.Address); + customerModel.EntityType.HasMany(o => o.Orders); + + var orderLineModel = builder.EntitySet(nameof(OrderLine)); + orderLineModel.EntityType.HasKey(o => o.Id); + orderLineModel.EntityType.Property(o => o.Discount); + orderLineModel.EntityType.Property(o => o.Quantity); + orderLineModel.EntityType.Property(o => o.UnitPrice); + orderLineModel.EntityType.HasRequired(o => o.Order); + + var orderModel = builder.EntitySet(nameof(Order)); + orderModel.EntityType.HasKey(o => o.OrderId); + orderModel.EntityType.Property(o => o.Freight); + orderModel.EntityType.Property(o => o.OrderDate); + orderModel.EntityType.Property(o => o.RequiredDate); + orderModel.EntityType.Property(o => o.ShippedTo); + orderModel.EntityType.Property(o => o.ShippingDate); + orderModel.EntityType.ComplexProperty(o => o.ShippingAddress); + orderModel.EntityType.HasRequired(o => o.Customer); + orderModel.EntityType.HasOptional(o => o.Employee); + orderModel.EntityType.HasMany(o => o.OrderLines); + + var employeeModel = builder.EntitySet(nameof(Employee)); + employeeModel.EntityType.HasKey(o => o.EmployeeId); + employeeModel.EntityType.Property(o => o.BirthDate); + employeeModel.EntityType.Property(o => o.Extension); + employeeModel.EntityType.Property(o => o.FirstName); + employeeModel.EntityType.Property(o => o.HireDate); + employeeModel.EntityType.Property(o => o.LastName); + employeeModel.EntityType.Property(o => o.Notes); + employeeModel.EntityType.Property(o => o.Title); + employeeModel.EntityType.HasMany(o => o.Orders); + + builder.EntitySet(nameof(CustomerVm)); + + return builder.GetEdmModel(); + } + + private class ODataServiceProvider : IServiceProvider + { + private readonly Dictionary _singletonObjects = new Dictionary(); + + public ODataServiceProvider(Dictionary singletonObjects) + { + _singletonObjects = singletonObjects; + } + + public object GetService(System.Type serviceType) + { + if (_singletonObjects.TryGetValue(serviceType, out var service)) + { + return service; + } + + var ctor = serviceType.GetConstructor(new System.Type[0]); + if (ctor != null) + { + return ctor.Invoke(new object[0]); + } + + ctor = serviceType.GetConstructor(new[] { typeof(DefaultQuerySettings) }); + if (ctor != null) + { + return ctor.Invoke(new object[] { GetService(typeof(DefaultQuerySettings)) }); + } + + ctor = serviceType.GetConstructor(new[] { typeof(IServiceProvider) }); + if (ctor != null) + { + return ctor.Invoke(new object[] { this }); + } + + return null; + } + } + } +} diff --git a/src/NHibernate.Test/Async/Linq/PagingTests.cs b/src/NHibernate.Test/Async/Linq/PagingTests.cs index bce2a5d9ff8..6c72ddd1f4d 100644 --- a/src/NHibernate.Test/Async/Linq/PagingTests.cs +++ b/src/NHibernate.Test/Async/Linq/PagingTests.cs @@ -121,106 +121,6 @@ public async Task PageBetweenProjectionsReturningOrderedConstrainedNestedClassAs Assert.That(list, Has.Count.EqualTo(10)); } - [Test, Ignore("Not supported")] - public async Task PagedProductsWithOuterWhereClauseOrderedNestedAnonymousAsync() - { - // NH-2588 and NH-3326 - var inMemoryIds = (await (db.Products.ToListAsync())) - .OrderByDescending(x => x.ProductId) - .Select(p => new { p.ProductId, p.Name, p.UnitsInStock }) - .Skip(10).Take(20) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .Where(x => x.ProductId > 0) - .ToList(); - - var ids = await (db.Products - .OrderByDescending(x => x.ProductId) - .Select(p => new { p.ProductId, p.Name, p.UnitsInStock }) - .Skip(10).Take(20) - .Where(x => x.ProductId > 0) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .ToListAsync()); - - Assert.That(ids, Is.EqualTo(inMemoryIds)); - } - - [Test, Ignore("Not supported")] - public async Task PagedProductsWithOuterWhereClauseOrderedNestedAnonymousEquivalentAsync() - { - // NH-2588 and NH-3326 - var inMemoryIds = (await (db.Products.ToListAsync())) - .OrderByDescending(x => x.ProductId) - .Select(p => new { p.ProductId, p.Name, p.UnitsInStock }) - .Skip(10).Take(20) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .Where(x => x.ProductId > 0) - .ToList(); - - var subquery = db.Products - .OrderByDescending(x => x.ProductId) - .Select(p => new { p.ProductId, p.Name, p.UnitsInStock }) - .Skip(10).Take(20); - - var ids = await (db.Products - .Select(p => new { p.ProductId, p.Name, p.UnitsInStock }) - .Where(x => subquery.Contains(x)) - .Where(x => x.ProductId > 0) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .ToListAsync()); - - Assert.That(ids, Is.EqualTo(inMemoryIds)); - } - - [Test, Ignore("Not supported")] - public async Task PagedProductsWithOuterWhereClauseOrderedNestedClassAsync() - { - // NH-2588 and NH-3326 - var inMemoryIds = (await (db.Products.ToListAsync())) - .OrderByDescending(x => x.ProductId) - .Select(p => new ProductProjection { ProductId = p.ProductId, Name = p.Name }) - .Skip(10).Take(20) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .Where(x => x.ProductId > 0) - .ToList(); - - var ids = await (db.Products - .OrderByDescending(x => x.ProductId) - .Select(p => new ProductProjection { ProductId = p.ProductId, Name = p.Name }) - .Skip(10).Take(20) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .Where(x => x.ProductId > 0) - .ToListAsync()); - - Assert.That(ids, Is.EqualTo(inMemoryIds)); - } - - [Test, Ignore("Not supported")] - public async Task PagedProductsWithOuterWhereClauseOrderedNestedClassEquivalentAsync() - { - // NH-2588 and NH-3326 - var inMemoryIds = (await (db.Products.ToListAsync())) - .OrderByDescending(x => x.ProductId) - .Select(p => new ProductProjection { ProductId = p.ProductId, Name = p.Name }) - .Skip(10).Take(20) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .Where(x => x.ProductId > 0) - .ToList(); - - var subquery = db.Products - .OrderByDescending(x => x.ProductId) - .Select(p => new ProductProjection { ProductId = p.ProductId, Name = p.Name }) - .Skip(10).Take(20); - - var ids = await (db.Products - .Select(p => new ProductProjection { ProductId = p.ProductId, Name = p.Name }) - .Where(x => subquery.Contains(x)) - .Where(x => x.ProductId > 0) - .Select(a => new { ExpandedElement = a, a.Name, a.ProductId }) - .ToListAsync()); - - Assert.That(ids, Is.EqualTo(inMemoryIds)); - } - [Test] public async Task Customers1to5Async() { @@ -262,39 +162,6 @@ orderby c.CustomerId Assert.AreEqual(20, query.Count); } - [Test] - [Ignore("Multiple Takes (or Skips) not handled correctly")] - public async Task CustomersChainedTakeAsync() - { - var q = (from c in db.Customers - orderby c.CustomerId - select c.CustomerId).Take(5).Take(6); - - var query = await (q.ToListAsync()); - - Assert.AreEqual(5, query.Count); - Assert.AreEqual("ALFKI", query[0]); - Assert.AreEqual("BLAUS", query[4]); - } - - [Test] - [Ignore("Multiple Takes (or Skips) not handled correctly")] - public async Task CustomersChainedSkipAsync() - { - var q = (from c in db.Customers select c.CustomerId).Skip(10).Skip(5); - var query = await (q.ToListAsync()); - Assert.AreEqual(query[0], "CONSH"); - Assert.AreEqual(76, query.Count); - } - - [Test] - [Ignore("Count with Skip or Take is incorrect (Skip / Take done on the query not the HQL, so get applied at the wrong point")] - public async Task CountAfterTakeShouldReportTheCorrectNumberAsync() - { - var users = db.Customers.Skip(3).Take(10); - Assert.AreEqual(10, await (users.CountAsync())); - } - [Test] public async Task OrderedPagedProductsWithOuterProjectionAsync() { diff --git a/src/NHibernate.Test/Async/Linq/ParameterTests.cs b/src/NHibernate.Test/Async/Linq/ParameterTests.cs new file mode 100644 index 00000000000..e5d239f7ed2 --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/ParameterTests.cs @@ -0,0 +1,938 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text.RegularExpressions; +using NHibernate.Dialect; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Driver; +using NHibernate.Engine.Query; +using NHibernate.Linq; +using NHibernate.Util; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace NHibernate.Test.Linq +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public class ParameterTestsAsync : LinqTestCase + { + [Test] + public async Task UsingArrayParameterTwiceAsync() + { + var ids = new[] {11008, 11019, 11039}; + await (AssertTotalParametersAsync( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)), + ids.Length, + 1)); + } + + [Test] + public async Task UsingTwoArrayParametersAsync() + { + var ids = new[] {11008, 11019, 11039}; + var ids2 = new[] {11008, 11019, 11039}; + await (AssertTotalParametersAsync( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)), + ids.Length + ids2.Length, + 2)); + } + + [Test] + public async Task UsingListParameterTwiceAsync() + { + var ids = new List {11008, 11019, 11039}; + await (AssertTotalParametersAsync( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)), + ids.Count, + 1)); + } + + [Test] + public async Task UsingTwoListParametersAsync() + { + var ids = new List {11008, 11019, 11039}; + var ids2 = new List {11008, 11019, 11039}; + await (AssertTotalParametersAsync( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)), + ids.Count + ids2.Count, + 2)); + } + + [Test] + public async Task UsingEntityParameterTwiceAsync() + { + var order = await (db.Orders.FirstAsync()); + await (AssertTotalParametersAsync( + db.Orders.Where(o => o == order && o != order), + 1)); + } + + [Test] + public async Task UsingEntityParameterForCollectionAsync() + { + var item = await (db.OrderLines.FirstAsync()); + await (AssertTotalParametersAsync( + db.Orders.Where(o => o.OrderLines.Contains(item)), + 1)); + } + + [Test] + public async Task UsingProxyParameterForCollectionAsync() + { + var item = await (session.LoadAsync(10248)); + Assert.That(NHibernateUtil.IsInitialized(item), Is.False); + await (AssertTotalParametersAsync( + db.Customers.Where(o => o.Orders.Contains(item)), + 1)); + } + + [Test] + public async Task UsingFieldProxyParameterForCollectionAsync() + { + var item = await (session.Query().FirstAsync()); + await (AssertTotalParametersAsync( + session.Query().Where(o => o.RequiredRelatedItems.Contains(item)), + 1)); + } + + [Test] + public async Task UsingEntityParameterInSubQueryAsync() + { + var item = await (db.Customers.FirstAsync()); + var subQuery = db.Orders.Select(o => o.Customer).Where(o => o == item); + await (AssertTotalParametersAsync( + db.Orders.Where(o => subQuery.Contains(o.Customer)), + 1)); + } + + [Test] + public async Task UsingEntityParameterForCollectionSelectionAsync() + { + var item = await (db.OrderLines.FirstAsync()); + await (AssertTotalParametersAsync( + db.Orders.SelectMany(o => o.OrderLines).Where(o => o == item), + 1)); + } + + [Test] + public async Task UsingFieldProxyParameterForCollectionSelectionAsync() + { + var item = await (session.Query().FirstAsync()); + await (AssertTotalParametersAsync( + session.Query().SelectMany(o => o.RequiredRelatedItems).Where(o => o == item), + 1)); + } + + [Test] + public async Task UsingEntityListParameterForCollectionSelectionAsync() + { + var items = new[] {await (db.OrderLines.FirstAsync())}; + await (AssertTotalParametersAsync( + db.Orders.SelectMany(o => o.OrderLines).Where(o => items.Contains(o)), + 1)); + } + + [Test] + public async Task UsingFieldProxyListParameterForCollectionSelectionAsync() + { + var items = new[] {await (session.Query().FirstAsync())}; + await (AssertTotalParametersAsync( + session.Query().SelectMany(o => o.RequiredRelatedItems).Where(o => items.Contains(o)), + 1)); + } + + [Test] + public async Task UsingTwoEntityParametersAsync() + { + var order = await (db.Orders.FirstAsync()); + var order2 = await (db.Orders.FirstAsync()); + await (AssertTotalParametersAsync( + db.Orders.Where(o => o == order && o != order2), + 2)); + } + + [Test] + public async Task UsingEntityEnumerableParameterTwiceAsync() + { + if (!Dialect.SupportsSubSelects) + { + Assert.Ignore(); + } + + var enumerable = await (db.DynamicUsers.FirstAsync()); + await (AssertTotalParametersAsync( + db.DynamicUsers.Where(o => o == enumerable && o != enumerable), + 1)); + } + + [Test] + public async Task UsingEntityEnumerableListParameterTwiceAsync() + { + if (!Dialect.SupportsSubSelects) + { + Assert.Ignore(); + } + + var enumerable = new[] {await (db.DynamicUsers.FirstAsync())}; + await (AssertTotalParametersAsync( + db.DynamicUsers.Where(o => enumerable.Contains(o) && enumerable.Contains(o)), + 1)); + } + + [Test] + public async Task UsingValueTypeParameterTwiceAsync() + { + var value = 1; + await (AssertTotalParametersAsync( + db.Orders.Where(o => o.OrderId == value && o.OrderId != value), + 1)); + } + + [Test] + public async Task CompareIntegralParametersAndColumnsAsync() + { + short shortParam = 1; + var intParam = 2; + var longParam = 3L; + short? nullShortParam = 1; + int? nullIntParam = 2; + long? nullLongParam = 3L; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Short == shortParam || o.Short < intParam || o.Short > longParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == shortParam || o.NullableShort <= intParam || o.NullableShort != longParam), "Int16"}, + {db.NumericEntities.Where(o => o.Short == nullShortParam || o.Short < nullIntParam || o.Short > nullLongParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == nullShortParam || o.NullableShort <= nullIntParam || o.NullableShort != nullLongParam), "Int16"}, + + {db.NumericEntities.Where(o => o.Integer == shortParam || o.Integer < intParam || o.Integer > longParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == shortParam || o.NullableInteger <= intParam || o.NullableInteger != longParam), "Int32"}, + {db.NumericEntities.Where(o => o.Integer == nullShortParam || o.Integer < nullIntParam || o.Integer > nullLongParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == nullShortParam || o.NullableInteger <= nullIntParam || o.NullableInteger != nullLongParam), "Int32"}, + + {db.NumericEntities.Where(o => o.Long == shortParam || o.Long < intParam || o.Long > longParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == shortParam || o.NullableLong <= intParam || o.NullableLong != longParam), "Int64"}, + {db.NumericEntities.Where(o => o.Long == nullShortParam || o.Long < nullIntParam || o.Long > nullLongParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == nullShortParam || o.NullableLong <= nullIntParam || o.NullableLong != nullLongParam), "Int64"} + }; + + foreach (var pair in queriables) + { + // Parameters should be pre-evaluated + await (AssertTotalParametersAsync( + pair.Key, + 3, + sql => + { + Assert.That(sql, Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(3)); + })); + } + } + + [Test] + public async Task CompareIntegralParametersWithFloatingPointColumnsAsync() + { + short shortParam = 1; + var intParam = 2; + var longParam = 3L; + short? nullShortParam = 1; + int? nullIntParam = 2; + long? nullLongParam = 3L; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Decimal == shortParam || o.Decimal < intParam || o.Decimal > longParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == shortParam || o.NullableDecimal <= intParam || o.NullableDecimal != longParam), "Decimal"}, + {db.NumericEntities.Where(o => o.Decimal == nullShortParam || o.Decimal < nullIntParam || o.Decimal > nullLongParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == nullShortParam || o.NullableDecimal <= nullIntParam || o.NullableDecimal != nullLongParam), "Decimal"}, + + {db.NumericEntities.Where(o => o.Single == shortParam || o.Single < intParam || o.Single > longParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == shortParam || o.NullableSingle <= intParam || o.NullableSingle != longParam), "Single"}, + {db.NumericEntities.Where(o => o.Single == nullShortParam || o.Single < nullIntParam || o.Single > nullLongParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == nullShortParam || o.NullableSingle <= nullIntParam || o.NullableSingle != nullLongParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double == shortParam || o.Double < intParam || o.Double > longParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == shortParam || o.NullableDouble <= intParam || o.NullableDouble != longParam), "Double"}, + {db.NumericEntities.Where(o => o.Double == nullShortParam || o.Double < nullIntParam || o.Double > nullLongParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == nullShortParam || o.NullableDouble <= nullIntParam || o.NullableDouble != nullLongParam), "Double"}, + }; + + foreach (var pair in queriables) + { + // Parameters should be pre-evaluated + await (AssertTotalParametersAsync( + pair.Key, + 3, + sql => + { + Assert.That(sql, Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(3)); + })); + } + } + + [Test] + public async Task CompareFloatingPointParametersAndColumnsAsync() + { + var decimalParam = 1.1m; + var singleParam = 2.2f; + var doubleParam = 3.3d; + decimal? nullDecimalParam = 1.1m; + float? nullSingleParam = 2.2f; + double? nullDoubleParam = 3.3d; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Decimal == decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.Decimal == nullDecimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == nullDecimalParam), "Decimal"}, + + {db.NumericEntities.Where(o => o.Single <= singleParam || o.Single >= doubleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.NullableSingle != doubleParam), "Single"}, + {db.NumericEntities.Where(o => o.Single <= nullSingleParam || o.Single >= nullDoubleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == nullSingleParam || o.NullableSingle != nullDoubleParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double <= singleParam || o.Double >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == singleParam || o.NullableDouble != doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Double <= nullSingleParam || o.Double >= nullDoubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == nullSingleParam || o.NullableDouble != nullDoubleParam), "Double"}, + }; + + foreach (var pair in queriables) + { + var totalParameters = pair.Value == "Decimal" ? 1 : 2; + // Parameters should be pre-evaluated + await (AssertTotalParametersAsync( + pair.Key, + totalParameters, + sql => + { + Assert.That(sql, Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(totalParameters)); + })); + } + } + + [Test] + public async Task CompareFloatingPointParametersWithIntegralColumnsAsync() + { + var decimalParam = 1.1m; + var singleParam = 2.2f; + var doubleParam = 3.3d; + decimal? nullDecimalParam = 1.1m; + float? nullSingleParam = 2.2f; + double? nullDoubleParam = 3.3d; + var queriables = new List> + { + db.NumericEntities.Where(o => o.Short == decimalParam || o.Short != singleParam || o.Short <= doubleParam), + db.NumericEntities.Where(o => o.NullableShort <= decimalParam || o.NullableShort == singleParam || o.NullableShort >= doubleParam), + db.NumericEntities.Where(o => o.Short == nullDecimalParam || o.Short != nullSingleParam || o.Short <= nullDoubleParam), + db.NumericEntities.Where(o => o.NullableShort <= nullDecimalParam || o.NullableShort == nullSingleParam || o.NullableShort >= nullDoubleParam), + + db.NumericEntities.Where(o => o.Integer == decimalParam || o.Integer != singleParam || o.Integer <= doubleParam), + db.NumericEntities.Where(o => o.NullableInteger <= decimalParam || o.NullableInteger == singleParam || o.NullableInteger >= doubleParam), + db.NumericEntities.Where(o => o.Integer == nullDecimalParam || o.Integer != nullSingleParam || o.Integer <= nullDoubleParam), + db.NumericEntities.Where(o => o.NullableInteger <= nullDecimalParam || o.NullableInteger == nullSingleParam || o.NullableInteger >= nullDoubleParam), + + db.NumericEntities.Where(o => o.Long == decimalParam || o.Long != singleParam || o.Long <= doubleParam), + db.NumericEntities.Where(o => o.NullableLong <= decimalParam || o.NullableLong == singleParam || o.NullableLong >= doubleParam), + db.NumericEntities.Where(o => o.Long == nullDecimalParam || o.Long != nullSingleParam || o.Long <= nullDoubleParam), + db.NumericEntities.Where(o => o.NullableLong <= nullDecimalParam || o.NullableLong == nullSingleParam || o.NullableLong >= nullDoubleParam), + }; + + foreach (var query in queriables) + { + // Columns should be casted + await (AssertTotalParametersAsync( + query, + 3, + sql => + { + var matches = Regex.Matches(sql, @"cast\([\w\d]+\..+\)"); + Assert.That(matches.Count, Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, $"Type: Decimal"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: Single"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: Double"), Is.EqualTo(1)); + })); + } + } + + [Test] + public async Task CompareFloatingPointParameterWithIntegralAndFloatingPointColumnsAsync() + { + var decimalParam = 1.1m; + var singleParam = 2.2f; + var doubleParam = 3.3d; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Decimal == decimalParam || o.NullableShort >= decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.Decimal == decimalParam || o.Long >= decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == decimalParam || o.Integer != decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == decimalParam || o.NullableInteger == decimalParam), "Decimal"}, + + {db.NumericEntities.Where(o => o.Single == singleParam || o.NullableShort >= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.Single == singleParam || o.Long >= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.Integer != singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.NullableInteger == singleParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double == doubleParam || o.NullableShort >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Double == doubleParam || o.Long >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.Integer != doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.NullableInteger == doubleParam), "Double"} + }; + var odbcDriver = Sfi.ConnectionProvider.Driver is OdbcDriver; + + foreach (var pair in queriables) + { + // Integral columns should be casted + await (AssertTotalParametersAsync( + pair.Key, + 1, + sql => + { + var matches = Regex.Matches(sql, @"cast\([\w\d]+\..+\)"); + Assert.That(matches.Count, Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(odbcDriver ? 2 : 1)); + })); + } + } + + [Test] + public async Task CompareFloatingPointParameterWithDifferentFloatingPointColumnsAsync() + { + if (Sfi.Dialect is FirebirdDialect) + { + Assert.Ignore("Due to the regex hack in FirebirdClientDriver, the parameters can be casted twice."); + } + + var singleParam = 2.2f; + var doubleParam = 3.3d; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Single == singleParam || o.Double >= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.Double >= singleParam || o.Single == singleParam), "Single"}, + {db.NumericEntities.Where(o => o.Single == singleParam || o.NullableDouble <= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.Double != singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.NullableDouble == singleParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double == doubleParam || o.Single >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Single >= doubleParam || o.Double == doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Double == doubleParam || o.NullableSingle >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.Single != doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.NullableSingle == doubleParam), "Double"} + }; + var sameType = Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Single.SqlType, out var singleCast) && + Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Double.SqlType, out var doubleCast) && + singleCast == doubleCast; + var odbcDriver = Sfi.ConnectionProvider.Driver is OdbcDriver; + + foreach (var pair in queriables) + { + // Columns should be casted for Double parameter and parameters for Single parameter + await (AssertTotalParametersAsync( + pair.Key, + 1, + sql => + { + var matches = pair.Value == "Double" + ? Regex.Matches(sql, @"cast\([\w\d]+\..+\)") + : Regex.Matches(sql, @"cast\(((@|\?|:)p\d+|\?)\s+as.*\)"); + // SQLiteDialect uses sql cast for transparentcast method + Assert.That(matches.Count, Is.EqualTo(sameType && !(Sfi.Dialect is SQLiteDialect) ? 0 : 1)); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(odbcDriver ? 2 : 1)); + })); + } + } + + [Test] + public async Task CompareIntegralParameterWithIntegralAndFloatingPointColumnsAsync() + { + if (Sfi.Dialect is FirebirdDialect) + { + Assert.Ignore("Due to the regex hack in FirebirdClientDriver, the parameters can be casted twice."); + } + + short shortParam = 1; + var intParam = 2; + var longParam = 3L; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Short == shortParam || o.Double >= shortParam), "Int16"}, + {db.NumericEntities.Where(o => o.Short == shortParam || o.NullableDouble >= shortParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == shortParam || o.Decimal != shortParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == shortParam || o.NullableSingle > shortParam), "Int16"}, + + {db.NumericEntities.Where(o => o.Integer == intParam || o.Double >= intParam), "Int32"}, + {db.NumericEntities.Where(o => o.Integer == intParam || o.NullableDouble >= intParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == intParam || o.Decimal != intParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == intParam || o.NullableSingle > intParam), "Int32"}, + + {db.NumericEntities.Where(o => o.Long == longParam || o.Double >= longParam), "Int64"}, + {db.NumericEntities.Where(o => o.Long == longParam || o.NullableDouble >= longParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == longParam || o.Decimal != longParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == longParam || o.NullableSingle > longParam), "Int64"} + }; + var odbcDriver = Sfi.ConnectionProvider.Driver is OdbcDriver; + + foreach (var pair in queriables) + { + // Parameters should be casted + await (AssertTotalParametersAsync( + pair.Key, + 1, + sql => + { + var matches = Regex.Matches(sql, @"cast\(((@|\?|:)p\d+|\?)\s+as.*\)"); + Assert.That(matches.Count, Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(odbcDriver ? 2 : 1)); + })); + } + } + + [Test] + public async Task UsingValueTypeParameterOfDifferentTypeAsync() + { + var value = 1; + var queriables = new List> + { + db.NumericEntities.Where(o => o.Short == value), + db.NumericEntities.Where(o => o.Short != value), + db.NumericEntities.Where(o => o.Short >= value), + db.NumericEntities.Where(o => o.Short <= value), + db.NumericEntities.Where(o => o.Short > value), + db.NumericEntities.Where(o => o.Short < value), + + db.NumericEntities.Where(o => o.NullableShort == value), + db.NumericEntities.Where(o => o.NullableShort != value), + db.NumericEntities.Where(o => o.NullableShort >= value), + db.NumericEntities.Where(o => o.NullableShort <= value), + db.NumericEntities.Where(o => o.NullableShort > value), + db.NumericEntities.Where(o => o.NullableShort < value), + + db.NumericEntities.Where(o => o.NullableShort.Value == value), + db.NumericEntities.Where(o => o.NullableShort.Value != value), + db.NumericEntities.Where(o => o.NullableShort.Value >= value), + db.NumericEntities.Where(o => o.NullableShort.Value <= value), + db.NumericEntities.Where(o => o.NullableShort.Value > value), + db.NumericEntities.Where(o => o.NullableShort.Value < value) + }; + + foreach (var query in queriables) + { + await (AssertTotalParametersAsync( + query, + 1, + sql => Assert.That(sql, Does.Not.Contain("cast")))); + } + + if (Sfi.Dialect is FirebirdDialect) + { + Assert.Ignore("Due to the regex bug in FirebirdClientDriver, the parameters are not casted."); + } + + queriables = new List> + { + db.NumericEntities.Where(o => o.Short + value > value), + db.NumericEntities.Where(o => o.Short - value > value), + db.NumericEntities.Where(o => o.Short * value > value), + + db.NumericEntities.Where(o => o.NullableShort + value > value), + db.NumericEntities.Where(o => o.NullableShort - value > value), + db.NumericEntities.Where(o => o.NullableShort * value > value), + + db.NumericEntities.Where(o => o.NullableShort.Value + value > value), + db.NumericEntities.Where(o => o.NullableShort.Value - value > value), + db.NumericEntities.Where(o => o.NullableShort.Value * value > value), + }; + + var sameType = Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Int16.SqlType, out var shortCast) && + Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Int32.SqlType, out var intCast) && + shortCast == intCast; + foreach (var query in queriables) + { + await (AssertTotalParametersAsync( + query, + 1, + sql => { + // SQLiteDialect uses sql cast for transparentcast method + Assert.That(sql, !sameType || Sfi.Dialect is SQLiteDialect ? Does.Match("where\\s+cast") : (IResolveConstraint)Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, "cast"), Is.EqualTo(!sameType || Sfi.Dialect is SQLiteDialect ? 1 : 0)); + })); + } + } + + [Test] + public async Task UsingValueTypeParameterTwiceOnNullablePropertyAsync() + { + short value = 1; + await (AssertTotalParametersAsync( + db.NumericEntities.Where(o => o.NullableShort == value && o.NullableShort != value && o.Short == value), + 1, sql => { + + Assert.That(GetTotalOccurrences(sql, "cast"), Is.EqualTo(0)); + })); + } + + [Test] + public async Task UsingValueTypeParameterOnDifferentPropertiesAsync() + { + int value = 1; + await (AssertTotalParametersAsync( + db.NumericEntities.Where(o => o.NullableShort == value && o.NullableShort != value && o.Integer == value), + 1)); + + await (AssertTotalParametersAsync( + db.NumericEntities.Where(o => o.Integer == value && o.NullableShort == value && o.NullableShort != value), + 1)); + } + + [Test] + public async Task UsingParameterInEvaluatableExpressionAsync() + { + var value = "test"; + await (db.Orders.Where(o => string.Format("{0}", value) != o.ShippedTo).ToListAsync()); + await (db.Orders.Where(o => $"{value}_" != o.ShippedTo).ToListAsync()); + await (db.Orders.Where(o => string.Copy(value) != o.ShippedTo).ToListAsync()); + + var guid = Guid.Parse("2D7E6EB3-BD08-4A40-A4E7-5150F7895821"); + await (db.Orders.Where(o => o.ShippedTo.Contains($"VALUE {guid}")).ToListAsync()); + + var names = new[] {"name"}; + await (db.Users.Where(x => names.Length == 0 || names.Contains(x.Name)).ToListAsync()); + names = new string[] { }; + await (db.Users.Where(x => names.Length == 0 || names.Contains(x.Name)).ToListAsync()); + } + + [Test] + public async Task UsingParameterOnSelectorsAsync() + { + var user = new User() {Id = 1}; + await (db.Users.Where(o => o == user).ToListAsync()); + await (db.Users.FirstOrDefaultAsync(o => o == user)); + await (db.Timesheets.Where(o => o.Users.Any(u => u == user)).ToListAsync()); + + var users = new[] {new User() {Id = 1}}; + await (db.Users.Where(o => users.Contains(o)).ToListAsync()); + await (db.Users.FirstOrDefaultAsync(o => users.Contains(o))); + await (db.Timesheets.Where(o => o.Users.Any(u => users.Contains(u))).ToListAsync()); + } + + [Test] + public async Task UsingNegateValueTypeParameterTwiceAsync() + { + var value = 1; + await (AssertTotalParametersAsync( + db.Orders.Where(o => o.OrderId == -value && o.OrderId != -value), + 1)); + } + + [Test] + public async Task UsingNegateValueTypeParameterAsync() + { + var value = 1; + await (AssertTotalParametersAsync( + db.Orders.Where(o => o.OrderId == value && o.OrderId != -value), + 1)); + } + + [Test] + public async Task UsingValueTypeParameterInArrayAsync() + { + var id = 11008; + await (AssertTotalParametersAsync( + db.Orders.Where(o => new[] {id, 11019}.Contains(o.OrderId) && new[] {id, 11019}.Contains(o.OrderId)), + 4, + 2)); + } + + [Test] + public async Task UsingTwoValueTypeParametersAsync() + { + var value = 1; + var value2 = 1; + await (AssertTotalParametersAsync( + db.Orders.Where(o => o.OrderId == value && o.OrderId != value2), + 2)); + } + + [Test] + public async Task UsingStringParameterTwiceAsync() + { + var value = "test"; + await (AssertTotalParametersAsync( + db.Products.Where(o => o.Name == value && o.Name != value), + 1)); + } + + [Test] + public async Task UsingTwoStringParametersAsync() + { + var value = "test"; + var value2 = "test"; + await (AssertTotalParametersAsync( + db.Products.Where(o => o.Name == value && o.Name != value2), + 2)); + } + + [Test] + public async Task UsingObjectPropertyParameterTwiceAsync() + { + var value = new Product {Name = "test"}; + await (AssertTotalParametersAsync( + db.Products.Where(o => o.Name == value.Name && o.Name != value.Name), + 1)); + } + + [Test] + public async Task UsingTwoObjectPropertyParametersAsync() + { + var value = new Product {Name = "test"}; + var value2 = new Product {Name = "test"}; + await (AssertTotalParametersAsync( + db.Products.Where(o => o.Name == value.Name && o.Name != value2.Name), + 2)); + } + + [Test] + public async Task UsingParameterInWhereSkipTakeAsync() + { + var value3 = 1; + var q1 = db.Products.Where(o => o.ProductId < value3).Take(value3).Skip(value3); + await (AssertTotalParametersAsync(q1, 3)); + } + + [Test] + public async Task UsingParameterInTwoWhereAsync() + { + var value3 = 1; + var q1 = db.Products.Where(o => o.ProductId < value3).Where(o => o.ProductId < value3); + await (AssertTotalParametersAsync(q1, 1)); + } + + [Test] + public async Task UsingObjectNestedPropertyParameterTwiceAsync() + { + var value = new Employee {Superior = new Employee {Superior = new Employee {FirstName = "test"}}}; + await (AssertTotalParametersAsync( + db.Employees.Where(o => o.FirstName == value.Superior.Superior.FirstName && o.FirstName != value.Superior.Superior.FirstName), + 1)); + } + + [Test] + public async Task UsingDifferentObjectNestedPropertyParameterAsync() + { + var value = new Employee {Superior = new Employee {FirstName = "test", Superior = new Employee {FirstName = "test"}}}; + await (AssertTotalParametersAsync( + db.Employees.Where(o => o.FirstName == value.Superior.FirstName && o.FirstName != value.Superior.Superior.FirstName), + 2)); + } + + [Test] + public async Task UsingMethodObjectPropertyParameterTwiceAsync() + { + var value = new Product {Name = "test"}; + await (AssertTotalParametersAsync( + db.Products.Where(o => o.Name == value.Name.Trim() && o.Name != value.Name.Trim()), + 2)); + } + + [Test] + public async Task UsingStaticMethodObjectPropertyParameterTwiceAsync() + { + var value = new Product {Name = "test"}; + await (AssertTotalParametersAsync( + db.Products.Where(o => o.Name == string.Copy(value.Name) && o.Name != string.Copy(value.Name)), + 2)); + } + + [Test] + public async Task UsingObjectPropertyParameterWithSecondLevelClosureAsync() + { + var value = new Product {Name = "test"}; + Expression> predicate = o => o.Name == value.Name && o.Name != value.Name; + await (AssertTotalParametersAsync( + db.Products.Where(predicate), + 1)); + } + + [Test] + public async Task UsingObjectPropertyParameterWithThirdLevelClosureAsync() + { + var value = new Product {Name = "test"}; + Expression> orderLinePredicate = o => o.Order.ShippedTo == value.Name && o.Order.ShippedTo != value.Name; + Expression> predicate = o => o.Name == value.Name && o.OrderLines.AsQueryable().Any(orderLinePredicate); + await (AssertTotalParametersAsync( + db.Products.Where(predicate), + 1)); + } + + [Test] + public async Task UsingParameterInDMLInsertIntoFourTimesAsync() + { + var value = "test"; + await (AssertTotalParametersAsync( + QueryMode.Insert, + db.Customers.Where(c => c.CustomerId == value), + x => new Customer {CustomerId = value, ContactName = value, CompanyName = value}, + 4)); + } + + [Test] + public async Task UsingFourParametersInDMLInsertIntoAsync() + { + var value = "test"; + var value2 = "test"; + var value3 = "test"; + var value4 = "test"; + await (AssertTotalParametersAsync( + QueryMode.Insert, + db.Customers.Where(c => c.CustomerId == value3), + x => new Customer {CustomerId = value4, ContactName = value2, CompanyName = value}, + 4)); + } + + [Test] + public async Task UsingParameterInDMLUpdateThreeTimesAsync() + { + var value = "test"; + await (AssertTotalParametersAsync( + QueryMode.Update, + db.Customers.Where(c => c.CustomerId == value), + x => new Customer {ContactName = value, CompanyName = value}, + 3)); + } + + [Test] + public async Task UsingThreeParametersInDMLUpdateAsync() + { + var value = "test"; + var value2 = "test"; + var value3 = "test"; + await (AssertTotalParametersAsync( + QueryMode.Update, + db.Customers.Where(c => c.CustomerId == value3), + x => new Customer { ContactName = value2, CompanyName = value }, + 3)); + } + + [Test] + public async Task UsingParameterInDMLDeleteTwiceAsync() + { + var value = "test"; + await (AssertTotalParametersAsync( + QueryMode.Delete, + db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value), + 2)); + } + + [Test] + public async Task UsingTwoParametersInDMLDeleteAsync() + { + var value = "test"; + var value2 = "test"; + await (AssertTotalParametersAsync( + QueryMode.Delete, + db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value2), + 2)); + } + + private Task AssertTotalParametersAsync(IQueryable query, int parameterNumber, Action sqlAction, CancellationToken cancellationToken = default(CancellationToken)) + { + return AssertTotalParametersAsync(query, parameterNumber, null, sqlAction, cancellationToken); + } + + private async Task AssertTotalParametersAsync(IQueryable query, int parameterNumber, int? linqParameterNumber = null, Action sqlAction = null, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var sqlSpy = new SqlLogSpy()) + { + // In case of arrays linqParameterNumber and parameterNumber will be different + Assert.That( + GetLinqExpression(query).ParameterValuesByName.Count, + Is.EqualTo(linqParameterNumber ?? parameterNumber), + "Linq expression has different number of parameters"); + + var queryPlanCacheType = typeof(QueryPlanCache); + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + await (query.ToListAsync(cancellationToken)); + + sqlAction?.Invoke(sqlSpy.GetWholeLog()); + + // In case of arrays two query plans will be stored, one with an one without expended parameters + Assert.That(cache, Has.Count.EqualTo(linqParameterNumber.HasValue ? 2 : 1), "Query should be cacheable"); + + AssertParameters(sqlSpy, parameterNumber); + } + } + + private static Task AssertTotalParametersAsync(QueryMode queryMode, IQueryable query, int parameterNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + return AssertTotalParametersAsync(queryMode, query, null, parameterNumber, cancellationToken); + } + + private static async Task AssertTotalParametersAsync(QueryMode queryMode, IQueryable query, Expression> expression, int parameterNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + var provider = query.Provider as INhQueryProvider; + Assert.That(provider, Is.Not.Null); + + var dmlExpression = expression != null + ? DmlExpressionRewriter.PrepareExpression(query.Expression, expression) + : query.Expression; + + using (var sqlSpy = new SqlLogSpy()) + { + Assert.That(await (provider.ExecuteDmlAsync(queryMode, dmlExpression, cancellationToken)), Is.EqualTo(0), "The DML query updated the data"); // Avoid updating the data + AssertParameters(sqlSpy, parameterNumber); + } + } + + private static void AssertParameters(SqlLogSpy sqlSpy, int parameterNumber) + { + var sqlParameters = sqlSpy.GetWholeLog().Split(';')[1]; + var matches = Regex.Matches(sqlParameters, @"([\d\w]+)[\s]+\=", RegexOptions.IgnoreCase); + + // Due to ODBC drivers not supporting parameter names, we have to do a distinct of parameter names. + var distinctParameters = matches.OfType().Select(m => m.Groups[1].Value.Trim()).Distinct().ToList(); + Assert.That(distinctParameters, Has.Count.EqualTo(parameterNumber)); + } + + private NhLinqExpression GetLinqExpression(QueryMode queryMode, IQueryable query, Expression> expression) + { + return GetLinqExpression(queryMode, DmlExpressionRewriter.PrepareExpression(query.Expression, expression)); + } + + private NhLinqExpression GetLinqExpression(QueryMode queryMode, IQueryable query) + { + return GetLinqExpression(queryMode, query.Expression); + } + + private NhLinqExpression GetLinqExpression(IQueryable query) + { + return GetLinqExpression(QueryMode.Select, query.Expression); + } + + private NhLinqExpression GetLinqExpression(QueryMode queryMode, Expression expression) + { + return queryMode == QueryMode.Select + ? new NhLinqExpression(expression, Sfi) + : new NhLinqDmlExpression(queryMode, expression, Sfi); + } + } +} diff --git a/src/NHibernate.Test/Async/Linq/PreEvaluationTests.cs b/src/NHibernate.Test/Async/Linq/PreEvaluationTests.cs new file mode 100644 index 00000000000..1f55f7b6cb8 --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/PreEvaluationTests.cs @@ -0,0 +1,152 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.SqlTypes; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; +using NHibernate.Linq; + +namespace NHibernate.Test.Linq +{ + using System.Threading.Tasks; + [TestFixture(false, false)] + [TestFixture(true, false)] + [TestFixture(false, true)] + public class PreEvaluationTestsAsync : LinqTestCase + { + private readonly bool LegacyPreEvaluation; + private readonly bool FallbackOnPreEvaluation; + + public PreEvaluationTestsAsync(bool legacy, bool fallback) + { + LegacyPreEvaluation = legacy; + FallbackOnPreEvaluation = fallback; + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + configuration.SetProperty(Environment.FormatSql, "false"); + configuration.SetProperty(Environment.LinqToHqlLegacyPreEvaluation, LegacyPreEvaluation.ToString()); + configuration.SetProperty(Environment.LinqToHqlFallbackOnPreEvaluation, FallbackOnPreEvaluation.ToString()); + } + + private void RunTest(bool isSupported, Action test) + { + using (var spy = new SqlLogSpy()) + { + try + { + test(spy); + } + catch (QueryException) + { + if (!isSupported && !FallbackOnPreEvaluation) + // Expected failure + return; + throw; + } + } + + if (!isSupported && !FallbackOnPreEvaluation) + Assert.Fail("The test should have thrown a QueryException, but has not thrown anything"); + } + + [Test] + public async Task CanQueryByRandomIntAsync() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + var idMin = await (db.Orders.MinAsync(o => o.OrderId)); + RunTest( + isSupported, + spy => + { + var random = new Random(); + // Dodge a Firebird driver limitation by putting the constants before the order id. + // This driver cast parameters to their types in some cases for avoiding Firebird complaining of not + // knowing the type of the condition. For some reasons the driver considers the casting should not be + // done next to the conditional operator. Having the cast only on one side is enough for avoiding + // Firebird complain, so moving the constants on the left side have been put before the order id, in + // order for these constants to be casted by the driver. + var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next()); + + Assert.That(x, Is.GreaterThan(0)); + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public async Task CanQueryByRandomIntWithMaxAsync() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + var idMin = await (db.Orders.MinAsync(o => o.OrderId)); + RunTest( + isSupported, + spy => + { + var random = new Random(); + // Dodge a Firebird driver limitation by putting the constants before the order id. + // This driver cast parameters to their types in some cases for avoiding Firebird complaining of not + // knowing the type of the condition. For some reasons the driver considers the casting should not be + // done next to the conditional operator. Having the cast only on one side is enough for avoiding + // Firebird complain, so moving the constants on the left side have been put before the order id, in + // order for these constants to be casted by the driver. + var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10)); + + Assert.That(x, Is.GreaterThan(0).And.LessThan(11)); + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public async Task CanQueryByRandomIntWithMinMaxAsync() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + var idMin = await (db.Orders.MinAsync(o => o.OrderId)); + RunTest( + isSupported, + spy => + { + var random = new Random(); + // Dodge a Firebird driver limitation by putting the constants before the order id. + // This driver cast parameters to their types in some cases for avoiding Firebird complaining of not + // knowing the type of the condition. For some reasons the driver considers the casting should not be + // done next to the conditional operator. Having the cast only on one side is enough for avoiding + // Firebird complain, so moving the constants on the left side have been put before the order id, in + // order for these constants to be casted by the driver. + var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10)); + + Assert.That(x, Is.GreaterThan(0).And.LessThan(10)); + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + private void AssertFunctionInSql(string functionName, SqlLogSpy spy) + { + if (!IsFunctionSupported(functionName)) + Assert.Inconclusive($"{functionName} is not supported by the dialect"); + + var function = Dialect.Functions[functionName].Render(new List(), Sfi).ToString(); + + if (LegacyPreEvaluation) + Assert.That(spy.GetWholeLog(), Does.Not.Contain(function)); + else + Assert.That(spy.GetWholeLog(), Does.Contain(function)); + } + } +} diff --git a/src/NHibernate.Test/Async/Linq/ProjectionsTests.cs b/src/NHibernate.Test/Async/Linq/ProjectionsTests.cs index 9d25bac82cd..ecd7db50a9d 100644 --- a/src/NHibernate.Test/Async/Linq/ProjectionsTests.cs +++ b/src/NHibernate.Test/Async/Linq/ProjectionsTests.cs @@ -109,7 +109,6 @@ orderby user.Name Assert.AreEqual("nhibernate nhibernate", query[1].DoubleName); Assert.AreEqual("rahien rahien", query[2].DoubleName); - Assert.AreEqual(new DateTime(2010, 06, 17), query[0].RegisteredAt); Assert.AreEqual(new DateTime(2000, 1, 1), query[1].RegisteredAt); Assert.AreEqual(new DateTime(1998, 12, 31), query[2].RegisteredAt); @@ -127,7 +126,6 @@ orderby user.Id Assert.AreEqual("rahien", query[1].Key); Assert.AreEqual("nhibernate", query[2].Key); - Assert.AreEqual(new DateTime(2010, 06, 17), query[0].Value); Assert.AreEqual(new DateTime(1998, 12, 31), query[1].Value); Assert.AreEqual(new DateTime(2000, 1, 1), query[2].Value); @@ -144,7 +142,6 @@ orderby user.Id Assert.AreEqual("rahien", query[1].Name); Assert.AreEqual("nhibernate", query[2].Name); - Assert.AreEqual(new DateTime(2010, 06, 17), query[0].RegisteredAt); Assert.AreEqual(new DateTime(1998, 12, 31), query[1].RegisteredAt); Assert.AreEqual(new DateTime(2000, 1, 1), query[2].RegisteredAt); @@ -523,7 +520,6 @@ private string FormatName(string name, DateTime? lastLoginDate) return string.Format("User {0} logged in at {1}", name, lastLoginDate); } - /// /// This mimic classes in System.Data.Services.Internal. /// diff --git a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 8a91aea24ff..54a8a6cfc44 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -12,6 +12,7 @@ using NHibernate.Cfg; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Linq; +using NHibernate.Transform; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -336,7 +337,6 @@ public async Task FetchIsCachableAsync() Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); - } [Test] @@ -407,13 +407,102 @@ public async Task FutureFetchIsCachableAsync() Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); } + + [Explicit("Not working. dto.Customer retrieved from cache as uninitialized proxy")] + [Test] + public async Task ProjectedEntitiesAreCachableAsync() + { + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + var dto = await (session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new { x.Customer, Order = x }) + .FirstOrDefaultAsync()); + + Assert.That(dto, Is.Not.Null, "dto should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + session.Clear(); + + dto = await (session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new { x.Customer, Order = x }) + .FirstOrDefaultAsync()); + + Assert.That(dto, Is.Not.Null, "dto from cache should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order from cache should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() + { + if (!TestDialect.SupportsDuplicatedColumnAliases) + Assert.Ignore("Ignored due to GH-2092"); + + Sfi.Statistics.Clear(); + await (Sfi.EvictQueriesAsync()); + + // the combination of query and transformer doesn't make sense. + // It's simply used as example of returned data being transformed before caching leading to mismatch between + // Loader.ResultTypes collection and provided tuple + var order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResultAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + + session.Clear(); + Sfi.Statistics.Clear(); + + order = await (session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResultAsync()); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + } private static void AssertFetchedOrder(Order order) { + Assert.That(NHibernateUtil.IsInitialized(order), "Expected the order to be initialized"); + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized"); Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items"); var orderLine = order.OrderLines.First(); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized"); Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); } diff --git a/src/NHibernate.Test/Async/Linq/QueryFlushModeTests.cs b/src/NHibernate.Test/Async/Linq/QueryFlushModeTests.cs new file mode 100644 index 00000000000..54216c4642e --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/QueryFlushModeTests.cs @@ -0,0 +1,177 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.Linq +{ + using System.Threading.Tasks; + [TestFixture] + public class QueryFlushModeTestsAsync : LinqTestCase + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(configuration); + } + + [Test] + public async Task CanSetFlushModeOnQueriesAsync( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] + FlushMode flushMode) + { + Sfi.Statistics.Clear(); + + using (var t = session.BeginTransaction()) + { + var customer = await (db.Customers.FirstAsync()); + customer.CompanyName = "Blah"; + + var unused = + await (db.Customers + .Where(c => c.CompanyName == "Bon app'") + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToListAsync()); + + var expectedFlushCount = 0; + switch (flushMode) + { + case FlushMode.Always: + case FlushMode.Auto: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on same entity query"); + + customer.CompanyName = "Other blah"; + + var dummy = + await (db.Orders + .Where(o => o.OrderId > 10) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToListAsync()); + + switch (flushMode) + { + case FlushMode.Always: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on other entity query"); + + // Tests here should not alter data, LinqTestCase derives from ReadonlyTestCase + await (t.RollbackAsync()); + } + } + + [Test] + public async Task CanSetCommentOnPagingQueryAsync( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] + FlushMode flushMode) + { + Sfi.Statistics.Clear(); + + using (var t = session.BeginTransaction()) + { + var customer = await (db.Customers.FirstAsync()); + customer.CompanyName = "Blah"; + + var unused = + await (db.Customers + .Skip(1).Take(1) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToListAsync()); + + var expectedFlushCount = 0; + switch (flushMode) + { + case FlushMode.Always: + case FlushMode.Auto: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on same entity query"); + + customer.CompanyName = "Other blah"; + + var dummy = + await (db.Orders + .Skip(1).Take(1) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToListAsync()); + + switch (flushMode) + { + case FlushMode.Always: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on other entity query"); + + // Tests here should not alter data, LinqTestCase derives from ReadonlyTestCase + await (t.RollbackAsync()); + } + } + + [Test] + public async Task CanSetCommentBeforeSkipOnOrderedPageQueryAsync( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] + FlushMode flushMode) + { + Sfi.Statistics.Clear(); + + using (var t = session.BeginTransaction()) + { + var customer = await (db.Customers.FirstAsync()); + customer.CompanyName = "Blah"; + + var unused = + await (db.Customers + .OrderBy(c => c.CompanyName) + .Skip(5).Take(5) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToListAsync()); + + var expectedFlushCount = 0; + switch (flushMode) + { + case FlushMode.Always: + case FlushMode.Auto: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on same entity query"); + + customer.CompanyName = "Other blah"; + + var dummy = + await (db.Orders + .OrderBy(o => o.OrderId) + .Skip(5).Take(5) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToListAsync()); + + switch (flushMode) + { + case FlushMode.Always: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on other entity query"); + + // Tests here should not alter data, LinqTestCase derives from ReadonlyTestCase + await (t.RollbackAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/Linq/QueryLock.cs b/src/NHibernate.Test/Async/Linq/QueryLock.cs index 4ecb0404c6c..bcbc069c3cc 100644 --- a/src/NHibernate.Test/Async/Linq/QueryLock.cs +++ b/src/NHibernate.Test/Async/Linq/QueryLock.cs @@ -19,7 +19,6 @@ using NHibernate.Linq; using NUnit.Framework; - namespace NHibernate.Test.Linq { using System.Threading.Tasks; @@ -211,18 +210,18 @@ orderby e.CompanyName { // TODO: We should try to verify that the exception actually IS a locking failure and not something unrelated. Assert.ThrowsAsync( - async () => + async () => { var result2 = await (( - from e in s2.Query() - where e.CustomerId == customerId - select e - ).WithLock(LockMode.UpgradeNoWait) - .WithOptions(o => o.SetTimeout(5)) - .ToListAsync(cancellationToken)); + from e in s2.Query() + where e.CustomerId == customerId + select e + ).WithLock(LockMode.UpgradeNoWait) + .WithOptions(o => o.SetTimeout(5)) + .ToListAsync(cancellationToken)); Assert.That(result2, Is.Not.Null); }, - "Expected an exception to indicate locking failure due to already locked."); + "Expected an exception to indicate locking failure due to already locked."); } return Task.CompletedTask; } diff --git a/src/NHibernate.Test/Async/Linq/QueryTimeoutTests.cs b/src/NHibernate.Test/Async/Linq/QueryTimeoutTests.cs index 2f3cdba95a4..d7ae3c3917e 100644 --- a/src/NHibernate.Test/Async/Linq/QueryTimeoutTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryTimeoutTests.cs @@ -37,7 +37,6 @@ protected override void Configure(Configuration configuration) typeof(TimeoutCatchingNonBatchingBatcherFactory).AssemblyQualifiedName); } - [Test] public async Task CanSetTimeoutOnLinqQueriesAsync() { @@ -50,7 +49,6 @@ public async Task CanSetTimeoutOnLinqQueriesAsync() Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - [Test] public async Task CanSetTimeoutOnLinqPagingQueryAsync() { @@ -64,7 +62,6 @@ public async Task CanSetTimeoutOnLinqPagingQueryAsync() Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - [Test] public async Task CanSetTimeoutBeforeSkipOnLinqOrderedPageQueryAsync() { @@ -78,7 +75,6 @@ orderby e.CompanyName Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - [Test] public async Task CanSetTimeoutOnLinqGroupPageQueryAsync() { @@ -98,13 +94,11 @@ into g Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - public partial class TimeoutCatchingNonBatchingBatcher : NonBatchingBatcher { // Is there an easier way to inspect the DbCommand instead of // creating a custom batcher? - public static int LastCommandTimeout; public TimeoutCatchingNonBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor) @@ -125,7 +119,6 @@ public override DbDataReader ExecuteReader(DbCommand cmd) } } - public partial class TimeoutCatchingNonBatchingBatcherFactory : IBatcherFactory { public IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor) @@ -137,7 +130,6 @@ public IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor public partial class QueryTimeoutTests : LinqTestCase { - public partial class TimeoutCatchingNonBatchingBatcher : NonBatchingBatcher { diff --git a/src/NHibernate.Test/Async/Linq/SelectionTests.cs b/src/NHibernate.Test/Async/Linq/SelectionTests.cs index b4ac7e372e4..8d3e0556cc8 100644 --- a/src/NHibernate.Test/Async/Linq/SelectionTests.cs +++ b/src/NHibernate.Test/Async/Linq/SelectionTests.cs @@ -11,7 +11,9 @@ using System; using System.Collections.Generic; using System.Linq; +using NHibernate.DomainModel.NHSpecific; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Type; using NUnit.Framework; using NHibernate.Linq; @@ -287,6 +289,14 @@ public async Task CanSelectFirstElementFromChildCollectionAsync() } } + [Test] + public async Task CanSelectNotMappedEntityPropertyAsync() + { + var list = await (db.Animals.Where(o => o.Mother != null).Select(o => o.FatherOrMother.SerialNumber).ToListAsync()); + + Assert.That(list, Has.Count.GreaterThan(0)); + } + [Test] public async Task CanProjectWithCastAsync() { @@ -307,6 +317,10 @@ public async Task CanProjectWithCastAsync() var names5 = await (db.Users.Select(p => new { p1 = (p as IUser).Name }).ToListAsync()); Assert.AreEqual(3, names5.Count); + + var names6 = await (db.Users.Select(p => new { p1 = (long) p.Id }).ToListAsync()); + Assert.AreEqual(3, names6.Count); + // ReSharper restore RedundantCast } @@ -439,6 +453,39 @@ public async Task CanSelectConditionalEntityValueWithEntityComparisonAsync() Assert.That(fatherInsteadOfChild, Has.Exactly(2).EqualTo("5678")); } + [Test] + public async Task CanExecuteMethodWithNullObjectAndSubselectAsync() + { + var list1 = await (db.Animals.Select( + a => new + { + NullableId = (int?) a.Father.Father.Id, + }) + .ToListAsync()); + Assert.That(list1, Has.Count.GreaterThan(0)); + Assert.That(list1[0].NullableId, Is.Null); + + var list2 = await (db.Animals.Select( + a => new + { + Descriptions = a.Children.Select(z => z.Description) + }) + .ToListAsync()); + Assert.That(list2, Has.Count.GreaterThan(0)); + Assert.That(list2[0].Descriptions, Is.Not.Null); + + var list3 = await (db.Animals.Select( + a => new + { + NullableId = (int?) a.Father.Father.Id, + Descriptions = a.Children.Select(z => z.Description) + }) + .ToListAsync()); + Assert.That(list3, Has.Count.GreaterThan(0)); + Assert.That(list3[0].NullableId, Is.Null); + Assert.That(list3[0].Descriptions, Is.Not.Null); + } + [Test] public async Task CanSelectConditionalEntityValueWithEntityComparisonRepeatAsync() { @@ -453,6 +500,23 @@ public async Task CanSelectConditionalObjectAsync() Assert.That(fatherIsKnown, Has.Exactly(1).With.Property("FatherIsKnown").True); } + [Test] + public async Task CanCastToDerivedTypeAsync() + { + var dogs = await (db.Animals + .Where(a => ((Dog) a).Pregnant) + .Select(a => new {a.SerialNumber}) + .ToListAsync()); + Assert.That(dogs, Has.Exactly(1).With.Property("SerialNumber").Not.Null); + } + + [Test] + public async Task CanCastToCustomRegisteredTypeAsync() + { + TypeFactory.RegisterType(typeof(NullableInt32), new NullableInt32Type(), Enumerable.Empty()); + Assert.That(await (db.Users.Where(o => (NullableInt32) o.Id == 1).ToListAsync()), Has.Count.EqualTo(1)); + } + public class Wrapper { public T item; diff --git a/src/NHibernate.Test/Async/Linq/WhereSubqueryTests.cs b/src/NHibernate.Test/Async/Linq/WhereSubqueryTests.cs index 7ec1685319a..e21d2ca8969 100644 --- a/src/NHibernate.Test/Async/Linq/WhereSubqueryTests.cs +++ b/src/NHibernate.Test/Async/Linq/WhereSubqueryTests.cs @@ -11,9 +11,10 @@ using System; using System.Linq; using System.Linq.Expressions; +using NHibernate.Dialect; using NHibernate.DomainModel.Northwind.Entities; -using NUnit.Framework; using NHibernate.Linq; +using NUnit.Framework; namespace NHibernate.Test.Linq { @@ -113,34 +114,6 @@ public async Task TimeSheetsWithAverageSubqueryReversedAsync() Assert.That(query.Count, Is.EqualTo(1)); } - [Test] - [Ignore("Need to coalesce the subquery - timesheet with no entries should return average of 0, not null")] - public async Task TimeSheetsWithAverageSubqueryComparedToPropertyAsync() - { - if (!Dialect.SupportsScalarSubSelects) - Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); - - var query = await ((from timesheet in db.Timesheets - where timesheet.Entries.Average(e => e.NumberOfHours) < timesheet.Id - select timesheet).ToListAsync()); - - Assert.That(query.Count, Is.EqualTo(1)); - } - - [Test] - [Ignore("Need to coalesce the subquery - timesheet with no entries should return average of 0, not null")] - public async Task TimeSheetsWithAverageSubqueryComparedToPropertyReversedAsync() - { - if (!Dialect.SupportsScalarSubSelects) - Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); - - var query = await ((from timesheet in db.Timesheets - where timesheet.Id > timesheet.Entries.Average(e => e.NumberOfHours) - select timesheet).ToListAsync()); - - Assert.That(query.Count, Is.EqualTo(1)); - } - [Test] public async Task TimeSheetsWithMaxSubqueryAsync() { @@ -271,34 +244,6 @@ public async Task TimeSheetsWithSumSubqueryReversedAsync() Assert.That(query.Count, Is.EqualTo(1)); } - [Test] - [Ignore("Need to coalesce the subquery - timesheet with no entries should return sum of 0, not null")] - public async Task TimeSheetsWithSumSubqueryComparedToPropertyAsync() - { - if (!Dialect.SupportsScalarSubSelects) - Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); - - var query = await ((from timesheet in db.Timesheets - where timesheet.Entries.Sum(e => e.NumberOfHours) <= timesheet.Id - select timesheet).ToListAsync()); - - Assert.That(query.Count, Is.EqualTo(1)); - } - - [Test] - [Ignore("Need to coalesce the subquery - timesheet with no entries should return sum of 0, not null")] - public async Task TimeSheetsWithSumSubqueryComparedToPropertyReversedAsync() - { - if (!Dialect.SupportsScalarSubSelects) - Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); - - var query = await ((from timesheet in db.Timesheets - where timesheet.Id >= timesheet.Entries.Sum(e => e.NumberOfHours) - select timesheet).ToListAsync()); - - Assert.That(query.Count, Is.EqualTo(1)); - } - [Test] public async Task TimeSheetsWithStringContainsSubQueryAsync() { @@ -384,6 +329,17 @@ where timesheet.Entries.AsQueryable().Any(e => !new[] { 1 }.Contains(e.Id)) Assert.That(query.Count, Is.EqualTo(2)); } + [Test(Description = "GH-2471")] + public async Task TimeSheetsWithStringContainsSubQueryWithAsQueryableAfterWhereAsync() + { + var query = await (( + from timesheet in db.Timesheets + where timesheet.Entries.Where(e => e.Comments != null).AsQueryable().Any(e => e.Comments.Contains("testing")) + select timesheet).ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(2)); + } + [Test(Description = "NH-3002")] public async Task HqlOrderLinesWithInnerJoinAndSubQueryAsync() { @@ -521,6 +477,86 @@ where subquery.Any(x => x.OrderId == order.OrderId) Assert.That(query.Count, Is.EqualTo(61)); } + [Test] + public async Task OrdersWithSubquery9CountAsync() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); + + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId) + .Take(2); + + var orderLines = await (db.OrderLines + .Where(x => ordersQuery.Count(o => o == x.Order) > 0) + .OrderBy(x => x.Id) + .ToListAsync()); + + Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines)); + } + + [Test] + public async Task OrdersWithSubquery9SumAsync() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); + + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId) + .Take(2); + + var orderLines = await (db.OrderLines + .Where(x => ordersQuery.Where(o => o == x.Order).Sum(o => o.Freight.Value) > 0) + .OrderBy(x => x.Id) + .ToListAsync()); + + Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines)); + } + + [Test(Description = "GH2479")] + public async Task OrdersWithSubquery11Async() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + if (Dialect is MsSqlCeDialect) + Assert.Ignore("MS SQL CE does not support sorting on a subquery."); + + var ordersQuery = db.Orders + .OrderByDescending(x => x.OrderLines.Count).ThenBy(x => x.OrderId) + .Take(2); + + var orderLineQuery = ordersQuery.SelectMany(x => x.OrderLines); + var productsNotInLargestOrders = await (db.Products + .Where(x => orderLineQuery.All(p => p.Product != x)) + .OrderBy(x => x.ProductId) + .ToListAsync()); + + Assert.That(productsNotInLargestOrders.Count, Is.EqualTo(49), nameof(productsNotInLargestOrders)); + } + + [Test] + public async Task OrdersWithSubquery11AAsync() + { + var ordersQuery = db.Orders + .OrderByDescending(x => x.OrderLines.Count).ThenBy(x => x.OrderId); + + var orderLineQuery = ordersQuery.SelectMany(x => x.OrderLines); + var productsNotInLargestOrders = await (db.Products + .Where(x => orderLineQuery.All(p => p.Product != x)) + .OrderBy(x => x.ProductId) + .ToListAsync()); + + Assert.That(productsNotInLargestOrders.Count, Is.EqualTo(0), nameof(productsNotInLargestOrders)); + } + [Test(Description = "NH-2654")] public async Task CategoriesWithDiscountedProductsAsync() { @@ -592,21 +628,6 @@ where categories.Contains(p.Category) Assert.That(result.Count, Is.EqualTo(13)); } - [Test(Description = "NH-3155"), Ignore("Not fixed yet.")] - public async Task SubqueryWithGroupByAsync() - { - var sq = db.Orders - .GroupBy(x => x.ShippingDate) - .Select(x => x.Max(o => o.OrderId)); - - var result = await (db.Orders - .Where(x => sq.Contains(x.OrderId)) - .Select(x => x.OrderId) - .ToListAsync()); - - Assert.That(result.Count, Is.EqualTo(388)); - } - [Test(Description = "NH-3111")] public async Task SubqueryWhereFailingTestAsync() { @@ -709,28 +730,6 @@ where c.Products.OrderBy(p => p.ProductId).Select(p => p.Discontinued).FirstOrDe Assert.That(result.Count, Is.EqualTo(7)); } - [Test(Description = "NH-3190")] - [Ignore("Not fixed yet.")] - public async Task ProductsWithSubqueryReturningProjectionBoolFirstOrDefaultEqAsync() - { - if (!Dialect.SupportsScalarSubSelects) - Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); - - if (!TestDialect.SupportsOrderByAndLimitInSubQueries) - Assert.Ignore("Dialect does not support sub-selects with order by or limit/top"); - - //NH-3190 - var result = await ((from p in db.Products - where (from c in db.Categories - where c.Name == "Confections" - && c == p.Category - select new{R=p.Discontinued}).FirstOrDefault().R == false - select p) - .ToListAsync()); - - Assert.That(result.Count, Is.EqualTo(13)); - } - [Test(Description = "NH-3190")] public async Task ProductsWithSubqueryReturningStringFirstOrDefaultEqAsync() { @@ -751,7 +750,6 @@ public async Task ProductsWithSubqueryReturningStringFirstOrDefaultEqAsync() Assert.That(result.Count, Is.EqualTo(13)); } - [Test(Description = "NH-3423")] public async Task NullComparedToNewExpressionInWhereClauseAsync() { @@ -790,5 +788,30 @@ public async Task NullComparedToMemberInitExpressionInWhereClauseAsync() Assert.That(result.Count, Is.EqualTo(45)); } + + public class Specification + { + private Expression> _expression; + + public Specification(Expression> expression) + { + _expression = expression; + } + + public static implicit operator Expression>(Specification specification) + { + return specification._expression; + } + } + + [Test] + public async Task ImplicitConversionInsideWhereSubqueryExpressionAsync() + { + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); + + var spec = new Specification(x => x.Freight > 1000); + await (db.Orders.Where(o => db.Orders.Where(spec).Any(x => x.OrderId == o.OrderId)).ToListAsync()); + } } } diff --git a/src/NHibernate.Test/Async/Linq/WhereTests.cs b/src/NHibernate.Test/Async/Linq/WhereTests.cs index c5a293f03d9..3afa7cda451 100644 --- a/src/NHibernate.Test/Async/Linq/WhereTests.cs +++ b/src/NHibernate.Test/Async/Linq/WhereTests.cs @@ -14,14 +14,17 @@ using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; +using log4net.Core; using NHibernate.Engine.Query; using NHibernate.Linq; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Linq.Functions; using NUnit.Framework; namespace NHibernate.Test.Linq { using System.Threading.Tasks; + using System.Threading; [TestFixture] public class WhereTestsAsync : LinqTestCase { @@ -142,7 +145,6 @@ public async Task UsersRegisteredAtOrAfterY2KAsync() Assert.That(query.Count, Is.EqualTo(2)); } - [Test] public async Task UsersRegisteredAtOrAfterY2K_And_Before2001Async() { @@ -361,7 +363,6 @@ public async Task UsersWithStringContainsAndNotNullNameComplicatedAsync() await (query.ToListAsync()); } - [Test] [Description("NH-3337")] public async Task ProductWithDoubleStringContainsAndNotNullAsync() @@ -411,7 +412,6 @@ public async Task ProductWithDoubleStringContainsAndNotNullAsync() var results = await (db.Products.Where(expr).ToListAsync()); Assert.That(results, Has.Count.EqualTo(1)); } - [Test(Description = "NH-3261")] public async Task UsersWithStringContainsAndNotNullNameAsync() @@ -433,6 +433,34 @@ public async Task UsersWithStringContainsAndNotNullNameHQLAsync() Assert.That(users.Count, Is.EqualTo(1)); } + [Test] + public void StringComparisonParamEmitsWarningAsync() + { + Assert.Multiple( + async () => + { + await (AssertStringComparisonWarningAsync(x => string.Compare(x.CustomerId, "ANATR", StringComparison.Ordinal) <= 0, 2)); + await (AssertStringComparisonWarningAsync(x => x.CustomerId.StartsWith("ANATR", StringComparison.Ordinal), 1)); + await (AssertStringComparisonWarningAsync(x => x.CustomerId.EndsWith("ANATR", StringComparison.Ordinal), 1)); + await (AssertStringComparisonWarningAsync(x => x.CustomerId.IndexOf("ANATR", StringComparison.Ordinal) == 0, 1)); + await (AssertStringComparisonWarningAsync(x => x.CustomerId.IndexOf("ANATR", 0, StringComparison.Ordinal) == 0, 1)); +#if NETCOREAPP2_0 + await (AssertStringComparisonWarningAsync(x => x.CustomerId.Replace("AN", "XX", StringComparison.Ordinal) == "XXATR", 1)); +#endif + }); + } + + private async Task AssertStringComparisonWarningAsync(Expression> whereParam, int expected, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var log = new LogSpy(typeof(BaseHqlGeneratorForMethod))) + { + var customers = await (session.Query().Where(whereParam).ToListAsync(cancellationToken)); + + Assert.That(customers, Has.Count.EqualTo(expected), whereParam.ToString); + Assert.That(log.GetWholeLog(), Does.Contain($"parameter of type '{nameof(StringComparison)}' is ignored"), whereParam.ToString); + } + } + [Test] public async Task UsersWithArrayContainsAsync() { @@ -510,19 +538,6 @@ where names.Contains(user.Name) Assert.That(query.Count, Is.EqualTo(0)); } - [Test] - [Ignore("Inline empty list expression does not evaluate correctly")] - public async Task UsersWithEmptyInlineEnumerableAsync() - { - var allNames = new List { "ayende", "rahien" }; - - var query = await ((from user in db.Users - where allNames.Where(n => n == "does not exist").Contains(user.Name) - select user).ToListAsync()); - - Assert.That(query.Count, Is.EqualTo(0)); - } - [Test] public void WhenTheSourceOfConstantIsICollectionThenNoThrowsAsync() { @@ -774,7 +789,6 @@ public async Task AnimalsWithFathersSerialNumberListContainsWithLocalVariableAsy Assert.That(query.Count, Is.EqualTo(1)); } - [Test(Description = "NH-3366")] public async Task CanUseCompareInQueryWithNonConstantZeroAsync() { @@ -794,7 +808,6 @@ public async Task CanUseCompareInQueryWithNonConstantZeroAsync() } } - [Test(Description = "NH-3366")] [TestCaseSource(typeof(WhereTestsAsync), nameof(CanUseCompareInQueryDataSource))] public async Task CanUseCompareInQueryAsync(Expression> expression, int expectedCount, bool expectCase) @@ -810,7 +823,6 @@ public async Task CanUseCompareInQueryAsync(Expression> expr } } - [Test(Description = "NH-3665")] public async Task SelectOnCollectionReturnsResultAsync() { diff --git a/src/NHibernate.Test/Async/LinqBulkManipulation/Fixture.cs b/src/NHibernate.Test/Async/LinqBulkManipulation/Fixture.cs index bb8b9291a5e..57657ece5f1 100644 --- a/src/NHibernate.Test/Async/LinqBulkManipulation/Fixture.cs +++ b/src/NHibernate.Test/Async/LinqBulkManipulation/Fixture.cs @@ -300,6 +300,76 @@ public async Task InsertWithClientSideRequirementsThrowsExceptionAsync() } } + [Test(Description = "GH2594")] + public async Task InsertIntoWithSubqueryAsync() + { + if(!Dialect.SupportsScalarSubSelects) + Assert.Ignore("Dialect does not support scalar sub-select"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s + .Query() + .InsertIntoAsync( + x => new Pickup + { + Id = -s.Query().Where(c => c.Id == x.Id).Select(c => c.Id).FirstOrDefault(), + Vin = x.Vin, + Owner = x.Owner + })); + + await (t.CommitAsync()); + } + } + + [Test] + public async Task UpdateWithSubqueryAsync() + { + if(!TestDialect.SupportsModifyAndSelectSameTable || !Dialect.SupportsScalarSubSelects) + Assert.Ignore("Not supported by dialect"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s + .Query() + .UpdateAsync( + x => new + { + Id = -s.Query().Where(c => c.Id == x.Id).Select(c => c.Id).FirstOrDefault(), + Vin = x.Vin, + Owner = x.Owner + })); + + await (t.CommitAsync()); + } + } + + [Test] + public async Task MultiTableUpdateWithSubqueryAsync() + { + if (!Dialect.SupportsTemporaryTables) + Assert.Ignore("Cannot perform multi-table updates using dialect not supporting temp tables."); + + if(!TestDialect.SupportsModifyAndSelectSameTable || !Dialect.SupportsScalarSubSelects) + Assert.Ignore("Not supported by dialect"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s + .Query() + .UpdateAsync( + x => new + { + Description = s.Query().Where(c => c.Id == x.Id).Select(c => c.Description).FirstOrDefault(), + })); + + await (t.CommitAsync()); + } + } + [Test] public async Task InsertWithManyToOneAsync() { @@ -467,9 +537,8 @@ public async Task InsertWithSelectListUsingJoinsAsync() // this is just checking parsing and syntax... using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - Assert.DoesNotThrowAsync(() => { return s @@ -477,7 +546,7 @@ public async Task InsertWithSelectListUsingJoinsAsync() .InsertIntoAsync(x => new Animal { Description = x.Description, BodyWeight = x.BodyWeight }); }); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -760,7 +829,6 @@ public async Task UpdateOnDiscriminatorSubclassAsync() } using (var t = s.BeginTransaction()) { - var count = await (s.Query().UpdateBuilder().Set(y => y.Name, y => y.Name).UpdateAsync()); Assert.That(count, Is.EqualTo(2), "Incorrect discrim subclass update count"); @@ -804,7 +872,6 @@ public async Task UpdateOnAnimalAsync() await (s.Query().UpdateBuilder().Set(y => y.FireTemperature, 300).UpdateAsync()); Assert.That(count, Is.EqualTo(1), "Incorrect entity-updated count"); - count = await (s.Query().UpdateBuilder().Set(y => y.BodyWeight, y => y.BodyWeight + 1 + 1).UpdateAsync()); Assert.That(count, Is.EqualTo(10), "incorrect count on 'complex' update assignment"); @@ -985,11 +1052,29 @@ public async Task DeleteWithSubqueryAsync() } using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); var count = await (s.Query().Where(x => x.AssociatedEntities.Count == 0 && x.Name.Contains("myEntity")).DeleteAsync()); Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); + } + } + + [Test] + public async Task DeleteWithSubquery2Async() + { + if(!TestDialect.SupportsModifyAndSelectSameTable || !Dialect.SupportsScalarSubSelects) + Assert.Ignore("Not supported by dialect"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s + .Query() + .Where(x => x.Id == -s.Query().Where(c => c.Id == x.Id).Select(c => c.Id).FirstOrDefault()) + .DeleteAsync()); + + await (t.CommitAsync()); } } @@ -1017,7 +1102,6 @@ public async Task SimpleDeleteOnAnimalAsync() using (var s = OpenSession()) using (var t = s.BeginTransaction()) { - var count = await (s.Query().Where(x => x.Id == _polliwog.Id).DeleteAsync()); Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); diff --git a/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs new file mode 100644 index 00000000000..3fc06fb5a44 --- /dev/null +++ b/src/NHibernate.Test/Async/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -0,0 +1,265 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Data.Common; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NHibernate.MultiTenancy; +using NHibernate.Util; +using NUnit.Framework; + +namespace NHibernate.Test.MultiTenancy +{ + using System.Threading.Tasks; + [TestFixture] + public class DatabaseStrategyNoDbSpecificFixtureAsync : TestCaseMappingByCode + { + private Guid _id; + + protected override void Configure(Configuration configuration) + { + configuration.DataBaseIntegration( + x => + { + x.MultiTenancy = MultiTenancyStrategy.Database; + x.MultiTenancyConnectionProvider(); + }); + configuration.Properties[Cfg.Environment.GenerateStatistics] = "true"; + base.Configure(configuration); + } + + private static void ValidateSqlServerConnectionAppName(ISession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + + private static void ValidateSqlServerConnectionAppName(IStatelessSession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + + [Test] + public async Task SecondLevelCacheReusedForSameTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.GetAsync(_id)); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen2.GetAsync(_id)); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public async Task SecondLevelCacheSeparationPerTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.GetAsync(_id)); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = await (sesTen2.GetAsync(_id)); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(0)); + } + + [Test] + public async Task QueryCacheReusedForSameTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public async Task QueryCacheSeparationPerTenantAsync() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = await (sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + } + + [Test] + public async Task TenantSessionIsSerializableAndCanBeReconnectedAsync() + { + ISession deserializedSession = null; + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = await (sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefaultAsync()); + sesTen1.Disconnect(); + deserializedSession = SpoofSerialization(sesTen1); + } + + Sfi.Statistics.Clear(); + using (deserializedSession) + { + deserializedSession.Reconnect(); + + //Expect session cache hit + var entity = await (deserializedSession.GetAsync(_id)); + if (IsSqlServerDialect) + ValidateSqlServerConnectionAppName(deserializedSession, "tenant1"); + deserializedSession.Clear(); + + //Expect second level cache hit + await (deserializedSession.GetAsync(_id)); + Assert.That(GetTenantId(deserializedSession), Is.EqualTo("tenant1")); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + private static string GetTenantId(ISession session) + { + return session.GetSessionImplementation().GetTenantIdentifier(); + } + + private static string GetTenantId(IStatelessSession session) + { + return session.GetSessionImplementation().GetTenantIdentifier(); + } + + private T SpoofSerialization(T session) + { + var formatter = new BinaryFormatter + { +#if !NETFX + SurrogateSelector = new SerializationHelper.SurrogateSelector() +#endif + }; + var stream = new MemoryStream(); + formatter.Serialize(stream, session); + + stream.Position = 0; + + return (T) formatter.Deserialize(stream); + } + + private ISession OpenTenantSession(string tenantId) + { + return Sfi.WithOptions().Tenant(GetTenantConfig(tenantId)).OpenSession(); + } + + private IStatelessSession OpenTenantStatelessSession(string tenantId) + { + return Sfi.WithStatelessOptions().Tenant(GetTenantConfig(tenantId)).OpenStatelessSession(); + } + + private TenantConfiguration GetTenantConfig(string tenantId) + { + return new TestTenantConfiguration(tenantId, IsSqlServerDialect); + } + + private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); + + #region Test Setup + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + rc => + { + rc.Cache(m => m.Usage(CacheUsage.NonstrictReadWrite)); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override DbConnection OpenConnectionForSchemaExport() + { + return Sfi.Settings.MultiTenancyConnectionProvider + .GetConnectionAccess(GetTenantConfig("defaultTenant"), Sfi).GetConnection(); + } + + protected override ISession OpenSession() + { + return OpenTenantSession("defaultTenant"); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + session.Flush(); + transaction.Commit(); + _id = e1.Id; + } + } + + #endregion Test Setup + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs index 0447ad8286d..06f2172cf62 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs @@ -21,8 +21,8 @@ public class FixtureAsync: BugTestCase public async Task CanUseFilterForLazyExtraAsync() { using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); var machineRequest = new MachineRequest { EnvId = 1L, Id = 2L }; await (s.SaveAsync(new Env { @@ -33,7 +33,7 @@ public async Task CanUseFilterForLazyExtraAsync() } })); await (s.SaveAsync(machineRequest)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } using (var s = OpenSession()) @@ -50,11 +50,11 @@ public async Task CanUseFilterForLazyExtraAsync() } using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.DeleteAsync(await (s.LoadAsync(2L)))); await (s.DeleteAsync(await (s.LoadAsync(1L)))); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/BasicClassFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/BasicClassFixture.cs index d51b045511d..1db221f5158 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/BasicClassFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/BasicClassFixture.cs @@ -164,7 +164,6 @@ public async Task TestCRUDAsync() index++; - // update a property to make sure it picks up that it is dirty s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -395,7 +394,6 @@ public async Task TestArrayCRUDAsync() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -449,7 +447,6 @@ public async Task TestPrimitiveArrayCRUDAsync() index++; - // modify the array to a new array so it is recreated s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -465,7 +462,6 @@ public async Task TestPrimitiveArrayCRUDAsync() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -535,7 +531,6 @@ public async Task TestMapCRUDAsync() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -602,7 +597,6 @@ public async Task TestSetCRUDAsync() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -687,7 +681,6 @@ public async Task TestBagCRUDAsync() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -805,7 +798,6 @@ public async Task TestListCRUDAsync() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -1072,4 +1064,4 @@ private void InitializeBasicClass(int id, ref BasicClass basicClass) basicClass.AddToStringSet("zero"); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/BasicObjectFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/BasicObjectFixture.cs index 187e2881043..580836deb88 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/BasicObjectFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/BasicObjectFixture.cs @@ -60,7 +60,6 @@ public async Task TestCRUDAsync() s = OpenSession(); bo = (BasicObject) await (s.LoadAsync(typeof(BasicObject), bo.Id)); - Assert.IsNotNull(bo.AnyWithProxy, "AnyWithProxy should not be null"); Assert.IsTrue(bo.AnyWithProxy is IBasicObjectProxy, "AnyWithProxy should have been a IBasicObjectProxy instance"); Assert.AreEqual(anyProxy.Id, ((IBasicObjectProxy) bo.AnyWithProxy).Id); @@ -79,4 +78,4 @@ public async Task TestCRUDAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/CriteriaFromHql/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/CriteriaFromHql/Fixture.cs index df151e19960..a3f5fb16e8d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/CriteriaFromHql/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/CriteriaFromHql/Fixture.cs @@ -21,7 +21,6 @@ namespace NHibernate.Test.NHSpecificTest.CriteriaFromHql [TestFixture] public class FixtureAsync : TestCase { - protected override string[] Mappings { get { return new string[] { "NHSpecificTest.CriteriaFromHql.Mappings.hbm.xml" }; } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs index 71e0c986851..99961613b02 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using System.Collections.Generic; using NHibernate.Cfg; using NHibernate.Criterion; @@ -19,7 +18,6 @@ namespace NHibernate.Test.NHSpecificTest.CriteriaQueryOnComponentCollection { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : TestCase { @@ -31,7 +29,7 @@ protected override void Configure(Configuration configuration) protected override void OnSetUp() { using (var s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var parent = new Employee { @@ -57,18 +55,18 @@ protected override void OnSetUp() s.Save(parent); s.Save(emp); - s.Transaction.Commit(); + t.Commit(); } } protected override void OnTearDown() { using (var s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from System.Object"); - s.Transaction.Commit(); + t.Commit(); } } @@ -106,10 +104,9 @@ public async Task CanQueryByCriteriaOnSetOfElementAsync() } } - [TestCase(JoinType.LeftOuterJoin)] [TestCase(JoinType.InnerJoin)] - public async Task CanQueryByCriteriaOnSetOfElementByCreateAliasAsync(JoinType joinType, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CanQueryByCriteriaOnSetOfElementByCreateAliasAsync(JoinType joinType) { using (var s = Sfi.OpenSession()) { @@ -117,7 +114,7 @@ public async Task CanQueryByCriteriaOnSetOfElementAsync() .CreateAlias("x.Amounts", "amount", joinType) .Add(Restrictions.Gt("amount.Amount", 5m)) .SetResultTransformer(new RootEntityResultTransformer()) - .ListAsync(cancellationToken)); + .ListAsync()); Assert.That(list, Has.Count.EqualTo(1)); Assert.That(list[0], Is.Not.Null); Assert.That(list[0], Is.TypeOf()); @@ -125,7 +122,6 @@ public async Task CanQueryByCriteriaOnSetOfElementAsync() } } - [Test] public async Task CanQueryByCriteriaOnSetOfCompositeElement_UsingDetachedCriteriaAsync() { @@ -145,7 +141,6 @@ public async Task CanQueryByCriteriaOnSetOfCompositeElement_UsingDetachedCriteri } } - protected override string[] Mappings { get { return new[] {"NHSpecificTest.CriteriaQueryOnComponentCollection.Mappings.hbm.xml"}; } @@ -155,6 +150,5 @@ protected override string MappingsAssembly { get { return "NHibernate.Test"; } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/DataReaderWrapperTest/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/DataReaderWrapperTest/Fixture.cs index 6178bdea7c2..e50cfe42870 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/DataReaderWrapperTest/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/DataReaderWrapperTest/Fixture.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.NHSpecificTest.DataReaderWrapperTest { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -71,7 +70,7 @@ public async Task CanUseDatareadersGetValueWithQueryBatchAsync() var crit = s.CreateCriteria(typeof (TheEntity)); var multi = s.CreateQueryBatch(); multi.Add(crit); - var res = await (multi.GetResultAsync(0, CancellationToken.None)); + var res = await (multi.GetResultAsync(0)); Assert.That(res.Count, Is.EqualTo(1)); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTime2Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTime2Fixture.cs index 04d26775f82..7dbe99aea2b 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTime2Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTime2Fixture.cs @@ -37,7 +37,6 @@ public async Task SavingAndRetrievingTestAsync() await (SavingAndRetrievingActionAsync(new AllDates {Sql_datetime2 = Now}, entity => DateTimeAssert.AreEqual(entity.Sql_datetime2, Now))); - await (SavingAndRetrievingActionAsync(new AllDates { Sql_datetime2 = DateTime.MinValue }, entity => DateTimeAssert.AreEqual(entity.Sql_datetime2, DateTime.MinValue))); @@ -54,4 +53,4 @@ public async Task SaveMillisecondAsync() entity => Assert.That(entity.Sql_datetime2, Is.EqualTo(datetime2)))); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs index e0e5cac9994..9485aff8ae1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs @@ -49,7 +49,6 @@ protected override void Configure(Cfg.Configuration configuration) configuration.SetProperty(Environment.ShowSql, "true"); } - protected override void OnSetUp() { base.OnSetUp(); @@ -66,7 +65,6 @@ protected override void OnSetUp() } } - protected override void OnTearDown() { using (ISession s = OpenSession()) @@ -77,7 +75,6 @@ protected override void OnTearDown() } } - [Test] public async Task CanQueryWithCastInHqlAsync() { @@ -89,7 +86,6 @@ public async Task CanQueryWithCastInHqlAsync() } } - [Test(Description = "NH-3357")] public async Task CanQueryWithAggregateInLinqAsync() { @@ -104,7 +100,6 @@ public async Task CanQueryWithAggregateInLinqAsync() Assert.That(datesRecovered, Is.EqualTo(new DateTimeOffset(2012, 11, 1, 2, 0, 0, TimeSpan.FromHours(3)))); } - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs index 77b49bf1678..6b85b345d19 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs @@ -28,25 +28,25 @@ public async Task LoadEnumsAsync() { object savedId; using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { savedId = await (s.SaveAsync(new SimpleWithEnums { Things = new List { Something.B, Something.C, Something.D, Something.E } })); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var swe = await (s.GetAsync(savedId)); Assert.That(swe.Things, Is.EqualTo(new[] { Something.B, Something.C, Something.D, Something.E })); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.DeleteAsync("from SimpleWithEnums")); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/EntityNameAndInheritance/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/EntityNameAndInheritance/Fixture.cs index 7c85ffe89a2..f8392bb3c8a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/EntityNameAndInheritance/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/EntityNameAndInheritance/Fixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using System.Collections; using NUnit.Framework; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs index 29ac552b705..ddd35ad0018 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs @@ -17,7 +17,6 @@ namespace NHibernate.Test.NHSpecificTest.EntityWithUserTypeCanHaveLinqGenerators { using System.Threading.Tasks; - [TestFixture] public class FixtureAsync : TestCase { @@ -96,21 +95,6 @@ public async Task EqualityWorksForUserTypeAsync() } } - [Test, Ignore("Not implemented yet")] - public async Task LinqMethodWorksForUserTypeAsync() - { - using (var session = OpenSession()) - using (session.BeginTransaction()) - { - var newItem = new BarExample { Value = "Larry" }; - var entities = await (session.Query() - .Where(x => x.Example.IsEquivalentTo(newItem)) - .ToListAsync()); - - Assert.AreEqual(2, entities.Count); - } - } - [Test] public async Task EqualityWorksForExplicitUserTypeAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/Evicting/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/Evicting/Fixture.cs index 88c1cea3e76..26bf6401f42 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/Evicting/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/Evicting/Fixture.cs @@ -46,7 +46,6 @@ protected override void OnTearDown() base.OnTearDown(); } - [Test] public async Task Can_evict_entity_from_sessionAsync() { @@ -67,7 +66,6 @@ public async Task Can_evict_entity_from_sessionAsync() [Test] public async Task Can_evict_non_persistent_objectAsync() { - using (var session = Sfi.OpenSession()) using (var tx = session.BeginTransaction()) { @@ -85,11 +83,9 @@ public async Task Can_evict_non_persistent_objectAsync() [Test] public async Task Can_evict_when_trying_to_evict_entity_from_another_sessionAsync() { - using (var session1 = Sfi.OpenSession()) using (var tx1 = session1.BeginTransaction()) { - using (var session2 = Sfi.OpenSession()) using (var tx2 = session2.BeginTransaction()) { @@ -109,6 +105,5 @@ public async Task Can_evict_when_trying_to_evict_entity_from_another_sessionAsyn await (tx1.CommitAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs index 9e6d31db053..29c4aabe5cc 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH0000/Fixture.cs @@ -41,7 +41,7 @@ protected override void OnTearDown() // The HQL delete does all the job inside the database without loading the entities, but it does // not handle delete order for avoiding violating constraints if any. Use // session.Delete("from System.Object"); - // instead if in need of having NHbernate ordering the deletes, but this will cause + // instead if in need of having NHibernate ordering the deletes, but this will cause // loading the entities in the session. session.CreateQuery("delete from System.Object").ExecuteUpdate(); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1149/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1149/Fixture.cs index efa2cc99cd4..9015d86ad80 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1149/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1149/Fixture.cs @@ -83,7 +83,6 @@ public async Task StatelessSessionLoadsOneToOneRelatedObject_WithoutPropertyRefA } } - using (var stateless = Sfi.OpenStatelessSession()) { var loadedCompany = await (stateless.GetAsync(companyId)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1180/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1180/FixtureByCode.cs new file mode 100644 index 00000000000..31c755286f4 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1180/FixtureByCode.cs @@ -0,0 +1,157 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1180 +{ + using System.Threading.Tasks; + //NH-3847 + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => {m.Type(NHibernateUtil.AnsiString); m.Length(5); }); + rc.Property(x => x.Amount, m => { m.Precision(8); m.Scale(2); }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public async Task StringTypesAsync() + { + var whenFalse = + Dialect is Oracle8iDialect + //Most dialects allow to return DbType.String and DbType.AnsiString in case statement + //But Oracle throws 'ORA-12704: character set mismatch' + ? Projections.Constant("otherstring", NHibernateUtil.AnsiString) + : Projections.Constant("otherstring"); + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // data + await (session.SaveAsync(new Entity {Name = "Alpha"})); + await (session.SaveAsync(new Entity {Name = "Beta"})); + await (session.SaveAsync(new Entity {Name = "Gamma"})); + + await (transaction.CommitAsync()); + } + + // whenTrue is constant, whenFalse is property + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Not( + Restrictions.Like(nameof(Entity.Name), "B%")), + //Property - ansi string length 5; contstant - string, length 10 + whenFalse, + Projections.Property(nameof(Entity.Name))); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = await (tagCriteria.ListAsync()); + + Assert.That(results, Is.EquivalentTo(new[] {"otherstring", "Beta", "otherstring"})); + } + + // whenTrue is property, whenFalse is constant + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Like(nameof(Entity.Name), "B%"), + Projections.Property(nameof(Entity.Name)), + whenFalse); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = await (tagCriteria.ListAsync()); + + Assert.That(results, Is.EquivalentTo(new[] {"otherstring", "Beta", "otherstring"})); + } + } + + [Test] + public async Task DecimalTypesAsync() + { + //On some dialects (SQLite) Scale mapping is ignored + var propertyResult = TestDialect.HasBrokenDecimalType ? 42.131m : 42.13m; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + await (session.SaveAsync(new Entity {Amount = 3.141m})); + await (session.SaveAsync(new Entity {Amount = 42.131m})); + await (session.SaveAsync(new Entity {Amount = 17.991m})); + + await (transaction.CommitAsync()); + } + + // whenTrue is constant, whenFalse is property + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Not( + Restrictions.Ge(nameof(Entity.Amount), 20m)), + //Property scale is 2, make sure constant scale 3 is not lost + Projections.Constant(20.123m), + Projections.Property(nameof(Entity.Amount))); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = await (tagCriteria.ListAsync()); + + Assert.That(results, Is.EquivalentTo(new[] {20.123m, propertyResult, 20.123m})); + } + + // whenTrue is property, whenFalse is constant + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Ge(nameof(Entity.Amount), 20m), + Projections.Property(nameof(Entity.Amount)), + Projections.Constant(20.123m)); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = await (tagCriteria.ListAsync()); + + Assert.That(results, Is.EquivalentTo(new[] {20.123m, propertyResult, 20.123m})); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs index 11f20de84c2..3ead1a0369d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs @@ -25,7 +25,6 @@ namespace NHibernate.Test.NHSpecificTest.GH1300 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -121,7 +120,7 @@ public void InsertWithTooLongValuesShouldThrowAsync() [TestCase("Name", SqlDbType.NVarChar)] [TestCase("AnsiName", SqlDbType.VarChar)] - public async Task LinqEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + public async Task LinqEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType) { Driver.ClearCommands(); @@ -130,11 +129,11 @@ public void InsertWithTooLongValuesShouldThrowAsync() { if (property == "Name") { - await (session.Query().Where(x => x.Name == "Bob").ToListAsync(cancellationToken)); + await (session.Query().Where(x => x.Name == "Bob").ToListAsync()); } else { - await (session.Query().Where(x => x.AnsiName == "Bob").ToListAsync(cancellationToken)); + await (session.Query().Where(x => x.AnsiName == "Bob").ToListAsync()); } Assert.That(Driver.LastCommandParameters.First().Size, Is.EqualTo(3)); Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); @@ -158,14 +157,14 @@ public async Task MappedAsShouldUseExplicitSizeAsync() [TestCase("Name", SqlDbType.NVarChar)] [TestCase("AnsiName", SqlDbType.VarChar)] - public async Task HqlLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + public async Task HqlLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType) { Driver.ClearCommands(); using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - await (session.CreateQuery("from Entity where " + property + " like :name").SetParameter("name", "%Bob%").ListAsync(cancellationToken)); + await (session.CreateQuery("from Entity where " + property + " like :name").SetParameter("name", "%Bob%").ListAsync()); Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(5)); Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); @@ -174,7 +173,7 @@ public async Task MappedAsShouldUseExplicitSizeAsync() [TestCase("Name", SqlDbType.NVarChar)] [TestCase("AnsiName", SqlDbType.VarChar)] - public async Task CriteriaEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CriteriaEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType) { Driver.ClearCommands(); @@ -184,7 +183,7 @@ public async Task MappedAsShouldUseExplicitSizeAsync() Driver.ClearCommands(); await (session.CreateCriteria().Add(Restrictions.Eq(property, "Bob")) - .ListAsync(cancellationToken)); + .ListAsync()); Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(3)); Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); @@ -193,7 +192,7 @@ public async Task MappedAsShouldUseExplicitSizeAsync() [TestCase("Name", SqlDbType.NVarChar)] [TestCase("AnsiName", SqlDbType.VarChar)] - public async Task CriteriaLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CriteriaLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType) { Driver.ClearCommands(); @@ -201,7 +200,7 @@ public async Task MappedAsShouldUseExplicitSizeAsync() using (var transaction = session.BeginTransaction()) { await (session.CreateCriteria().Add(Restrictions.Like(property, "%Bob%")) - .ListAsync(cancellationToken)); + .ListAsync()); Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(5)); Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1496/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1496/Fixture.cs index 4b6dc180cae..117d2290eb4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1496/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1496/Fixture.cs @@ -51,7 +51,6 @@ private void SetupPerson() session.Save(person); testPerson = person; - address = new Address("Postal T", "State T", "Street T"); person = new Person(2, "Tom", address); session.Save(address); @@ -77,7 +76,6 @@ private void SetupEmployee() session.Save(employee); testEmployee = employee; - employee = new Employee(2, "Tom"); contact = new Contact { Phone = "666-666-6666", ContactIdentifier = new ContactIdentifier(WORK_TYPENAME, "2") }; session.Save(contact); @@ -104,7 +102,6 @@ protected override void OnTearDown() [Test] public async Task EventListener_Entity_NoChangeAsync() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -177,8 +174,6 @@ public async Task SelectBeforeUpdate_Entity_ChangePropertyAsync() } } - - [Test] public async Task EventListener_EntityWithCompositeId_ChangePropertyAsync() { @@ -218,11 +213,9 @@ public async Task SelectBeforeUpdate_EntityWithCompositeId_ChangePropertyAsync() } } - [Test] public async Task EventListener_ManyToOne_ChangePropertyAsync() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -262,7 +255,6 @@ public async Task SelectBeforeUpdate_ManyToOne_ChangePropertyAsync() [Test] public async Task EventListener_Entity_SetNewManyToOneAsync() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -306,7 +298,6 @@ public async Task SelectBeforeUpdate_Entity_SetNewManyToOneAsync() [Test] public async Task EventListener_ManyToOneWithCompositeId_ChangePropertyAsync() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -346,7 +337,6 @@ public async Task SelectBeforeUpdate_ManyToOneWithCompositeId_ChangePropertyAsyn [Test] public async Task EventListener_Entity_SetNewManyToOneWithCompositeIdAsync() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -366,7 +356,6 @@ public async Task EventListener_Entity_SetNewManyToOneWithCompositeIdAsync() } } - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -407,7 +396,6 @@ public async Task SelectBeforeUpdate_Entity_SetNewManyToOneWithCompositeIdAsync( } } - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1515/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1515/Fixture.cs index a9eef211e65..4b5e798b68d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1515/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1515/Fixture.cs @@ -39,7 +39,5 @@ public async Task IntializeForwaredToPersistentCollectionAsync() await (collection.Received().ForceInitializationAsync(CancellationToken.None)); } - - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1530/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1530/Fixture.cs index b6c0fa8fb0d..4d3416ecc23 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1530/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1530/Fixture.cs @@ -112,5 +112,4 @@ public class NonInverseFixtureAsync : FixtureBaseAsync { protected override string[] Mappings => new[] { "NonInverseMappings.hbm.xml" }; } - } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1547/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1547/Fixture.cs index f55778a975f..a9b6818c04f 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1547/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1547/Fixture.cs @@ -103,12 +103,12 @@ public async Task LinqWithListParameterPerfAsync() { var query = queryFactory(session); // Warm up. - await (RunBenchmarkUnitAsync(cache, query)); + await (RunBenchmarkUnitAsync(cache, query, cancellationToken)); for (var j = 0; j < 1000; j++) { sw.Restart(); - await (RunBenchmarkUnitAsync(cache, query)); + await (RunBenchmarkUnitAsync(cache, query, cancellationToken)); sw.Stop(); timings.Add(sw.Elapsed.TotalMilliseconds); } @@ -122,7 +122,7 @@ public async Task LinqWithListParameterPerfAsync() $"{test} average time: {avg}ms (s {Math.Sqrt(timings.Sum(t => Math.Pow(t - avg, 2)) / (timings.Count - 1))}ms)"); } - private static Task RunBenchmarkUnitAsync(SoftLimitMRUCache cache, IQueryable query) + private static Task RunBenchmarkUnitAsync(SoftLimitMRUCache cache, IQueryable query, CancellationToken cancellationToken = default(CancellationToken)) { try { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1594/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1594/Fixture.cs index ca584ea9c43..520df4f6c12 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1594/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1594/Fixture.cs @@ -62,8 +62,13 @@ public async Task ExecutionContextLocalValuesLeakAsync() { await (RunInTransactionAsync(session)); var localValuesCountAfterFirstCall = ExecutionContext.Capture().LocalValuesCount(); + if (!localValuesCountAfterFirstCall.HasValue) + Assert.Ignore("Unable to get async local values count"); await (RunInTransactionAsync(session)); var localValuesCountAfterSecondCall = ExecutionContext.Capture().LocalValuesCount(); + if (!localValuesCountAfterSecondCall.HasValue) + Assert.Ignore("Unable to get async local values count"); + Assert.AreEqual(localValuesCountAfterFirstCall, localValuesCountAfterSecondCall); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1738/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1738/FixtureByCode.cs new file mode 100644 index 00000000000..f5a2a037566 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1738/FixtureByCode.cs @@ -0,0 +1,98 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1738 +{ + using System.Threading.Tasks; + [TestFixture] + public class RefreshLocallyRemovedCollectionItemFixtureAsync : TestCaseMappingByCode + { + private Guid _id; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag( + x => x.Children, + m => + { + m.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + m.Inverse(true); + m.Key(km => km.Column("Parent")); + }, + relation => relation.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Bob"}; + e1.Children.Add(new Child() {Name = "Child", Parent = e1}); + session.Save(e1); + transaction.Commit(); + _id = e1.Id; + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [Test] + public async Task RefreshLocallyRemovedCollectionItemAsync() + { + Entity entity; + using (var session = OpenSession()) + { + entity = await (session.GetAsync(_id)); + entity.Children.RemoveAt(0); + } + + using (var session = OpenSession()) + { + await (session.UpdateAsync(entity)); + await (session.RefreshAsync(entity)); + foreach (var child in entity.Children) + { + await (session.RefreshAsync(child)); + } + + Assert.That(session.GetSessionImplementation().PersistenceContext.IsReadOnly(entity), Is.False); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs index d57b65898b5..0dac0e740f5 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Exceptions; using NHibernate.Mapping.ByCode; using NHibernate.Type; using NUnit.Framework; @@ -124,8 +125,7 @@ protected async Task AreEqualAsync( { expectedResult = await (expectedQuery(session.Query()).ToListAsync(cancellationToken)); } - catch (OperationCanceledException) { throw; } - catch (Exception e) + catch (GenericADOException e) { Assert.Ignore($"Not currently supported query: {e}"); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs index 75693b26430..e740a2abd59 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs @@ -14,7 +14,6 @@ namespace NHibernate.Test.NHSpecificTest.GH1920 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -35,15 +34,15 @@ protected override void OnSetUp() [TestCase(true)] [TestCase(false)] - public async Task CanLoadEntityAsync(bool loadProxyOfOtherEntity, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CanLoadEntityAsync(bool loadProxyOfOtherEntity) { using (var session = OpenSession()) using (session.BeginTransaction()) { if (loadProxyOfOtherEntity) - await (session.LoadAsync(someOtherEntityId, cancellationToken)); + await (session.LoadAsync(someOtherEntityId)); - var result = await (session.GetAsync(entityId, cancellationToken)); + var result = await (session.GetAsync(entityId)); Assert.That(result.Name, Is.Not.Null); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1921/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1921/Fixture.cs index 06469395f15..b8575b49ed4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1921/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1921/Fixture.cs @@ -15,7 +15,6 @@ namespace NHibernate.Test.NHSpecificTest.GH1921 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -105,7 +104,7 @@ public async Task DmlDeleteAsync(bool filtered) [TestCase(null)] [TestCase("NameFilter")] [TestCase("OtherNameFilter")] - public async Task MultiTableDmlInsertAsync(string filter, CancellationToken cancellationToken = default(CancellationToken)) + public async Task MultiTableDmlInsertAsync(string filter) { var isFiltered = !string.IsNullOrEmpty(filter); @@ -119,8 +118,8 @@ public async Task DmlDeleteAsync(bool filtered) .CreateQuery( // No insert of OtherName: not supported (INSERT statements cannot refer to superclass/joined properties) "insert into MultiTableEntity (Name) select e.Name from MultiTableEntity e") - .ExecuteUpdateAsync(cancellationToken)); - await (transaction.CommitAsync(cancellationToken)); + .ExecuteUpdateAsync()); + await (transaction.CommitAsync()); Assert.That(rowCount, Is.EqualTo(isFiltered ? 1 : 2), "ResultCount"); } @@ -129,19 +128,19 @@ public async Task DmlDeleteAsync(bool filtered) using (var transaction = session.BeginTransaction()) { Assert.That( - await (session.Query().CountAsync(e => e.Name != "Bob", cancellationToken)), + await (session.Query().CountAsync(e => e.Name != "Bob")), Is.EqualTo(isFiltered ? 1 : 2), "Name != \"Bob\""); Assert.That( - await (session.Query().CountAsync(e => e.OtherName == null, cancellationToken)), + await (session.Query().CountAsync(e => e.OtherName == null)), Is.EqualTo(isFiltered ? 1 : 2), "OtherName is null"); - await (transaction.CommitAsync(cancellationToken)); + await (transaction.CommitAsync()); } } [TestCase(null)] [TestCase("NameFilter")] [TestCase("OtherNameFilter")] - public async Task MultiTableDmlUpdateAsync(string filter, CancellationToken cancellationToken = default(CancellationToken)) + public async Task MultiTableDmlUpdateAsync(string filter) { var isFiltered = !string.IsNullOrEmpty(filter); @@ -157,8 +156,8 @@ public async Task DmlDeleteAsync(bool filtered) " set Name = 'newName', OtherName = 'newOtherName'" + // Check referencing columns is supported " where e.Name is not null and e.OtherName is not null") - .ExecuteUpdateAsync(cancellationToken)); - await (transaction.CommitAsync(cancellationToken)); + .ExecuteUpdateAsync()); + await (transaction.CommitAsync()); Assert.That(rowCount, Is.EqualTo(isFiltered ? 1 : 2), "ResultCount"); } @@ -167,19 +166,19 @@ public async Task DmlDeleteAsync(bool filtered) using (var transaction = session.BeginTransaction()) { Assert.That( - await (session.Query().CountAsync(e => e.Name == "newName", cancellationToken)), + await (session.Query().CountAsync(e => e.Name == "newName")), Is.EqualTo(isFiltered ? 1 : 2), "Name == \"newName\""); Assert.That( - await (session.Query().CountAsync(e => e.OtherName == "newOtherName", cancellationToken)), + await (session.Query().CountAsync(e => e.OtherName == "newOtherName")), Is.EqualTo(isFiltered ? 1 : 2), "Name == \"newOtherName\""); - await (transaction.CommitAsync(cancellationToken)); + await (transaction.CommitAsync()); } } [TestCase(null)] [TestCase("NameFilter")] [TestCase("OtherNameFilter")] - public async Task MultiTableDmlDeleteAsync(string filter, CancellationToken cancellationToken = default(CancellationToken)) + public async Task MultiTableDmlDeleteAsync(string filter) { var isFiltered = !string.IsNullOrEmpty(filter); @@ -194,8 +193,8 @@ public async Task DmlDeleteAsync(bool filtered) "delete MultiTableEntity e" + // Check referencing columns is supported " where e.Name is not null and e.OtherName is not null") - .ExecuteUpdateAsync(cancellationToken)); - await (transaction.CommitAsync(cancellationToken)); + .ExecuteUpdateAsync()); + await (transaction.CommitAsync()); Assert.That(rowCount, Is.EqualTo(isFiltered ? 1 : 2), "ResultCount"); } @@ -204,12 +203,12 @@ public async Task DmlDeleteAsync(bool filtered) using (var transaction = session.BeginTransaction()) { Assert.That( - await (session.Query().CountAsync(e => e.Name != "Bob", cancellationToken)), + await (session.Query().CountAsync(e => e.Name != "Bob")), Is.EqualTo(isFiltered ? 1 : 0), "Name != \"Bob\""); Assert.That( - await (session.Query().CountAsync(e => e.OtherName != "Bob", cancellationToken)), + await (session.Query().CountAsync(e => e.OtherName != "Bob")), Is.EqualTo(isFiltered ? 1 : 0), "OtherName != \"Bob\""); - await (transaction.CommitAsync(cancellationToken)); + await (transaction.CommitAsync()); } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1963/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1963/Fixture.cs new file mode 100644 index 00000000000..a25dc76758e --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1963/Fixture.cs @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.UserTypes; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH1963 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob", Flag = true}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void LinqFilterOnNonLiteralCustomTypeAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var result = from e in session.Query() + where e.Flag + select e; + + Assert.That( + () => result.ToListAsync(), + Throws + .InnerException.TypeOf() + .And.InnerException.Message.Contains(nameof(IEnhancedUserType))); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs new file mode 100644 index 00000000000..ff47bd2ad14 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs @@ -0,0 +1,154 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Linq; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1994 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var a = new Asset(); + a.Documents.Add(new Document { IsDeleted = true }); + a.Documents.Add(new Document { IsDeleted = false }); + + session.Save(a); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + + session.Delete("from System.Object"); + + transaction.Commit(); + } + } + + [Test] + public async Task TestUnfilteredLinqQueryAsync() + { + using (var s = OpenSession()) + { + var query = await (s.Query() + .FetchMany(x => x.Documents) + .ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(2), "unfiltered asset documents"); + } + } + + [Test] + public async Task TestFilteredByWhereCollectionLinqQueryAsync() + { + if(Dialect is PostgreSQLDialect) + Assert.Ignore("Dialect doesn't support 0/1 to bool implicit cast"); + + using (var s = OpenSession()) + { + var query = await (s.Query() + .FetchMany(x => x.DocumentsFiltered) + .ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].DocumentsFiltered.Count, Is.EqualTo(1), "unfiltered asset documents"); + } + } + + //GH-1994 + [Test] + public async Task TestFilteredLinqQueryAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + var query = await (s.Query() + .FetchMany(x => x.Documents) + .ToListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + [Test] + public async Task TestFilteredQueryOverAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var query = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + [Test] + public async Task TestFilteredBagQueryOverAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var query = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DocumentsBag) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].DocumentsBag.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + //NH-2991 + [Test] + public async Task TestQueryOverRestrictionWithClauseAsync() + { + using (var s = OpenSession()) + { + Document docs = null; + var query = await (s.QueryOver() + .JoinQueryOver(a => a.Documents, () => docs, JoinType.LeftOuterJoin, Restrictions.Where(() => docs.IsDeleted != true)) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync()); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2029/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2029/Fixture.cs new file mode 100644 index 00000000000..7b148a864dc --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2029/Fixture.cs @@ -0,0 +1,190 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2029 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.NullableInt32Prop); + rc.Property(x => x.Int32Prop); + rc.Property(x => x.NullableInt64Prop); + rc.Property(x => x.Int64Prop); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return !(dialect is SQLiteDialect); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.Save(new TestClass + { + Int32Prop = int.MaxValue, + NullableInt32Prop = int.MaxValue, + Int64Prop = int.MaxValue, + NullableInt64Prop = int.MaxValue + }); + session.Save(new TestClass + { + Int32Prop = int.MaxValue, + NullableInt32Prop = int.MaxValue, + Int64Prop = int.MaxValue, + NullableInt64Prop = int.MaxValue + }); + session.Save(new TestClass + { + Int32Prop = int.MaxValue, + NullableInt32Prop = null, + Int64Prop = int.MaxValue, + NullableInt64Prop = null + }); + + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.CreateQuery("delete from TestClass").ExecuteUpdate(); + + tx.Commit(); + } + } + + [Test] + public async Task NullableIntOverflowAsync() + { + var hasCast = Dialect.GetCastTypeName(NHibernateUtil.Int32.SqlType) != + Dialect.GetCastTypeName(NHibernateUtil.Int64.SqlType); + + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = await (session.Query() + .GroupBy(i => 1) + .Select(g => new + { + s = g.Sum(i => (long) i.NullableInt32Prop) + }) + .ToListAsync()); + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "cast"), Is.EqualTo(hasCast ? 1 : 0)); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 2)); + } + } + + [Test] + public async Task IntOverflowAsync() + { + var hasCast = Dialect.GetCastTypeName(NHibernateUtil.Int32.SqlType) != + Dialect.GetCastTypeName(NHibernateUtil.Int64.SqlType); + + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = await (session.Query() + .GroupBy(i => 1) + .Select(g => new + { + s = g.Sum(i => (long) i.Int32Prop) + }) + .ToListAsync()); + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "cast"), Is.EqualTo(hasCast ? 1 : 0)); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 3)); + } + } + + [Test] + public async Task NullableInt64NoCastAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = await (session.Query() + .GroupBy(i => 1) + .Select(g => new { + s = g.Sum(i => i.NullableInt64Prop) + }) + .ToListAsync()); + + Assert.That(sqlLog.GetWholeLog(), Does.Not.Contains("cast")); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 2)); + } + } + + [Test] + public async Task Int64NoCastAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = await (session.Query() + .GroupBy(i => 1) + .Select(g => new { + s = g.Sum(i => i.Int64Prop) + }) + .ToListAsync()); + + Assert.That(sqlLog.GetWholeLog(), Does.Not.Contains("cast")); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 3)); + } + } + + private int FindAllOccurrences(string source, string substring) + { + if (source == null) + { + return 0; + } + int n = 0, count = 0; + while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1) + { + n += substring.Length; + count++; + } + return count; + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2064/OneToOneSelectProjectionFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2064/OneToOneSelectProjectionFixture.cs new file mode 100644 index 00000000000..07b0adabd83 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2064/OneToOneSelectProjectionFixture.cs @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2064 +{ + using System.Threading.Tasks; + [TestFixture] + public class OneToOneSelectProjectionFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.Assigned)); + rc.Property(e => e.Name); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + rc.OneToOne(e => e.OneToOne, m => { }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var nullableOwner = new ParentEntity() {Name = "Owner",}; + var oneToOne = new OneToOneEntity() {Name = "OneToOne"}; + nullableOwner.OneToOne = oneToOne; + session.Save(nullableOwner); + oneToOne.Id = nullableOwner.Id; + session.Save(oneToOne); + session.Flush(); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task QueryOneToOneAsync() + { + using (var session = OpenSession()) + { + var entity = + await (session + .Query() + .FirstOrDefaultAsync()); + Assert.That(entity.OneToOne, Is.Not.Null); + } + } + + [Test] + public async Task QueryOneToOneProjectionAsync() + { + using (var session = OpenSession()) + { + var entity = + await (session + .Query() + .Select( + x => new + { + x.Id, + SubType = new {x.OneToOne, x.Name}, + SubType2 = new + { + x.Id, + x.OneToOne, + SubType3 = new {x.Id, x.OneToOne} + }, + x.OneToOne + }).FirstOrDefaultAsync()); + Assert.That(entity.OneToOne, Is.Not.Null); + Assert.That(entity.SubType.OneToOne, Is.Not.Null); + Assert.That(entity.SubType2.OneToOne, Is.Not.Null); + Assert.That(entity.SubType2.SubType3.OneToOne, Is.Not.Null); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2137/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2137/Fixture.cs new file mode 100644 index 00000000000..ee365dff61e --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2137/Fixture.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2137 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + private Entity e1; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task TestUpdateDetachedEntityAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + e1.Name = "Sally"; + await (session.UpdateAsync(e1)); + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2175/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2175/Fixture.cs new file mode 100644 index 00000000000..333ee1a60aa --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2175/Fixture.cs @@ -0,0 +1,121 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2175 +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(m => + { + m.Lazy(false); + m.Id(c => c.Id, id => + { + id.Column("concept_id"); + id.Generator(Generators.Native); + }); + m.Set(c => c.Mappings, + collection => + { + collection.Fetch(CollectionFetchMode.Subselect); + collection.Lazy(CollectionLazy.NoLazy); + collection.Table("concept_mapping"); + collection.Key(key => + { + key.Column("concept_id"); + }); + }, + element => + { + element.Component(component => + { + component.Property(c => c.Relationship, p => p.Column("relationship")); + component.ManyToOne(c => c.Code, manyToOne => + { + manyToOne.Column("code_id"); + manyToOne.Fetch(FetchKind.Join); + manyToOne.Cascade(NHibernate.Mapping.ByCode.Cascade.None); + }); + }); + }); + }); + + mapper.Class(m => + { + m.Table("concept_code"); + m.Lazy(false); + + m.Id(c => c.Id, id => + { + id.Column("code_id"); + id.Generator(Generators.Native); + }); + + m.Property(c => c.CodeSource, p => + { + p.Column("source"); + }); + m.Property(c => c.Value, p => + { + p.Column("`value`"); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public async Task SubcriteriaOnNestedComponentAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var concepts = new List(); + for (var i = 1; i < 10; i++) + { + var concept = new Concept { DisplayName = $"Concept{i}" }; + concept.Mappings.Add(new ConceptCodeMapping(ConceptCodeRelationship.SameAs, + await (CreatePersistentCodeAsync(session, "src-x", "C" + i)))); + concept.Mappings.Add(new ConceptCodeMapping(ConceptCodeRelationship.SameAs, + await (CreatePersistentCodeAsync(session, "src-y", "CX" + (0x1000 + i).ToString("X"))))); + concepts.Add(concept); + await (session.SaveAsync(concept)); + } + + var criteria = session.CreateCriteria() + .CreateAlias(nameof(Concept.Mappings), "cm") + .CreateCriteria("cm." + nameof(ConceptCodeMapping.Code), "code") + .Add(Restrictions.Eq(nameof(ConceptCode.CodeSource), "src-y")) + .Add(Restrictions.In(nameof(ConceptCode.Value), new object[] { "CX1001", "CX1003", "CX1008" })); + + var persistentConcepts = await (criteria.ListAsync()); + Assert.That(persistentConcepts, Is.EquivalentTo(new[] { concepts[0], concepts[2], concepts[7] })); + } + } + + private async Task CreatePersistentCodeAsync(ISession session, string codeSource, string value, CancellationToken cancellationToken = default(CancellationToken)) + { + var code = new ConceptCode(codeSource, value); + await (session.SaveAsync(code, cancellationToken)); + return code; + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2286/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2286/Fixture.cs new file mode 100644 index 00000000000..92e17faa6a5 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2286/Fixture.cs @@ -0,0 +1,59 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2286 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public async Task FilterOnJoinedSubclassKeyAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var c1 = new IndividualCustomer {Id = 2, Name = "2"}; + var c2 = new IndividualCustomer {Id = 4, Name = "4"}; + await (session.SaveAsync(c1)); + await (session.SaveAsync(c2)); + + await (session.FlushAsync()); + await (transaction.CommitAsync()); + } + + using (var s = OpenSession()) + { + var count = (await (s.Query().Select(c => c.Id).ToListAsync())).Count; + s.EnableFilter("filterName"); + var countFiltered = (await (s.Query().Select(c => c.Id).ToListAsync())).Count; + + Assert.AreEqual(2, count); + Assert.AreEqual(1, countFiltered); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs new file mode 100644 index 00000000000..3aefd7ae543 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2437/Fixture.cs @@ -0,0 +1,156 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var user = new User { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; + session.Save(user); + + for (var i = 0; i < 10; i++) + { + var userSession = new UserSession + { + Claims = "My Claims", + ExpireDateTime = DateTime.Now.AddDays(1), + MbrId = 1, + OpenDate = DateTime.Now, + LocalIpAddress = "192.168.1.1", + RemoteIpAddress = "127.0.0.1", + LocalPort = 80 + i.ToString(), + RemotePort = 80 + i.ToString(), + DeviceId = "None", + UserCode = "gokhanabatay", + IsOpen = true + }; + + session.Save(userSession); + } + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from UserSession").ExecuteUpdate(); + session.CreateQuery("delete from User").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task Get_DateCustomType_NullableDateValueEqualsAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = await (session.Query().Where(x => x.OpenDate.Value == DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public async Task Get_DateCustomType_NullableDateValueEqualsMethodAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = await (session.Query().Where(x => x.OpenDate.Value.Equals(DateTime.Now)).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public async Task Get_DateTimeCustomType_NullableDateValueEqualsAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = await (session.Query().Where(x => x.ExpireDateTime.Value > DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public async Task Get_DateCustomType_DateEqualsAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = await (session.Query().Where(x => x.OpenDate == DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public async Task Get_DateTimeCustomType_DateEqualsAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = await (session.Query().Where(x => x.ExpireDateTime > DateTime.Now).ToListAsync()); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public async Task Get_BooleanCustomTypeAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var results = await (session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) + .ToListAsync()); + + Assert.That(results, Has.Count.EqualTo(10)); + } + } + + public class NullableBooleanResult + { + public bool? IsOpen { get; set; } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2445/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2445/Fixture.cs new file mode 100644 index 00000000000..4ef3087ca31 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2445/Fixture.cs @@ -0,0 +1,168 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NHibernate.Test.NHSpecificTest.GH2445; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2446 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.Hbm2ddlKeyWords, "auto-quote"); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MySQLDialect || dialect is SQLiteDialect; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(m => + { + m.Id(c => c.Id, id => + { + id.Generator(Generators.Native); + }); + m.Property(o => o.Short, o => o.NotNullable(true)); + m.Property(o => o.Integer, o => o.NotNullable(true)); + m.Property(o => o.Long, o => o.NotNullable(true)); + m.Property(o => o.NullableNumber); + m.Property(o => o.ShortNumber, o => o.NotNullable(true)); + m.Property(o => o.NullableShortNumber); + m.Property(o => o.LargeNumber, o => o.NotNullable(true)); + m.Property(o => o.NullableLargeNumber); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var entity = new UnsignedEntity() + { + Short = 123, + Integer = 12345, + Long = 123456789, + LargeNumber = 123456789, + NullableLargeNumber = null, + NullableNumber = null, + ShortNumber = 123, + NullableShortNumber = null + }; + session.Save(entity); + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.CreateQuery("delete from UnsignedEntity").ExecuteUpdate(); + tx.Commit(); + } + } + + [Test] + public async Task TestUIntAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var number = 10; + await (session.Query().Where(o => o.Id == (uint) number).ToListAsync()); + await (session.Query().Where(o => o.Id == (uint) o.Integer).ToListAsync()); + await (session.Query().Where(o => o.Id > number).ToListAsync()); + } + } + + [Test] + public async Task TestNullableUIntAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var number = 10; + await (session.Query().Where(o => o.NullableNumber == (uint?) number).ToListAsync()); + await (session.Query().Where(o => o.NullableNumber == (uint?) o.Integer).ToListAsync()); + await (session.Query().Where(o => o.NullableNumber > number).ToListAsync()); + } + } + + + [Test] + public async Task TestUShortAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + short number = 10; + await (session.Query().Where(o => o.ShortNumber == (ushort) number).ToListAsync()); + await (session.Query().Where(o => o.ShortNumber == (ushort) o.Short).ToListAsync()); + await (session.Query().Where(o => o.ShortNumber > number).ToListAsync()); + } + } + + [Test] + public async Task TestNullableUShortAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + short number = 10; + await (session.Query().Where(o => o.NullableShortNumber == (ushort?) number).ToListAsync()); + await (session.Query().Where(o => o.NullableShortNumber == (ushort?) o.Short).ToListAsync()); + await (session.Query().Where(o => o.NullableShortNumber > number).ToListAsync()); + } + } + + [Test] + public async Task TestULongAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + long number = 10; + await (session.Query().Where(o => o.LargeNumber == (ulong) number).ToListAsync()); + await (session.Query().Where(o => o.LargeNumber == (ulong) o.Long).ToListAsync()); + await (session.Query().Where(o => o.LargeNumber > (ulong) number).ToListAsync()); + } + } + + [Test] + public async Task TestNullableULongAsync() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + long number = 10; + await (session.Query().Where(o => o.NullableLargeNumber == (ulong?) number).ToListAsync()); + await (session.Query().Where(o => o.NullableLargeNumber == (ulong?) o.Long).ToListAsync()); + await (session.Query().Where(o => o.NullableLargeNumber > (ulong?) number).ToListAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2454/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2454/FixtureByCode.cs new file mode 100644 index 00000000000..11e91807118 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2454/FixtureByCode.cs @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2454 +{ + using System.Threading.Tasks; + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Project, m => { m.Column("ProjectId"); m.NotNullable(true); }); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Component1, m => { m.Column("Component1Id"); m.NotNullable(true); }); + rc.ManyToOne(x => x.Component2, m => { m.Column("Component2Id"); m.NotNullable(false); }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // alpha entities + var projectAlpha = new Project {Name = "Alpha"}; + session.Save(projectAlpha); + + var componentAlpha = new Component {Project = projectAlpha, Name = "Thingie"}; + session.Save(componentAlpha); + + var tagAlpha = new Tag {Component1 = componentAlpha, Name = "A20"}; + session.Save(tagAlpha); + + // beta entities + var projectBeta = new Project {Name = "Beta"}; + session.Save(projectBeta); + + var componentBeta = new Component {Project = projectBeta, Name = "Thingie"}; + session.Save(componentBeta); + + var tagBeta = new Tag {Component1 = componentBeta, Name = "B17"}; + session.Save(tagBeta); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Tag").ExecuteUpdate(); + session.CreateQuery("delete from Component").ExecuteUpdate(); + session.CreateQuery("delete from Project").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public async Task SubqueryCorrelatedThroughConditionalAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var tagCriteria = session.CreateCriteria(typeof(Tag), "t"); + tagCriteria.CreateCriteria("Component1", "c1"); + tagCriteria.CreateCriteria("Component2", "c2", JoinType.LeftOuterJoin); + + // create correlated subquery + var projectCriteria = DetachedCriteria.For(typeof(Project), "p"); + + var conditionalCorrelationProjection = Projections.Conditional( + Restrictions.IsNotNull(Projections.Property("t.Component2")), + Projections.Property("c2.Project"), + Projections.Property("c1.Project")); + projectCriteria.Add(Restrictions.EqProperty("p.Id", conditionalCorrelationProjection)); + + projectCriteria.SetProjection(Projections.Property("p.Name")); + + var projectNameProjection = Projections.SubQuery(projectCriteria); + + tagCriteria.Add(Restrictions.Eq(projectNameProjection, "Beta")); + tagCriteria.SetProjection(Projections.Property("t.Name")); + + // run query + var results = await (tagCriteria.ListAsync()); + + Assert.That(results, Is.EquivalentTo(new[] {"B17"})); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2463/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2463/Fixture.cs new file mode 100644 index 00000000000..777e74491ce --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2463/Fixture.cs @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Criterion; +using NHibernate.DomainModel; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2463 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return Dialect.SupportsScalarSubSelects; + } + + protected override string[] Mappings + { + get { return new[] {"ABC.hbm.xml"}; } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var a = new A {Name = "A", AnotherName = "X"}; + session.Save(a); + + var b = new B {Name = "B", AnotherName = "X"}; + session.Save(b); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + //Also see GH-2599 + [Test] + public async Task CanJoinOnEntityWithDiscriminatorLinqAsync() + { + using (var s = OpenSession()) + { + var list = await (s.Query().Join( + s.Query(), + a => a.AnotherName, + b => b.AnotherName, + (a, b) => + new {a, b}).ToListAsync()); + } + } + + [Test] + public async Task CanJoinOnEntityWithDiscriminatorQueryOverAsync() + { + using (var s = OpenSession()) + { + A a = null; + B b = null; + var list = await (s.QueryOver(() => a) + .JoinEntityAlias(() => b, () => a.AnotherName == b.AnotherName) + .Select((x) => a.AsEntity(), (x) => b.AsEntity()).ListAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2465/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2465/Fixture.cs new file mode 100644 index 00000000000..2084a41ac03 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2465/Fixture.cs @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2465 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var applicant = new Entity {IdentityNames = {"name1", "name2"}}; + session.Save(applicant); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [Test] + public async Task ContainsInsideValueCollectionAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var identityNames = new[] {"name1", "x"}; + await (session + .Query() + .Where(a => a.IdentityNames.Any(n => identityNames.Contains(n))) + .ToListAsync()); + await (session + .Query() + .Where(a => a.IdentityNames.All(n => identityNames.Contains(n))) + .ToListAsync()); + await (session + .Query() + .Where(a => a.IdentityNames.FirstOrDefault(n => identityNames.Contains(n)) == "test") + .ToListAsync()); + + await (transaction.CommitAsync()); + } + } + + [Test] + public async Task EqualsInsideValueCollectionAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var value = "test"; + await (session + .Query() + .Where(a => a.IdentityNames.Any(n => n == value)) + .ToListAsync()); + await (session + .Query() + .Where(a => a.IdentityNames.Any(n => (string) n == value)) + .ToListAsync()); + await (session + .Query() + .Where(a => a.IdentityNames.All(n => n == value)) + .ToListAsync()); + await (session + .Query() + .Where(a => a.IdentityNames.FirstOrDefault(n => n == "test") == "test") + .ToListAsync()); + + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2508/AuditEventListener.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2508/AuditEventListener.cs new file mode 100644 index 00000000000..3c2aaac0566 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2508/AuditEventListener.cs @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cfg; +using NHibernate.Event; +using NHibernate.Persister.Entity; + +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + public partial class AuditEventListener : IPreCollectionUpdateEventListener + { + public Task OnPreUpdateCollectionAsync(PreCollectionUpdateEvent @event, CancellationToken cancellationToken) + { + try + { + var ownerEntity = @event.AffectedOwnerOrNull; + var collectionEntry = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection); + if (!collectionEntry.LoadedPersister.IsInverse) + return Task.CompletedTask; + + var abstractCollectionPersister = collectionEntry.LoadedPersister as Persister.Collection.AbstractCollectionPersister; + if (abstractCollectionPersister == null) + return Task.CompletedTask; + + var ownerEntityPersister = abstractCollectionPersister.OwnerEntityPersister; + ownerEntityPersister.GetPropertyValues(ownerEntity); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2508/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2508/Fixture.cs new file mode 100644 index 00000000000..2dd46752b97 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2508/Fixture.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void Configure(Configuration configuration) + { + var listeners = configuration.EventListeners; + listeners.PreCollectionUpdateEventListeners = + new[] {new AuditEventListener()} + .Concat(listeners.PreCollectionUpdateEventListeners) + .ToArray(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new LoggerCase {Name = "Bob"}; + session.Save(e1); + + var e2 = new LoggerCase {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [Test] + public async Task TestPreCollectionUpdateEventAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var result = await ((from e in session.Query() + where e.Name == "Bob" + select e).FirstAsync()); + + result.Children.Add(new Child { Logger = result, Name = "child" }); + await (session.SaveOrUpdateAsync(result)); + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2549/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2549/Fixture.cs new file mode 100644 index 00000000000..82227867203 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2549/Fixture.cs @@ -0,0 +1,82 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH2549 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new Person {Id = 1, Name = "Name"}); + s.Save(new Customer {Deleted = false, Name = "Name", Id = 1}); + s.Save(new Customer {Deleted = true, Name = "Name", Id = 2}); + + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + t.Commit(); + } + } + + [Test] + public async Task EntityJoinFilterLinqAsync() + { + using (var s = OpenSession()) + { + var list = await ((from p in s.Query() + join c in s.Query() on p.Name equals c.Name + select p).ToListAsync()); + + s.EnableFilter("DeletedCustomer").SetParameter("deleted", false); + + var filteredList = await ((from p in s.Query() + join c in s.Query() on p.Name equals c.Name + select p).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(2)); + Assert.That(filteredList, Has.Count.EqualTo(1)); + } + } + + [Test] + public async Task EntityJoinFilterQueryOverAsync() + { + using (var s = OpenSession()) + { + Customer c = null; + Person p = null; + var list = await (s.QueryOver(() => p).JoinEntityAlias(() => c, () => c.Name == p.Name).ListAsync()); + + s.EnableFilter("DeletedCustomer").SetParameter("deleted", false); + + var filteredList = await (s.QueryOver(() => p).JoinEntityAlias(() => c, () => c.Name == p.Name).ListAsync()); + + Assert.That(list, Has.Count.EqualTo(2)); + Assert.That(filteredList, Has.Count.EqualTo(1)); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2559/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2559/FixtureByCode.cs new file mode 100644 index 00000000000..8dea23c6d76 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2559/FixtureByCode.cs @@ -0,0 +1,156 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + using System.Threading.Tasks; + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Guid)); + rc.Property(x => x.Name); + rc.Property(x => x.Age); + rc.Set( + x => x.Children, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Set( + x => x.Cars, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Parent); + + ch.Set( + x => x.Pets, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var person = new Person { Name = "Person 1", Age = 18 }; + + var car1 = new Car { Name = "Car1", Owner = person }; + var car2 = new Car { Name = "Car2", Owner = person }; + session.Save(car1); + session.Save(car2); + + session.Save(person); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Pet").ExecuteUpdate(); + session.CreateQuery("delete from Child").ExecuteUpdate(); + session.CreateQuery("delete from Car").ExecuteUpdate(); + session.CreateQuery("delete from Person").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public async Task TestQueryCachingWithThenFetchManyAsync() + { + Person dbPerson; + Person cachePerson; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = + session + .Query() + .FetchMany(p => p.Children) + .ThenFetchMany(ch => ch.Pets) + .FetchMany(p => p.Cars) as IQueryable; + + query = query.WithOptions(opt => + opt.SetCacheable(true) + .SetCacheMode(CacheMode.Normal) + .SetCacheRegion("Long_Cache")); + + dbPerson = (await (query.ToListAsync())).First(); // First time the result will be cached + cachePerson = (await (query.ToListAsync())).First(); + + await (transaction.CommitAsync()); + } + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Cars), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Cars), Is.True); + Assert.That(dbPerson.Cars, Has.Count.EqualTo(2)); + Assert.That(cachePerson.Cars, Has.Count.EqualTo(2)); + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Children), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Children), Is.True); + Assert.That(dbPerson.Children, Has.Count.EqualTo(0)); + Assert.That(cachePerson.Children, Has.Count.EqualTo(0)); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/ListsWithHoles/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/ListsWithHoles/Fixture.cs index d7c789c65b5..4a473e6fa15 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/ListsWithHoles/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/ListsWithHoles/Fixture.cs @@ -56,7 +56,6 @@ public async Task CanHandleHolesInListAsync() await (tx.CommitAsync()); } - using (ISession sess = OpenSession()) using (ITransaction tx = sess.BeginTransaction()) { @@ -65,8 +64,6 @@ public async Task CanHandleHolesInListAsync() await (tx.CommitAsync()); } - - using (ISession sess = OpenSession()) using (ITransaction tx = sess.BeginTransaction()) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs index 291287c9b0e..27da253ad58 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs @@ -21,7 +21,6 @@ namespace NHibernate.Test.NHSpecificTest.LoadingNullEntityInSet [TestFixture] public class FixtureAsync : TestCase { - protected override string[] Mappings { get { return new string[] { "NHSpecificTest.LoadingNullEntityInSet.Mappings.hbm.xml" }; } @@ -84,7 +83,6 @@ public async Task CanHandleNullEntityInListAsync() await (criteria.ListAsync()); } - using (ISession sess = OpenSession()) { await (sess.DeleteAsync("from WantedProfession")); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs index 7b1946e8d45..04b4336cdf7 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs @@ -267,6 +267,5 @@ public async Task Verify20BehaviourForPropertyRefAndFilterAsync() Assert.That(resHql[0].Address, Is.Not.Null); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/MapFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/MapFixture.cs index 93922431bd7..4af77a8f67c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/MapFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/MapFixture.cs @@ -56,7 +56,6 @@ protected override void OnTearDown() } } - [Test] public async Task TestSelectAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1001/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1001/Fixture.cs index f49893b5472..2fe3e8a8894 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1001/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1001/Fixture.cs @@ -255,7 +255,7 @@ public async Task Department5IsNullAsync() } } - [Test, Ignore("Not fixed yet")] + [Test] public void Department5IsNotFoundAsync() { var statistics = Sfi.Statistics; @@ -265,7 +265,7 @@ public void Department5IsNotFoundAsync() using(var transaction = session.BeginTransaction()) { ExecuteStatement(session, transaction, $"UPDATE EMPLOYEES SET DEPARTMENT_ID_1 = 11, DEPARTMENT_ID_2 = 12, DEPARTMENT_ID_3 = 13, DEPARTMENT_ID_4 = 24, DEPARTMENT_ID_5 = 99999, ADDRESS_ID = 31 WHERE EMPLOYEE_ID = {employeeId}"); - Assert.That(() => session.GetAsync(employeeId), Throws.InstanceOf()); + Assert.That(() => session.GetAsync(employeeId), Throws.InstanceOf()); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1033/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1033/Fixture.cs index 6440b9d7647..c28da99d600 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1033/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1033/Fixture.cs @@ -26,7 +26,6 @@ protected override void OnSetUp() { using (var tran = session.BeginTransaction()) { - var animal0 = new Animal(); var animal1 = new Reptile(); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1039/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1039/Fixture.cs index ce655bf85f3..9f0849514df 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1039/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1039/Fixture.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.NHSpecificTest.NH1039 { using System.Threading.Tasks; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs index f3accaa4294..53e32d2423a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.NHSpecificTest.NH1039Generic { using System.Threading.Tasks; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1080/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1080/Fixture.cs index c755659d3f5..c0d794c3911 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1080/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1080/Fixture.cs @@ -45,9 +45,6 @@ public async Task TestBugAsync() c.ID = 1; c.Value = "OneToOne"; - - - A a = new A(); a.ID = 1; a.Value = "Parent"; @@ -62,7 +59,6 @@ public async Task TestBugAsync() try { - using (ISession s = Sfi.OpenSession()) { await (s.SaveAsync(c)); @@ -85,7 +81,6 @@ public async Task TestBugAsync() { using (ISession s = Sfi.OpenSession()) { - await (s.DeleteAsync(a)); await (s.DeleteAsync(b1)); await (s.DeleteAsync(c)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1093/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1093/Fixture.cs index 327eafd76c6..0a44ef6c733 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1093/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1093/Fixture.cs @@ -34,13 +34,11 @@ protected override string CacheConcurrencyStrategy private async Task CleanupAsync(CancellationToken cancellationToken = default(CancellationToken)) { - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { - await (s.CreateQuery("delete from SimpleCached").ExecuteUpdateAsync(cancellationToken)); - await (s.Transaction.CommitAsync(cancellationToken)); - } + await (s.CreateQuery("delete from SimpleCached").ExecuteUpdateAsync(cancellationToken)); + await (t.CommitAsync(cancellationToken)); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1097/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1097/Fixture.cs index 4dca355b01f..6be4f7b1679 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1097/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1097/Fixture.cs @@ -47,7 +47,6 @@ public void ThrowsExceptionWhenColumnNameIsUsedInQueryAsync() using (var session = this.OpenSession()) using (var tran = session.BeginTransaction()) { - Assert.ThrowsAsync(delegate { var query = session.CreateQuery("from Person p where p.namecolumn=:nameOfPerson"); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs index 55f6219548b..8be29780dca 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs @@ -24,7 +24,6 @@ protected override void OnSetUp() { using (ISession session = OpenSession()) { - var a1 = new A { Id = 1, ValueA = 5, Enabled = false }; session.Save(a1); @@ -209,4 +208,4 @@ public async Task QueryMapElementsAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1159/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1159/Fixture.cs index df8bddd77c2..c15f3b1561e 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1159/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1159/Fixture.cs @@ -20,7 +20,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1159 [TestFixture] public class FixtureAsync:BugTestCase { - protected override void OnSetUp() { using (ISession session = OpenSession()) @@ -31,8 +30,7 @@ protected override void OnSetUp() tran.Commit(); } HibernateInterceptor.CallCount = 0; - - } + } protected override void OnTearDown() { @@ -61,9 +59,9 @@ public async Task DoesNotFlushWithCriteriaWithCommitAsync() Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); - ICriteria query = session.CreateCriteria(typeof(ContactTitle)); + ICriteria query = session.CreateCriteria(typeof(Contact)); query.Add(Expression.Eq("Id", (Int64)1)); - await (query.UniqueResultAsync()); + await (query.UniqueResultAsync()); Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); @@ -91,9 +89,9 @@ public async Task DoesNotFlushWithCriteriaWithNeverAsync() Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); - ICriteria query = session.CreateCriteria(typeof(ContactTitle)); + ICriteria query = session.CreateCriteria(typeof(Contact)); query.Add(Expression.Eq("Id", (Int64)1)); - await (query.UniqueResultAsync()); + await (query.UniqueResultAsync()); Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); @@ -122,9 +120,9 @@ public async Task DoesNotFlushWithCriteriaWithAutoAsync() Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); - ICriteria query = session.CreateCriteria(typeof(ContactTitle)); + ICriteria query = session.CreateCriteria(typeof(Contact)); query.Add(Expression.Eq("Id", (Int64)1)); - await (query.UniqueResultAsync()); + await (query.UniqueResultAsync()); Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(2)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1179/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1179/Fixture.cs index 358f558661a..f3eac799e70 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1179/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1179/Fixture.cs @@ -67,7 +67,6 @@ public async Task ApplyFilterExplicitJoinAsync() await (s.DeleteAsync("from RelatedClass")); await (tx.CommitAsync()); } - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1235/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1235/Fixture.cs index 6de1b24944a..44f3aef26d7 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1235/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1235/Fixture.cs @@ -53,7 +53,6 @@ public async Task TestAsync() await (tx.CommitAsync()); } - using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) { @@ -67,6 +66,5 @@ public async Task TestAsync() await (tx.CommitAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1252/NH1252Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1252/NH1252Fixture.cs index e5727577ebb..1c3046d8760 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1252/NH1252Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1252/NH1252Fixture.cs @@ -58,7 +58,6 @@ public async Task TestAsync() Assert.IsNull(await (s.GetAsync(savedId))); await (tx.CommitAsync()); } - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1253/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1253/Fixture.cs index 9919113e183..20691ec53ea 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1253/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1253/Fixture.cs @@ -15,7 +15,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1253 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -119,7 +118,7 @@ public async Task QueryBatchSingleInListAsync() await (s.CreateQueryBatch() .Add(q1) .Add(q2) - .ExecuteAsync(CancellationToken.None)); + .ExecuteAsync()); await (tx.CommitAsync()); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs index bb8093267c8..cdcd11bb2f2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs @@ -114,7 +114,6 @@ private Configuration GetConfiguration() return cfg; } - protected static string MappingsAssembly { get { return "NHibernate.Test"; } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1275/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1275/Fixture.cs index 57ecaea5761..5503e4bb9db 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1275/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1275/Fixture.cs @@ -53,6 +53,13 @@ public async Task RetrievingAsync() string sql = sqlLogSpy.Appender.GetEvents()[0].RenderedMessage; Assert.That(sql.IndexOf(Dialect.ForUpdateString, StringComparison.Ordinal), Is.GreaterThan(0)); } + s.Clear(); + using (SqlLogSpy sqlLogSpy = new SqlLogSpy()) + { + await (s.GetAsync(typeof(A).FullName, savedId, LockMode.Upgrade)); + string sql = sqlLogSpy.Appender.GetEvents()[0].RenderedMessage; + Assert.That(sql.IndexOf(Dialect.ForUpdateString, StringComparison.Ordinal), Is.GreaterThan(0)); + } using (SqlLogSpy sqlLogSpy = new SqlLogSpy()) { await (s.CreateQuery("from A a where a.Id= :pid").SetLockMode("a", LockMode.Upgrade).SetParameter("pid", savedId). diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1291AnonExample/NH1291AnonExampleFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1291AnonExample/NH1291AnonExampleFixture.cs index 3b93c39ccf6..5b1fe9c5637 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1291AnonExample/NH1291AnonExampleFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1291AnonExample/NH1291AnonExampleFixture.cs @@ -69,8 +69,6 @@ public int IQ } } - - [Test] public async Task CanCreateAnonExampleForIntAsync() { @@ -188,4 +186,4 @@ public async Task CanQueryUsingAnonRelationsAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1313/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1313/Fixture.cs index 2d0b9b77f40..004a7837e66 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1313/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1313/Fixture.cs @@ -50,6 +50,5 @@ public async Task BugAsync() await (tx.CommitAsync()); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1316/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1316/Fixture.cs index 5b96cf69613..f9f2e558682 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1316/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1316/Fixture.cs @@ -62,7 +62,6 @@ protected override void OnTearDown() } } - [Test] public async Task Correct_Id_Returned_When_Using_TriggerAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1323/CheckViability.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1323/CheckViability.cs index 014d28190f0..419621b11f9 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1323/CheckViability.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1323/CheckViability.cs @@ -29,24 +29,24 @@ public FullInitializedRetrievedEntity(ISessionFactory factory) this.factory = factory; object savedId; using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var entity = new MyClass(); entity.Children.Add(new MyChild { Parent = entity }); entity.Components.Add(new MyComponent { Something = "something" }); entity.Elements.Add("somethingelse"); savedId = session.Save(entity); - session.Transaction.Commit(); + tran.Commit(); } using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { entity = session.Get(savedId); NHibernateUtil.Initialize(entity.Children); NHibernateUtil.Initialize(entity.Components); NHibernateUtil.Initialize(entity.Elements); - session.Transaction.Commit(); + tran.Commit(); } } @@ -76,13 +76,13 @@ public async Task WhenReassociateCollectionUsingMergeThenReassingOwnerAsync() // When I reassociate the collections the Owner has value using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var merged = (MyClass)await (session.MergeAsync(scenario.Entity)); Assert.That(((IPersistentCollection)merged.Children).Owner, Is.Not.Null); Assert.That(((IPersistentCollection)merged.Components).Owner, Is.Not.Null); Assert.That(((IPersistentCollection)merged.Elements).Owner, Is.Not.Null); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } @@ -97,7 +97,7 @@ public async Task WhenReassociateCollectionUsingLockThenTheCommitNotThrowsAsync( ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { // When I reassociate the collections the Owner is null await (session.LockAsync(scenario.Entity, LockMode.None)); @@ -105,17 +105,17 @@ public async Task WhenReassociateCollectionUsingLockThenTheCommitNotThrowsAsync( scenario.Entity.Children.Add(new MyChild { Parent = scenario.Entity }); scenario.Entity.Components.Add(new MyComponent { Something = "something" }); scenario.Entity.Elements.Add("somethingelse"); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = await (session.GetAsync(scenario.Entity.Id)); Assert.That(fresh.Children, Has.Count.EqualTo(2)); Assert.That(fresh.Components, Has.Count.EqualTo(2)); Assert.That(fresh.Elements, Has.Count.EqualTo(2)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } @@ -130,24 +130,24 @@ public async Task WhenReassociateCollectionUsingUpdateThenTheCommitNotThrowsAsyn ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { scenario.Entity.Children.Add(new MyChild { Parent = scenario.Entity }); scenario.Entity.Components.Add(new MyComponent { Something = "something" }); scenario.Entity.Elements.Add("somethingelse"); // When I reassociate the collections the Owner is null await (session.UpdateAsync(scenario.Entity)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = await (session.GetAsync(scenario.Entity.Id)); Assert.That(fresh.Children, Has.Count.EqualTo(2)); Assert.That(fresh.Components, Has.Count.EqualTo(2)); Assert.That(fresh.Elements, Has.Count.EqualTo(2)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } @@ -162,24 +162,24 @@ public async Task WhenReassociateCollectionUsingSaveOrUpdateThenTheCommitNotThro ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { scenario.Entity.Children.Add(new MyChild { Parent = scenario.Entity }); scenario.Entity.Components.Add(new MyComponent { Something = "something" }); scenario.Entity.Elements.Add("somethingelse"); // When I reassociate the collections the Owner is null await (session.SaveOrUpdateAsync(scenario.Entity)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = await (session.GetAsync(scenario.Entity.Id)); Assert.That(fresh.Children, Has.Count.EqualTo(2)); Assert.That(fresh.Components, Has.Count.EqualTo(2)); Assert.That(fresh.Elements, Has.Count.EqualTo(2)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } @@ -194,18 +194,18 @@ public async Task WhenReassociateCollectionUsingDeleteThenTheCommitNotThrowsAsyn ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { await (session.DeleteAsync(scenario.Entity)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = await (session.GetAsync(scenario.Entity.Id)); Assert.That(fresh, Is.Null); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1332/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1332/Fixture.cs index c4bd314364b..e0c2d4714fb 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1332/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1332/Fixture.cs @@ -37,7 +37,6 @@ public async Task BugAsync() using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) { - await (s.SaveAsync(a)); await (tx.CommitAsync()); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1359/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1359/Fixture.cs index 0d4464db461..efe68c3fb13 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1359/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1359/Fixture.cs @@ -138,7 +138,6 @@ public async Task CanGetSelectSubqueryWithSpecifiedParameterAsync() } } - [Test] public async Task CanPageAndSortResultsWithParametersAndFiltersAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1388/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1388/Fixture.cs index fe29f63e407..5b9d086c2b0 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1388/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1388/Fixture.cs @@ -95,9 +95,9 @@ public async Task BagTestAsync() protected override void OnTearDown() { // clean up the database - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); foreach (var student in session.CreateCriteria(typeof (Student)).List()) { session.Delete(student); @@ -106,7 +106,7 @@ protected override void OnTearDown() { session.Delete(subject); } - session.Transaction.Commit(); + tran.Commit(); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture.cs index b72a783b7f8..9092c16a619 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture.cs @@ -57,7 +57,6 @@ protected override void OnSetUp() var sivasKangalForSivasKangals = new SivasKangal { Name = "Karabas4", Country = "Turkey", HouseAddress = "Atakoy", Owner = personWithSivasKangals }; personWithSivasKangals.AnimalsGeneric.Add(sivasKangalForSivasKangals); - session.Save(animalForCats); session.Save(dogForCats); @@ -71,8 +70,6 @@ protected override void OnSetUp() _idOfPersonWithCats = session.Save(personWithCats); _idOfPersonWithDogs = session.Save(personWithDogs); _idOfPersonWithSivasKangals = session.Save(personWithSivasKangals); - - tran.Commit(); } @@ -88,7 +85,6 @@ protected override void OnTearDown() } } - [Test] public async Task Can_discriminate_subclass_on_list_with_lazy_loading_when_used_getAsync() { @@ -110,4 +106,4 @@ public async Task Can_discriminate_subclass_on_list_with_lazy_loading_when_used_ } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture2.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture2.cs index 75030d31ede..a5269e97e7a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture2.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1391/Fixture2.cs @@ -53,7 +53,6 @@ protected override void OnTearDown() } } - [Test] public async Task Can_discriminate_subclass_on_list_with_lazy_loading_when_used_and_person_had_multiple_listAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1443/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1443/Fixture.cs index cf89b57537f..9a588e23982 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1443/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1443/Fixture.cs @@ -27,14 +27,12 @@ public class FixtureAsync await (su.ExecuteAsync(x => sb.AppendLine(x), false, false, cancellationToken)); string script = sb.ToString(); - if (Dialect.Dialect.GetDialect(cfg.Properties).SupportsIfExistsBeforeTableName) Assert.That(script, Does.Match("drop table if exists nhibernate.dbo.Aclass")); else Assert.That(script, Does.Match("drop table nhibernate.dbo.Aclass")); Assert.That(script, Does.Match("create ((column|row) )?table nhibernate.dbo.Aclass")); - } [Test] diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1478/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1478/Fixture.cs index 3dac80ad57e..abd6551dfa3 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1478/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1478/Fixture.cs @@ -53,13 +53,10 @@ public async Task TestIfColonInStringIsNotInterpretedAsParameterInSQLAsync() { using (ISession session=OpenSession()) { - - IList lst = await (session.CreateSQLQuery("select Biography from Person where Biography='Born in Istanbul :Turkey'") .AddScalar("Biography", NHibernateUtil.String).ListAsync()); Assert.AreEqual(1,lst.Count); } - } [Test] @@ -67,14 +64,10 @@ public async Task TestIfColonInStringIsNotInterpretedAsParameterInHQLAsync() { using (ISession session = OpenSession()) { - - IList lst = await (session.CreateSQLQuery("select p.Biography from Person p where p.Biography='Born in Istanbul :Turkey'") .ListAsync()); Assert.AreEqual(1, lst.Count); } - } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1487/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1487/Fixture.cs index e6c959c81a5..feb240d4691 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1487/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1487/Fixture.cs @@ -25,7 +25,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1487 [TestFixture] public class FixtureAsync { - public Configuration GetConf() { var cfg = new Configuration(); @@ -212,6 +211,5 @@ public async Task GenerateSchemaUniqueOnIdAsync() await (new SchemaExport(cfg).DropAsync(false, true)); } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1495/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1495/Fixture.cs index a50092c2f97..1a62ac23dcb 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1495/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1495/Fixture.cs @@ -48,6 +48,5 @@ public async Task CreateTestAsync() } } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1499/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1499/Fixture.cs index 3748d1f1edd..9c3fd4d6824 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1499/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1499/Fixture.cs @@ -69,7 +69,5 @@ public void CheckIfDetachedCriteriaCanBeUsedOnPropertyRestrictionAsync() Assert.ThrowsAsync(() => criteria.ListAsync()); } } - - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1508/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1508/Fixture.cs index bd1b7cb21a5..7af6e4327e5 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1508/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1508/Fixture.cs @@ -15,7 +15,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1508 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -81,7 +80,7 @@ public async Task DoesntThrowExceptionWhenHqlQueryIsGivenToQueryBatchAsync() var q = session .CreateQueryBatch() .Add(sqlQuery); - await (q.ExecuteAsync(CancellationToken.None)); + await (q.ExecuteAsync()); } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1549/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1549/Fixture.cs index e7692c7575b..900f1648e4f 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1549/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1549/Fixture.cs @@ -89,7 +89,6 @@ public async Task CanLoadForEntitiesWithTheirOwnIdsAsync() protected override void OnTearDown() { using (ISession session = OpenSession()) { - using (ITransaction trans = session.BeginTransaction()) { session.Delete("from ProductWithId"); @@ -101,4 +100,4 @@ protected override void OnTearDown() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1552/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1552/Fixture.cs index a5a8f1ca69f..81f6e36eccd 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1552/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1552/Fixture.cs @@ -94,7 +94,6 @@ public async Task Paging_with_sql_works_as_expected_with_MaxResultAsync() } } - [Test] public async Task Paging_with_sql_works_as_expected_with_FirstResultMaxResultAsync() { @@ -114,4 +113,4 @@ public async Task Paging_with_sql_works_as_expected_with_FirstResultMaxResultAsy } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture1.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture1.cs index 1f261039983..34cc2026819 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture1.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture1.cs @@ -108,7 +108,6 @@ public async Task TestRefreshWithCountAsync() return project; } - public async Task LoadProjectAsync(CancellationToken cancellationToken = default(CancellationToken)) { ProjectWithOneList project; @@ -127,7 +126,6 @@ public async Task TestRefreshWithCountAsync() public async Task RefreshProjectAsync(ProjectWithOneList project, CancellationToken cancellationToken = default(CancellationToken)) { - using (ISession session = OpenSession()) using (ITransaction tx = session.BeginTransaction()) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture2.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture2.cs index 7fc351741cd..36ed8f9862f 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture2.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1601/Fixture2.cs @@ -63,7 +63,6 @@ public async Task TestRefreshWithThreeCountsAsync() await (SaveLoadAndRefreshProjectAsync()); } - /// /// Create and save a Project /// @@ -85,7 +84,6 @@ public async Task TestRefreshWithThreeCountsAsync() await (RefreshProjectAsync(project, cancellationToken)); } - public async Task SaveProjectAsync( CancellationToken cancellationToken = default(CancellationToken)) { Project project; @@ -99,13 +97,11 @@ public async Task TestRefreshWithThreeCountsAsync() Scenario scenario2 = new Scenario(); Scenario scenario3 = new Scenario(); - //Add the scenario to all lists project.ScenarioList1.Add(scenario1); project.ScenarioList2.Add(scenario2); project.ScenarioList3.Add(scenario3); - //Set the primary key on the project project.Name = "Test"; @@ -117,7 +113,6 @@ public async Task TestRefreshWithThreeCountsAsync() return project; } - public async Task LoadProjectAsync(CancellationToken cancellationToken = default(CancellationToken)) { Project project; @@ -136,7 +131,6 @@ public async Task TestRefreshWithThreeCountsAsync() public async Task RefreshProjectAsync(Project project, CancellationToken cancellationToken = default(CancellationToken)) { - using (ISession session = OpenSession()) using (ITransaction tx = session.BeginTransaction()) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1609/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1609/Fixture.cs index 0ef9c665113..6500ec03282 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1609/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1609/Fixture.cs @@ -86,9 +86,9 @@ public async Task TestWithQueryBatchAsync() session.CreateCriteria(typeof (EntityB)).Add(Restrictions.Eq("A.Id", a1.Id)).Add(Restrictions.Eq("C.Id", c.Id)). SetFirstResult(0).SetMaxResults(1)); - Assert.That(await (multi.GetResultAsync(0, CancellationToken.None)), Has.Count.EqualTo(1)); - Assert.That(await (multi.GetResultAsync(1, CancellationToken.None)), Has.Count.EqualTo(1)); - Assert.That(await (multi.GetResultAsync(2, CancellationToken.None)), Has.Count.EqualTo(1)); + Assert.That(await (multi.GetResultAsync(0)), Has.Count.EqualTo(1)); + Assert.That(await (multi.GetResultAsync(1)), Has.Count.EqualTo(1)); + Assert.That(await (multi.GetResultAsync(2)), Has.Count.EqualTo(1)); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs index 4b60f5f59f1..d8593466983 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs @@ -51,7 +51,6 @@ protected override void OnSetUp() } } - [Test] public async Task CanQueryOneToOneWithCompositeIdAsync() { @@ -63,10 +62,8 @@ public async Task CanQueryOneToOneWithCompositeIdAsync() IList list = await (criteria.ListAsync()); Assert.AreEqual("blarg", list[0].Description); Assert.AreEqual("nuts", list[0].Adjunct.AdjunctDescription); - } } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1632/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1632/Fixture.cs index 95c3f37665a..5cb05d3db21 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1632/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1632/Fixture.cs @@ -74,7 +74,6 @@ public async Task When_using_DTC_HiLo_knows_to_create_isolated_DTC_transactionAs Assert.AreNotEqual(scalar1, scalar2, "HiLo must run with in its own transaction"); } - [Test] public async Task When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_cacheAsync() { @@ -88,7 +87,6 @@ public async Task When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_ } try { - using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { using (var s = OpenSession()) @@ -189,7 +187,6 @@ public async Task When_using_two_sessions_with_explicit_flushAsync() using (ISession s1 = Sfi.OpenSession()) using (ISession s2 = Sfi.OpenSession()) { - id1 = await (s1.SaveAsync(new Nums { NumA = 1, NumB = 2, ID = 5 })); await (s1.FlushAsync()); @@ -229,7 +226,6 @@ public async Task When_using_two_sessionsAsync() using (ISession s1 = Sfi.OpenSession()) using (ISession s2 = Sfi.OpenSession()) { - id1 = await (s1.SaveAsync(new Nums { NumA = 1, NumB = 2, ID = 5 })); id2 = await (s2.SaveAsync(new Nums { NumA = 1, NumB = 2, ID = 6 })); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1643/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1643/Fixture.cs index 20d9ed12fab..117b7c42422 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1643/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1643/Fixture.cs @@ -53,8 +53,6 @@ public async Task TestAsync() await (tx.CommitAsync()); } - - using (ISession sess = OpenSession()) using (ITransaction tx = sess.BeginTransaction()) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1665/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1665/Fixture.cs index 495a552d22e..58367836c64 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1665/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1665/Fixture.cs @@ -24,16 +24,17 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public async Task SupportsHibernateQuotingSequenceNameAsync() { - ISession session = OpenSession(); - session.BeginTransaction(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntity { Name = "entity-1" }; + await (session.SaveAsync(e)); + Assert.AreEqual(1, (int) session.GetIdentifier(e)); - var e = new MyEntity { Name = "entity-1" }; - await (session.SaveAsync(e)); - Assert.AreEqual(1, (int)session.GetIdentifier(e)); - - await (session.DeleteAsync(e)); - await (session.Transaction.CommitAsync()); - session.Close(); + await (session.DeleteAsync(e)); + await (tran.CommitAsync()); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1688/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1688/Fixture.cs index 21e890b1394..63de1264791 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1688/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1688/Fixture.cs @@ -68,7 +68,7 @@ protected override void OnTearDown() { using (ISession session = OpenSession()) { - DetachedCriteria criteria = DetachedCriteria.For("alias"); + DetachedCriteria criteria = DetachedCriteria.For("alias"); action.Invoke(criteria); @@ -77,4 +77,4 @@ protected override void OnTearDown() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1689/SampleTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1689/SampleTest.cs index cb0e2a1fa61..65b410f0af4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1689/SampleTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1689/SampleTest.cs @@ -54,5 +54,4 @@ public async Task ShouldBeAbleToCallGenericMethodAsync() } } } - } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1693/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1693/Fixture.cs index d61b0ac8bcb..5c73e3d25d4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1693/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1693/Fixture.cs @@ -81,6 +81,5 @@ public async Task with_filterAsync() await (tx.CommitAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1727/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1727/Fixture.cs index a0e8b13062d..6213a98583e 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1727/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1727/Fixture.cs @@ -24,7 +24,6 @@ public class FixtureAsync : BugTestCase * The second test passes where I've just switched the order in the where clause */ - [Test] public async Task VerifyFilterAndInAndProperty_DoesNotWorkTodayAsync() { @@ -48,7 +47,6 @@ where a.Value in (:aValues) } } - [Test] public async Task VerifyFilterAndInAndProperty_WorksTodayAsync() { @@ -82,6 +80,5 @@ protected override void OnTearDown() t.Commit(); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1747/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1747/Fixture.cs index 6f39e46ad71..16624cdaf30 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1747/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1747/Fixture.cs @@ -52,5 +52,4 @@ public async Task TraversingBagToJoinChildElementShouldWorkAsync() } } } - } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1761/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1761/FixtureByCode.cs index fbbd94ba28f..f881f767239 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1761/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1761/FixtureByCode.cs @@ -30,7 +30,6 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); rc.Bag(x => x.FundingPrograms, m => {}, r => r.OneToMany()); - }); mapper.Class(rc => { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1788/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1788/Fixture.cs index c3826d8b826..2347fb080ce 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1788/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1788/Fixture.cs @@ -37,7 +37,6 @@ public async Task CanUseSqlTimestampWithDynamicInsertAsync() await (tx.CommitAsync()); } - using (var session = OpenSession()) using (var tx = session.BeginTransaction()) { @@ -46,14 +45,12 @@ public async Task CanUseSqlTimestampWithDynamicInsertAsync() await (tx.CommitAsync()); } - using (ISession session = OpenSession()) using (var tx = session.BeginTransaction()) { await (session.DeleteAsync(await (session.GetAsync(1)))); await (tx.CommitAsync()); } - } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1818/Fixture1818.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1818/Fixture1818.cs index 5c76c4d4a39..569c4e2e08d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1818/Fixture1818.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1818/Fixture1818.cs @@ -43,7 +43,6 @@ protected override bool AppliesTo(Dialect.Dialect dialect) return dialect as PostgreSQL82Dialect != null; } - [Test] [Description("Test HQL query on a property mapped with a formula.")] public async Task ComputedPropertyShouldRetrieveDataCorrectlyAsync() @@ -55,4 +54,4 @@ public async Task ComputedPropertyShouldRetrieveDataCorrectlyAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1836/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1836/Fixture.cs index 5bd5c737d32..cecc135a677 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1836/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1836/Fixture.cs @@ -18,7 +18,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1836 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -70,7 +69,7 @@ public async Task AliasToBeanTransformerShouldApplyCorrectlyToQueryBatchAsync() .SetResultTransformer(Transformers.AliasToBean(typeof(EntityDTO)))); Assert.That(multiQuery.Execute, Throws.Nothing); - var results = await (multiQuery.GetResultAsync(0, CancellationToken.None)); + var results = await (multiQuery.GetResultAsync(0)); Assert.That(results.First(), Is.TypeOf().And.Property("EntityId").EqualTo(1)); await (t.CommitAsync()); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1837/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1837/Fixture.cs index ef360e18c6d..708eb08f502 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1837/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1837/Fixture.cs @@ -48,7 +48,6 @@ protected override void OnTearDown() [Test] public async Task ExecutesOneQueryWithUniqueResultWithChildCriteriaNonGenericAsync() { - Sfi.Statistics.Clear(); using (ISession session = this.OpenSession()) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1845/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1845/Fixture.cs index 25db65d4a61..1aa3e83e6a4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1845/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1845/Fixture.cs @@ -8,36 +8,14 @@ //------------------------------------------------------------------------------ -using NHibernate.Cfg.MappingSchema; -using NHibernate.Mapping.ByCode; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH1845 { using System.Threading.Tasks; using System.Threading; [TestFixture] - public class FixtureAsync : TestCaseMappingByCode + public class FixtureAsync : BugTestCase { - - protected override HbmMapping GetMappings() - { - var mapper = new ModelMapper(); - mapper.Class(rc => - { - rc.Id(x => x.Id, map => map.Generator(Generators.Native)); - rc.Property(x => x.Name); - rc.ManyToOne(x => x.Parent, map => map.Column("ParentId")); - rc.Bag(x => x.Subcategories, map => - { - map.Access(Accessor.NoSetter); - map.Key(km => km.Column("ParentId")); - map.Cascade(Mapping.ByCode.Cascade.All.Include(Mapping.ByCode.Cascade.DeleteOrphans)); - }, rel => rel.OneToMany()); - }); - var mappings = mapper.CompileMappingForAllExplicitlyAddedEntities(); - return mappings; - } - [Test] public async Task LazyLoad_Initialize_AndEvictAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1850/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1850/Fixture.cs index ba9f0a8f99a..28b735d8ee4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1850/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1850/Fixture.cs @@ -7,7 +7,6 @@ // //------------------------------------------------------------------------------ - using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH1850 diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1863/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1863/Fixture.cs index 7745b213f83..b189752c9e7 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1863/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1863/Fixture.cs @@ -66,7 +66,6 @@ public async Task CanGetCustomerWithCategoryWhenFilterIsEnabledAsync() Assert.That(hasCategoryResult.Count, Is.EqualTo(1)); } - } [Test] @@ -98,7 +97,6 @@ public async Task CanGetCustomerWithNoCategoryWhenFilterIsEnabledAsync() Assert.That(hasNoCategoryResult.Count, Is.EqualTo(1)); } - } [Test] @@ -114,7 +112,6 @@ public async Task CanGetCustomerWithNoCategoryWhenFilterIsDisabledAsync() Assert.That(hasNoCategoryResult.Count, Is.EqualTo(1)); } - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1869/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1869/Fixture.cs index 99e5824c791..96e60add7f7 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1869/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1869/Fixture.cs @@ -107,8 +107,8 @@ public async Task TestWithQueryBatchAsync() using (var session = Sfi.OpenSession()) { var result = await (GetResultWithQueryBatchAsync(session)); - Assert.That(await (result.GetResultAsync(0, CancellationToken.None)), Has.Count.EqualTo(1)); - Assert.That(await (result.GetResultAsync(1, CancellationToken.None)), Has.Count.EqualTo(1)); + Assert.That(await (result.GetResultAsync(0)), Has.Count.EqualTo(1)); + Assert.That(await (result.GetResultAsync(1)), Has.Count.EqualTo(1)); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs index 848c35cab4f..3f90814e610 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs @@ -123,29 +123,29 @@ public async Task TestInitializationDuringFlushAsync() Assert.False(listener.FoundAny); using (var s1 = OpenSession()) + using (var t1 = s1.BeginTransaction()) { - s1.BeginTransaction(); var publisher = new Publisher("acme"); var author = new Author("john"); author.Publisher = publisher; publisher.Authors.Add(author); author.Books.Add(new Book("Reflections on a Wimpy Kid", author)); await (s1.SaveAsync(author)); - await (s1.Transaction.CommitAsync()); + await (t1.CommitAsync()); s1.Clear(); using (var s2 = OpenSession()) + using (var t2 = s2.BeginTransaction()) { - s2.BeginTransaction(); publisher = await (s2.GetAsync(publisher.Id)); publisher.Name = "random nally"; await (s2.FlushAsync()); - await (s2.Transaction.CommitAsync()); + await (t2.CommitAsync()); s2.Clear(); using (var s3 = OpenSession()) + using (var t3 = s3.BeginTransaction()) { - s3.BeginTransaction(); await (s3.DeleteAsync(author)); - await (s3.Transaction.CommitAsync()); + await (t3.CommitAsync()); s3.Clear(); s3.Close(); } @@ -184,4 +184,4 @@ public async Task OnPreUpdateAsync(PreUpdateEvent @event, CancellationToke } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1908/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1908/Fixture.cs index 469d804f16f..9227c2eb47d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1908/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1908/Fixture.cs @@ -17,7 +17,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1908 [TestFixture] public class FixtureAsync : BugTestCase { - [Test] public async Task QueryPropertyInBothFilterAndQueryAsync() { @@ -69,4 +68,4 @@ Invoice inv } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1911/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1911/Fixture.cs index d3a4d261e02..678de458ba1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1911/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1911/Fixture.cs @@ -16,11 +16,9 @@ namespace NHibernate.Test.NHSpecificTest.NH1911 { using System.Threading.Tasks; - [TestFixture] public class FixtureAsync : BugTestCase { - protected override void OnSetUp() { base.OnSetUp(); @@ -70,7 +68,5 @@ public async Task ConditionalAggregateProjectionAsync() Assert.That(actual[0][1], Is.EqualTo(2)); } } - } - } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1920/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1920/Fixture.cs index 5c9d4c99fc4..5bb3f51aae2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1920/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1920/Fixture.cs @@ -18,7 +18,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1920 [TestFixture] public class FixtureAsync : BugTestCase { - [Test] public async Task Can_Query_Without_Collection_Size_ConditionAsync() { @@ -77,6 +76,5 @@ public async Task Can_Query_With_Collection_Size_ConditionAsync() await (tx.CommitAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1922/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1922/Fixture.cs index 6cd098c22ff..39fdbfa26ec 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1922/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1922/Fixture.cs @@ -46,7 +46,6 @@ protected override void OnTearDown() base.OnTearDown(); } - [Test] public async Task CanExecuteQueryOnStatelessSessionUsingDetachedCriteriaAsync() { @@ -61,7 +60,5 @@ public async Task CanExecuteQueryOnStatelessSessionUsingDetachedCriteriaAsync() Assert.IsNotNull(cust); } } - - - } + } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1927/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1927/Fixture.cs index 94009e1b626..3e57122ca0c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1927/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1927/Fixture.cs @@ -72,8 +72,7 @@ protected override void OnTearDown() Assert.That(queryFactoryFunc(session), Is.Not.Null, "failed with filter on"); await (tx.CommitAsync(cancellationToken)); } - - } + } [Test] public async Task CriteriaWithEagerFetchAsync() diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1938/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1938/Fixture.cs index cdfde4cd8dc..58a96323e5b 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1938/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1938/Fixture.cs @@ -19,7 +19,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1938 [TestFixture] public class FixtureAsync : BugTestCase { - protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) { // Database needs to be case-sensitive @@ -59,6 +58,5 @@ public async Task Can_Query_By_Example_Case_InsensitiveAsync() await (t.RollbackAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1939/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1939/Fixture.cs index fc01172cb66..10c968d6f0b 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1939/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1939/Fixture.cs @@ -21,7 +21,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1939 [TestFixture] public class FixtureAsync : BugTestCase { - private StringBuilder schemaBuilder; private void AddString(string sqlString) @@ -34,7 +33,6 @@ protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) return (dialect is Dialect.MsSql2000Dialect); } - [Test] public async Task Can_Parameterise_Auxiliary_Database_ObjectsAsync() { @@ -51,6 +49,5 @@ public async Task Can_Parameterise_Auxiliary_Database_ObjectsAsync() Assert.That(schema.Contains("select 'create script'"), Is.True, "parameterised schema create script not exported"); } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1948/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1948/Fixture.cs index e514f1bec06..ab47efcd7d6 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1948/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1948/Fixture.cs @@ -17,7 +17,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1948 [TestFixture] public class FixtureAsync : BugTestCase { - [Test] public async Task CanUseDecimalScaleZeroAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1959/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1959/Fixture.cs index fd40501ad2c..e5ba4a06d25 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1959/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1959/Fixture.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1959 [TestFixture] public class FixtureAsync : BugTestCase { - protected override void OnTearDown() { using (ISession s = OpenSession()) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1985/SampleTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1985/SampleTest.cs index 83f1fca21a2..11839d59e4e 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1985/SampleTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1985/SampleTest.cs @@ -39,45 +39,6 @@ protected override void OnTearDown() ExecuteStatement("DELETE FROM DomainClass WHERE Id=1;"); } - - [Test] - [Ignore("It is valid to delete immutable entities")] - public async Task AttemptToDeleteImmutableObjectShouldThrowAsync() - { - using (ISession session = OpenSession()) - { - Assert.ThrowsAsync(async () => - { - using (ITransaction trans = session.BeginTransaction()) - { - var entity = await (session.GetAsync(1)); - await (session.DeleteAsync(entity)); - - await (trans.CommitAsync()); // This used to throw... - } - }); - } - - using (IConnectionProvider prov = ConnectionProviderFactory.NewConnectionProvider(cfg.Properties)) - { - var conn = await (prov.GetConnectionAsync(CancellationToken.None)); - - try - { - using (var comm = conn.CreateCommand()) - { - comm.CommandText = "SELECT Id FROM DomainClass WHERE Id=1 AND Label='TEST record'"; - object result = await (comm.ExecuteScalarAsync()); - - Assert.That(result != null, "Immutable object has been deleted!"); - } - } - finally - { - prov.CloseConnection(conn); - } - } - } [Test] public async Task AllowDeletionOfImmutableObjectAsync() diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2037/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2037/Fixture.cs index 323fc0c8a7e..0891ca9b4ae 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2037/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2037/Fixture.cs @@ -8,17 +8,18 @@ //------------------------------------------------------------------------------ - using NUnit.Framework; +using NUnit.Framework; + namespace NHibernate.Test.NHSpecificTest.NH2037 { - using System.Threading.Tasks; - [TestFixture] - public class FixtureAsync : BugTestCase + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase { - [Test] - public async Task TestAsync() - { - var country = new Country {Name = "Argentina"}; + [Test] + public async Task TestAsync() + { + var country = new Country { Name = "Argentina" }; var city = new City { @@ -27,41 +28,40 @@ public async Task TestAsync() Name = "Cordoba" }; - + using (ISession session = OpenSession()) - using(var tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { await (session.SaveAsync(city.Country)); await (session.SaveAsync(city)); await (tx.CommitAsync()); - } - - using(ISession session = OpenSession()) + } + + using (ISession session = OpenSession()) using (var tx = session.BeginTransaction()) - { + { //THROW await (session.SaveOrUpdateAsync(city)); await (tx.CommitAsync()); - } - + } + using (var session = OpenSession()) using (var tx = session.BeginTransaction()) - { + { Assert.IsNotNull(await (session.GetAsync(city.Id))); await (tx.CommitAsync()); - } + } } - + protected override void OnTearDown() { - using(var session = OpenSession()) - using(var tx = session.BeginTransaction()) - { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { session.Delete("from City"); session.Delete("from Country"); tx.Commit(); } } - - } - } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2044/SampleTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2044/SampleTest.cs index 86ca0bc0e3b..76204f45c17 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2044/SampleTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2044/SampleTest.cs @@ -45,7 +45,6 @@ protected override void OnTearDown() } } - [Test] public async Task IgnoreCaseShouldWorkWithCharCorrectlyAsync() { @@ -56,8 +55,7 @@ public async Task IgnoreCaseShouldWorkWithCharCorrectlyAsync() IList list = await (criteria.ListAsync()); Assert.AreEqual(1, list.Count); - - } + } } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2049/Fixture2049.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2049/Fixture2049.cs index 58de5b9cf80..3431ed1c59f 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2049/Fixture2049.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2049/Fixture2049.cs @@ -37,7 +37,6 @@ protected override void OnSetUp() } } - protected override void OnTearDown() { base.OnTearDown(); @@ -48,15 +47,13 @@ protected override void OnTearDown() } } - [Test] - [KnownBug("Known bug NH-2049.")] public async Task CanCriteriaQueryWithFilterOnJoinClassBaseClassPropertyAsync() { using (ISession session = OpenSession()) { session.EnableFilter("DeletedCustomer").SetParameter("deleted", false); - IList persons = await (session.CreateCriteria(typeof (Person)).ListAsync()); + IList persons = await (session.QueryOver().JoinQueryOver(x => x.IndividualCustomer).ListAsync()); Assert.That(persons, Has.Count.EqualTo(1)); Assert.That(persons[0].Id, Is.EqualTo(1)); @@ -66,16 +63,14 @@ public async Task CanCriteriaQueryWithFilterOnJoinClassBaseClassPropertyAsync() } } - [Test] - [KnownBug("Known bug NH-2049.", "NHibernate.Exceptions.GenericADOException")] public async Task CanHqlQueryWithFilterOnJoinClassBaseClassPropertyAsync() { using (ISession session = OpenSession()) { session.EnableFilter("DeletedCustomer").SetParameter("deleted", false); - var persons = await (session.CreateQuery("from Person as person left join person.IndividualCustomer as indCustomer") - .ListAsync()); + var persons = await (session.CreateQuery("from Person as person inner join fetch person.IndividualCustomer as indCustomer") + .ListAsync()); Assert.That(persons, Has.Count.EqualTo(1)); Assert.That(persons[0].Id, Is.EqualTo(1)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2053/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2053/Fixture.cs index 37c670a0252..910c31b2494 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2053/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2053/Fixture.cs @@ -87,6 +87,5 @@ public async Task JoinedSubClass_FilterAsync() } } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2055/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2055/Fixture.cs index 57fccf1c1fa..37e4fe39bc0 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2055/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2055/Fixture.cs @@ -52,6 +52,5 @@ public async Task CanCreateAndDropSchemaAsync() } } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2065/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2065/Fixture.cs index 402955be34a..2181ab63c07 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2065/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2065/Fixture.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.NHSpecificTest.NH2065 { using System.Threading.Tasks; @@ -21,7 +20,7 @@ public class FixtureAsync : BugTestCase protected override void OnSetUp() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var person = new Person { @@ -32,17 +31,17 @@ protected override void OnSetUp() s.Save(child); person.Children.Add(child); - s.Transaction.Commit(); + t.Commit(); } } protected override void OnTearDown() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from Person"); - s.Transaction.Commit(); + t.Commit(); } } @@ -51,17 +50,17 @@ public async Task GetGoodErrorForDirtyReassociatedCollectionAsync() { Person person; using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { person = await (s.GetAsync(1)); await (NHibernateUtil.InitializeAsync(person.Children)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } person.Children.Clear(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Assert.That( () => @@ -73,6 +72,5 @@ public async Task GetGoodErrorForDirtyReassociatedCollectionAsync() "reassociated object has dirty collection: NHibernate.Test.NHSpecificTest.NH2065.Person.Children")); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2069/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2069/Fixture.cs index a6c14e29048..af88f9c6de2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2069/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2069/Fixture.cs @@ -8,13 +8,7 @@ //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Transactions; -using NHibernate; -using NHibernate.Impl; using NHibernate.Proxy; -using NHibernate.Criterion; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH2069 @@ -26,7 +20,7 @@ public class FixtureAsync : BugTestCase protected override void OnSetUp() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var test2 = new Test2(); test2.Cid = 5; @@ -40,19 +34,19 @@ protected override void OnSetUp() s.Save(test2); s.Save(test); - s.Transaction.Commit(); - } + t.Commit(); + } } protected override void OnTearDown() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from Test"); s.Delete("from Test2"); - s.Transaction.Commit(); - } + t.Commit(); + } } [Test] diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2074/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2074/Fixture.cs index 285fb991674..4ed53122158 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2074/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2074/Fixture.cs @@ -17,7 +17,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2074 [TestFixture] public class FixtureAsync : BugTestCase { - protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) { return dialect is MsSql2000Dialect; @@ -31,6 +30,5 @@ public async Task CanQueryOnPropertyUsingUnicodeTokenAsync() await (s.CreateQuery("from Person").ListAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2077/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2077/Fixture.cs index 506278859fe..242c38dcd86 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2077/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2077/Fixture.cs @@ -22,7 +22,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2077 [TestFixture] public class FixtureAsync : BugTestCase { - protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) { return dialect is MsSql2000Dialect; @@ -44,6 +43,5 @@ public async Task CanExecuteMultipleQueriesUsingNativeSQLAsync() .ExecuteUpdateAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2112/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2112/Fixture.cs index 02d30edf561..ee297d7a0c8 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2112/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2112/Fixture.cs @@ -84,6 +84,5 @@ protected void AssertDeleteCount(int expected) { Assert.That(Sfi.Statistics.EntityDeleteCount, Is.EqualTo(expected), "unexpected delete count"); } - - } + } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2113/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2113/Fixture.cs index 843558f2ad0..cc4fa3ef6e4 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2113/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2113/Fixture.cs @@ -22,7 +22,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2113 [TestFixture] public class FixtureAsync : BugTestCase { - [Test] public async Task ShouldNotEagerLoadKeyManyToOneWhenOverridingGetHashCodeAsync() { @@ -53,7 +52,6 @@ public async Task ShouldNotEagerLoadKeyManyToOneWhenOverridingGetHashCodeAsync() await (tx.CommitAsync()); } - using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2148/BugFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2148/BugFixture.cs index 7ea2756eaf7..7cccfb4a7e1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2148/BugFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2148/BugFixture.cs @@ -28,7 +28,6 @@ protected override void OnSetUp() }); tx.Commit(); } - } protected override void OnTearDown() diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2195/SQLiteMultiCriteriaTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2195/SQLiteMultiCriteriaTest.cs index 549d14f3d6f..2d379ed23f5 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2195/SQLiteMultiCriteriaTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2195/SQLiteMultiCriteriaTest.cs @@ -20,7 +20,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2195 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class SQLiteMultiCriteriaTestAsync : BugTestCase { @@ -173,8 +172,8 @@ public async Task QueryBatchWithIntsShouldExecuteCorrectlyAsync() multi.Add(criteriaWithPagination); multi.Add(criteriaWithRowCount); - var numResults = (await (multi.GetResultAsync(1, CancellationToken.None))).Single(); - var list = await (multi.GetResultAsync(0, CancellationToken.None)); + var numResults = (await (multi.GetResultAsync(1))).Single(); + var list = await (multi.GetResultAsync(0)); Assert.That(numResults, Is.EqualTo(2)); Assert.That(list.Count, Is.EqualTo(1)); @@ -198,8 +197,8 @@ public async Task QueryBatchWithStringsShouldExecuteCorrectlyAsync() multi.Add(criteriaWithPagination); multi.Add(criteriaWithRowCount); - var numResults = (await (multi.GetResultAsync(1, CancellationToken.None))).Single(); - var list = await (multi.GetResultAsync(0, CancellationToken.None)); + var numResults = (await (multi.GetResultAsync(1))).Single(); + var list = await (multi.GetResultAsync(0)); Assert.That(numResults, Is.EqualTo(2)); Assert.That(list.Count, Is.EqualTo(1)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2201/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2201/Fixture.cs index c8d31458508..a85ccc55831 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2201/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2201/Fixture.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2201 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -78,8 +77,8 @@ public async Task CanUseQueryBatchAndFetchSelectAsync() .Add(s.CreateCriteria()) .Add(s.CreateCriteria()); - var result1 = await (multi.GetResultAsync(0, CancellationToken.None)); - var result2 = await (multi.GetResultAsync(1, CancellationToken.None)); + var result1 = await (multi.GetResultAsync(0)); + var result2 = await (multi.GetResultAsync(1)); Assert.That(result1.Count, Is.EqualTo(2)); Assert.That(result2.Count, Is.EqualTo(2)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2208/Filter.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2208/Filter.cs index 3677f3c3428..7d0b3077007 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2208/Filter.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2208/Filter.cs @@ -16,8 +16,8 @@ namespace NHibernate.Test.NHSpecificTest.NH2208 [TestFixture] public class FilterAsync : BugTestCase { - [Test, Ignore("Not fixed yet")] - public async Task TestAsync() + [Test] + public async Task TestHqlAsync() { using (ISession session = OpenSession()) { @@ -25,5 +25,15 @@ public async Task TestAsync() await (session.CreateQuery("from E1 e join fetch e.BO").ListAsync()); } } + + [Test] + public async Task TestQueryOverAsync() + { + using (ISession session = OpenSession()) + { + session.EnableFilter("myfilter"); + await (session.QueryOver().JoinQueryOver(x => x.BO).ListAsync()); + } + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2294/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2294/Fixture.cs deleted file mode 100644 index 905cd3b3645..00000000000 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2294/Fixture.cs +++ /dev/null @@ -1,32 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by AsyncGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -using System.Linq; -using NHibernate.Hql.Ast.ANTLR; -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH2294 -{ - using System.Threading.Tasks; - [TestFixture] - public class FixtureAsync : TestCase - { - protected override string[] Mappings => System.Array.Empty(); - - [Test, Ignore("External issue. The bug is inside RecognitionException of Antlr.")] - public void WhenQueryHasJustAWhereThenThrowQuerySyntaxExceptionAsync() - { - using (ISession session = OpenSession()) - { - Assert.That(() => session.CreateQuery("where").ListAsync(), Throws.TypeOf()); - } - } - } -} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2302/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2302/Fixture.cs index 009e8c61141..49fb2b1ccd9 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2302/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2302/Fixture.cs @@ -82,35 +82,6 @@ public async Task StringHugeLengthAsync() } } - [Test, Ignore("Not supported without specify the string length.")] - public async Task StringSqlTypeAsync() - { - int id; - // buildup a string the exceed the mapping - string str = GetFixedLengthString12000(); - - using (ISession sess = OpenSession()) - using (ITransaction tx = sess.BeginTransaction()) - { - // create and save the entity - StringLengthEntity entity = new StringLengthEntity(); - entity.StringSqlType = str; - await (sess.SaveAsync(entity)); - await (tx.CommitAsync()); - id = entity.ID; - } - - using (ISession sess = OpenSession()) - using (ITransaction tx = sess.BeginTransaction()) - { - StringLengthEntity loaded = await (sess.GetAsync(id)); - Assert.IsNotNull(loaded); - Assert.AreEqual(12000, loaded.StringSqlType.Length); - Assert.AreEqual(str, loaded.StringSqlType); - await (tx.CommitAsync()); - } - } - [Test] public async Task BlobSqlTypeAsync() { @@ -214,6 +185,5 @@ private static string GetFixedLengthString12000() { return new string('a', 12000); } - - } + } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2322/PostUpdateEventListener.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2322/PostUpdateEventListener.cs index 67960f24cd7..a91b7cf9e75 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2322/PostUpdateEventListener.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2322/PostUpdateEventListener.cs @@ -26,9 +26,9 @@ Task IPostUpdateEventListener.OnPostUpdateAsync(PostUpdateEvent @event, Cancella if (@event.Entity is Person) { return @event.Session - .CreateSQLQuery("update Person set Name = :newName") - .SetString("newName", "new updated name") - .ExecuteUpdateAsync(cancellationToken); + .CreateSQLQuery("update Person set Name = :newName") + .SetString("newName", "new updated name") + .ExecuteUpdateAsync(cancellationToken); } return Task.CompletedTask; } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2331/Nh2331Test.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2331/Nh2331Test.cs index 6fc7de64bfb..8e6ff501e0a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2331/Nh2331Test.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2331/Nh2331Test.cs @@ -154,4 +154,4 @@ DetachedCriteria personCriteria } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2362/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2362/Fixture.cs index 5d512775722..41cc52163cd 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2362/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2362/Fixture.cs @@ -29,7 +29,6 @@ in session.Query() into g let totalPrice = g.Sum(p => p.Price) select new { g.Key.CategoryId, g.Key.SupplierId, TotalPrice = totalPrice }).ToListAsync()); - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2378/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2378/Fixture.cs index 70dcb164a61..d6579992b75 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2378/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2378/Fixture.cs @@ -70,9 +70,8 @@ public async Task ShortEntityCanBeQueryCorrectlyUsingLinqProviderAsync() .Where(o => o.PersonId == 2) .ToListAsync()); - Assert.That(m.Count, Is.EqualTo(1)); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs index 0f71ee82087..0029f758444 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs @@ -11,8 +11,8 @@ using System.Data.Common; using System.Data.Odbc; using System.Data.SqlClient; -using System.Configuration; using System.Transactions; +using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -43,9 +43,7 @@ private string FetchConnectionStringFromConfiguration() string connectionStringName; if (cfg.Properties.TryGetValue(Environment.ConnectionStringName, out connectionStringName)) { - var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; - Assert.That(connectionStringSettings, Is.Not.Null); - connectionString = connectionStringSettings.ConnectionString; + connectionString = ConfigurationProvider.Current.GetNamedConnectionString(connectionStringName); Assert.That(connectionString, Is.Not.Null.Or.Empty); return connectionString; } @@ -73,10 +71,8 @@ public async Task ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransac new DummyEnlistment(), EnlistmentOptions.None); - if (Sfi.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver)) - connection = new OdbcConnection(connectionString); - else - connection = new SqlConnection(connectionString); + connection = Sfi.ConnectionProvider.Driver.CreateConnection(); + connection.ConnectionString = connectionString; await (connection.OpenAsync()); using (s = Sfi.WithOptions().Connection(connection).OpenSession()) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2467/NH2467Test.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2467/NH2467Test.cs index 9f5166eb198..1c51f7bc365 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2467/NH2467Test.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2467/NH2467Test.cs @@ -49,7 +49,6 @@ public async Task ShouldNotThrowOnFuturePagingAsync() { using (var session = OpenSession()) { - var contentQuery = session .CreateCriteria() .Add(Restrictions.Eq("Data", "Test")); @@ -73,7 +72,6 @@ public async Task ShouldNotThrowOnReversedFuturePagingAsync() { using (var session = OpenSession()) { - var countQuery = session .CreateCriteria() .Add(Restrictions.Eq("Data", "Test")); @@ -97,7 +95,6 @@ public async Task ShouldNotThrowOnFuturePagingUsingHqlAsync() { using (var session = OpenSession()) { - var contentQuery = session.CreateQuery("from DomainClass as d where d.Data = ?"); contentQuery.SetString(0, "Test"); contentQuery.SetMaxResults(2); @@ -118,7 +115,6 @@ public async Task ShouldNotThrowOnReversedFuturePagingUsingHqlAsync() { using (var session = OpenSession()) { - var contentQuery = session.CreateQuery("from DomainClass as d where d.Data = ?"); contentQuery.SetString(0, "Test"); contentQuery.SetMaxResults(2); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2484/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2484/Fixture.cs index 88685a31a0e..99f9c036ac1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2484/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2484/Fixture.cs @@ -7,7 +7,6 @@ // //------------------------------------------------------------------------------ - using System; using System.Drawing; using System.Reflection; @@ -88,4 +87,4 @@ public async Task TestPersistenceOfClassWithSerializableTypeAsync() stream.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2490/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2490/Fixture.cs index 364088e8576..5e0b6af1c61 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2490/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2490/Fixture.cs @@ -55,6 +55,5 @@ public async Task BadSqlFromJoinLogicErrorAsync() } } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2500/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2500/Fixture.cs index 89dbb8258ab..f4fb100583d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2500/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2500/Fixture.cs @@ -23,7 +23,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2500 { using System.Threading.Tasks; - [TestFixture] public class FixtureAsync : TestCaseMappingByCode { @@ -63,13 +62,13 @@ protected override void OnTearDown() [Test] public async Task TestLinqProjectionExpressionDoesntCacheParametersAsync() - { - using (ISession session = Sfi.OpenSession()) - using (ITransaction transaction = session.BeginTransaction()) { - this.count = 1; + using (ISession session = Sfi.OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + this.count = 1; - var foos1 = await (session.Query() + var foos1 = await (session.Query() .Where(x => x.Name == "Banana") .Select(x => new { @@ -78,9 +77,9 @@ public async Task TestLinqProjectionExpressionDoesntCacheParametersAsync() User = "abc" }).FirstAsync()); - this.count = 2; + this.count = 2; - var foos2 = await (session.Query() + var foos2 = await (session.Query() .Where(x => x.Name == "Egg") .Select(x => new { @@ -89,13 +88,13 @@ public async Task TestLinqProjectionExpressionDoesntCacheParametersAsync() User = "def" }).FirstAsync()); -Assert.AreEqual(1, foos1.count); -Assert.AreEqual(2, foos2.count); -Assert.AreEqual("abc", foos1.User); -Assert.AreEqual("def", foos2.User); + Assert.AreEqual(1, foos1.count); + Assert.AreEqual(2, foos2.count); + Assert.AreEqual("abc", foos1.User); + Assert.AreEqual("def", foos2.User); - await (transaction.CommitAsync()); + await (transaction.CommitAsync()); + } } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2510/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2510/Fixture.cs index c88d14a0fec..9229c9b1191 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2510/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2510/Fixture.cs @@ -10,7 +10,6 @@ using System; using NHibernate.Cache; -using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NUnit.Framework; @@ -48,21 +47,21 @@ public Scenario(ISessionFactory factory) { this.factory = factory; using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.Persist(new Image { Id = 1 }); - session.Transaction.Commit(); + tran.Commit(); } } public void Dispose() { using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.CreateQuery("delete from Image").ExecuteUpdate(); - session.Transaction.Commit(); - } + tran.Commit(); + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2580/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2580/Fixture.cs index 02c080e8880..501d4890ebe 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2580/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2580/Fixture.cs @@ -18,7 +18,6 @@ public class FixtureAsync: BugTestCase { private class MyClass { - } [Test] diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2583/ManualTestFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2583/ManualTestFixture.cs index e3a1afd2f45..50261537488 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2583/ManualTestFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2583/ManualTestFixture.cs @@ -166,8 +166,7 @@ public async Task OrShouldBeCompatibleWithAdditionForNullReferencesAsync() //Assert.AreEqual(0, leftResult.Count); //Assert.AreEqual(0, rightResult.Count); //Assert.AreEqual(0, orResult.Count); - - } + } } private static void TestCoreOrShouldBeCompatibleWithSum(ISession session, @@ -214,44 +213,6 @@ public async Task OrShouldBeCompatibleWithAdditionForNonNullReferencesAsync() } } - [Test, Ignore("Pure Outer Join semantics has projection anomaly!")] - public async Task ProjectionDoesNotChangeResultAsync() - { - // This tests against the "projection anomaly" of (||-4) semantics. - using (var session = OpenSession()) - { - using (var tx = session.BeginTransaction()) - { - var i1001 = new MyRef1 { Id = 1001, I1 = null, I2 = 101 }; - var i1101 = new MyRef1 { Id = 1101, I1 = null, I2 = 111 }; - await (session.SaveAsync(i1001)); - await (session.SaveAsync(i1101)); - - var b1 = new MyBO { Id = 1, Name = "1:1001", BO1 = i1001 }; - var b2 = new MyBO { Id = 2, Name = "2:1101", BO1 = i1101 }; - var b3 = new MyBO { Id = 3, Name = "3:1101", BO1 = i1101 }; - var b4 = new MyBO { Id = 4, Name = "4:NULL", BO1 = null }; - await (session.SaveAsync(b1)); - await (session.SaveAsync(b2)); - await (session.SaveAsync(b3)); - await (session.SaveAsync(b4)); - await (tx.CommitAsync()); - } - } - - using (var session = OpenSession()) - { - var directResult = (await (session.Query() - .Where(bo => bo.I1 == null).ToListAsync())).Select(bo => bo.Id); - var resultViaProjection = (await ((from bo in session.Query() - where bo.BO1.I1 == null || bo.BO2.J2 == 999 - select bo.BO1).Distinct().ToListAsync())).Select(bo => bo.Id); - // With "projection anomaly", the previous Select will fail, as one "bo" is null, - // and hence bo.Id throws a NRE. - Assert.That(() => resultViaProjection.ToList(), Is.EquivalentTo(directResult.ToList())); - } - } - [Test, Explicit("Exploratory Test")] public async Task NHibernateLinqExploratoryTestWhichProvesNothingAsync() { @@ -267,7 +228,6 @@ public async Task NHibernateLinqExploratoryTestWhichProvesNothingAsync() // ); //result.ToList(); - var result = from r in session.Query() orderby (r.Id == 1101 || r.Id == 1102 ? r.Id - 1000 : r.Id) select (r.Id == 1101 || r.Id == 1102 ? r.Id + 1000 : r.Id); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH266/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH266/Fixture.cs index 845c5ec998d..0d857bcfd58 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH266/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH266/Fixture.cs @@ -65,7 +65,6 @@ protected override void OnTearDown() s.Close(); } - [Test] public async Task BaseClassLoadAsync() { @@ -75,7 +74,6 @@ public async Task BaseClassLoadAsync() Assert.AreEqual("the a", a.Name); s.Close(); - // load instance through hql s = OpenSession(); IQuery q = s.CreateQuery("from A as a where a.id = :id "); @@ -125,4 +123,4 @@ public async Task SpecificSubclassAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH266/UserFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH266/UserFixture.cs index 08528e3fa51..79353df2b45 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH266/UserFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH266/UserFixture.cs @@ -59,7 +59,6 @@ protected override void OnTearDown() s.Close(); } - /// /// This is testing problems that existed in 0.8.0-2 with extra "AND" /// being added to the sql when there was an attribute where="some sql". @@ -93,7 +92,6 @@ public async Task WhereAttributeAsync() Assert.AreEqual(0, list.Count, "no 'inactive user' according to where clause"); s.Close(); - // // load a instance of B through hql // s = OpenSession(); // IQuery q = s.CreateQuery( "from B as b where b.id = :id" ); @@ -112,4 +110,4 @@ public async Task WhereAttributeAsync() // s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2662/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2662/Fixture.cs index e4899183bc7..347b8c4ae52 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2662/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2662/Fixture.cs @@ -47,7 +47,7 @@ public async Task WhenCastAliasInQueryOverThenDoNotThrowAsync() c => new {c.Id, c.Order.OrderDate, ((PizzaOrder)c.Order).PizzaName }) .ToArray(); - foreach (var item in temp) { Trace.WriteLine(item.PizzaName);} + foreach (var item in temp) { Trace.WriteLine(item.PizzaName); } }, Throws.Nothing); Assert.That(async () => @@ -67,7 +67,6 @@ public async Task WhenCastAliasInQueryOverThenDoNotThrowAsync() Assert.That(results.Count, Is.EqualTo(2)); Assert.That(results[0][2], Is.EqualTo("Margarita")); - }, Throws.Nothing); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2664/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2664/Fixture.cs index 55c363b7edd..4bd1e0537af 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2664/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2664/Fixture.cs @@ -87,7 +87,6 @@ protected override void OnTearDown() tran.Commit(); } } - } [Test] @@ -129,4 +128,4 @@ public async Task Multiple_Query_Does_Not_CacheAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2673/CachingWithTransformerTests.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2673/CachingWithTransformerTests.cs index bc30da1c3cc..cad92efc004 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2673/CachingWithTransformerTests.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2673/CachingWithTransformerTests.cs @@ -63,7 +63,6 @@ public Scenario(ISessionFactory factory) blog.Posts.Add(new Post { Title = "Second post", Body = "Some other text" }); blog.Posts.Add(new Post { Title = "Third post", Body = "Third post text" }); - blog.Comments.Add(new Comment { Title = "First comment", Body = "Some text" }); blog.Comments.Add(new Comment { Title = "Second comment", Body = "Some other text" }); session.Save(blog); @@ -221,7 +220,6 @@ public async Task WhenEagerLoadingWithHqlThenNotThrowsAsync() } } - [Test(Description = "NH2961/3311")] public async Task CanCacheCriteriaWithLeftJoinAndResultTransformerAsync() { @@ -239,7 +237,6 @@ public async Task CanCacheCriteriaWithLeftJoinAndResultTransformerAsync() } } - [Test(Description = "NH2961/3311")] public async Task CanCacheCriteriaWithEagerLoadAndResultTransformerAsync() { @@ -255,7 +252,6 @@ public async Task CanCacheCriteriaWithEagerLoadAndResultTransformerAsync() } } - [Test(Description = "NH2961/3311")] public async Task CanCacheCriteriaWithLeftJoinAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2691/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2691/Fixture.cs index 02979816ae1..8e93c1e0006 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2691/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2691/Fixture.cs @@ -10,9 +10,9 @@ using System.Linq; using NHibernate.Cfg.MappingSchema; -using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NUnit.Framework; +using NHibernate.Linq; namespace NHibernate.Test.NHSpecificTest.NH2691 { @@ -33,11 +33,11 @@ protected override HbmMapping GetMappings() public async Task WhenUseCountWithOrderThenCutTheOrderAsync() { using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var baseQuery = from cat in session.Query() orderby cat.BirthDate select cat; Assert.That(() => baseQuery.CountAsync(), Throws.Nothing); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } @@ -45,11 +45,11 @@ public async Task WhenUseCountWithOrderThenCutTheOrderAsync() public async Task WhenUseLongCountWithOrderThenCutTheOrderAsync() { using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var baseQuery = from cat in session.Query() orderby cat.BirthDate select cat; Assert.That(() => baseQuery.LongCountAsync(), Throws.Nothing); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2693/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2693/Fixture.cs index b3111d2e76a..d3187ef2ee8 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2693/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2693/Fixture.cs @@ -43,7 +43,6 @@ protected override void OnSetUp() var fourthLevel2 = new FourthLevel { SomeString = "second", SpecificThirdLevel = thirdLevel1 }; thirdLevel1.FourthLevels.Add(fourthLevel2); - var firstLevel2 = new FirstLevel(); var secondLevel2 = new SecondLevelComponent { FirstLevel = firstLevel2 }; @@ -58,7 +57,6 @@ protected override void OnSetUp() var fourthLevel4 = new FourthLevel { SomeString = "fourth", SpecificThirdLevel = thirdLevel2 }; thirdLevel2.FourthLevels.Add(fourthLevel4); - var firstLevel3 = new FirstLevel(); var secondLevel3 = new SecondLevelComponent { FirstLevel = firstLevel3 }; @@ -68,7 +66,6 @@ protected override void OnSetUp() secondLevel3.ThirdLevel = thirdLevel3; secondLevel3.SpecificThirdLevel = thirdLevel3; - session.Save(thirdLevel1); session.Save(thirdLevel2); session.Save(thirdLevel3); @@ -102,158 +99,6 @@ protected override void OnTearDown() } } - [Test, Ignore("Not fixed yet")] - public async Task _1_Querying_BasedOnFourthLevelExistence_WithIsAndCasting_ShouldReturnSameEntitiesAsLinqToObjectsAsync() - { - var expected = _firstLevels - .Where(first => first.SecondLevels - .Any(second => second.ThirdLevel is SpecificThirdLevel && - ((SpecificThirdLevel)second.ThirdLevel).FourthLevels - .Any() - ) - ) - .ToList(); - - using (ISession session = OpenSession()) - { - using (ITransaction tx = session.BeginTransaction()) - { - var result = await (session.Query() - .Where(first => first.SecondLevels - .Any(second => second.ThirdLevel is SpecificThirdLevel && - ((SpecificThirdLevel)second.ThirdLevel).FourthLevels - .Any() - ) - ) - .ToListAsync()); - - Assert.AreEqual(expected.Count, result.Count); - Assert.IsTrue(result - .All(f => f.SecondLevels - .Any(s => ((SpecificThirdLevel)s.ThirdLevel).FourthLevels - .Any() - ) - ) - ); - } - } - } - - [Test, Ignore("Not fixed yet")] - public async Task _2_Querying_BasedOnFourthLevelExistence_WithSelectAndOfType_ShouldReturnSameEntitiesAsLinqToObjectsAsync() - { - var expected = _firstLevels - .Where(first => first.SecondLevels - .Select(second => second.ThirdLevel) - .OfType() - .Any(specificThird => specificThird.FourthLevels - .Any() - ) - ) - .ToList(); - - using (ISession session = OpenSession()) - { - using (ITransaction tx = session.BeginTransaction()) - { - var result = await (session.Query() - .Where(first => first.SecondLevels - .Select(second => second.ThirdLevel) - .OfType() - .Any(specificThird => specificThird.FourthLevels - .Any() - ) - ) - .ToListAsync()); - - Assert.AreEqual(expected.Count, result.Count); - Assert.IsTrue(result - .All(f => f.SecondLevels - .Any(s => ((SpecificThirdLevel)s.ThirdLevel).FourthLevels - .Any() - ) - ) - ); - } - } - } - - [Test, Ignore("Not fixed yet")] - public async Task _3_Querying_BasedOnFourthLevelProperty_WithIsAndCasting_ShouldReturnSameEntitiesAsLinqToObjectsAsync() - { - var expected = _firstLevels - .Where(first => first.SecondLevels - .Any(second => second.ThirdLevel is SpecificThirdLevel && - ((SpecificThirdLevel)second.ThirdLevel).FourthLevels - .Any(fourth => fourth.SomeString == "first") - ) - ) - .ToList(); - - using (ISession session = OpenSession()) - { - using (ITransaction tx = session.BeginTransaction()) - { - var result = await (session.Query() - .Where(first => first.SecondLevels - .Any(second => second.ThirdLevel is SpecificThirdLevel && - ((SpecificThirdLevel)second.ThirdLevel).FourthLevels - .Any(fourth => fourth.SomeString == "first") - ) - ) - .ToListAsync()); - - Assert.AreEqual(expected.Count, result.Count); - Assert.IsTrue(result - .All(f => f.SecondLevels - .Any(s => ((SpecificThirdLevel)s.ThirdLevel).FourthLevels - .Any(fo => fo.SomeString == "first") - ) - ) - ); - } - } - } - - [Test, Ignore("Not fixed yet")] - public async Task _4_Querying_BasedOnFourthLevelProperty_WithSelectAndOfType_ShouldReturnSameEntitiesAsLinqToObjectsAsync() - { - var expected = _firstLevels - .Where(first => first.SecondLevels - .Select(second => second.ThirdLevel) - .OfType() - .Any(specificThird => specificThird.FourthLevels - .Any(fourth => fourth.SomeString == "first") - ) - ) - .ToList(); - - using (ISession session = OpenSession()) - { - using (ITransaction tx = session.BeginTransaction()) - { - var result = await (session.Query() - .Where(first => first.SecondLevels - .Select(second => second.ThirdLevel) - .OfType() - .Any(specificThird => specificThird.FourthLevels - .Any(fourth => fourth.SomeString == "first") - ) - ) - .ToListAsync()); - - Assert.AreEqual(expected.Count, result.Count); - Assert.IsTrue(result - .All(f => f.SecondLevels - .Any(s => ((SpecificThirdLevel)s.ThirdLevel).FourthLevels - .Any(fo => fo.SomeString == "first") - ) - ) - ); - } - } - } - [Test] public async Task _5_Querying_BasedOnFourthLevelExistence_ByUsingSpecificThirdLevelProperty_ShouldReturnSameEntitiesAsLinqToObjectsAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2697/SampleTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2697/SampleTest.cs index 6cb55ea3d91..5847a87e277 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2697/SampleTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2697/SampleTest.cs @@ -59,7 +59,6 @@ protected override void OnTearDown() { base.OnTearDown(); - using (ISession session = this.OpenSession()) { IList list = session.CreateCriteria("Article").List(); foreach (ArticleItem item in list) @@ -79,7 +78,6 @@ protected override void OnTearDown() session.Delete(hql); session.Flush(); } - } [Test] @@ -126,8 +124,6 @@ public async Task Can_GetListOfArticlesAsync() Assert.That(result.Count, Is.GreaterThan(0)); } - - [Test] public async Task Can_SetArticleFavoriteWithHQL_NamedParamAsync() { @@ -153,7 +149,6 @@ public async Task Can_SetArticleFavoriteWithHQL_NamedParamAsync() result = await (session.CreateQuery(HQL).ListAsync()); } Assert.That(result.Count, Is.GreaterThan(0)); - - } +} } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2705/Test.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2705/Test.cs index f81135448a8..711ba18eeff 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2705/Test.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2705/Test.cs @@ -74,24 +74,11 @@ public void LinqQueryWithFetch_WhenDerivedClassesUseComponentAndManyToOne_DoesNo Assert.That(() => s.Query() .Fetch(p => p.SubItem).ToListAsync(), Throws.Nothing); - // fetching second level properties should work too Assert.That(() => s.Query() .Fetch(p => p.SubItem).ThenFetch(p => p.Details).ToListAsync(), Throws.Nothing); } } } - - [Test, Ignore("Locked by re-linq")] - public void LinqQueryWithFetch_WhenDerivedClassesUseComponentAndEagerFetchManyToOne_DoesNotGenerateInvalidSqlAsync() - { - using (ISession s = OpenSession()) - { - using (var log = new SqlLogSpy()) - { - Assert.That(() => s.Query().Fetch(p => p.SubItem.Details).ToListAsync(), Throws.Nothing); - } - } - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2714/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2714/Fixture.cs new file mode 100644 index 00000000000..3f36005ebe0 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2714/Fixture.cs @@ -0,0 +1,76 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH2714 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + private const int ExtraId = 500; + + protected override void OnTearDown() + { + using (ISession s = OpenSession()) + { + s.Delete($"from {nameof(Item)}"); + s.Flush(); + s.Delete($"from {nameof(Information)}"); + s.Flush(); + } + } + + [Test] + public async Task PropertyRefUsesOtherColumnsAsync() + { + var information = new Information {Name = "First", ExtraId = ExtraId}; + + var item = new Item {Id = 1, Name = information.Name, ExtraId = information.ExtraId}; + + using (ISession session = OpenSession()) + { + await (session.SaveAsync(information)); + await (session.SaveAsync(item)); + await (session.FlushAsync()); + } + + using (ISession session = OpenSession()) + { + var otherInformation = await (session.GetAsync(information.Id)); + Assert.That(otherInformation.Items.Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task ChildKeyPropertiesOfParentAreRetrievedAsync() + { + var information = new Information {Name = "First", ExtraId = ExtraId}; + + var item = new Item {Id = 1, Name = information.Name, ExtraId = information.ExtraId}; + + using (ISession session = OpenSession()) + { + await (session.SaveAsync(information)); + await (session.SaveAsync(item)); + await (session.FlushAsync()); + } + + using (ISession session = OpenSession()) + { + var otherInformation = await (session.GetAsync(information.Id)); + + Assert.That(otherInformation.Name, Is.EqualTo(information.Name)); + Assert.That(otherInformation.ExtraId, Is.EqualTo(information.ExtraId)); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2721/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2721/Fixture.cs deleted file mode 100644 index e70e5245410..00000000000 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2721/Fixture.cs +++ /dev/null @@ -1,79 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by AsyncGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -using System.Collections.Generic; -using System.Linq; -using System.Collections; - -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH2721 -{ - using System.Threading.Tasks; - [TestFixture] - public class FixtureAsync : BugTestCase - { - protected override void OnTearDown() - { - using( ISession s = Sfi.OpenSession() ) - { - s.Delete("from A"); - s.Flush(); - } - } - - [Test] - [Ignore("Does not work because of extraneous update that sets reference to A to null. Can be worked-around by removing not-null on reference to A.")] - public async Task ListRemoveInsertAsync() - { - A a = new A("a"); - - B b1 = new B("b1"); - a.Bs.Add(b1); - b1.A = a; - - B b2 = new B("b2"); - a.Bs.Add(b2); - b2.A = a; - - B b3 = new B("b3"); - a.Bs.Add(b3); - b3.A = a; - - using (ISession s = OpenSession()) - { - await (s.SaveOrUpdateAsync(a)); - await (s.FlushAsync()); - } - - using (ISession s = OpenSession()) - { - a = (A) await (s.LoadAsync(typeof (A), a.Id)); - CollectionAssert.AreEquivalent(new[] {"b1", "b2", "b3"}, a.Bs.Select(b => b.Name)); - - B removed = a.Bs[2]; - a.Bs.Remove(removed); - a.Bs.Insert(1, removed); - Assert.IsNotNull(a.Bs[0].A); - Assert.IsNotNull(a.Bs[1].A); - Assert.IsNotNull(a.Bs[2].A); - CollectionAssert.AreEquivalent(new[] {"b1", "b3", "b2"}, a.Bs.Select(b => b.Name)); - await (s.SaveOrUpdateAsync(a)); - await (s.FlushAsync()); - } - - using (ISession s = OpenSession()) - { - a = (A) await (s.LoadAsync(typeof (A), a.Id)); - CollectionAssert.AreEquivalent(new[] { "b1", "b3", "b2" }, a.Bs.Select(b => b.Name)); - } - } - } -} \ No newline at end of file diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2773/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2773/Fixture.cs index bcf8f13fc7b..d70a584b2b9 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2773/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2773/Fixture.cs @@ -62,7 +62,6 @@ public async Task DeserializedSession_ProxyType_ShouldBeEqualToOriginalProxyType await (tx.CommitAsync()); } - using (MemoryStream sessionMemoryStream = new MemoryStream()) { var formatter = new BinaryFormatter diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH280/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH280/Fixture.cs index a14dfac0d12..02c14828039 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH280/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH280/Fixture.cs @@ -60,7 +60,6 @@ public async Task ConstInSelectAsync() Assert.AreEqual(123, result[0]); Assert.AreEqual("Fiammy", (result[1] as Foo).Description); - l = await (s.CreateQuery("select 123, f.Description from Foo f").ListAsync()); result = l[0] as IList; Assert.AreEqual(123, result[0]); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2846/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2846/Fixture.cs index 71a6dd7fb6d..43650b4b5f2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2846/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2846/Fixture.cs @@ -71,22 +71,18 @@ protected override void OnTearDown() } } - [Test] public async Task FetchOnCountWorksAsync() { using (var session = OpenSession()) { - var count = await (session.Query() .Fetch(p => p.Category) .FetchMany(p => p.Comments) .CountAsync()); Assert.AreEqual(1, count); - } } - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2892/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2892/Fixture.cs new file mode 100644 index 00000000000..1b8a6a7309e --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2892/Fixture.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Cfg; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.NH2892 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("hbm2ddl.keywords", "auto-quote"); + } + + protected override void OnSetUp() + { + using (ISession session = OpenSession()) + { + var order = new Order(); + + session.Save(order); + session.Flush(); + + var orderLine = new OrderLine + { + Orders = order + }; + + session.Save(orderLine); + session.Flush(); + } + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + { + session.Delete($"from {nameof(OrderLine)}"); + session.Delete($"from {nameof(Order)}"); + session.Flush(); + } + } + + [Test] + public async Task SelectOrderLineFromOrderAsync() + { + using (ISession session = OpenSession()) + { + var order = await (session.Query().FirstOrDefaultAsync()); + + Assert.That(order, Is.Not.Null); + Assert.DoesNotThrow(() => order.Elements.FirstOrDefault()); + Assert.That(order.OrderLines, Is.Not.Null); + Assert.NotZero(order.OrderLines.Count); + Assert.NotZero(order.OrderLines.First().Id); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2898/BinaryFormatterCache.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2898/BinaryFormatterCache.cs index 5fbc3ca0ef7..c32fcb0ef0c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2898/BinaryFormatterCache.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2898/BinaryFormatterCache.cs @@ -32,11 +32,11 @@ public override Task GetAsync(object key, CancellationToken cancellation return Task.FromResult(null); var fmt = new BinaryFormatter - { + { #if !NETFX SurrogateSelector = new SerializationHelper.SurrogateSelector() #endif - }; + }; using (var stream = new MemoryStream(entry)) { return Task.FromResult(fmt.Deserialize(stream)); @@ -53,11 +53,11 @@ public override Task PutAsync(object key, object value, CancellationToken cancel try { var fmt = new BinaryFormatter - { + { #if !NETFX SurrogateSelector = new SerializationHelper.SurrogateSelector() #endif - }; + }; using (var stream = new MemoryStream()) { fmt.Serialize(stream, value); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH295/SubclassFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH295/SubclassFixture.cs index 3bbf98231e6..309d11750b8 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH295/SubclassFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH295/SubclassFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NHibernate.Criterion; using NUnit.Framework; @@ -32,29 +31,35 @@ protected override string MappingsAssembly public async Task LoadByIDFailureSameSessionAsync() { User ui1 = new User("User1"); + object uid1; - ISession s = OpenSession(); - s.BeginTransaction(); - object uid1 = await (s.SaveAsync(ui1)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + uid1 = await (s.SaveAsync(ui1)); + await (t.CommitAsync()); + s.Close(); + } - Assert.IsNotNull(await (s.GetAsync(typeof(User), uid1))); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.IsNotNull(await (s.GetAsync(typeof(User), uid1))); - UserGroup ug = (UserGroup) await (s.GetAsync(typeof(UserGroup), uid1)); - Assert.IsNull(ug); + UserGroup ug = (UserGroup) await (s.GetAsync(typeof(UserGroup), uid1)); + Assert.IsNull(ug); - await (s.Transaction.CommitAsync()); - s.Close(); + await (t.CommitAsync()); + s.Close(); + } - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from Party")); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from Party")); + await (t.CommitAsync()); + s.Close(); + } } [Test] @@ -63,79 +68,98 @@ public async Task LoadByIDFailureAsync() UserGroup ug1 = new UserGroup(); ug1.Name = "Group1"; User ui1 = new User("User1"); + object gid1, uid1; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gid1 = await (s.SaveAsync(ug1)); + uid1 = await (s.SaveAsync(ui1)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load user with USER NAME: + ICriteria criteria1 = s.CreateCriteria(typeof(User)); + criteria1.Add(Expression.Eq("Name", "User1")); + Assert.AreEqual(1, (await (criteria1.ListAsync())).Count); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load group with USER NAME: + ICriteria criteria2 = s.CreateCriteria(typeof(UserGroup)); + criteria2.Add(Expression.Eq("Name", "User1")); + Assert.AreEqual(0, (await (criteria2.ListAsync())).Count); + await (t.CommitAsync()); + s.Close(); + } - ISession s = OpenSession(); - s.BeginTransaction(); - object gid1 = await (s.SaveAsync(ug1)); - object uid1 = await (s.SaveAsync(ui1)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load user with USER NAME: - ICriteria criteria1 = s.CreateCriteria(typeof(User)); - criteria1.Add(Expression.Eq("Name", "User1")); - Assert.AreEqual(1, (await (criteria1.ListAsync())).Count); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load group with USER NAME: - ICriteria criteria2 = s.CreateCriteria(typeof(UserGroup)); - criteria2.Add(Expression.Eq("Name", "User1")); - Assert.AreEqual(0, (await (criteria2.ListAsync())).Count); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load group with GROUP NAME - ICriteria criteria3 = s.CreateCriteria(typeof(UserGroup)); - criteria3.Add(Expression.Eq("Name", "Group1")); - Assert.AreEqual(1, (await (criteria3.ListAsync())).Count); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load user with GROUP NAME - ICriteria criteria4 = s.CreateCriteria(typeof(User)); - criteria4.Add(Expression.Eq("Name", "Group1")); - Assert.AreEqual(0, (await (criteria4.ListAsync())).Count); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load group with USER IDENTITY - ug1 = (UserGroup) await (s.GetAsync(typeof(UserGroup), uid1)); - Assert.IsNull(ug1); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - ui1 = (User) await (s.GetAsync(typeof(User), gid1)); - Assert.IsNull(ui1); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - Party p = (Party) await (s.GetAsync(typeof(Party), uid1)); - Assert.IsTrue(p is User); - p = (Party) await (s.GetAsync(typeof(Party), gid1)); - Assert.IsTrue(p is UserGroup); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from Party")); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load group with GROUP NAME + ICriteria criteria3 = s.CreateCriteria(typeof(UserGroup)); + criteria3.Add(Expression.Eq("Name", "Group1")); + Assert.AreEqual(1, (await (criteria3.ListAsync())).Count); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load user with GROUP NAME + ICriteria criteria4 = s.CreateCriteria(typeof(User)); + criteria4.Add(Expression.Eq("Name", "Group1")); + Assert.AreEqual(0, (await (criteria4.ListAsync())).Count); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load group with USER IDENTITY + ug1 = (UserGroup) await (s.GetAsync(typeof(UserGroup), uid1)); + Assert.IsNull(ug1); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + ui1 = (User) await (s.GetAsync(typeof(User), gid1)); + Assert.IsNull(ui1); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Party p = (Party) await (s.GetAsync(typeof(Party), uid1)); + Assert.IsTrue(p is User); + p = (Party) await (s.GetAsync(typeof(Party), gid1)); + Assert.IsTrue(p is UserGroup); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from Party")); + await (t.CommitAsync()); + s.Close(); + } } [Test] @@ -161,4 +185,4 @@ public async Task ListAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2951/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2951/Fixture.cs index 7d7aa4a2aa3..4a7cc36b7c1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2951/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2951/Fixture.cs @@ -18,6 +18,11 @@ namespace NHibernate.Test.NHSpecificTest.NH2951 [TestFixture] public class FixtureAsync : BugTestCase { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + protected override void OnTearDown() { using (ISession session = OpenSession()) @@ -31,13 +36,11 @@ protected override void OnTearDown() } [Test] - [Ignore("Not working.")] public async Task UpdateWithSubqueryToJoinedSubclassAsync() { using (ISession session = OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { - var c = new Customer { Name = "Bob" }; await (session.SaveAsync(c)); @@ -60,4 +63,4 @@ public async Task UpdateWithSubqueryToJoinedSubclassAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2959/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2959/Fixture.cs index 68829391305..72fd21ca57a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2959/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2959/Fixture.cs @@ -15,7 +15,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2959 { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -89,7 +88,7 @@ public async Task CanUsePolymorphicCriteriaInQueryBatchAsync() { var results = await (session.CreateQueryBatch() .Add(session.CreateCriteria(typeof(BaseEntity))) - .GetResultAsync(0, CancellationToken.None)); + .GetResultAsync(0)); Assert.That(results, Has.Count.EqualTo(2)); } @@ -103,7 +102,7 @@ public async Task CanUsePolymorphicQueryInQueryBatchAsync() { var results = await (session.CreateQueryBatch() .Add(session.CreateQuery("from " + typeof(BaseEntity).FullName)) - .GetResultAsync(0, CancellationToken.None)); + .GetResultAsync(0)); Assert.That(results, Has.Count.EqualTo(2)); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs index dc8780ae67b..5397716af0d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs @@ -17,10 +17,8 @@ namespace NHibernate.Test.NHSpecificTest.NH298 { using System.Threading.Tasks; - [TestFixture] public class IndexedBidirectionalOneToManyTestAsync : BugTestCase { - protected override void OnSetUp() { base.OnSetUp(); using ( ISession session = this.OpenSession() ) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs index 052557dc4c0..ad960e60d4c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs @@ -259,7 +259,6 @@ public async Task ConnectionPoolCorruptionAfterDeadlockAsync(bool distributed, b ? "Deadlock not reported on initial request, and initial request failed" : "Initial request failed", subsequentFailedRequests); - } while (tryCount < 3); // // I'll change this to while(true) sometimes so I don't have to keep running the test @@ -295,7 +294,6 @@ private void RunScript(string script) foreach (var batch in Regex.Split(sql, @"^go\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline) .Where(b => !string.IsNullOrEmpty(b))) { - using (var cmd = new System.Data.SqlClient.SqlCommand(batch, cxn)) { cmd.ExecuteNonQuery(); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3037/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3037/FixtureByCode.cs index 404d0c5a6b6..a47d5d6230d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3037/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3037/FixtureByCode.cs @@ -19,7 +19,6 @@ namespace NHibernate.Test.NHSpecificTest.NH3037 { using System.Threading.Tasks; - using System.Threading; [TestFixture, Explicit("This is a performance test and may take a while.")] public class ByCodeFixtureAsync : TestCaseMappingByCode { @@ -42,14 +41,14 @@ protected override HbmMapping GetMappings() [TestCase(20000)] [TestCase(30000)] [TestCase(40000)] - public async Task SortInsertionActionsAsync(int iterations, CancellationToken cancellationToken = default(CancellationToken)) + public async Task SortInsertionActionsAsync(int iterations) { using (ISession session = OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { for (int i = 1; i <= iterations; i++) { - await (session.SaveAsync(new Entity() { Id = i, Name = i.ToString() }, cancellationToken)); + await (session.SaveAsync(new Entity() { Id = i, Name = i.ToString() })); } var impl = ((NHibernate.Impl.SessionImpl)session); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3046/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3046/Fixture.cs index dd5fd4b837f..656b6c1dcc6 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3046/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3046/Fixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using System; using NUnit.Framework; using NHibernate.Mapping.ByCode; @@ -23,7 +22,6 @@ public class FixtureAsync : BugTestCase [Test, Explicit] public async Task MemoryLeakAsync() { - long initialMemory = GC.GetTotalMemory(true); long nextId = 1; long nextIdChild = 1; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3074/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3074/Fixture.cs deleted file mode 100644 index 1ceb87fc686..00000000000 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3074/Fixture.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by AsyncGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -using NHibernate.Cfg.MappingSchema; -using NHibernate.Criterion; -using NHibernate.Mapping.ByCode; -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH3074 -{ - using System.Threading.Tasks; - [TestFixture] - public class FixtureAsync : TestCaseMappingByCode - { - private const int Id = 123; - - protected override HbmMapping GetMappings() - { - var mapper = new ModelMapper(); - mapper.Class(rc => - { - rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); - rc.Property(x => x.Weight); - }); - - mapper.UnionSubclass(x => x.Property(p => p.NumberOfLegs)); - - return mapper.CompileMappingForAllExplicitlyAddedEntities(); - } - - protected override void OnSetUp() - { - using (var s = OpenSession()) - using (var tx = s.BeginTransaction()) - { - var cat = new Cat { Id = Id, NumberOfLegs = 2, Weight = 100 }; - s.Save(cat); - tx.Commit(); - } - } - - [Test] - [Ignore("Fails on at least Oracle and PostgreSQL. See NH-3074 and NH-2408.")] - public async Task HqlCanSetLockModeAsync() - { - using (var s = OpenSession()) - using (s.BeginTransaction()) - { - var cats = await (s.CreateQuery("select c from Animal c where c.Id=:id") - .SetInt32("id", Id) - .SetLockMode("c", LockMode.Upgrade) - .ListAsync()); - - Assert.That(cats, Is.Not.Empty); - } - } - - [Test, Ignore("Not fixed yet")] - public async Task CritriaCanSetLockModeAsync() - { - using (var s = OpenSession()) - using (s.BeginTransaction()) - { - var cats = await (s.CreateCriteria("c") - .Add(Restrictions.IdEq(Id)) - .SetLockMode("c", LockMode.Upgrade) - .ListAsync()); - - Assert.That(cats, Is.Not.Empty); - } - } - - protected override void OnTearDown() - { - using (var s = OpenSession()) - using (var tx = s.BeginTransaction()) - { - s.Delete(s.Get(Id)); - tx.Commit(); - } - } - } -} \ No newline at end of file diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3079/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3079/Fixture.cs new file mode 100644 index 00000000000..e9980e3a398 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3079/Fixture.cs @@ -0,0 +1,246 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + // Disable second level cache + protected override string CacheConcurrencyStrategy => null; + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from Employment").ExecuteUpdate(); + + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + + t.Commit(); + } + } + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var personList = new List(); + var employerList = new List(); + + // Global id to avoid false positive assertion with positional parameter + var gId = 1; + + for (var i = 0; i < 3; i++) + { + var personCpId = new PersonCpId { IdA = gId++, IdB = gId++ }; + var personObj = new Person + { CpId = personCpId, Name = "PERSON_" + personCpId.IdA + "_" + personCpId.IdB }; + s.Save(personObj); + personList.Add(personObj); + } + + for (var i = 0; i < 3; i++) + { + var employerCpId = new EmployerCpId { IdA = gId++, IdB = gId++ }; + var employerObj = new Employer + { CpId = employerCpId, Name = "EMPLOYER_" + employerCpId.IdA + "_" + employerCpId.IdB }; + s.Save(employerObj); + employerList.Add(employerObj); + } + + var employmentIds = new[] + { + gId++, + gId++, + gId++, + gId++, + }; + var employmentNames = new[] + { + //P1 + E1 + "EMPLOYMENT_" + employmentIds[0] + "_" + + personList[0].CpId.IdA + "_" + personList[0].CpId.IdA + "_" + + employerList[0].CpId.IdA + "_" + employerList[0].CpId.IdB, + //P1 + E2 + "EMPLOYMENT_" + employmentIds[1] + "_" + + personList[0].CpId.IdA + "_" + personList[0].CpId.IdA + "_" + + employerList[1].CpId.IdA + "_" + employerList[1].CpId.IdB, + //P2 + E2 + "EMPLOYMENT_" + employmentIds[2] + "_" + + personList[1].CpId.IdA + "_" + personList[1].CpId.IdA + "_" + + employerList[1].CpId.IdA + "_" + employerList[1].CpId.IdB, + //P2 + E3 + "EMPLOYMENT_" + employmentIds[2] + "_" + + personList[1].CpId.IdA + "_" + personList[1].CpId.IdA + "_" + + employerList[2].CpId.IdA + "_" + employerList[2].CpId.IdB + }; + var employmentPersons = new[] + { + personList[0], + personList[0], + personList[1], + personList[1] + }; + var employmentEmployers = new[] + { + employerList[0], + employerList[1], + employerList[1], + employerList[2] + }; + + for (var k = 0; k < employmentIds.Length; k++) + { + var employmentCpId = new EmploymentCpId + { + Id = employmentIds[k], + PersonObj = employmentPersons[k], + EmployerObj = employmentEmployers[k] + }; + var employmentObj = new Employment { CpId = employmentCpId, Name = employmentNames[k] }; + s.Save(employmentObj); + } + + for (var i = 0; i < 3; i++) + { + var personNoComponentObj = new PersonNoComponent { IdA = gId++, IdB = gId++ }; + personNoComponentObj.Name = "PERSON_NO_COMPONENT_" + personNoComponentObj.IdA + "_" + + personNoComponentObj.IdB; + s.Save(personNoComponentObj); + } + + t.Commit(); + } + } + + // Test reproducing the problem. + [Test] + public async Task GetPersonTestAsync() + { + using (var session = OpenSession()) + { + var person1_2 = await (session.GetAsync(new PersonCpId { IdA = 1, IdB = 2 })); + Assert.That(person1_2.Name, Is.EqualTo("PERSON_1_2")); + Assert.That( + person1_2.EmploymentList.Select(e => e.Name), + Is.EquivalentTo(new[] { "EMPLOYMENT_13_1_1_7_8", "EMPLOYMENT_14_1_1_9_10" })); + } + } + + // Test reproducing the problem. + [Test] + public async Task GetEmployerTestAsync() + { + using (var session = OpenSession()) + { + var employer7_8 = await (session.GetAsync(new EmployerCpId { IdA = 7, IdB = 8 })); + Assert.That(employer7_8.Name, Is.EqualTo("EMPLOYER_7_8")); + Assert.That( + employer7_8.EmploymentList.Select(e => e.Name), + Is.EquivalentTo(new[] { "EMPLOYMENT_13_1_1_7_8" })); + } + } + + [Test] + public async Task GetEmploymentTestAsync() + { + using (var session = OpenSession()) + { + var employment_13_1_2_7_8 = + await (session.GetAsync( + new EmploymentCpId + { + Id = 13, + PersonObj = + new Person + { + CpId = new PersonCpId { IdA = 1, IdB = 2 } + }, + EmployerObj = + new Employer + { + CpId = new EmployerCpId { IdA = 7, IdB = 8 } + } + })); + Assert.That(employment_13_1_2_7_8.Name, Is.EqualTo("EMPLOYMENT_13_1_1_7_8")); + } + } + + [Test] + public async Task HqlPersonPositionalAsync() + { + using (var session = OpenSession()) + { + var personList = + await (session + .GetNamedQuery("personPositional") + .SetParameter(0, new PersonCpId { IdA = 1, IdB = 2 }) + .SetParameter(1, new PersonCpId { IdA = 3, IdB = 4 }) + .ListAsync()); + Assert.That( + personList.Select(e => e.Name), + Is.EquivalentTo(new[] { "PERSON_1_2", "PERSON_3_4" })); + } + } + + [Test] + public async Task HqlPersonNamedAsync() + { + using (var session = OpenSession()) + { + var personList = + await (session + .GetNamedQuery("personNamed") + .SetParameter("id1", new PersonCpId { IdA = 1, IdB = 2 }) + .SetParameter("id2", new PersonCpId { IdA = 3, IdB = 4 }) + .ListAsync()); + Assert.That( + personList.Select(e => e.Name), + Is.EquivalentTo(new[] { "PERSON_1_2", "PERSON_3_4" })); + } + } + + [Test] + public async Task GetPersonNoComponentAsync() + { + using (var session = OpenSession()) + { + var person17_18 = + await (session.GetAsync(new PersonNoComponent { IdA = 17, IdB = 18 })); + Assert.That(person17_18.Name, Is.EqualTo("PERSON_NO_COMPONENT_17_18")); + } + } + + [Test] + public async Task SqlPersonNoComponentAsync() + { + using (var session = OpenSession()) + { + var personList = + await (session + .GetNamedQuery("personNoComponentSql") + .SetParameter(0, new PersonNoComponent { IdA = 17, IdB = 18 }) + .SetParameter(1, new PersonNoComponent { IdA = 19, IdB = 20 }) + .ListAsync()); + Assert.That( + personList.Select(e => e.Name), + Is.EquivalentTo(new[] { "PERSON_NO_COMPONENT_17_18", "PERSON_NO_COMPONENT_19_20" })); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH309/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH309/Fixture.cs index 222040c8313..cd58ee89e2d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH309/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH309/Fixture.cs @@ -81,11 +81,10 @@ public async Task RemoveNodeFromNodesCollectionAsync() Assert.AreEqual(2, menu2.Nodes.Count, "Test count after removal"); Assert.AreEqual(rootNode, menu2.Nodes[0], "Test identity first node in menu"); - await (s.DeleteAsync("from Node")); await (s.DeleteAsync("from Menu")); await (s.FlushAsync()); s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3121/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3121/Fixture.cs index 3c6a760e9ec..599081849d2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3121/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3121/Fixture.cs @@ -7,7 +7,6 @@ // //------------------------------------------------------------------------------ - using System; using System.Drawing; using System.Linq; @@ -58,7 +57,6 @@ public void ShouldThrowWhenByteArrayTooLongAsync() Is.EqualTo("The length of the byte[] value exceeds the length configured in the mapping/parameter.")); } - [Test] public void ShouldThrowWhenImageTooLargeAsync() { @@ -75,7 +73,6 @@ public void ShouldThrowWhenImageTooLargeAsync() Is.EqualTo("The length of the byte[] value exceeds the length configured in the mapping/parameter.")); } - [Test] public void ShouldThrowWhenImageAsISerializableTooLargeAsync() { @@ -92,7 +89,6 @@ public void ShouldThrowWhenImageAsISerializableTooLargeAsync() Is.EqualTo("The length of the byte[] value exceeds the length configured in the mapping/parameter.")); } - private async Task PersistReportAsync(Report report, CancellationToken cancellationToken = default(CancellationToken)) { using (var session = OpenSession()) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3138/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3138/FixtureByCode.cs index b4faa0ee71f..a3ddbe62d6c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3138/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3138/FixtureByCode.cs @@ -45,20 +45,5 @@ public void PageQueryWithDistinctAndOrderByContainingFunctionWithCommaSeparatedP .ListAsync()); } } - - [Test] - [Ignore("Failing")] - public void PageQueryWithDistinctAndOrderByContainingAliasedFunctionAsync() - { - using (var session = OpenSession()) - { - Assert.DoesNotThrowAsync(() => - session - .CreateQuery("select distinct e.Id, coalesce(e.EnglishName, e.GermanName) as LocalizedName from Entity e order by LocalizedName asc") - .SetFirstResult(10) - .SetMaxResults(20) - .ListAsync()); - } - } } } \ No newline at end of file diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3139/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3139/Fixture.cs index 62a33d27160..616a62d3264 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3139/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3139/Fixture.cs @@ -75,7 +75,6 @@ protected override void OnTearDown() tran.Commit(); } } - } [Test] diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3142/ChildrenTest.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3142/ChildrenTest.cs index c5f420db6de..7d0e2aa0c2f 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3142/ChildrenTest.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3142/ChildrenTest.cs @@ -68,7 +68,6 @@ protected override void OnTearDown() } } - [Test] public async Task ChildrenCollectionOfAllParentsShouldContainsThreeElementsAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3202/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3202/Fixture.cs index 61a8bf45526..0cbd29f617b 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3202/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3202/Fixture.cs @@ -73,7 +73,6 @@ protected override void OnTearDown() base.OnTearDown(); } - [Test] public async Task OffsetNotStartingAtOneSetsParameterToSkipValueAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3252/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3252/Fixture.cs index 3f2dd8084a8..753173fddca 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3252/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3252/Fixture.cs @@ -32,7 +32,6 @@ public async Task VerifyThatWeCanSaveAndLoadAsync() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - await (session.SaveAsync(new Note { Text = new String('0', 9000) })); await (transaction.CommitAsync()); } @@ -40,7 +39,6 @@ public async Task VerifyThatWeCanSaveAndLoadAsync() using (var session = OpenSession()) using (session.BeginTransaction()) { - var note = await (session.Query().FirstAsync()); Assert.AreEqual(9000, note.Text.Length); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs index e9f029b6656..35d1fcc0004 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs @@ -82,13 +82,11 @@ from std in me.State.StateDescriptions } } - private void CreateObjects(ISession session) { // Create the English culture Culture englishCulture = new Culture(); - englishCulture.CountryCode = "CA"; englishCulture.LanguageCode = "en"; @@ -115,8 +113,6 @@ private void CreateObjects(ISession session) dataType1.DataTypeDescriptions.Add(dataTypeDescription1); - - // Create a State and attach it an English description and a Spanish description State state1 = new State(); @@ -136,7 +132,6 @@ private void CreateObjects(ISession session) state1.StateDescriptions.Add(spanishStateDescription); - MasterEntity masterEntity = new MasterEntity(); masterEntity.Name = "MasterEntity 1"; @@ -147,4 +142,4 @@ private void CreateObjects(ISession session) session.Flush(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3372/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3372/Fixture.cs index 13fa3c39930..1bb33d42d49 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3372/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3372/Fixture.cs @@ -53,6 +53,5 @@ public async Task CanGeneratePropertyOnUpdateOfEntityWithCustomLoaderAsync() Assert.That(entity.ShardId, Is.Not.Null & Has.Length.GreaterThan(0)); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3392/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3392/Fixture.cs index 20f576ab904..dcba14487df 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3392/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3392/Fixture.cs @@ -38,7 +38,6 @@ public async Task ExpandSubCollectionWithEmbeddedCompositeIDAsync() using (ISession s = OpenSession()) using (var tx = s.BeginTransaction()) { - var jenny = new Mum {Name = "Jenny"}; await (s.SaveAsync(jenny)); var benny = new Dad {Name = "Benny"}; @@ -67,7 +66,6 @@ public async Task ExpandSubCollectionWithCompositeIDAsync() using (ISession s = OpenSession()) using (var tx = s.BeginTransaction()) { - var jenny = new Mum { Name = "Jenny" }; await (s.SaveAsync(jenny)); var benny = new Dad { Name = "Benny" }; @@ -89,7 +87,5 @@ public async Task ExpandSubCollectionWithCompositeIDAsync() Assert.That(result[0].x.Friends, Is.EquivalentTo(result[0].Friends)); } } - - } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3401/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3401/Fixture.cs deleted file mode 100644 index 728f2592a6b..00000000000 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3401/Fixture.cs +++ /dev/null @@ -1,64 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by AsyncGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -using System.Linq; -using NHibernate.Linq; -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH3401 -{ - using System.Threading.Tasks; - [TestFixture] - public class FixtureAsync : BugTestCase - { - protected override void OnTearDown() - { - using (ISession session = OpenSession()) - using (ITransaction transaction = session.BeginTransaction()) - { - session.Delete("from System.Object"); - - session.Flush(); - transaction.Commit(); - } - } - - [Test(Description = "NH-3401")] - [Ignore("Test not implemented - this can be used a base for a proper test case for NH-3401.")] - public async Task YesNoParameterLengthShouldBe1Async() - { - // MISSING PART: Asserts for the SQL parameter sizes in the generated commands. - - using (ISession session = OpenSession()) - using (ITransaction transaction = session.BeginTransaction()) - { - var e1 = new Entity {Name = "Bob"}; - await (session.SaveAsync(e1)); - - var e2 = new Entity {Name = "Sally", YesNo = true}; - await (session.SaveAsync(e2)); - - await (session.FlushAsync()); - await (transaction.CommitAsync()); - } - - - using (ISession session = OpenSession()) - using (session.BeginTransaction()) - { - var result = from e in session.Query() - where e.YesNo - select e; - - Assert.AreEqual(1, (await (result.ToListAsync())).Count); - } - } - } -} \ No newline at end of file diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3426/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3426/Fixture.cs index c48782508bb..afd515c3f44 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3426/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3426/Fixture.cs @@ -10,17 +10,60 @@ using System; using System.Linq; +using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; using NHibernate.Mapping.ByCode; using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; using NHibernate.Linq; namespace NHibernate.Test.NHSpecificTest.NH3426 { using System.Threading.Tasks; - [TestFixture] + /// + /// Verify that we can convert a GUID column to a string in the standard GUID format inside + /// the database engine. + /// + [TestFixture(true)] + [TestFixture(false)] public class FixtureAsync : TestCaseMappingByCode { + private readonly bool _useBinaryGuid; + + public FixtureAsync(bool useBinaryGuid) + { + _useBinaryGuid = useBinaryGuid; + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + // For SQLite, we run the tests for both storage modes (SQLite specific setting). + if (dialect is SQLiteDialect) + return true; + + // For all other dialects, run the tests only once since the storage mode + // is not relevant. (We use the case of _useBinaryGuid==true since this is probably + // what most engines do internally.) + return _useBinaryGuid; + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + if (Dialect is SQLiteDialect) + { + var connStr = configuration.Properties[Environment.ConnectionString]; + + if (_useBinaryGuid) + connStr += "BinaryGuid=True;"; + else + connStr += "BinaryGuid=False;"; + + configuration.Properties[Environment.ConnectionString] = connStr; + } + } protected override HbmMapping GetMappings() { @@ -68,7 +111,7 @@ public async Task SelectGuidToStringAsync() .Select(x => new { Id = x.Id.ToString() }) .ToListAsync()); - Assert.AreEqual(id.ToUpper(), list[0].Id.ToUpper()); + Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper())); } } @@ -110,5 +153,53 @@ public async Task CompareStringColumnWithNullableGuidToStringAsync() Assert.That(list, Has.Count.EqualTo(1)); } } + + [Test] + public async Task SelectGuidToStringImplicitAsync() + { + if (Dialect is SQLiteDialect && _useBinaryGuid) + Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09)."); + + if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect) + Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " + + "because a simple cast cannot be used for GUID to string conversion on " + + "these dialects. See GH-2109."); + + using (var session = OpenSession()) + { + // Verify in-db GUID to string conversion when ToString() is applied to the entity that has + // a GUID id column (that is, we deliberately avoid mentioning the Id property). This + // exposes bug GH-2109. + var list = await (session.Query() + .Select(x => new { Id = x.ToString() }) + .ToListAsync()); + + Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper())); + } + } + + [Test] + public async Task WhereGuidToStringImplicitAsync() + { + if (Dialect is SQLiteDialect && _useBinaryGuid) + Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09)."); + + if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect) + Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " + + "because a simple cast cannot be used for GUID to string conversion on " + + "these dialects. See GH-2109."); + + using (var session = OpenSession()) + { + // Verify in-db GUID to string conversion when ToString() is applied to the entity that has + // a GUID id column (that is, we deliberately avoid mentioning the Id property). This + // exposes bug GH-2109. + var list = await (session.Query() + .Where(x => x.ToString().ToUpper() == id) + .ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + } + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3428/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3428/Fixture.cs index ce0c12df83c..be5ff595aac 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3428/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3428/Fixture.cs @@ -18,7 +18,6 @@ namespace NHibernate.Test.NHSpecificTest.NH3428 [TestFixture] public class FixtureAsync : BugTestCase { - protected override bool AppliesTo(Dialect.Dialect dialect) { return dialect is Dialect.MsSql2005Dialect; @@ -73,4 +72,4 @@ public async Task QueryFailsWhenDistinctOrderedResultIsPagedPastPageOneAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3453/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3453/Fixture.cs index 3f8743c547c..5b282c96762 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3453/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3453/Fixture.cs @@ -24,7 +24,6 @@ public async Task PropertyRefWithCompositeIdUpdateTestAsync() using (var session = OpenSession()) using (session.BeginTransaction()) { - var direction1 = new Direction { Id1 = 1, Id2 = 1, GUID = Guid.NewGuid() }; await (session.SaveAsync(direction1)); @@ -51,6 +50,5 @@ public async Task PropertyRefWithCompositeIdUpdateTestAsync() Assert.That(true); } } - - } -} \ No newline at end of file + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3487/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3487/Fixture.cs index 5616a72861a..541a2c57048 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3487/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3487/Fixture.cs @@ -89,7 +89,6 @@ public async Task CanDeserializeSessionWithEntityHashCollisionAsync() { formatter.Deserialize(serializationStream); } - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3567/NH3567Tests.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3567/NH3567Tests.cs index 5f539d42eb5..480eb1452d7 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3567/NH3567Tests.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3567/NH3567Tests.cs @@ -21,9 +21,9 @@ public class NH3567TestsAsync : BugTestCase protected override void OnSetUp() { base.OnSetUp(); - using (ISession session = this.OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); var id = 0; var site1 = new Site { Id = ++id, Name = "Site 1" }; @@ -31,7 +31,6 @@ protected override void OnSetUp() session.Save(site1); session.Save(site2); - var p1 = new Post { Id = ++id, Content = "Post 1", Site = site1 }; var p2 = new Post { Id = ++id, Content = "Post 2", Site = site2 }; @@ -43,7 +42,7 @@ protected override void OnSetUp() session.Save(new Comment { Id = ++id, Content = "Comment 2.1", Post = p2 }); session.Save(new Comment { Id = ++id, Content = "Comment 2.2", Post = p2 }); session.Flush(); - session.Transaction.Commit(); + tran.Commit(); } } @@ -103,16 +102,9 @@ public async Task TestFlushModeAutoAsync() Assert.That(numberOfComments, Is.EqualTo(2), "Query with sub-query returned an invalid number of rows."); - await (transaction.RollbackAsync()); - } - } } - - } - - } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3571/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3571/Fixture.cs index 4c7241ba7a0..757c701fe2d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3571/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3571/Fixture.cs @@ -84,7 +84,6 @@ protected override void OnTearDown() tran.Commit(); } } - } [Test] @@ -103,7 +102,6 @@ public async Task CanQueryDynamicComponentInComponentAsync() } } - [Test] public async Task MultipleQueriesShouldNotCacheAsync() { @@ -124,4 +122,4 @@ public async Task MultipleQueriesShouldNotCacheAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3604/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3604/FixtureByCode.cs index 65857f6540e..4d20d5fb2af 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3604/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3604/FixtureByCode.cs @@ -52,7 +52,6 @@ protected override void OnSetUp() var e1 = new Entity { Name = "Bob" }; session.Save(e1); - var e2 = new Entity { Name = "Sally" }; var ed2 = new EntityDetail(e2) { ExtraInfo = "Jo" }; e2.Detail = ed2; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3622/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3622/FixtureByCode.cs new file mode 100644 index 00000000000..a2dadab897c --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3622/FixtureByCode.cs @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.NH3622 +{ + using System.Threading.Tasks; + /// + /// Fixture using 'by code' mappings + /// + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public async Task MissingJoinsInSubqueryAsync() + { + var id = Guid.NewGuid(); + using( var logSpy = new SqlLogSpy()) + using (var s = OpenSession()) + { + var x = s.Query() + .Where(tag => tag.Equipment.Discipline.DisciplineType.Id == id) + .Select(tag => tag.Id); + + var y = await (s.Query() + .Where(tag => x.Contains(tag.Id)) + .Fetch(tag => tag.Equipment) + .ThenFetch(equipment => equipment.Discipline) + .ThenFetch(discipline => discipline.DisciplineType) + .ToListAsync()); + + var sql = logSpy.GetWholeLog(); + var findSubqyeryIndex = sql.IndexOf(" in ("); + var capturesCount = Regex.Matches(sql.Substring(findSubqyeryIndex), "inner join").Count; + //Expected joins for tag.Equipment.Discipline in subquery + Assert.That(capturesCount, Is.EqualTo(2), "Missing inner joins in subquery: " + sql); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3813/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3813/FixtureByCode.cs new file mode 100644 index 00000000000..366f83da4b6 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3813/FixtureByCode.cs @@ -0,0 +1,189 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using System.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3813 +{ + using System.Threading.Tasks; + [TestFixture] + public class KeyManyToOneInnerJoinFetchFixtureAsync : TestCaseMappingByCode + { + protected override Cfg.MappingSchema.HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + m => + { + m.Lazy(false); + + m.Id( + i => i.Id, + id => + { + id.Column("ID"); + id.Generator(Generators.Identity); + }); + m.Property(x => x.Name); + + m.Bag( + b => b.AssociationTableCollection, + bm => + { + bm.Inverse(true); + bm.Key(k => k.Column("FirstTableID")); + }, + mp => mp.OneToMany()); + }); + + mapper.Class( + m => + { + m.Lazy(false); + + m.Id( + i => i.Id, + id => + { + id.Column("ID"); + id.Generator(Generators.Identity); + }); + m.Property(x => x.Name); + }); + + mapper.Class( + m => + { + m.ComposedId( + i => + { + i.ManyToOne(c => c.FirstTable, p => { p.Column("FirstTableID"); }); + i.ManyToOne(c => c.OtherTable, p => { p.Column("OtherTableID"); }); + }); + m.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public async Task FetchQueryDoesNotContainInnerJoinAsync() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var q = await (session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection).ListAsync()); + + var sql = logSpy.GetWholeLog(); + Assert.That(sql, Is.Not.Contains("inner join")); + } + } + + [Test] + public async Task FetchQueryDoesNotContainInnerJoinMultiAsync() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var q = await (session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection) + .Left.JoinQueryOver(a => a.OtherTable).ListAsync()); + + var sql = logSpy.GetWholeLog(); + + Assert.That(sql, Does.Not.Contain("inner join")); + Assert.That(sql, Does.Match(@"join\s*AssociationTable").IgnoreCase); + Assert.That(sql, Does.Match(@"join\s*OtherTable")); + } + } + + [Test] + public async Task FetchLoadsAllRecordsAsync() + { + IList result = null; + + using (var session = OpenSession()) + { + // the query should return all records from the table with their collections fetched + result = await (session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection) + .ListAsync()); + } + + Assert.AreEqual(2, result.Count, "Query returned wrong number of records."); + Assert.IsTrue(result.All(x => NHibernateUtil.IsInitialized(x.AssociationTableCollection)), "Not all collections have been initialized"); + } + + [Test] + public async Task FetchInitializesAllCollectionsAsync() + { + IList result = null; + + using (var session = OpenSession()) + { + // load all records + result = await (session.QueryOver() + .ListAsync()); + + // lazy-load the association collection + await (session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection) + .ListAsync()); + } + + Assert.IsTrue(result.All(x => NHibernateUtil.IsInitialized(x.AssociationTableCollection)), "Not all collections have been initialized"); + } + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + // a record that has association records will be loaded regularly + var withAssociations = new FirstTable(); + + var other1 = new OtherTable(); + var other2 = new OtherTable(); + + var assoc1 = new AssociationTable() {OtherTable = other1, FirstTable = withAssociations}; + var assoc2 = new AssociationTable() {OtherTable = other2, FirstTable = withAssociations}; + + withAssociations.AssociationTableCollection.Add(assoc1); + withAssociations.AssociationTableCollection.Add(assoc2); + s.Save(withAssociations); + + // a record with no associations will have problems if inner joined to association table + var withoutAssociations = new FirstTable(); + s.Save(withoutAssociations); + + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + t.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3818/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3818/Fixture.cs index f657909653b..69ea1bebfdb 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3818/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3818/Fixture.cs @@ -70,9 +70,7 @@ public async Task SelectConditionalValuesTestAsync() //Console.WriteLine(spy.ToString()); Assert.That(catInfo2.AliveDays, Is.EqualTo(0)); - - } + } } - - } + } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3844/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3844/Fixture.cs index 006536ab127..58ca033f629 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3844/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3844/Fixture.cs @@ -65,7 +65,6 @@ protected override void OnSetUp() session.Save(compP3_x); session.Save(compP3_y); - session.Save(new TimeRecord { TimeInHours = 1, Project = project1, Components = { } }); session.Save(new TimeRecord { TimeInHours = 2, Project = project1, Components = { compP1_x } }); session.Save(new TimeRecord { TimeInHours = 3, Project = project1, Components = { compP1_y } }); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs index 3955fe3e783..766c7f8307a 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/CriteriaTestFixture.cs @@ -24,11 +24,11 @@ public class CriteriaTestFixtureAsync : FixtureAsync try { return - session - .CreateCriteria() - .Fetch("Orders") - .SetResultTransformer(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .Fetch("Orders") + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -41,10 +41,10 @@ public class CriteriaTestFixtureAsync : FixtureAsync try { return - session - .CreateCriteria() - .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -57,11 +57,11 @@ public class CriteriaTestFixtureAsync : FixtureAsync try { return - session - .CreateCriteria() - .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) - .Fetch(SelectMode.Fetch, "Orders") - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, "Orders") + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -74,12 +74,12 @@ public class CriteriaTestFixtureAsync : FixtureAsync try { return - session - .CreateCriteria() - .Fetch(SelectMode.Fetch, "Orders") - .CreateCriteria("Orders", JoinType.InnerJoin) - .Add(Restrictions.Eq("Number", orderNumber)) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .Fetch(SelectMode.Fetch, "Orders") + .CreateCriteria("Orders", JoinType.InnerJoin) + .Add(Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -92,12 +92,12 @@ public class CriteriaTestFixtureAsync : FixtureAsync try { return - session - .CreateCriteria() - .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) - .Fetch(SelectMode.Fetch, "Orders") - .CreateAlias("Companies", "company", JoinType.LeftOuterJoin, Restrictions.Eq("Name", name)) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, "Orders") + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin, Restrictions.Eq("Name", name)) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -110,12 +110,12 @@ public class CriteriaTestFixtureAsync : FixtureAsync try { return - session - .CreateCriteria() - .Fetch(SelectMode.Fetch, "Orders") - .CreateAlias("Orders", "order", JoinType.InnerJoin) - .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .Fetch(SelectMode.Fetch, "Orders") + .CreateAlias("Orders", "order", JoinType.InnerJoin) + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -130,11 +130,11 @@ protected override Task> GetCustomersWithCompaniesByOrderNumberU try { return - session - .CreateCriteria() - .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) - .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .CreateAlias("Companies", "company", JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -147,12 +147,12 @@ protected override Task> GetCustomersWithCompaniesByOrderNumberU try { return - session - .CreateCriteria() - .CreateCriteria("Orders", "Order", JoinType.LeftOuterJoin) - .Add(Restrictions.Eq("Number", orderNumber)) - .SetResultTransformer(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateCriteria("Orders", "Order", JoinType.LeftOuterJoin) + .Add(Restrictions.Eq("Number", orderNumber)) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -165,12 +165,12 @@ protected override Task> GetCustomersWithCompaniesByOrderNumberU try { return - session - .CreateCriteria() - .CreateAlias("Orders", "order", JoinType.LeftOuterJoin) - .Add(Restrictions.Eq("Name", "First Customer")) - .SetResultTransformer(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin) + .Add(Restrictions.Eq("Name", "First Customer")) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -186,19 +186,19 @@ protected override Task> GetCustomersByOrderNumberUsingSubquerie try { var detachedCriteria = - DetachedCriteria - .For() - .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) - .SetProjection(Projections.Id()); + DetachedCriteria + .For() + .CreateAlias("Orders", "order", JoinType.LeftOuterJoin, Restrictions.Eq("Number", orderNumber)) + .SetProjection(Projections.Id()); return - session - .CreateCriteria() - .CreateAlias("Orders", "order1", JoinType.LeftOuterJoin) - .Add(Subqueries.PropertyIn("Id", detachedCriteria)) - .Add(Restrictions.Eq("Name", customerName)) - .SetResultTransformer(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .CreateCriteria() + .CreateAlias("Orders", "order1", JoinType.LeftOuterJoin) + .Add(Subqueries.PropertyIn("Id", detachedCriteria)) + .Add(Restrictions.Eq("Name", customerName)) + .SetResultTransformer(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs index a7bbd5e8e8c..242e8fc5fd2 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/Fixture.cs @@ -286,7 +286,6 @@ public virtual async Task ChildCollectionsWithSelectModeFetchAndWhereClauseShoul } } - [Test] public async Task ChildCollectionsFromLeftOuterJoinWithWhereClauseRestrictionOnCollectionShouldNotBeInSecondLevelCacheAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/HqlTestFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/HqlTestFixture.cs index 6f7588dc11a..0afaaf8dd33 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/HqlTestFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/HqlTestFixture.cs @@ -68,9 +68,9 @@ public override Task ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeI try { var query = session.CreateQuery( - "from Customer as c " + - "left join fetch c.Orders as o " + - "where o.Number = :number"); + "from Customer as c " + + "left join fetch c.Orders as o " + + "where o.Number = :number"); query.SetParameter("number", orderNumber); return query.ListAsync(cancellationToken); } @@ -85,9 +85,9 @@ public override Task ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeI try { var query = session.CreateQuery( - "from Customer as c " + - "left join fetch c.Orders as o " + - "where c.Name = :name"); + "from Customer as c " + + "left join fetch c.Orders as o " + + "where c.Name = :name"); query.SetParameter("name", customerName); query.SetResultTransformer(new DistinctRootEntityResultTransformer()); return query.ListAsync(cancellationToken); @@ -109,9 +109,9 @@ public override Task ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeI try { var query = session.CreateQuery( - "from Customer as c " + - "inner join fetch c.Orders as o " + - "where o.Number = :number"); + "from Customer as c " + + "inner join fetch c.Orders as o " + + "where o.Number = :number"); query.SetParameter("number", orderNumber); return query.ListAsync(cancellationToken); } @@ -132,9 +132,9 @@ public override Task ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeI try { var query = session.CreateQuery( - "from Customer as c " + - "inner join fetch c.Orders as o " + - "left join fetch c.Companies as cmp"); + "from Customer as c " + + "inner join fetch c.Orders as o " + + "left join fetch c.Companies as cmp"); return query.ListAsync(cancellationToken); } catch (Exception ex) @@ -148,10 +148,10 @@ public override Task ChildCollectionsWithSelectModeFetchOnCollectionShouldNotBeI try { var query = session.CreateQuery( - "from Customer as c " + - "left join fetch c.Orders as o " + - "where c.Name = :name and " + - "c.Id in (select c1.Id from Customer as c1 left join c1.Orders as o2 where o2.Number = :number)"); + "from Customer as c " + + "left join fetch c.Orders as o " + + "where c.Name = :name and " + + "c.Id in (select c1.Id from Customer as c1 left join c1.Orders as o2 where o2.Number = :number)"); query.SetParameter("name", customerName); query.SetParameter("number", orderNumber); query.SetResultTransformer(new DistinctRootEntityResultTransformer()); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs index d38763a2748..83150da3a8d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3848/QueryOverTestFixture.cs @@ -24,11 +24,11 @@ public class QueryOverTestFixtureAsync : FixtureAsync try { return - session - .QueryOver() - .Fetch(SelectMode.Fetch, n => n.Orders) - .TransformUsing(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .QueryOver() + .Fetch(SelectMode.Fetch, n => n.Orders) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -42,14 +42,14 @@ public class QueryOverTestFixtureAsync : FixtureAsync { Order ordersAlias = null; return - session - .QueryOver() - .JoinAlias( - n => n.Orders, - () => ordersAlias, - JoinType.LeftOuterJoin, - Restrictions.Eq("Number", orderNumber)) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -64,12 +64,12 @@ public class QueryOverTestFixtureAsync : FixtureAsync Order ordersAlias = null; return - session - .QueryOver() - .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) - .Fetch(SelectMode.Fetch, () => ordersAlias) - .TransformUsing(Transformers.DistinctRootEntity) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin, Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -84,13 +84,13 @@ public class QueryOverTestFixtureAsync : FixtureAsync Order ordersAlias = null; return - session - .QueryOver() - .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin) - .Where(Restrictions.Eq("Number", orderNumber)) - .Fetch(SelectMode.Fetch, () => ordersAlias) - .TransformUsing(Transformers.DistinctRootEntity) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinQueryOver(ec => ec.Orders, () => ordersAlias, JoinType.InnerJoin) + .Where(Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .TransformUsing(Transformers.DistinctRootEntity) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -106,20 +106,20 @@ public class QueryOverTestFixtureAsync : FixtureAsync Company companiesAlias = null; return - session - .QueryOver() - .JoinAlias( - n => n.Orders, - () => ordersAlias, - JoinType.InnerJoin, - Restrictions.Eq("Number", orderNumber)) - .Fetch(SelectMode.Fetch, () => ordersAlias) - .JoinAlias( - n => n.Companies, - () => companiesAlias, - JoinType.LeftOuterJoin, - Restrictions.Eq("Name", name)) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.InnerJoin, + Restrictions.Eq("Number", orderNumber)) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Name", name)) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -135,18 +135,18 @@ public class QueryOverTestFixtureAsync : FixtureAsync Company companiesAlias = null; return - session - .QueryOver() - .JoinAlias( - n => n.Orders, - () => ordersAlias, - JoinType.InnerJoin) - .Fetch(SelectMode.Fetch, () => ordersAlias) - .JoinAlias( - n => n.Companies, - () => companiesAlias, - JoinType.LeftOuterJoin) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.InnerJoin) + .Fetch(SelectMode.Fetch, () => ordersAlias) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -164,18 +164,18 @@ protected override Task> GetCustomersWithCompaniesByOrderNumberU Company companiesAlias = null; return - session - .QueryOver() - .JoinAlias( - n => n.Orders, - () => ordersAlias, - JoinType.LeftOuterJoin, - Restrictions.Eq("Number", orderNumber)) - .JoinAlias( - n => n.Companies, - () => companiesAlias, - JoinType.LeftOuterJoin) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .JoinAlias( + n => n.Companies, + () => companiesAlias, + JoinType.LeftOuterJoin) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -189,11 +189,11 @@ protected override Task> GetCustomersWithCompaniesByOrderNumberU { Order ordersAlias = null; return - session - .QueryOver() - .JoinQueryOver(n => n.Orders, () => ordersAlias, JoinType.LeftOuterJoin) - .Where(Restrictions.Eq("Number", orderNumber)) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinQueryOver(n => n.Orders, () => ordersAlias, JoinType.LeftOuterJoin) + .Where(Restrictions.Eq("Number", orderNumber)) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -207,15 +207,15 @@ protected override Task> GetCustomersWithCompaniesByOrderNumberU { Order ordersAlias = null; return - session - .QueryOver() - .JoinAlias( - n => n.Orders, - () => ordersAlias, - JoinType.LeftOuterJoin) - .Where(Restrictions.Eq("Name", customerName)) - .TransformUsing(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin) + .Where(Restrictions.Eq("Name", customerName)) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { @@ -233,23 +233,23 @@ protected override Task> GetCustomersByOrderNumberUsingSubquerie Order ordersAlias = null; Order ordersAlias2 = null; var subquery = - QueryOver - .Of() - .JoinAlias( - n => n.Orders, - () => ordersAlias, - JoinType.LeftOuterJoin, - Restrictions.Eq("Number", orderNumber)) - .Select(n => n.Id); + QueryOver + .Of() + .JoinAlias( + n => n.Orders, + () => ordersAlias, + JoinType.LeftOuterJoin, + Restrictions.Eq("Number", orderNumber)) + .Select(n => n.Id); return - session - .QueryOver() - .JoinAlias(n => n.Orders, () => ordersAlias2, JoinType.LeftOuterJoin) - .WithSubquery.WhereProperty(n => n.Id).In(subquery) - .Where(Restrictions.Eq("Name", customerName)) - .TransformUsing(new DistinctRootEntityResultTransformer()) - .ListAsync(cancellationToken); + session + .QueryOver() + .JoinAlias(n => n.Orders, () => ordersAlias2, JoinType.LeftOuterJoin) + .WithSubquery.WhereProperty(n => n.Id).In(subquery) + .Where(Restrictions.Eq("Name", customerName)) + .TransformUsing(new DistinctRootEntityResultTransformer()) + .ListAsync(cancellationToken); } catch (System.Exception ex) { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3850/MainFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3850/MainFixture.cs index d4c40c427c5..ecbd2e774d1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3850/MainFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3850/MainFixture.cs @@ -43,25 +43,6 @@ public async Task AggregateGBaseAsync() } } - // Failing case due to lack of polymorphic results ordering. - [Test, Ignore("Polymorphic results sets are unioned without reordering, whatever the API")] - public async Task AggregateGBaseOrderingMismatchAsync() - { - using (var session = OpenSession()) - { - // This case cannot work because the aggregate is sensitive to ordering, and NHibernate currently always order polymorphic queries by class names, - // then only honors query ordering as secondary order criteria. - var query = session.Query() - .OrderByDescending(dc => dc.Id) - .Select(dc => dc.Id.ToString()); - var result = query.Aggregate((p, n) => p + "," + n); - // Currently yields "2,1,4,3" instead. - Assert.That(result, Is.EqualTo("4,3,2,1")); - var futureQuery = query.ToFutureValue(qdc => qdc.Aggregate((p, n) => p + "," + n)); - Assert.That(await (futureQuery.GetValueAsync()), Is.EqualTo("4,3,2,1"), "Future"); - } - } - // Non-reg case [Test] public async Task AggregateMutableSeedGBaseAsync() @@ -323,18 +304,6 @@ public async Task AnyObjectAsync() } } - [Test, Ignore("Won't fix: requires reshaping the query")] - public async Task AverageBBaseAsync() - { - await (AverageAsync(1.5m)); - } - - [Test, Ignore("Won't fix: requires reshaping the query")] - public async Task AverageCBaseAsync() - { - await (AverageAsync(1.5m)); - } - // Non-reg case [Test] public async Task AverageEAsync() @@ -349,12 +318,6 @@ public async Task AverageFAsync() await (AverageAsync(null)); } - [Test, Ignore("Won't fix: requires reshaping the query")] - public async Task AverageGBaseAsync() - { - await (AverageAsync(2.5m)); - } - private async Task AverageAsync(decimal? expectedResult, CancellationToken cancellationToken = default(CancellationToken)) where DC : DomainClassBase { using (var session = OpenSession()) @@ -411,18 +374,6 @@ public async Task AverageGBaseAsync() } } - [Test, Ignore("Won't fix: requires reshaping the query")] - public async Task AverageObjectAsync() - { - using (var session = OpenSession()) - { - var result = await (session.Query().AverageAsync(o => (int?)2)); - Assert.That(result, Is.EqualTo(2)); - result = await (session.Query().ToFutureValue(qdc => qdc.Average(o => (int?)2)).GetValueAsync()); - Assert.That(result, Is.EqualTo(2), "Future"); - } - } - // Failing case till NH-3850 is fixed [Test] public async Task CountBBaseAsync() diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3909/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3909/FixtureByCode.cs index 18709a941d0..02e26378eb3 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3909/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3909/FixtureByCode.cs @@ -90,7 +90,6 @@ public async Task YourTestNameAsync() //select b; Assert.AreEqual(2, (await (q.ToListAsync())).Count); - } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3918/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3918/FixtureByCode.cs index 0c62dbf30a2..4f1a9e26e03 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH3918/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3918/FixtureByCode.cs @@ -84,10 +84,10 @@ protected Owner CreateOwner(ISession session, string name) try { var t = new Entity - { - Name = name, - Owner = owner, - }; + { + Name = name, + Owner = owner, + }; return session.SaveAsync(t, cancellationToken); } catch (Exception ex) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH440/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH440/Fixture.cs index 0d7bc046b96..94646285f5c 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH440/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH440/Fixture.cs @@ -32,7 +32,6 @@ protected override bool AppliesTo(Dialect.Dialect dialect) return TestDialect.SupportsEmptyInsertsOrHasNonIdentityNativeGenerator; } - protected override void OnSetUp() { base.OnSetUp(); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH521/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH521/Fixture.cs index 36edb20ef78..e1a6b5128cb 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH521/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH521/Fixture.cs @@ -62,7 +62,6 @@ public async Task AttachUninitProxyCausesInitAsync() NHibernateUtil.IsInitialized(uninitEntity), "session.GetCurrentLockMode() causes initialization of an unitialized entity."); - await (session.DeleteAsync("from System.Object")); await (transaction.CommitAsync()); } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH681/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH681/Fixture.cs index 982ac3e3aae..2cf457e6646 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH681/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH681/Fixture.cs @@ -19,7 +19,6 @@ public class FixtureAsync : BugTestCase { protected override void Configure(NHibernate.Cfg.Configuration cfg) { - } [Test] diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH732/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH732/Fixture.cs index 55faa37363d..0e5c95f1c1d 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH732/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH732/Fixture.cs @@ -23,7 +23,6 @@ namespace NHibernate.Test.NHSpecificTest.NH732 [TestFixture] public class FixtureAsync : BugTestCase { - protected override bool AppliesTo(Dialect.Dialect dialect) { return dialect is MsSql2000Dialect; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH734/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH734/Fixture.cs index 7045fe768a7..3df0c4ff9e7 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH734/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH734/Fixture.cs @@ -20,26 +20,15 @@ public class FixtureAsync : BugTestCase [TestAttribute] public async Task LimitProblemAsync() { - using (ISession session = Sfi.OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { ICriteria criteria = session.CreateCriteria(typeof(MyClass)); criteria.SetMaxResults(100); criteria.SetFirstResult(0); - try - { - session.BeginTransaction(); - IList result = await (criteria.ListAsync()); - await (session.Transaction.CommitAsync()); - } - catch - { - if (session.Transaction != null) - { - await (session.Transaction.RollbackAsync()); - } - throw; - } + IList result = await (criteria.ListAsync()); + await (tran.CommitAsync()); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs index 828332a5867..a7d88d0d321 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs @@ -9,6 +9,7 @@ using System; +using NHibernate.Cfg; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH750 @@ -27,6 +28,17 @@ protected override void OnTearDown() } } + protected override string CacheConcurrencyStrategy + { + get { return null; } + } + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false"); + base.Configure(configuration); + } + [Test] public async Task DeviceOfDriveAsync() { @@ -66,7 +78,8 @@ public async Task DeviceOfDriveAsync() dv2 = (Device) await (s.LoadAsync(typeof(Device), dvSavedId[1])); } Assert.AreEqual(2, dv1.Drives.Count); - Assert.AreEqual(2, dv2.Drives.Count); + // Verify one is missing + Assert.AreEqual(1, dv2.Drives.Count); // Verify dv1 unchanged Assert.IsTrue(dv1.Drives.Contains(dr1)); Assert.IsTrue(dv1.Drives.Contains(dr2)); @@ -74,13 +87,43 @@ public async Task DeviceOfDriveAsync() // Verify dv2 Assert.IsTrue(dv2.Drives.Contains(dr1)); Assert.IsFalse(dv2.Drives.Contains(dr3)); - // Verify one null - int nullCount = 0; - for (int i = 0; i < dv2.Drives.Count; i++) + + //Make sure that flush didn't touch not-found="ignore" records for not modified collection + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) { - if (dv2.Drives[i] == null) nullCount++; + dv2 = await (s.GetAsync(dv2.Id)); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + await (VerifyResultAsync(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection")); + + //Many-to-many clears collection and recreates it so not-found ignore records are lost + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = await (s.GetAsync(dv2.Id)); + dv2.Drives.Add(dr2); + await (t.CommitAsync()); + } + + await (VerifyResultAsync(2, 2, msg: "modified collection")); + + async Task VerifyResultAsync(int expectedInCollection, int expectedInDb, string msg) + { + using (var s = Sfi.OpenSession()) + { + var realCound = Convert.ToInt32( + await (s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") + .SetParameter("id", dv2.Id) + .UniqueResultAsync())); + dv2 = await (s.GetAsync(dv2.Id)); + + Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); + Assert.That(realCound, Is.EqualTo(expectedInDb), msg); + } } - Assert.AreEqual(1, nullCount); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/SimpleComponentFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/SimpleComponentFixture.cs index ca883765fe2..33d2abf1c64 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/SimpleComponentFixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/SimpleComponentFixture.cs @@ -47,7 +47,6 @@ protected override void OnSetUp() simpleComp.Audit.UpdatedDate = DateTime.Now; simpleComp.Audit.UpdatedUserId = "TestUpdated"; - s.Save(simpleComp, 10L); t.Commit(); @@ -63,7 +62,6 @@ protected override void OnTearDown() } } - [Test] public async Task TestLoadAsync() { diff --git a/src/NHibernate.Test/Async/NHSpecificTest/SqlConverterAndMultiQuery/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/SqlConverterAndMultiQuery/Fixture.cs index 0875183d026..4e4c3d5c4bc 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/SqlConverterAndMultiQuery/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/SqlConverterAndMultiQuery/Fixture.cs @@ -18,7 +18,6 @@ namespace NHibernate.Test.NHSpecificTest.SqlConverterAndMultiQuery { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class FixtureAsync : BugTestCase { @@ -76,7 +75,7 @@ public void QueryBatchShouldThrowUserExceptionAsync() var multi = s.CreateQueryBatch(); multi.Add(s.CreateQuery(hqlQuery)); s.Connection.Close(); - Assert.ThrowsAsync(() => multi.ExecuteAsync(CancellationToken.None)); + Assert.ThrowsAsync(() => multi.ExecuteAsync()); } } @@ -118,7 +117,7 @@ public void CriteriaQueryBatchShouldThrowUserExceptionAsync() var multi = s.CreateQueryBatch(); multi.Add(s.CreateCriteria(typeof(ClassA))); s.Connection.Close(); - Assert.ThrowsAsync(() => multi.ExecuteAsync(CancellationToken.None)); + Assert.ThrowsAsync(() => multi.ExecuteAsync()); } } } diff --git a/src/NHibernate.Test/Async/Naturalid/Immutable/ImmutableNaturalIdFixture.cs b/src/NHibernate.Test/Async/Naturalid/Immutable/ImmutableNaturalIdFixture.cs index 7d673b25d0d..d3fccab6b8a 100644 --- a/src/NHibernate.Test/Async/Naturalid/Immutable/ImmutableNaturalIdFixture.cs +++ b/src/NHibernate.Test/Async/Naturalid/Immutable/ImmutableNaturalIdFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NHibernate.Cfg; using NHibernate.Criterion; using NUnit.Framework; @@ -41,31 +40,31 @@ public async Task UpdateAsync() { // prepare some test data... User user; - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); user = new User(); user.UserName = "steve"; user.Email = "steve@hibernate.org"; user.Password = "brewhaha"; await (session.SaveAsync(user)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } // 'user' is now a detached entity, so lets change a property and reattch... user.Password = "homebrew"; - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); await (session.UpdateAsync(user)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } // clean up - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); await (session.DeleteAsync(user)); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } @@ -93,62 +92,72 @@ public async Task NaturalIdCheckAsync() [Test] public async Task NaturalIdCacheAsync() { - ISession s = OpenSession(); - s.BeginTransaction(); - User u = new User("steve", "superSecret"); - await (s.PersistAsync(u)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + User u = new User("steve", "superSecret"); + await (s.PersistAsync(u)); + await (t.CommitAsync()); + s.Close(); + } Sfi.Statistics.Clear(); - s = OpenSession(); - s.BeginTransaction(); - u = - (User) - await (s.CreateCriteria(typeof (User)).Add(Restrictions.NaturalId().Set("UserName", "steve")).SetCacheable(true). - UniqueResultAsync()); - Assert.That(u, Is.Not.Null); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var u = + (User) + await (s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")) + .SetCacheable(true).UniqueResultAsync()); + Assert.That(u, Is.Not.Null); + await (t.CommitAsync()); + s.Close(); + } Assert.AreEqual(1, Sfi.Statistics.QueryExecutionCount); Assert.AreEqual(0, Sfi.Statistics.QueryCacheHitCount); Assert.AreEqual(1, Sfi.Statistics.QueryCachePutCount); - s = OpenSession(); - s.BeginTransaction(); - User v = new User("gavin", "supsup"); - await (s.PersistAsync(v)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + User v = new User("gavin", "supsup"); + await (s.PersistAsync(v)); + await (t.CommitAsync()); + s.Close(); + } Sfi.Statistics.Clear(); - s = OpenSession(); - s.BeginTransaction(); - u = - (User) - await (s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")).SetCacheable(true). - UniqueResultAsync()); - Assert.That(u, Is.Not.Null); - Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); - Assert.AreEqual(1, Sfi.Statistics.QueryCacheHitCount); - u = - (User) - await (s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")).SetCacheable(true). - UniqueResultAsync()); - Assert.That(u, Is.Not.Null); - Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); - Assert.AreEqual(2, Sfi.Statistics.QueryCacheHitCount); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var u = + (User) + await (s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")) + .SetCacheable(true).UniqueResultAsync()); + Assert.That(u, Is.Not.Null); + Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); + Assert.AreEqual(1, Sfi.Statistics.QueryCacheHitCount); + u = + (User) + await (s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")) + .SetCacheable(true).UniqueResultAsync()); + Assert.That(u, Is.Not.Null); + Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); + Assert.AreEqual(2, Sfi.Statistics.QueryCacheHitCount); + await (t.CommitAsync()); + s.Close(); + } - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync("from User")); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync("from User")); + await (t.CommitAsync()); + s.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Naturalid/Mutable/MutableNaturalIdFixture.cs b/src/NHibernate.Test/Async/Naturalid/Mutable/MutableNaturalIdFixture.cs index cff588874ae..578f9cb2d9d 100644 --- a/src/NHibernate.Test/Async/Naturalid/Mutable/MutableNaturalIdFixture.cs +++ b/src/NHibernate.Test/Async/Naturalid/Mutable/MutableNaturalIdFixture.cs @@ -8,8 +8,6 @@ //------------------------------------------------------------------------------ -using System; -using System.Collections; using System.Reflection; using NHibernate.Cfg; using NHibernate.Criterion; @@ -42,41 +40,33 @@ protected override void Configure(Configuration configuration) [Test] public async Task ReattachmentNaturalIdCheckAsync() { - ISession s = OpenSession(); - s.BeginTransaction(); - User u = new User("gavin", "hb", "secret"); - await (s.PersistAsync(u)); - await (s.Transaction.CommitAsync()); - s.Close(); + User u; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + u = new User("gavin", "hb", "secret"); + await (s.PersistAsync(u)); + await (t.CommitAsync()); + s.Close(); + } FieldInfo name = u.GetType().GetField("name", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); name.SetValue(u, "Gavin"); - s = OpenSession(); - s.BeginTransaction(); - try + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { await (s.UpdateAsync(u)); - await (s.Transaction.CommitAsync()); - } - catch (HibernateException) - { - await (s.Transaction.RollbackAsync()); - } - catch (Exception) - { - await (s.Transaction.RollbackAsync()); + await (t.CommitAsync()); } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { + await (s.DeleteAsync(u)); + await (t.CommitAsync()); s.Close(); } - - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(u)); - await (s.Transaction.CommitAsync()); - s.Close(); } [Test] @@ -227,7 +217,6 @@ public async Task NaturalIdCacheAsync() await (t.CommitAsync()); s.Close(); - s = OpenSession(); t = s.BeginTransaction(); await (s.DeleteAsync("from User")); @@ -258,4 +247,4 @@ public async Task QueryingAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Operations/MergeFixture.cs b/src/NHibernate.Test/Async/Operations/MergeFixture.cs index 125950bd47d..9f41851d87e 100644 --- a/src/NHibernate.Test/Async/Operations/MergeFixture.cs +++ b/src/NHibernate.Test/Async/Operations/MergeFixture.cs @@ -85,23 +85,31 @@ private void Cleanup() [Test] public async Task DeleteAndMergeAsync() { - using (ISession s = OpenSession()) + using (var s = OpenSession()) { - s.BeginTransaction(); - var jboss = new Employer(); - await (s.PersistAsync(jboss)); - await (s.Transaction.CommitAsync()); - s.Clear(); - - s.BeginTransaction(); - var otherJboss = await (s.GetAsync(jboss.Id)); - await (s.DeleteAsync(otherJboss)); - await (s.Transaction.CommitAsync()); - s.Clear(); + Employer jboss; + using (var t = s.BeginTransaction()) + { + jboss = new Employer(); + await (s.PersistAsync(jboss)); + await (t.CommitAsync()); + s.Clear(); + } + + using (var t = s.BeginTransaction()) + { + var otherJboss = await (s.GetAsync(jboss.Id)); + await (s.DeleteAsync(otherJboss)); + await (t.CommitAsync()); + s.Clear(); + } + jboss.Vers = 1; - s.BeginTransaction(); - await (s.MergeAsync(jboss)); - await (s.Transaction.CommitAsync()); + using (var t = s.BeginTransaction()) + { + await (s.MergeAsync(jboss)); + await (t.CommitAsync()); + } } } @@ -126,13 +134,11 @@ public async Task MergeBidiForeignKeyOneToOneAsync() p.Address.StreetAddress = "321 Main"; - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { - p = (Person) await (s.MergeAsync(p)); - await (s.Transaction.CommitAsync()); - } + p = (Person) await (s.MergeAsync(p)); + await (t.CommitAsync()); } AssertInsertCount(0); @@ -150,41 +156,6 @@ public async Task MergeBidiForeignKeyOneToOneAsync() } } - [Test, Ignore("Need some more investigation about id sync.")] - public async Task MergeBidiPrimayKeyOneToOneAsync() - { - Person p; - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - p = new Person {Name = "steve"}; - new PersonalDetails {SomePersonalDetail = "I have big feet", Person = p}; - await (s.PersistAsync(p)); - await (tx.CommitAsync()); - } - - ClearCounts(); - - p.Details.SomePersonalDetail = p.Details.SomePersonalDetail + " and big hands too"; - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - p = (Person) await (s.MergeAsync(p)); - await (tx.CommitAsync()); - } - - AssertInsertCount(0); - AssertUpdateCount(1); - AssertDeleteCount(0); - - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - await (s.DeleteAsync(p)); - await (tx.CommitAsync()); - } - } - [Test] public async Task MergeDeepTreeAsync() { @@ -452,44 +423,41 @@ public async Task MergeStaleVersionFailsAsync() { var entity = new VersionedEntity {Id = "entity", Name = "entity"}; using(ISession s = OpenSession()) - using(s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.PersistAsync(entity)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } // make the detached 'entity' reference stale... using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var entity2 = await (s.GetAsync(entity.Id)); entity2.Name = "entity-name"; - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } - // now try to reattch it - ISession s2 = null; - try - { - s2 = OpenSession(); - s2.BeginTransaction(); - - await (s2.MergeAsync(entity)); - await (s2.Transaction.CommitAsync()); - Assert.Fail("was expecting staleness error"); - } - catch (StaleObjectStateException) + // now try to reattach it + using (var s2 = OpenSession()) + using (var t = s2.BeginTransaction()) { - // expected outcome... - } - finally - { - if (s2 != null) + try + { + await (s2.MergeAsync(entity)); + await (t.CommitAsync()); + Assert.Fail("was expecting staleness error"); + } + catch (StaleObjectStateException) + { + // expected outcome... + } + finally { - await (s2.Transaction.RollbackAsync()); + await (t.RollbackAsync()); s2.Close(); + await (CleanupAsync()); } - await (CleanupAsync()); } } @@ -586,10 +554,10 @@ public async Task NoExtraUpdatesOnMergeAsync() Name = "test" }; using(ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.PersistAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -597,10 +565,10 @@ public async Task NoExtraUpdatesOnMergeAsync() // node is now detached, but we have made no changes. so attempt to merge it // into this new session; this should cause no updates... using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { node = (Node) await (s.MergeAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -611,10 +579,10 @@ public async Task NoExtraUpdatesOnMergeAsync() // make sure we get an update as a result... node.Description = "new description"; using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { node = (Node) await (s.MergeAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(0); @@ -626,10 +594,10 @@ public async Task NoExtraUpdatesOnMergeVersionedAsync() { var entity = new VersionedEntity {Id = "entity", Name = "entity"}; using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { await (s.PersistAsync(entity)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -638,10 +606,10 @@ public async Task NoExtraUpdatesOnMergeVersionedAsync() // into this new session; this should cause no updates... VersionedEntity mergedEntity; using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { mergedEntity = (VersionedEntity) await (s.MergeAsync(entity)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -653,10 +621,10 @@ public async Task NoExtraUpdatesOnMergeVersionedAsync() // make sure we get an update as a result... entity.Name = "new name"; using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { entity = (VersionedEntity) await (s.MergeAsync(entity)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(0); @@ -670,12 +638,12 @@ public async Task NoExtraUpdatesOnMergeVersionedWithCollectionAsync() var child = new VersionedEntity {Id = "child", Name = "child"}; using(ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent.Children.Add(child); child.Parent = parent; await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -684,10 +652,10 @@ public async Task NoExtraUpdatesOnMergeVersionedWithCollectionAsync() // into this new session; this should cause no updates... VersionedEntity mergedParent; using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { mergedParent = (VersionedEntity) await (s.MergeAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -704,10 +672,10 @@ public async Task NoExtraUpdatesOnMergeVersionedWithCollectionAsync() mergedParent.Name = "new name"; mergedParent.Children.Add(new VersionedEntity {Id = "child2", Name = "new child"}); using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent = (VersionedEntity) await (s.MergeAsync(mergedParent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(1); @@ -723,7 +691,7 @@ public async Task NoExtraUpdatesOnMergeWithCollectionAsync() Name = "parent" }; using(ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var child = new Node { @@ -733,7 +701,7 @@ public async Task NoExtraUpdatesOnMergeWithCollectionAsync() parent.Children.Add(child); child.Parent = parent; await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -741,10 +709,10 @@ public async Task NoExtraUpdatesOnMergeWithCollectionAsync() // parent is now detached, but we have made no changes. so attempt to merge it // into this new session; this should cause no updates... using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent = (Node) await (s.MergeAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -763,10 +731,10 @@ public async Task NoExtraUpdatesOnMergeWithCollectionAsync() Name = "second child" }); using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent = (Node) await (s.MergeAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(1); diff --git a/src/NHibernate.Test/Async/Pagination/CustomDialectFixture.cs b/src/NHibernate.Test/Async/Pagination/CustomDialectFixture.cs index 6a0526e1272..c33de557a91 100644 --- a/src/NHibernate.Test/Async/Pagination/CustomDialectFixture.cs +++ b/src/NHibernate.Test/Async/Pagination/CustomDialectFixture.cs @@ -23,7 +23,6 @@ namespace NHibernate.Test.Pagination { using System.Threading.Tasks; - using System.Threading; [TestFixture] public class CustomDialectFixtureAsync : TestCase { @@ -80,7 +79,6 @@ protected override void OnSetUp() protected override void OnTearDown() { - using (ISession s = OpenSession()) using (ITransaction t = s.BeginTransaction()) { @@ -154,7 +152,7 @@ public async Task LimitFirstQueryBatchAsync() .SetFirstResult(1) .SetMaxResults(2)); - var points = await (query.GetResultAsync(0, CancellationToken.None)); + var points = await (query.GetResultAsync(0)); Assert.That(points.Count, Is.EqualTo(2)); Assert.That(points[0].X, Is.EqualTo(7d)); diff --git a/src/NHibernate.Test/Async/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs b/src/NHibernate.Test/Async/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs index 233dcdfe2a8..1d35318f531 100644 --- a/src/NHibernate.Test/Async/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs +++ b/src/NHibernate.Test/Async/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs @@ -113,7 +113,6 @@ public void WhenSaveDeleteBaseClassCastedToInterfaceThenNotThrowsAsync() await (s.FlushAsync()); } }, Throws.Nothing); - } [Test] diff --git a/src/NHibernate.Test/Async/ProjectionFixtures/Fixture.cs b/src/NHibernate.Test/Async/ProjectionFixtures/Fixture.cs index b9b01442c88..5069ff607f6 100644 --- a/src/NHibernate.Test/Async/ProjectionFixtures/Fixture.cs +++ b/src/NHibernate.Test/Async/ProjectionFixtures/Fixture.cs @@ -75,7 +75,6 @@ protected override void OnTearDown() } } - [Test] public void ErrorFromDBWillGiveTheActualSQLExecutedAsync() { @@ -100,7 +99,6 @@ public void ErrorFromDBWillGiveTheActualSQLExecutedAsync() .Add(Projections.Count("child.Key.Area")) ); - var e = Assert.ThrowsAsync(async () => { using (var s = Sfi.OpenSession()) @@ -118,10 +116,10 @@ public void ErrorFromDBWillGiveTheActualSQLExecutedAsync() [Test] public async Task AggregatingHirearchyWithCountAsync() - { - var root = new Key {Id = 1, Area = 2}; + { + var root = new Key {Id = 1, Area = 2}; - DetachedCriteria projection = DetachedCriteria.For("child") + DetachedCriteria projection = DetachedCriteria.For("child") .Add(Restrictions.Eq("Parent.id", root)) .Add(Restrictions.Gt("Key.Id", 0)) .Add(Restrictions.Eq("Type", NodeType.Blue)) @@ -133,19 +131,19 @@ public async Task AggregatingHirearchyWithCountAsync() .Add(Projections.Count(Projections.Property("grandchild.Key.Id"))) ); - using(var s = Sfi.OpenSession()) - using(var tx = s.BeginTransaction()) - { - var criteria = projection.GetExecutableCriteria(s); - var list = await (criteria.ListAsync()); - Assert.AreEqual(1, list.Count); - var tuple = (object[]) list[0]; - Assert.AreEqual(11, tuple[0]); - Assert.AreEqual(2, tuple[1]); - Assert.AreEqual(1, tuple[2]); - await (tx.CommitAsync()); - } - } + using(var s = Sfi.OpenSession()) + using(var tx = s.BeginTransaction()) + { + var criteria = projection.GetExecutableCriteria(s); + var list = await (criteria.ListAsync()); + Assert.AreEqual(1, list.Count); + var tuple = (object[]) list[0]; + Assert.AreEqual(11, tuple[0]); + Assert.AreEqual(2, tuple[1]); + Assert.AreEqual(1, tuple[2]); + await (tx.CommitAsync()); + } + } [Test] public async Task LimitingResultSetOnQueryThatIsOrderedByProjectionAsync() diff --git a/src/NHibernate.Test/Async/PropertyRef/KeyPropertyRefFixture.cs b/src/NHibernate.Test/Async/PropertyRef/KeyPropertyRefFixture.cs index 0f466622dd8..076a31d8567 100644 --- a/src/NHibernate.Test/Async/PropertyRef/KeyPropertyRefFixture.cs +++ b/src/NHibernate.Test/Async/PropertyRef/KeyPropertyRefFixture.cs @@ -63,7 +63,6 @@ public async Task PropertyRefUsesOtherColumnAsync() Assert.AreEqual(1, newA.Items.Count); s.Close(); - } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs index 16026d41f87..ff81250d911 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs @@ -498,7 +498,6 @@ public async Task MultiCriteriaAutoFlushAsync() await (tx.CommitAsync()); Assert.That(count, Is.EqualTo(0), "Session wasn't auto flushed."); - } } } diff --git a/src/NHibernate.Test/Async/QueryTest/QueryBatchFixture.cs b/src/NHibernate.Test/Async/QueryTest/QueryBatchFixture.cs index 6f42383623c..3636fe07ab4 100644 --- a/src/NHibernate.Test/Async/QueryTest/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/QueryBatchFixture.cs @@ -78,12 +78,12 @@ public async Task CanExecuteMultipleCriteriaQueriesInSingleRoundTrip_InTransacti var queries = s.CreateQueryBatch() .Add(getItems) .Add(countItems); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); var fromDb = items.First(); Assert.That(fromDb.Id, Is.EqualTo(1)); Assert.That(fromDb.Name, Is.EqualTo("foo")); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1)); await (transaction.CommitAsync()); @@ -109,11 +109,11 @@ public async Task CanExecuteMultipleCriteriaQueriesInSingleRoundTripAsync() var queries = s.CreateQueryBatch() .Add(getItems) .Add(countItems); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); var fromDb = items.First(); Assert.That(fromDb.Id, Is.EqualTo(1)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1)); } } @@ -176,9 +176,9 @@ public async Task TwoMultiCriteriaWithDifferentPagingGetDifferentResultsWhenUsin .Add( CriteriaTransformer .Clone(criteria).SetProjection(Projections.RowCount()).SetCacheable(true)); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); Assert.That(items.Count, Is.EqualTo(89)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99)); } @@ -191,12 +191,12 @@ public async Task TwoMultiCriteriaWithDifferentPagingGetDifferentResultsWhenUsin .Add( CriteriaTransformer .Clone(criteria).SetProjection(Projections.RowCount()).SetCacheable(true)); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); Assert.That( items.Count, Is.EqualTo(79), "Should have gotten different result here, because the paging is different"); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99)); } } @@ -218,9 +218,9 @@ public async Task CanUseWithParameterizedCriteriaAndLimitAsync() .Add( CriteriaTransformer.Clone(criteria) .SetProjection(Projections.RowCount())); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); Assert.That(items.Count, Is.EqualTo(89)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99)); } } @@ -248,11 +248,11 @@ public async Task CanUseCriteriaWithParameterListAsync() CriteriaTransformer.Clone(criteria) .SetProjection(Projections.RowCount())); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); var fromDb = items.First(); Assert.That(fromDb.Id, Is.EqualTo(1)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1)); } } @@ -274,8 +274,8 @@ public async Task CanAddCriteriaWithKeyAndRetrieveResultsWithKeyAsync() multiCriteria.Add("firstCriteria", firstCriteria); multiCriteria.Add("secondCriteria", secondCriteria); - var secondResult = await (multiCriteria.GetResultAsync("secondCriteria", CancellationToken.None)); - var firstResult = await (multiCriteria.GetResultAsync("firstCriteria", CancellationToken.None)); + var secondResult = await (multiCriteria.GetResultAsync("secondCriteria")); + var firstResult = await (multiCriteria.GetResultAsync("firstCriteria")); Assert.That(secondResult.Count, Is.GreaterThan(firstResult.Count)); } @@ -298,8 +298,8 @@ public async Task CanAddDetachedCriteriaWithKeyAndRetrieveResultsWithKeyAsync() multiCriteria.Add("firstCriteria", firstCriteria); multiCriteria.Add("secondCriteria", secondCriteria); - var secondResult = await (multiCriteria.GetResultAsync("secondCriteria", CancellationToken.None)); - var firstResult = await (multiCriteria.GetResultAsync("firstCriteria", CancellationToken.None)); + var secondResult = await (multiCriteria.GetResultAsync("secondCriteria")); + var firstResult = await (multiCriteria.GetResultAsync("firstCriteria")); Assert.That(secondResult.Count, Is.GreaterThan(firstResult.Count)); } @@ -317,7 +317,7 @@ public async Task ExecutingCriteriaThroughTransformsResultsAsync() .SetResultTransformer(transformer); var multiCriteria = session.CreateQueryBatch() .Add(criteria); - await (multiCriteria.GetResultAsync(0, CancellationToken.None)); + await (multiCriteria.GetResultAsync(0)); Assert.That(transformer.WasTransformTupleCalled, Is.True, "Transform Tuple was not called"); Assert.That(transformer.WasTransformListCalled, Is.True, "Transform List was not called"); @@ -356,7 +356,7 @@ public async Task UsingManyParametersAndQueries_DoesNotCauseParameterNameCollisi for (var i = 0; i < 12; i++) { - Assert.That((await (multi.GetResultAsync(i, CancellationToken.None))).Count, Is.EqualTo(1)); + Assert.That((await (multi.GetResultAsync(i))).Count, Is.EqualTo(1)); } } } @@ -380,7 +380,7 @@ public async Task MultiCriteriaAutoFlushAsync() await (s.DeleteAsync(p1)); var multi = s.CreateQueryBatch(); multi.Add(s.QueryOver().ToRowCountQuery()); - var count = (await (multi.GetResultAsync(0, CancellationToken.None))).Single(); + var count = (await (multi.GetResultAsync(0))).Single(); await (tx.CommitAsync()); Assert.That(count, Is.EqualTo(0), "Session wasn't auto flushed."); @@ -441,9 +441,9 @@ public async Task TwoMultiHqlWithDifferentPagingGetDifferentResultsWhenUsingCach s.CreateQuery("select count(*) from Item i where i.Id > ?") .SetInt32(0, 50) .SetCacheable(true)); - var items = await (multiQuery.GetResultAsync(0, CancellationToken.None)); + var items = await (multiQuery.GetResultAsync(0)); Assert.That(items.Count, Is.EqualTo(89)); - var count = (await (multiQuery.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (multiQuery.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99L)); } @@ -459,12 +459,12 @@ public async Task TwoMultiHqlWithDifferentPagingGetDifferentResultsWhenUsingCach s.CreateQuery("select count(*) from Item i where i.Id > ?") .SetInt32(0, 50) .SetCacheable(true)); - var items = await (multiQuery.GetResultAsync(0, CancellationToken.None)); + var items = await (multiQuery.GetResultAsync(0)); Assert.That( items.Count, Is.EqualTo(79), "Should have gotten different result here, because the paging is different"); - var count = (await (multiQuery.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (multiQuery.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99L)); } } @@ -472,7 +472,6 @@ public async Task TwoMultiHqlWithDifferentPagingGetDifferentResultsWhenUsingCach [Test] public async Task CanUseSecondLevelCacheWithPositionalParametersAndHqlAsync() { - await (Sfi.QueryCache.ClearAsync(CancellationToken.None)); await (CreateItemsAsync()); @@ -500,9 +499,9 @@ public async Task CanUseHqlWithParameterizedQueriesAndLimitAsync() var queries = s.CreateQueryBatch() .Add(getItems) .Add(countItems); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); Assert.That(items.Count, Is.EqualTo(89)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99L)); } } @@ -531,10 +530,10 @@ public async Task CanUseSetParameterListWithHqlAsync() "items", new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 })); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); Assert.That(items.First().Id, Is.EqualTo(1)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1L)); } } @@ -557,11 +556,11 @@ public async Task CanExecuteMultiplyHqlInSingleRoundTripAsync() var queries = s.CreateQueryBatch() .Add(getItems) .Add(countItems); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); var fromDb = items.First(); Assert.That(fromDb.Id, Is.EqualTo(1)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1L)); } } @@ -582,8 +581,8 @@ public async Task CanAddHqlWithKeyAndRetrieveResultsWithKeyAsync() multiQuery.Add("first", firstQuery).Add("second", secondQuery); - var secondResult = await (multiQuery.GetResultAsync("second", CancellationToken.None)); - var firstResult = await (multiQuery.GetResultAsync("first", CancellationToken.None)); + var secondResult = await (multiQuery.GetResultAsync("second")); + var firstResult = await (multiQuery.GetResultAsync("first")); Assert.That(secondResult.Count, Is.GreaterThan(firstResult.Count)); } @@ -601,7 +600,7 @@ public async Task ExecutingHqlThroughMultiQueryTransformsResultsAsync() .SetResultTransformer(transformer); await (session.CreateQueryBatch() .Add(criteria) - .GetResultAsync(0, CancellationToken.None)); + .GetResultAsync(0)); Assert.That(transformer.WasTransformTupleCalled, Is.True, "Transform Tuple was not called"); Assert.That(transformer.WasTransformListCalled, Is.True, "Transform List was not called"); @@ -643,7 +642,7 @@ public void NH_1085_WillGiveReasonableErrorIfBadParameterNameAsync() s.CreateSQLQuery("select * from ITEM where Id in (:ids)") .AddEntity(typeof(Item))) .Add(s.CreateQuery("from Item i where i.Id in (:ids2)")); - var e = Assert.ThrowsAsync(() => multiQuery.ExecuteAsync(CancellationToken.None)); + var e = Assert.ThrowsAsync(() => multiQuery.ExecuteAsync()); Assert.That( e.Message, Is.EqualTo( @@ -682,7 +681,7 @@ public async Task CanSpecifyParameterOnMultiQueryWhenItIsNotUsedInAllQueriesAsyn .Add( s.CreateQuery("from Item i where i.Id = :id2") .SetInt32("id2", 5)) - .ExecuteAsync(CancellationToken.None)); + .ExecuteAsync()); } } @@ -704,9 +703,9 @@ public async Task TwoMultiQueriesWithDifferentPagingGetDifferentResultsWhenUsing .SetInt32(0, 50) .SetCacheable(true)); - var items = await (multiQuery.GetResultAsync(0, CancellationToken.None)); + var items = await (multiQuery.GetResultAsync(0)); Assert.That(items.Count, Is.EqualTo(89)); - var count = (await (multiQuery.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (multiQuery.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99L)); } @@ -723,12 +722,12 @@ public async Task TwoMultiQueriesWithDifferentPagingGetDifferentResultsWhenUsing .AddScalar("itemCount", NHibernateUtil.Int64) .SetInt32(0, 50) .SetCacheable(true)); - var items = await (multiQuery.GetResultAsync(0, CancellationToken.None)); + var items = await (multiQuery.GetResultAsync(0)); Assert.That( items.Count, Is.EqualTo(79), "Should have gotten different result here, because the paging is different"); - var count = (await (multiQuery.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (multiQuery.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99L)); } } @@ -736,7 +735,6 @@ public async Task TwoMultiQueriesWithDifferentPagingGetDifferentResultsWhenUsing [Test] public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() { - await (Sfi.QueryCache.ClearAsync(CancellationToken.None)); await (CreateItemsAsync()); @@ -765,9 +763,9 @@ public async Task CanUseWithParameterizedQueriesAndLimitAsync() var queries = s.CreateQueryBatch() .Add(getItems) .Add(countItems); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); Assert.That(items.Count, Is.EqualTo(89)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(99L)); } } @@ -796,11 +794,11 @@ public async Task CanUseSetParameterListAsync() "items", new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 })); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); var fromDb = items.First(); Assert.That(fromDb.Id, Is.EqualTo(1)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1L)); } } @@ -823,11 +821,11 @@ public async Task CanExecuteMultiplyQueriesInSingleRoundTripAsync() var queries = s.CreateQueryBatch() .Add(getItems) .Add(countItems); - var items = await (queries.GetResultAsync(0, CancellationToken.None)); + var items = await (queries.GetResultAsync(0)); var fromDb = items.First(); Assert.That(fromDb.Id, Is.EqualTo(1)); - var count = (await (queries.GetResultAsync(1, CancellationToken.None))).Single(); + var count = (await (queries.GetResultAsync(1))).Single(); Assert.That(count, Is.EqualTo(1L)); } } @@ -849,8 +847,8 @@ public async Task CanAddIQueryWithKeyAndRetrieveResultsWithKeyAsync() multiQuery.Add("first", firstQuery).Add("second", secondQuery); - var secondResult = await (multiQuery.GetResultAsync("second", CancellationToken.None)); - var firstResult = await (multiQuery.GetResultAsync("first", CancellationToken.None)); + var secondResult = await (multiQuery.GetResultAsync("second")); + var firstResult = await (multiQuery.GetResultAsync("first")); Assert.That(secondResult.Count, Is.GreaterThan(firstResult.Count)); } @@ -869,7 +867,7 @@ public async Task ExecutingQueryThroughMultiQueryTransformsResultsAsync() .SetResultTransformer(transformer); await (session.CreateQueryBatch() .Add(query) - .GetResultAsync(0, CancellationToken.None)); + .GetResultAsync(0)); Assert.That(transformer.WasTransformTupleCalled, Is.True, "Transform Tuple was not called"); Assert.That(transformer.WasTransformListCalled, Is.True, "Transform List was not called"); @@ -891,7 +889,7 @@ public async Task CannotRetrieveResultWithUnknownKeyAsync() multiCriteria.Add("firstCriteria", firstCriteria); Assert.That( - () => multiCriteria.GetResultAsync("unknownKey", CancellationToken.None), + () => multiCriteria.GetResultAsync("unknownKey"), Throws.InstanceOf()); } } diff --git a/src/NHibernate.Test/Async/ReadOnly/ReadOnlyProxyTest.cs b/src/NHibernate.Test/Async/ReadOnly/ReadOnlyProxyTest.cs index 4e1c7f0a5fd..65d55227f31 100644 --- a/src/NHibernate.Test/Async/ReadOnly/ReadOnlyProxyTest.cs +++ b/src/NHibernate.Test/Async/ReadOnly/ReadOnlyProxyTest.cs @@ -9,17 +9,8 @@ using System; -using System.Collections; -using System.Collections.Generic; -using NHibernate.Cfg; -using NHibernate.Dialect; -using NHibernate.Criterion; using NHibernate.Engine; using NHibernate.Proxy; -using NHibernate.SqlCommand; -using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -34,10 +25,10 @@ protected override string[] Mappings get { return new string[] - { - "ReadOnly.DataPoint.hbm.xml", - //"ReadOnly.TextHolder.hbm.xml" - }; + { + "ReadOnly.DataPoint.hbm.xml", + //"ReadOnly.TextHolder.hbm.xml" + }; } } @@ -45,642 +36,610 @@ protected override string[] Mappings public async Task ReadOnlyViaSessionDoesNotInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (s.FlushAsync()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (s.Transaction.CommitAsync()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.SetReadOnly(dp, false); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (s.FlushAsync()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (t.CommitAsync()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyViaLazyInitializerDoesNotInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dpLI.ReadOnly = true; - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dpLI.ReadOnly = false; - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (s.FlushAsync()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (s.Transaction.CommitAsync()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dpLI.ReadOnly = true; + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dpLI.ReadOnly = false; + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (s.FlushAsync()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (t.CommitAsync()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyViaSessionNoChangeAfterInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(dp, Is.InstanceOf()); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (CheckReadOnlyAsync(s, dp, true)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(dp, Is.InstanceOf()); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (CheckReadOnlyAsync(s, dp, true)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.SetReadOnly(dp, false); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyViaLazyInitializerNoChangeAfterInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(dpLI.IsUninitialized); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(dpLI.IsUninitialized, Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - dpLI.ReadOnly = true; - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(dpLI.IsUninitialized); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(dpLI.IsUninitialized, Is.False); - await (CheckReadOnlyAsync(s, dp, true)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - dpLI.ReadOnly = true; - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(dpLI.IsUninitialized); - dpLI.ReadOnly = false; - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(dpLI.IsUninitialized); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(dpLI.IsUninitialized, Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(dpLI.IsUninitialized); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(dpLI.IsUninitialized, Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + var dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + dpLI.ReadOnly = true; + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(dpLI.IsUninitialized); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(dpLI.IsUninitialized, Is.False); + await (CheckReadOnlyAsync(s, dp, true)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + var dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + dpLI.ReadOnly = true; + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(dpLI.IsUninitialized); + dpLI.ReadOnly = false; + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(dpLI.IsUninitialized); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(dpLI.IsUninitialized, Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } [Test] public async Task ReadOnlyViaSessionBeforeInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - s.SetReadOnly(dp, true); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + s.SetReadOnly(dp, true); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } [Test] public async Task ModifiableViaSessionBeforeInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, false)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyViaSessionBeforeInitByModifiableQueryAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where id = " + dpOrig.Id).SetReadOnly(false).UniqueResultAsync()); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where id = " + dpOrig.Id).SetReadOnly(false) + .UniqueResultAsync()); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } [Test] public async Task ReadOnlyViaSessionBeforeInitByReadOnlyQueryAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - DataPoint dpFromQuery = await (s.CreateQuery( "from DataPoint where Id = " + dpOrig.Id).SetReadOnly(true).UniqueResultAsync()); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where Id = " + dpOrig.Id).SetReadOnly(true) + .UniqueResultAsync()); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ModifiableViaSessionBeforeInitByModifiableQueryAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where Id = " + dpOrig.Id).SetReadOnly(false).UniqueResultAsync()); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where Id = " + dpOrig.Id).SetReadOnly(false) + .UniqueResultAsync()); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ModifiableViaSessionBeforeInitByReadOnlyQueryAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where id=" + dpOrig.Id).SetReadOnly(true).UniqueResultAsync()); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + DataPoint dpFromQuery = await (s.CreateQuery("from DataPoint where id=" + dpOrig.Id).SetReadOnly(true) + .UniqueResultAsync()); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } [Test] public async Task ReadOnlyViaLazyInitializerBeforeInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dpLI.IsUninitialized); - await (CheckReadOnlyAsync(s, dp, false)); - dpLI.ReadOnly = true; - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dpLI.IsUninitialized); + await (CheckReadOnlyAsync(s, dp, false)); + dpLI.ReadOnly = true; + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } [Test] public async Task ModifiableViaLazyInitializerBeforeInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dp, Is.InstanceOf()); - Assert.That(dpLI.IsUninitialized); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dp, Is.InstanceOf()); + Assert.That(dpLI.IsUninitialized); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, false)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyViaLazyInitializerAfterInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dpLI.IsUninitialized); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, false)); - dpLI.ReadOnly = true; - await (CheckReadOnlyAsync(s, dp, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); - } - - [Test] - public async Task ModifiableViaLazyInitializerAfterInitAsync() - { - DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dpLI.IsUninitialized); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); - } - [Test] - [Ignore("Failing test. See HHH-4642")] - public async Task ModifyToReadOnlyToModifiableIsUpdatedFailureExpectedAsync() - { - DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - - try + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dpLI.IsUninitialized); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); Assert.That(dp.Description, Is.EqualTo("changed")); - // should fail due to HHH-4642 + await (CheckReadOnlyAsync(s, dp, false)); + dpLI.ReadOnly = true; + await (CheckReadOnlyAsync(s, dp, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - await (s.Transaction.RollbackAsync()); - s.Close(); - s = OpenSession(); - s.BeginTransaction(); + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); s.Close(); } } - + [Test] - [Ignore("Failing test. See HHH-4642")] - public async Task ReadOnlyModifiedToModifiableIsUpdatedFailureExpectedAsync() + public async Task ModifiableViaLazyInitializerAfterInitAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - - try + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dpLI.IsUninitialized); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); Assert.That(dp.Description, Is.EqualTo("changed")); - // should fail due to HHH-4642 + await (CheckReadOnlyAsync(s, dp, false)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - await (s.Transaction.RollbackAsync()); - s.Close(); - s = OpenSession(); - s.BeginTransaction(); + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); s.Close(); } } @@ -689,1083 +648,1199 @@ public async Task ReadOnlyModifiedToModifiableIsUpdatedFailureExpectedAsync() public async Task ReadOnlyChangedEvictedUpdateAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.EvictAsync(dp)); - Assert.That(s.Contains(dp), Is.False); - await (s.UpdateAsync(dp)); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.EvictAsync(dp)); + Assert.That(s.Contains(dp), Is.False); + await (s.UpdateAsync(dp)); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyToModifiableInitWhenModifiedIsUpdatedAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + s.SetReadOnly(dp, false); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyInitToModifiableModifiedIsUpdatedAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (CheckReadOnlyAsync(s, dp, true)); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (CheckReadOnlyAsync(s, dp, true)); + s.SetReadOnly(dp, false); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyModifiedUpdateAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, true)); - await (s.UpdateAsync(dp)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, true)); + await (s.UpdateAsync(dp)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyDeleteAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (s.DeleteAsync(dp)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp, Is.Null); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (s.DeleteAsync(dp)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp, Is.Null); + await (t.CommitAsync()); + s.Close(); + } } [Test] public async Task ReadOnlyRefreshAsync() { DataPoint dp = await (this.CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - ITransaction t = s.BeginTransaction(); - dp = await (s.LoadAsync(dp.Id)); - s.SetReadOnly(dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (s.RefreshAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - Assert.That(dp.Description, Is.EqualTo("original")); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(s.IsReadOnly(dp), Is.True); - Assert.That(s.IsReadOnly(await (((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None))), Is.True); - await (s.RefreshAsync(dp)); - Assert.That(dp.Description, Is.EqualTo("original")); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(s.IsReadOnly(dp), Is.True); - Assert.That(s.IsReadOnly(await (((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None))), Is.True); - await (t.CommitAsync()); - - s.Clear(); - t = s.BeginTransaction(); - dp = await (s.GetAsync(dp.Id)); - Assert.That(dp.Description, Is.EqualTo("original")); - await (s.DeleteAsync(dp)); - await (t.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = await (s.LoadAsync(dp.Id)); + s.SetReadOnly(dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (s.RefreshAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + Assert.That(dp.Description, Is.EqualTo("original")); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(s.IsReadOnly(dp), Is.True); + Assert.That( + s.IsReadOnly(await (((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None))), + Is.True); + await (s.RefreshAsync(dp)); + Assert.That(dp.Description, Is.EqualTo("original")); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(s.IsReadOnly(dp), Is.True); + Assert.That( + s.IsReadOnly(await (((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None))), + Is.True); + await (t.CommitAsync()); + } + + s.Clear(); + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dp.Id)); + Assert.That(dp.Description, Is.EqualTo("original")); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } + } } - + [Test] public async Task ReadOnlyRefreshDeletedAsync() { DataPoint dp = await (this.CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - ITransaction t = s.BeginTransaction(); - INHibernateProxy dpProxy = (INHibernateProxy)await (s.LoadAsync(dp.Id)); - Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); - await (t.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - t = s.BeginTransaction(); - dp = await (s.GetAsync(dp.Id)); - await (s.DeleteAsync(dp)); - await (s.FlushAsync()); - + INHibernateProxy dpProxy; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dpProxy = (INHibernateProxy) await (s.LoadAsync(dp.Id)); + Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); + await (t.CommitAsync()); + s.Close(); + } + try { - await (s.RefreshAsync(dp)); - Assert.Fail("should have thrown UnresolvableObjectException" ); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = await (s.GetAsync(dp.Id)); + await (s.DeleteAsync(dp)); + await (s.FlushAsync()); + + await (s.RefreshAsync(dp)); + Assert.Fail("should have thrown UnresolvableObjectException"); + } } catch (UnresolvableObjectException) { // expected } - finally + + DataPoint dpProxyInit; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - await (t.RollbackAsync()); + s.CacheMode = CacheMode.Ignore; + dpProxyInit = await (s.LoadAsync(dp.Id)); + Assert.That(dp.Description, Is.EqualTo("original")); + await (s.DeleteAsync(dpProxyInit)); + await (t.CommitAsync()); s.Close(); } - - s = OpenSession(); - t = s.BeginTransaction(); - s.CacheMode = CacheMode.Ignore; - DataPoint dpProxyInit = await (s.LoadAsync(dp.Id)); - Assert.That(dp.Description, Is.EqualTo("original")); - await (s.DeleteAsync(dpProxyInit)); - await (t.CommitAsync()); - s.Close(); - - s = OpenSession(); - t = s.BeginTransaction(); - Assert.That(dpProxyInit, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dpProxyInit), Is.True); try { - await (s.RefreshAsync(dpProxyInit)); - Assert.Fail("should have thrown UnresolvableObjectException"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(dpProxyInit, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dpProxyInit), Is.True); + + await (s.RefreshAsync(dpProxyInit)); + Assert.Fail("should have thrown UnresolvableObjectException"); + } } catch (UnresolvableObjectException) { // expected } - finally - { - await (t.RollbackAsync()); - s.Close(); - } - - s = OpenSession(); - t = s.BeginTransaction(); - Assert.That(dpProxyInit, Is.InstanceOf()); - + try { - await (s.RefreshAsync(dpProxy)); - Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); - await (NHibernateUtil.InitializeAsync(dpProxy)); - Assert.Fail("should have thrown UnresolvableObjectException"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(dpProxyInit, Is.InstanceOf()); + + await (s.RefreshAsync(dpProxy)); + Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); + await (NHibernateUtil.InitializeAsync(dpProxy)); + Assert.Fail("should have thrown UnresolvableObjectException"); + } } catch (UnresolvableObjectException) { // expected } - finally - { - await (t.RollbackAsync()); - s.Close(); - } } - + [Test] public async Task ReadOnlyRefreshDetachedAsync() { DataPoint dp = await (this.CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - ITransaction t = s.BeginTransaction(); - dp = await (s.LoadAsync(dp.Id)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - Assert.That(s.IsReadOnly(dp), Is.False); - s.SetReadOnly(dp, true); - Assert.That(s.IsReadOnly(dp), Is.True); - await (s.EvictAsync(dp)); - await (s.RefreshAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - Assert.That(s.IsReadOnly(dp), Is.False); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.SetReadOnly(dp, true); - await (s.EvictAsync(dp)); - await (s.RefreshAsync(dp)); - Assert.That(dp.Description, Is.EqualTo("original")); - Assert.That(s.IsReadOnly(dp), Is.False); - await (t.CommitAsync()); - - s.Clear(); - t = s.BeginTransaction(); - dp = await (s.GetAsync(dp.Id)); - Assert.That(dp.Description, Is.EqualTo("original")); - await (s.DeleteAsync(dp)); - await (t.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = await (s.LoadAsync(dp.Id)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + Assert.That(s.IsReadOnly(dp), Is.False); + s.SetReadOnly(dp, true); + Assert.That(s.IsReadOnly(dp), Is.True); + await (s.EvictAsync(dp)); + await (s.RefreshAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + Assert.That(s.IsReadOnly(dp), Is.False); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + s.SetReadOnly(dp, true); + await (s.EvictAsync(dp)); + await (s.RefreshAsync(dp)); + Assert.That(dp.Description, Is.EqualTo("original")); + Assert.That(s.IsReadOnly(dp), Is.False); + await (t.CommitAsync()); + } + + s.Clear(); + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dp.Id)); + Assert.That(dp.Description, Is.EqualTo("original")); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } + } } - + [Test] public async Task ReadOnlyProxyMergeDetachedProxyWithChangeAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (t.CommitAsync()); + s.Close(); + } + // modify detached proxy dp.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dpLoaded, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dpLoaded, false)); - s.SetReadOnly(dpLoaded, true); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - DataPoint dpMerged = (DataPoint)await (s.MergeAsync(dp)); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dpLoaded, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dpLoaded, false)); + s.SetReadOnly(dpLoaded, true); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + DataPoint dpMerged = (DataPoint) await (s.MergeAsync(dp)); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyProxyInitMergeDetachedProxyWithChangeAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (t.CommitAsync()); + s.Close(); + } + // modify detached proxy dp.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dpLoaded, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - await (NHibernateUtil.InitializeAsync(dpLoaded)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - await (CheckReadOnlyAsync(s, dpLoaded, false)); - s.SetReadOnly(dpLoaded, true); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - DataPoint dpMerged = (DataPoint)await (s.MergeAsync(dp)); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dpLoaded, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + await (NHibernateUtil.InitializeAsync(dpLoaded)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + await (CheckReadOnlyAsync(s, dpLoaded, false)); + s.SetReadOnly(dpLoaded, true); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + DataPoint dpMerged = (DataPoint) await (s.MergeAsync(dp)); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyProxyMergeDetachedEntityWithChangeAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (t.CommitAsync()); + s.Close(); + } + // modify detached proxy target - DataPoint dpEntity = (DataPoint)await (((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None)); + DataPoint dpEntity = (DataPoint) await (((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None)); dpEntity.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dpLoaded, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dpLoaded, false)); - s.SetReadOnly(dpLoaded, true); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - DataPoint dpMerged = (DataPoint)await (s.MergeAsync(dpEntity)); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dpLoaded, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dpLoaded, false)); + s.SetReadOnly(dpLoaded, true); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + DataPoint dpMerged = (DataPoint) await (s.MergeAsync(dpEntity)); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyProxyInitMergeDetachedEntityWithChangeAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (t.CommitAsync()); + s.Close(); + } + // modify detached proxy target - DataPoint dpEntity = (DataPoint)await (((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None)); + DataPoint dpEntity = (DataPoint) await (((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementationAsync(CancellationToken.None)); dpEntity.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dpLoaded, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - await (NHibernateUtil.InitializeAsync(dpLoaded)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - await (CheckReadOnlyAsync(s, dpLoaded, false)); - s.SetReadOnly(dpLoaded, true); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - DataPoint dpMerged = (DataPoint)await (s.MergeAsync(dpEntity)); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dpLoaded, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dpLoaded, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + await (NHibernateUtil.InitializeAsync(dpLoaded)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + await (CheckReadOnlyAsync(s, dpLoaded, false)); + s.SetReadOnly(dpLoaded, true); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + DataPoint dpMerged = (DataPoint) await (s.MergeAsync(dpEntity)); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dpLoaded, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task ReadOnlyEntityMergeDetachedProxyWithChangeAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (NHibernateUtil.InitializeAsync(dp)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (NHibernateUtil.InitializeAsync(dp)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + await (t.CommitAsync()); + s.Close(); + } + // modify detached proxy dp.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpEntity = await (s.GetAsync(dpOrig.Id)); - Assert.That(dpEntity, Is.Not.InstanceOf()); - Assert.That(s.IsReadOnly(dpEntity), Is.False); - s.SetReadOnly(dpEntity, true); - Assert.That(s.IsReadOnly(dpEntity), Is.True); - DataPoint dpMerged = (DataPoint)await (s.MergeAsync(dp)); - Assert.That(dpMerged, Is.SameAs(dpEntity)); - Assert.That(dpEntity.Description, Is.EqualTo("changed")); - Assert.That(s.IsReadOnly(dpEntity), Is.True); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpEntity = await (s.GetAsync(dpOrig.Id)); + Assert.That(dpEntity, Is.Not.InstanceOf()); + Assert.That(s.IsReadOnly(dpEntity), Is.False); + s.SetReadOnly(dpEntity, true); + Assert.That(s.IsReadOnly(dpEntity), Is.True); + DataPoint dpMerged = (DataPoint) await (s.MergeAsync(dp)); + Assert.That(dpMerged, Is.SameAs(dpEntity)); + Assert.That(dpEntity.Description, Is.EqualTo("changed")); + Assert.That(s.IsReadOnly(dpEntity), Is.True); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task SetReadOnlyInTwoTransactionsSameSessionAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - - await (CheckReadOnlyAsync(s, dp, true)); - - s.BeginTransaction(); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed again"; - Assert.That(dp.Description, Is.EqualTo("changed again")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + DataPoint dp; + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + await (CheckReadOnlyAsync(s, dp, true)); + + using (var t = s.BeginTransaction()) + { + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed again"; + Assert.That(dp.Description, Is.EqualTo("changed again")); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task SetReadOnlyBetweenTwoTransactionsSameSessionAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - - s.BeginTransaction(); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed again"; - Assert.That(dp.Description, Is.EqualTo("changed again")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + DataPoint dp; + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, false)); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + + using (var t = s.BeginTransaction()) + { + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed again"; + Assert.That(dp.Description, Is.EqualTo("changed again")); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task SetModifiableBetweenTwoTransactionsSameSessionAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - s.SetReadOnly(dp, true); - await (CheckReadOnlyAsync(s, dp, true)); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (CheckReadOnlyAsync(s, dp, true)); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - - await (CheckReadOnlyAsync(s, dp, true)); - s.SetReadOnly(dp, false); - await (CheckReadOnlyAsync(s, dp, false)); - - s.BeginTransaction(); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(dp.Description, Is.EqualTo("changed")); - await (s.RefreshAsync(dp)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - await (CheckReadOnlyAsync(s, dp, false)); - dp.Description = "changed again"; - Assert.That(dp.Description, Is.EqualTo("changed again")); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.GetAsync(dpOrig.Id)); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed again")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + DataPoint dp; + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + s.SetReadOnly(dp, true); + await (CheckReadOnlyAsync(s, dp, true)); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (CheckReadOnlyAsync(s, dp, true)); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + await (CheckReadOnlyAsync(s, dp, true)); + s.SetReadOnly(dp, false); + await (CheckReadOnlyAsync(s, dp, false)); + + using (var t = s.BeginTransaction()) + { + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(dp.Description, Is.EqualTo("changed")); + await (s.RefreshAsync(dp)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + await (CheckReadOnlyAsync(s, dp, false)); + dp.Description = "changed again"; + Assert.That(dp.Description, Is.EqualTo("changed again")); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = await (s.GetAsync(dpOrig.Id)); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed again")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } - + [Test] public async Task IsReadOnlyAfterSessionClosedAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp = null; + try { - s.IsReadOnly(dp); - Assert.Fail("should have failed because session was closed"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + s.Close(); + s.IsReadOnly(dp); + Assert.Fail("should have failed because session was closed"); + } } catch (ObjectDisposedException) // SessionException in Hibernate { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task IsReadOnlyAfterSessionClosedViaLazyInitializerAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - Assert.That(s.Contains(dp), Is.True); - s.Close(); - - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + Assert.That(s.Contains(dp), Is.True); + s.Close(); + } + + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); try { - var value = ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly; + var value = ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly; Assert.Fail("should have failed because session was detached"); } catch (TransientObjectException) { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task DetachedIsReadOnlyAfterEvictViaSessionAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(s.Contains(dp), Is.True); - await (s.EvictAsync(dp)); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - - try - { - s.IsReadOnly(dp); - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(s.Contains(dp), Is.True); + await (s.EvictAsync(dp)); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + + try + { + s.IsReadOnly(dp); + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task DetachedIsReadOnlyAfterEvictViaLazyInitializerAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.EvictAsync(dp)); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - try - { - var value = ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly; - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (s.EvictAsync(dp)); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + try + { + var value = ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly; + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task SetReadOnlyAfterSessionClosedAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - s.Close(); - + DataPoint dp = null; + try { - s.SetReadOnly(dp, true); - Assert.Fail("should have failed because session was closed"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + s.Close(); + s.SetReadOnly(dp, true); + Assert.Fail("should have failed because session was closed"); + } } catch (ObjectDisposedException) // SessionException in Hibernate { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task SetReadOnlyAfterSessionClosedViaLazyInitializerAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - Assert.That(s.Contains(dp), Is.True); - s.Close(); - - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + Assert.That(s.Contains(dp), Is.True); + s.Close(); + } + + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); try { - ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly = true; + ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly = true; Assert.Fail("should have failed because session was detached"); } catch (TransientObjectException) { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task SetClosedSessionInLazyInitializerAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.Transaction.CommitAsync()); - Assert.That(s.Contains(dp), Is.True); - s.Close(); - - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - Assert.That(((ISessionImplementor)s).IsClosed, Is.True); - + DataPoint dp = null; + try { - ((INHibernateProxy)dp).HibernateLazyInitializer.SetSession((ISessionImplementor)s); - Assert.Fail("should have failed because session was closed"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (t.CommitAsync()); + Assert.That(s.Contains(dp), Is.True); + s.Close(); + + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + Assert.That(((ISessionImplementor) s).IsClosed, Is.True); + + ((INHibernateProxy) dp).HibernateLazyInitializer.SetSession((ISessionImplementor) s); + Assert.Fail("should have failed because session was closed"); + } } catch (ObjectDisposedException) // SessionException in Hibernate { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task DetachedSetReadOnlyAfterEvictViaSessionAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - Assert.That(s.Contains(dp), Is.True); - await (s.EvictAsync(dp)); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - - try - { - s.SetReadOnly(dp, true); - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + Assert.That(s.Contains(dp), Is.True); + await (s.EvictAsync(dp)); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + + try + { + s.SetReadOnly(dp, true); + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + [Test] public async Task DetachedSetReadOnlyAfterEvictViaLazyInitializerAsync() { DataPoint dpOrig = await (CreateDataPointAsync(CacheMode.Ignore)); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = await (s.LoadAsync(dpOrig.Id)); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - await (CheckReadOnlyAsync(s, dp, false)); - await (s.EvictAsync(dp)); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - - try - { - ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly = true; - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - await (s.DeleteAsync(dp)); - await (s.Transaction.CommitAsync()); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = await (s.LoadAsync(dpOrig.Id)); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + await (CheckReadOnlyAsync(s, dp, false)); + await (s.EvictAsync(dp)); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + + try + { + ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly = true; + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + await (s.DeleteAsync(dp)); + await (t.CommitAsync()); + s.Close(); + } } } - + private async Task CreateDataPointAsync(CacheMode mode, CancellationToken cancellationToken = default(CancellationToken)) { - DataPoint dp = null; - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - - using (ITransaction t = s.BeginTransaction()) - { - dp = new DataPoint(); - dp.X = 0.1M; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - dp.Description = "original"; - await (s.SaveAsync(dp, cancellationToken)); - await (t.CommitAsync(cancellationToken)); - } + + var dp = new DataPoint(); + dp.X = 0.1M; + dp.Y = (decimal) System.Math.Cos((double) dp.X); + dp.Description = "original"; + await (s.SaveAsync(dp, cancellationToken)); + await (t.CommitAsync(cancellationToken)); + return dp; } - - return dp; } - + private async Task CheckReadOnlyAsync(ISession s, object proxy, bool expectedReadOnly, CancellationToken cancellationToken = default(CancellationToken)) { Assert.That(proxy, Is.InstanceOf()); - ILazyInitializer li = ((INHibernateProxy)proxy).HibernateLazyInitializer; + ILazyInitializer li = ((INHibernateProxy) proxy).HibernateLazyInitializer; Assert.That(s, Is.SameAs(li.Session)); Assert.That(s.IsReadOnly(proxy), Is.EqualTo(expectedReadOnly)); Assert.That(li.ReadOnly, Is.EqualTo(expectedReadOnly)); diff --git a/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs b/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs index 02f626a77e6..059ffec0eac 100644 --- a/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs +++ b/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs @@ -284,7 +284,6 @@ public async Task ExistingReadOnlyAfterSetSessionModifiableAsync() [Test] public async Task ExistingReadOnlyAfterSetSessionModifiableExistingAsync() { - Container cOrig = CreateContainer(); ISet expectedInitializedObjects = new HashSet( @@ -376,13 +375,11 @@ public async Task ExistingReadOnlyAfterSetSessionModifiableExistingAsync() await (s.CreateQuery("delete from Owner").ExecuteUpdateAsync()); await (t.CommitAsync()); s.Close(); - } [Test] public async Task ExistingReadOnlyAfterSetSessionModifiableExistingEntityReadOnlyAsync() { - Container cOrig = CreateContainer(); ISet expectedInitializedObjects = new HashSet( @@ -483,7 +480,6 @@ public async Task ExistingReadOnlyAfterSetSessionModifiableExistingEntityReadOnl [Test] public async Task ExistingReadOnlyAfterSetSessionModifiableProxyExistingAsync() { - Container cOrig = CreateContainer(); ISet expectedInitializedObjects = new HashSet( @@ -576,7 +572,6 @@ public async Task ExistingReadOnlyAfterSetSessionModifiableProxyExistingAsync() await (s.CreateQuery("delete from Owner").ExecuteUpdateAsync()); await (t.CommitAsync()); s.Close(); - } [Test] @@ -1398,8 +1393,7 @@ public async Task DefaultModifiableWithFilterCollectionEntitiesAsync() await (t.CommitAsync()); s.Close(); } - - + private Container CreateContainer() { Container c = new Container("container"); diff --git a/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs b/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs index c4c037cb7a8..97cae8df0fd 100644 --- a/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs +++ b/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs @@ -10,15 +10,7 @@ using System.Collections; using System.Collections.Generic; -using NHibernate.Cfg; -using NHibernate.Criterion; -using NHibernate.Dialect; -using NHibernate.Engine; using NHibernate.Proxy; -using NHibernate.SqlCommand; -using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -52,10 +44,10 @@ public async Task ReadOnlyOnProxiesAsync() DataPoint dp = null; long dpId = -1; - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); dp = new DataPoint(); dp.X = 0.1M; @@ -63,13 +55,13 @@ public async Task ReadOnlyOnProxiesAsync() dp.Description = "original"; await (s.SaveAsync(dp)); dpId = dp.Id; - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); s.DefaultReadOnly = true; Assert.That(s.DefaultReadOnly, Is.True); dp = (DataPoint)await (s.LoadAsync(dpId)); @@ -81,16 +73,16 @@ public async Task ReadOnlyOnProxiesAsync() Assert.That(NHibernateUtil.IsInitialized(dp), Is.True, "was not initialized during mod"); Assert.That(dp.Description, Is.EqualTo("changed"), "desc not changed in memory"); await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } - - using (ISession s = OpenSession()) + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); IList list = await (s.CreateQuery("from DataPoint where description = 'changed'").ListAsync()); Assert.That(list.Count, Is.EqualTo(0), "change written to database"); await (s.CreateQuery("delete from DataPoint").ExecuteUpdateAsync()); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -1043,7 +1035,6 @@ public async Task MergeWithReadOnlyEntityAsync() await (t.CommitAsync()); } } - } [Test] diff --git a/src/NHibernate.Test/Async/ReadOnly/ReadOnlyTest.cs b/src/NHibernate.Test/Async/ReadOnly/ReadOnlyTest.cs index dd8491f3696..b5d75bc9a64 100644 --- a/src/NHibernate.Test/Async/ReadOnly/ReadOnlyTest.cs +++ b/src/NHibernate.Test/Async/ReadOnly/ReadOnlyTest.cs @@ -8,10 +8,8 @@ //------------------------------------------------------------------------------ -using System; using System.Collections; using System.Collections.Generic; -using NHibernate.Dialect; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -38,45 +36,52 @@ protected override string[] Mappings public async Task ReadOnlyOnProxiesAsync() { ClearCounts(); - - ISession s = OpenSession(); - s.BeginTransaction(); - DataPoint dp = new DataPoint(); - dp.X = 0.1M; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - dp.Description = "original"; - await (s.SaveAsync(dp)); - long dpId = dp.Id; - await (s.Transaction.CommitAsync()); - s.Close(); - + long dpId; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + DataPoint dp = new DataPoint(); + dp.X = 0.1M; + dp.Y = (decimal) System.Math.Cos((double) dp.X); + dp.Description = "original"; + await (s.SaveAsync(dp)); + dpId = dp.Id; + await (t.CommitAsync()); + s.Close(); + } + AssertInsertCount(1); AssertUpdateCount(0); ClearCounts(); - - s = OpenSession(); - s.BeginTransaction(); - dp = await (s.LoadAsync(dpId)); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized"); - s.SetReadOnly(dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized during SetReadOnly"); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True, "was not initialized during mod"); - Assert.That(dp.Description, Is.EqualTo("changed"), "desc not changed in memory"); - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = await (s.LoadAsync(dpId)); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized"); + s.SetReadOnly(dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized during SetReadOnly"); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True, "was not initialized during mod"); + Assert.That(dp.Description, Is.EqualTo("changed"), "desc not changed in memory"); + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + AssertUpdateCount(0); - - s = OpenSession(); - s.BeginTransaction(); - IList list = await (s.CreateQuery("from DataPoint where Description = 'changed'").ListAsync()); - Assert.That(list.Count, Is.EqualTo(0), "change written to database"); - Assert.That(await (s.CreateQuery("delete from DataPoint").ExecuteUpdateAsync()), Is.EqualTo(1)); - await (s.Transaction.CommitAsync()); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + IList list = await (s.CreateQuery("from DataPoint where Description = 'changed'").ListAsync()); + Assert.That(list.Count, Is.EqualTo(0), "change written to database"); + Assert.That(await (s.CreateQuery("delete from DataPoint").ExecuteUpdateAsync()), Is.EqualTo(1)); + await (t.CommitAsync()); + s.Close(); + } + AssertUpdateCount(0); //deletes from Query.executeUpdate() are not tracked //AssertDeleteCount(1); @@ -556,38 +561,45 @@ public async Task ReadOnlyOnTextTypeAsync() string newText = "some even bigger text string"; ClearCounts(); - - ISession s = OpenSession(); - s.BeginTransaction(); - TextHolder holder = new TextHolder(origText); - await (s.SaveAsync(holder)); - long id = holder.Id; - await (s.Transaction.CommitAsync()); - s.Close(); - + long id; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + TextHolder holder = new TextHolder(origText); + await (s.SaveAsync(holder)); + id = holder.Id; + await (t.CommitAsync()); + s.Close(); + } + AssertInsertCount(1); AssertUpdateCount(0); ClearCounts(); - - s = OpenSession(); - s.BeginTransaction(); - holder = await (s.GetAsync(id)); - s.SetReadOnly(holder, true); - holder.TheText = newText; - await (s.FlushAsync()); - await (s.Transaction.CommitAsync()); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var holder = await (s.GetAsync(id)); + s.SetReadOnly(holder, true); + holder.TheText = newText; + await (s.FlushAsync()); + await (t.CommitAsync()); + s.Close(); + } + AssertUpdateCount(0); - - s = OpenSession(); - s.BeginTransaction(); - holder = await (s.GetAsync(id)); - Assert.That(origText, Is.EqualTo(holder.TheText), "change written to database"); - await (s.DeleteAsync(holder)); - await (s.Transaction.CommitAsync()); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var holder = await (s.GetAsync(id)); + Assert.That(origText, Is.EqualTo(holder.TheText), "change written to database"); + await (s.DeleteAsync(holder)); + await (t.CommitAsync()); + s.Close(); + } + AssertUpdateCount(0); AssertDeleteCount(1); } diff --git a/src/NHibernate.Test/Async/ReadOnly/ReadOnlyVersionedNodes.cs b/src/NHibernate.Test/Async/ReadOnly/ReadOnlyVersionedNodes.cs index ea677098be3..def3e860c51 100644 --- a/src/NHibernate.Test/Async/ReadOnly/ReadOnlyVersionedNodes.cs +++ b/src/NHibernate.Test/Async/ReadOnly/ReadOnlyVersionedNodes.cs @@ -8,18 +8,7 @@ //------------------------------------------------------------------------------ -using System.Collections; -using System.Collections.Generic; using System.Linq; -using NHibernate.Cfg; -using NHibernate.Criterion; -using NHibernate.Dialect; -using NHibernate.Engine; -using NHibernate.Proxy; -using NHibernate.SqlCommand; -using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -42,22 +31,23 @@ protected override string[] Mappings public async Task SetReadOnlyTrueAndFalseAsync() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) { - s.BeginTransaction(); - node = await (s.GetAsync(node.Id)); - s.SetReadOnly(node, true); - node.Name = "node-name"; - await (s.Transaction.CommitAsync()); + using (var t = s.BeginTransaction()) + { + node = await (s.GetAsync(node.Id)); + s.SetReadOnly(node, true); + node.Name = "node-name"; + await (t.CommitAsync()); + } AssertUpdateCount(0); AssertInsertCount(0); @@ -65,24 +55,25 @@ public async Task SetReadOnlyTrueAndFalseAsync() // the changed name is still in node Assert.That(node.Name, Is.EqualTo("node-name")); - s.BeginTransaction(); - node = await (s.GetAsync(node.Id)); - // the changed name is still in the session - Assert.That(node.Name, Is.EqualTo("node-name")); - await (s.RefreshAsync(node)); - // after refresh, the name reverts to the original value - Assert.That(node.Name, Is.EqualTo("node")); - node = await (s.GetAsync(node.Id)); - Assert.That(node.Name, Is.EqualTo("node")); - await (s.Transaction.CommitAsync()); + using (var t = s.BeginTransaction()) + { + node = await (s.GetAsync(node.Id)); + // the changed name is still in the session + Assert.That(node.Name, Is.EqualTo("node-name")); + await (s.RefreshAsync(node)); + // after refresh, the name reverts to the original value + Assert.That(node.Name, Is.EqualTo("node")); + node = await (s.GetAsync(node.Id)); + Assert.That(node.Name, Is.EqualTo("node")); + await (t.CommitAsync()); + } } AssertUpdateCount(0); AssertInsertCount(0); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = await (s.GetAsync(node.Id)); Assert.That(node.Name, Is.EqualTo("node")); s.SetReadOnly(node, true); @@ -93,22 +84,21 @@ public async Task SetReadOnlyTrueAndFalseAsync() Assert.That(node.Name, Is.EqualTo("node")); s.SetReadOnly(node, false); node.Name = "diff-node-name"; - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(0); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = await (s.GetAsync(node.Id)); Assert.That(node.Name, Is.EqualTo("diff-node-name")); Assert.That(node.Version, Is.EqualTo(2)); s.SetReadOnly(node, true); await (s.DeleteAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -119,37 +109,35 @@ public async Task SetReadOnlyTrueAndFalseAsync() public async Task UpdateSetReadOnlyTwiceAsync() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = await (s.GetAsync(node.Id)); node.Name = "node-name"; s.SetReadOnly(node, true); s.SetReadOnly(node, true); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertInsertCount(0); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = await (s.GetAsync(node.Id)); Assert.That(node.Name, Is.EqualTo("node")); Assert.That(node.Version, Is.EqualTo(1)); s.SetReadOnly(node, true); await (s.DeleteAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -160,151 +148,75 @@ public async Task UpdateSetReadOnlyTwiceAsync() public async Task UpdateSetModifiableAsync() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = await (s.GetAsync(node.Id)); node.Name = "node-name"; s.SetReadOnly(node, false); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = await (s.GetAsync(node.Id)); Assert.That(node.Name, Is.EqualTo("node-name")); Assert.That(node.Version, Is.EqualTo(2)); //s.SetReadOnly(node, true); await (s.DeleteAsync(node)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertDeleteCount(1); } - [Test] - [Ignore("Failure expected")] - public async Task UpdateSetReadOnlySetModifiableFailureExpectedAsync() - { - VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) - { - s.BeginTransaction(); - await (s.PersistAsync(node)); - await (s.Transaction.CommitAsync()); - } - - ClearCounts(); - - using (ISession s = OpenSession()) - { - s.BeginTransaction(); - node = await (s.GetAsync(node.Id)); - node.Name = "node-name"; - s.SetReadOnly(node, true); - s.SetReadOnly(node, false); - await (s.Transaction.CommitAsync()); - } - - AssertUpdateCount(1); - AssertInsertCount(0); - - using (ISession s = OpenSession()) - { - s.BeginTransaction(); - node = await (s.GetAsync(node.Id)); - Assert.That(node.Name, Is.EqualTo("node-name")); - Assert.That(node.Version, Is.EqualTo(2)); - await (s.DeleteAsync(node)); - await (s.Transaction.CommitAsync()); - } - } - - [Test] - [Ignore("Failure expected")] - public async Task SetReadOnlyUpdateSetModifiableFailureExpectedAsync() - { - ISession s = OpenSession(); - s.BeginTransaction(); - VersionedNode node = new VersionedNode("node", "node"); - await (s.PersistAsync(node)); - await (s.Transaction.CommitAsync()); - s.Close(); - - ClearCounts(); - - s = OpenSession(); - - s.BeginTransaction(); - node = await (s.GetAsync(node.Id)); - s.SetReadOnly(node, true); - node.Name = "node-name"; - s.SetReadOnly(node, false); - await (s.Transaction.CommitAsync()); - s.Close(); - - AssertUpdateCount(1); - AssertInsertCount(0); - - s = OpenSession(); - s.BeginTransaction(); - node = await (s.GetAsync(node.Id)); - Assert.That(node.Name, Is.EqualTo("node-name")); - Assert.That(node.Version, Is.EqualTo(2)); - await (s.DeleteAsync(node)); - await (s.Transaction.CommitAsync()); - s.Close(); - } - [Test] public async Task AddNewChildToReadOnlyParentAsync() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); VersionedNode parentManaged = await (s.GetAsync(parent.Id)); s.SetReadOnly(parentManaged, true); parentManaged.Name = "new parent name"; parentManaged.AddChild(child); await (s.SaveAsync(parentManaged)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(1); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); Assert.That(parent.Name, Is.EqualTo("parent")); Assert.That(parent.Children.Count, Is.EqualTo(1)); @@ -312,7 +224,7 @@ public async Task AddNewChildToReadOnlyParentAsync() child = await (s.GetAsync(child.Id)); Assert.That(child, Is.Not.Null); await (s.DeleteAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } } @@ -320,11 +232,11 @@ public async Task AddNewChildToReadOnlyParentAsync() public async Task UpdateParentWithNewChildCommitWithReadOnlyParentAsync() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -332,22 +244,20 @@ public async Task UpdateParentWithNewChildCommitWithReadOnlyParentAsync() parent.Name = "new parent name"; VersionedNode child = new VersionedNode("child", "child"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.UpdateAsync(parent)); s.SetReadOnly(parent, true); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -360,7 +270,7 @@ public async Task UpdateParentWithNewChildCommitWithReadOnlyParentAsync() s.SetReadOnly(child, true); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -371,11 +281,11 @@ public async Task UpdateParentWithNewChildCommitWithReadOnlyParentAsync() public async Task MergeDetachedParentWithNewChildCommitWithReadOnlyParentAsync() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -383,22 +293,20 @@ public async Task MergeDetachedParentWithNewChildCommitWithReadOnlyParentAsync() parent.Name = "new parent name"; VersionedNode child = new VersionedNode("child", "child"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = (VersionedNode) await (s.MergeAsync(parent)); s.SetReadOnly(parent, true); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -411,7 +319,7 @@ public async Task MergeDetachedParentWithNewChildCommitWithReadOnlyParentAsync() s.SetReadOnly(child, true); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -422,11 +330,11 @@ public async Task MergeDetachedParentWithNewChildCommitWithReadOnlyParentAsync() public async Task GetParentMakeReadOnlyThenMergeDetachedParentWithNewChildCAsync() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -434,24 +342,22 @@ public async Task GetParentMakeReadOnlyThenMergeDetachedParentWithNewChildCAsync parent.Name = "new parent name"; VersionedNode child = new VersionedNode("child", "child"); parent.AddChild(child); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode parentManaged = await (s.GetAsync(parent.Id)); s.SetReadOnly(parentManaged, true); VersionedNode parentMerged = (VersionedNode) await (s.MergeAsync(parent)); Assert.That(parentManaged, Is.SameAs(parentMerged)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(1); AssertInsertCount(1); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -462,7 +368,7 @@ public async Task GetParentMakeReadOnlyThenMergeDetachedParentWithNewChildCAsync Assert.That(child.Version, Is.EqualTo(1)); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -474,55 +380,50 @@ public async Task MergeUnchangedDetachedParentChildrenAsync() { VersionedNode parent = new VersionedNode("parent", "parent"); VersionedNode child = new VersionedNode("child", "child"); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent.AddChild(child); await (s.PersistAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = (VersionedNode) await (s.MergeAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode parentGet = await (s.GetAsync(parent.Id)); await (s.MergeAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode parentLoad = await (s.LoadAsync(parent.Id)); await (s.MergeAsync(parent)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -533,7 +434,7 @@ public async Task MergeUnchangedDetachedParentChildrenAsync() Assert.That(child.Version, Is.EqualTo(1)); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -544,32 +445,31 @@ public async Task MergeUnchangedDetachedParentChildrenAsync() public async Task AddNewParentToReadOnlyChildAsync() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode childManaged = await (s.GetAsync(child.Id)); s.SetReadOnly(childManaged, true); childManaged.Name = "new child name"; parent.AddChild(childManaged); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertInsertCount(1); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); child = await (s.GetAsync(child.Id)); Assert.That(child.Name, Is.EqualTo("child")); Assert.That(child.Parent, Is.Null); @@ -578,7 +478,7 @@ public async Task AddNewParentToReadOnlyChildAsync() Assert.That(parent, Is.Not.Null); s.SetReadOnly(child, true); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -589,11 +489,11 @@ public async Task AddNewParentToReadOnlyChildAsync() public async Task UpdateChildWithNewParentCommitWithReadOnlyChildAsync() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -601,22 +501,20 @@ public async Task UpdateChildWithNewParentCommitWithReadOnlyChildAsync() child.Name = "new child name"; VersionedNode parent = new VersionedNode("parent", "parent"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.UpdateAsync(child)); s.SetReadOnly(child, true); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(child.Name, Is.EqualTo("child")); @@ -629,7 +527,7 @@ public async Task UpdateChildWithNewParentCommitWithReadOnlyChildAsync() s.SetReadOnly(child, true); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -640,11 +538,11 @@ public async Task UpdateChildWithNewParentCommitWithReadOnlyChildAsync() public async Task MergeDetachedChildWithNewParentCommitWithReadOnlyChildAsync() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -652,22 +550,20 @@ public async Task MergeDetachedChildWithNewParentCommitWithReadOnlyChildAsync() child.Name = "new child name"; VersionedNode parent = new VersionedNode("parent", "parent"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); child = (VersionedNode) await (s.MergeAsync(child)); s.SetReadOnly(child, true); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); // NH-specific: Hibernate issues a separate UPDATE for the version number AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(child.Name, Is.EqualTo("child")); @@ -680,7 +576,7 @@ public async Task MergeDetachedChildWithNewParentCommitWithReadOnlyChildAsync() s.SetReadOnly(child, true); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -691,11 +587,11 @@ public async Task MergeDetachedChildWithNewParentCommitWithReadOnlyChildAsync() public async Task GetChildMakeReadOnlyThenMergeDetachedChildWithNewParentAsync() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); await (s.PersistAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } ClearCounts(); @@ -703,24 +599,22 @@ public async Task GetChildMakeReadOnlyThenMergeDetachedChildWithNewParentAsync() child.Name = "new child name"; VersionedNode parent = new VersionedNode("parent", "parent"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode childManaged = await (s.GetAsync(child.Id)); s.SetReadOnly(childManaged, true); VersionedNode childMerged = (VersionedNode) await (s.MergeAsync(child)); Assert.That(childManaged, Is.SameAs(childMerged)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); // NH-specific: Hibernate issues a separate UPDATE for the version number AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = await (s.GetAsync(parent.Id)); child = await (s.GetAsync(child.Id)); Assert.That(child.Name, Is.EqualTo("child")); @@ -734,7 +628,7 @@ public async Task GetChildMakeReadOnlyThenMergeDetachedChildWithNewParentAsync() s.SetReadOnly(child, true); await (s.DeleteAsync(parent)); await (s.DeleteAsync(child)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); } AssertUpdateCount(0); @@ -743,14 +637,13 @@ public async Task GetChildMakeReadOnlyThenMergeDetachedChildWithNewParentAsync() protected override void OnTearDown() { - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - s.CreateQuery("delete from VersionedNode where parent is not null").ExecuteUpdate(); s.CreateQuery("delete from VersionedNode").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } base.OnTearDown(); diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index a375e64c216..bf709eb7380 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -104,7 +104,6 @@ public async Task InvalidatesEntitiesAsync() await (tx.CommitAsync()); } - //Update Item using LINQ using (var tx = session.BeginTransaction()) { diff --git a/src/NHibernate.Test/Async/SqlTest/Custom/CustomSQLSupportTest.cs b/src/NHibernate.Test/Async/SqlTest/Custom/CustomSQLSupportTest.cs index 3b47237dc01..fad2a72a99d 100644 --- a/src/NHibernate.Test/Async/SqlTest/Custom/CustomSQLSupportTest.cs +++ b/src/NHibernate.Test/Async/SqlTest/Custom/CustomSQLSupportTest.cs @@ -92,6 +92,5 @@ public async Task HandSQLAsync() await (t.CommitAsync()); s.Close(); } - } } diff --git a/src/NHibernate.Test/Async/SqlTest/Custom/CustomStoredProcSupportTest.cs b/src/NHibernate.Test/Async/SqlTest/Custom/CustomStoredProcSupportTest.cs index 3baf9b8b792..5e91ba4870b 100644 --- a/src/NHibernate.Test/Async/SqlTest/Custom/CustomStoredProcSupportTest.cs +++ b/src/NHibernate.Test/Async/SqlTest/Custom/CustomStoredProcSupportTest.cs @@ -80,6 +80,5 @@ public async Task EntityStoredProcedureAsync() await (t.CommitAsync()); s.Close(); } - } } diff --git a/src/NHibernate.Test/Async/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs b/src/NHibernate.Test/Async/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs index a58ec17a5db..e88ed21e1ec 100644 --- a/src/NHibernate.Test/Async/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs +++ b/src/NHibernate.Test/Async/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs @@ -66,6 +66,5 @@ public async Task RefCursorOutStoredProcedureAsync() await (t.CommitAsync()); s.Close(); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs index d2f9c921855..b4111a4fe9e 100644 --- a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -410,80 +410,6 @@ public async Task MappedAliasStrategyAsync() s.Close(); } - /* test for native sql composite id joins which has never been implemented */ - - [Test, Ignore("Failure expected")] - public async Task CompositeIdJoinsFailureExpectedAsync() - { - ISession s = OpenSession(); - ITransaction t = s.BeginTransaction(); - Person person = new Person(); - person.Name = "Noob"; - - Product product = new Product(); - product.ProductId = new Product.ProductIdType(); - product.ProductId.Orgid = "x"; - product.ProductId.Productnumber = "1234"; - product.Name = "Hibernate 3"; - - Order order = new Order(); - order.OrderId = new Order.OrderIdType(); - order.OrderId.Ordernumber = "1"; - order.OrderId.Orgid = "y"; - - product.Orders.Add(order); - order.Product = product; - order.Person = person; - - await (s.SaveAsync(product)); - await (s.SaveAsync(order)); - await (s.SaveAsync(person)); - - await (t.CommitAsync()); - s.Close(); - - s = OpenSession(); - t = s.BeginTransaction(); - Product p = (Product) (await (s.CreateQuery("from Product p join fetch p.orders").ListAsync()))[0]; - Assert.IsTrue(NHibernateUtil.IsInitialized(p.Orders)); - await (t.CommitAsync()); - s.Close(); - - s = OpenSession(); - t = s.BeginTransaction(); - object[] o = (object[]) (await (s.CreateSQLQuery( - "select\r\n" + - " product.orgid as {product.id.orgid}," + - " product.productnumber as {product.id.productnumber}," + - " {prod_orders}.orgid as orgid3_1_,\r\n" + - " {prod_orders}.ordernumber as ordernum2_3_1_,\r\n" + - " product.name as {product.name}," + - " {prod_orders.element.*}," + - /*" orders.PROD_NO as PROD4_3_1_,\r\n" + - " orders.person as person3_1_,\r\n" + - " orders.PROD_ORGID as PROD3_0__,\r\n" + - " orders.PROD_NO as PROD4_0__,\r\n" + - " orders.orgid as orgid0__,\r\n" + - " orders.ordernumber as ordernum2_0__ \r\n" +*/ - " from\r\n" + - " Product product \r\n" + - " inner join\r\n" + - " TBL_ORDER {prod_orders} \r\n" + - " on product.orgid={prod_orders}.PROD_ORGID \r\n" + - " and product.productnumber={prod_orders}.PROD_NO") - .AddEntity("product", typeof(Product)) - .AddJoin("prod_orders", "product.orders") - .ListAsync()))[0]; - - p = (Product) o[0]; - Assert.IsTrue(NHibernateUtil.IsInitialized(p.Orders)); - IEnumerator en = p.Orders.GetEnumerator(); - Assert.IsTrue(en.MoveNext()); - Assert.IsNotNull(en.Current); - await (t.CommitAsync()); - s.Close(); - } - [Test] public async Task AutoDetectAliasingAsync() { @@ -547,7 +473,6 @@ public async Task AutoDetectAliasingAsync() AssertClassAssignability(o[0].GetType(), typeof(long)); AssertClassAssignability(o[1].GetType(), typeof(Employment)); - IQuery queryWithCollection = s.GetNamedQuery("organizationEmploymentsExplicitAliases"); queryWithCollection.SetInt64("id", jboss.Id); list = await (queryWithCollection.ListAsync()); @@ -795,7 +720,7 @@ public async Task CanExecuteFutureValueAsync() public async Task HandlesManualSynchronizationAsync() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.SessionFactory.Statistics.IsStatisticsEnabled = true; s.SessionFactory.Statistics.Clear(); @@ -814,7 +739,7 @@ public async Task HandlesManualSynchronizationAsync() // clean up await (s.DeleteAsync(jboss)); - await (s.Transaction.CommitAsync()); + await (t.CommitAsync()); s.Close(); } } diff --git a/src/NHibernate.Test/Async/Stateless/Fetching/StatelessSessionFetchingTest.cs b/src/NHibernate.Test/Async/Stateless/Fetching/StatelessSessionFetchingTest.cs index 5c3c77af291..18a62a49bfd 100644 --- a/src/NHibernate.Test/Async/Stateless/Fetching/StatelessSessionFetchingTest.cs +++ b/src/NHibernate.Test/Async/Stateless/Fetching/StatelessSessionFetchingTest.cs @@ -81,5 +81,4 @@ public async System.Threading.Tasks.Task DynamicFetchAsync() } } } - } diff --git a/src/NHibernate.Test/Async/Stateless/StatelessWithRelationsFixture.cs b/src/NHibernate.Test/Async/Stateless/StatelessWithRelationsFixture.cs index 820b389c6b4..9417cba7b8a 100644 --- a/src/NHibernate.Test/Async/Stateless/StatelessWithRelationsFixture.cs +++ b/src/NHibernate.Test/Async/Stateless/StatelessWithRelationsFixture.cs @@ -97,6 +97,5 @@ public async Task ShouldWorkLoadingComplexEntitiesAsync() await (tx.CommitAsync()); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/StaticProxyTest/InterfaceHandling/Fixture.cs b/src/NHibernate.Test/Async/StaticProxyTest/InterfaceHandling/Fixture.cs new file mode 100644 index 00000000000..80cb249dce3 --- /dev/null +++ b/src/NHibernate.Test/Async/StaticProxyTest/InterfaceHandling/Fixture.cs @@ -0,0 +1,202 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.StaticProxyTest.InterfaceHandling +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + private readonly Guid _id = Guid.NewGuid(); + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + #region Subclass hierarchy + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.UnionSubclass( + rc => + { + rc.Proxy(typeof(ISubEntityProxy)); + + rc.Property(x => x.AnotherName); + }); + + mapper.UnionSubclass( + rc => + { + rc.Proxy(typeof(IAnotherEntityProxy)); + + rc.Property(x => x.AnotherName); + }); + + mapper.Class( + rc => + { + rc.Table("ClassWithInterfaceLookup"); + rc.Id(x => x.Id); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityClassProxy))); + }); + + #endregion Subclass hierarchy + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Table("multiInterface"); + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Proxy(typeof(IMultiIdProxy)); + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public async Task ProxyForBaseSubclassCanBeCreatedAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.LoadAsync(_id)); + } + } + + //Id access via implicit interface should not lead to proxy initialization + [Test] + public async Task ProxyClassIdAccessByImplicitInterfaceAsync() + { + using (var session = OpenSession()) + { + var entity = (IEntity) await (session.LoadAsync(_id)); + CanAccessIEntityId(entity); + ThrowOnIEntityNameAccess(entity); + Assert.That(entity.Id, Is.EqualTo(_id)); + + var multiInterface = await (session.LoadAsync(_id)); + CanAccessIEntityId(multiInterface); + CanAccessIEntity2Id(multiInterface); + Assert.That(multiInterface.Id, Is.EqualTo(_id)); + } + } + + [Test] + public async Task ProxyClassIdAccessExplicitInterfaceAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.LoadAsync(_id)); + + ThrowOnIEntityIdAccess(entity); + Assert.That(entity.Id, Is.EqualTo(_id)); + } + } + + [Test] + public async Task ProxyClassIdAccessBothImplicitExplicitInterfacesAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.LoadAsync(_id)); + + //IEntity2 is implicit and should be accessible without proxy initialization + CanAccessIEntity2Id(entity); + ThrowOnIEntityIdAccess(entity); + } + } + + [Test] + public async Task ProxyInterfaceIdAccessAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.LoadAsync(_id)); + + CanAccessIEntityId(entity); + } + } + + [KnownBug("GH-2271")] + [Test] + public async Task ProxyInterfaceIdAccessFromDifferentInterfacesAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.LoadAsync(_id)); + + CanAccessIEntityId(entity); + CanAccessIEntity2Id(entity); + } + } + + private void ThrowOnIEntityNameAccess(IEntity entity) + { + Assert.That(() => entity.Name, Throws.TypeOf(), "IEntity.Name access should lead to proxy initialization"); + } + + private void ThrowOnIEntityIdAccess(IEntity entity) + { + Assert.That(() => entity.Id, Throws.TypeOf(), "IEntity.Id access should lead to proxy initialization"); + } + + private void CanAccessIEntityId(IEntity entity) + { + Assert.That(() => entity.Id, Throws.Nothing, "Failed to access proxy IEntity.Id interface"); + Assert.That(entity.Id, Is.EqualTo(_id)); + } + + private void CanAccessIEntity2Id(IEntity2 entity) + { + Assert.That(() => entity.Id, Throws.Nothing, "Failed to access proxy IEntity2.Id interface"); + Assert.That(entity.Id, Is.EqualTo(_id)); + } + } +} diff --git a/src/NHibernate.Test/Async/Stats/StatsFixture.cs b/src/NHibernate.Test/Async/Stats/StatsFixture.cs index f36c5cb7cd8..621a4b7d4e1 100644 --- a/src/NHibernate.Test/Async/Stats/StatsFixture.cs +++ b/src/NHibernate.Test/Async/Stats/StatsFixture.cs @@ -275,7 +275,7 @@ public async Task IncrementQueryExecutionCount_WhenExplicitQueryIsExecutedAsync( await (s.CreateQueryBatch() .Add(s.CreateQuery("from Country")) .Add(s.CreateQuery("from Continent")) - .ExecuteAsync(CancellationToken.None)); + .ExecuteAsync()); } Assert.That(stats.QueryExecutionCount, Is.EqualTo(1)); @@ -285,7 +285,7 @@ public async Task IncrementQueryExecutionCount_WhenExplicitQueryIsExecutedAsync( await (s.CreateQueryBatch() .Add(DetachedCriteria.For()) .Add(DetachedCriteria.For()) - .ExecuteAsync(CancellationToken.None)); + .ExecuteAsync()); } Assert.That(stats.QueryExecutionCount, Is.EqualTo(1)); } diff --git a/src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs b/src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs index adf6c455d3c..7973f10aa17 100644 --- a/src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs +++ b/src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs @@ -13,12 +13,11 @@ using System.Threading; using System.Transactions; using log4net; -using log4net.Repository.Hierarchy; using NHibernate.Cfg; using NHibernate.Engine; -using NHibernate.Linq; using NHibernate.Test.TransactionTest; using NUnit.Framework; +using NHibernate.Linq; namespace NHibernate.Test.SystemTransactions { diff --git a/src/NHibernate.Test/Async/SystemTransactions/SystemTransactionFixture.cs b/src/NHibernate.Test/Async/SystemTransactions/SystemTransactionFixture.cs index c5089580535..056ae272b0a 100644 --- a/src/NHibernate.Test/Async/SystemTransactions/SystemTransactionFixture.cs +++ b/src/NHibernate.Test/Async/SystemTransactions/SystemTransactionFixture.cs @@ -10,15 +10,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Transactions; using NHibernate.Cfg; using NHibernate.Driver; using NHibernate.Engine; -using NHibernate.Linq; using NHibernate.Test.TransactionTest; using NUnit.Framework; +using NHibernate.Linq; namespace NHibernate.Test.SystemTransactions { diff --git a/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs b/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs index 7ac79c8af5b..1f453753d93 100644 --- a/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs +++ b/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs @@ -13,6 +13,8 @@ using System.Reflection; using NHibernate.Cfg; using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Exceptions; using NHibernate.Tool.hbm2ddl; using NHibernate.Util; using NUnit.Framework; @@ -25,18 +27,36 @@ namespace NHibernate.Test.Tools.hbm2ddl.SchemaUpdate [TestFixture] public class MigrationFixtureAsync { - private async Task MigrateSchemaAsync(string resource1, string resource2, CancellationToken cancellationToken = default(CancellationToken)) + private Configuration _configurationToDrop; + private FirebirdClientDriver _fireBirdDriver; + + [OneTimeSetUp] + public void OneTimeSetup() { - Configuration v1cfg = TestConfigurationHelper.GetDefaultConfiguration(); - var driverClass = ReflectHelper.ClassForName(v1cfg.GetProperty(Environment.ConnectionDriver)); + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + var driverClass = ReflectHelper.ClassForName(cfg.GetProperty(Environment.ConnectionDriver)); // Odbc is not supported by schema update: System.Data.Odbc.OdbcConnection.GetSchema("ForeignKeys") fails with an ArgumentException: ForeignKeys is undefined. // It seems it would require its own DataBaseSchema, but this is bound to the dialect, not the driver. if (typeof(OdbcDriver).IsAssignableFrom(driverClass)) Assert.Ignore("Test is not compatible with ODBC"); - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource1)) - v1cfg.AddInputStream(stream); - await (new SchemaExport(v1cfg).ExecuteAsync(false, true, true, cancellationToken)); + if (typeof(FirebirdClientDriver).IsAssignableFrom(driverClass)) + _fireBirdDriver = new FirebirdClientDriver(); + } + + [TearDown] + public void TearDown() + { + if (_configurationToDrop != null) + DropSchema(_configurationToDrop); + _configurationToDrop = null; + } + + private async Task MigrateSchemaAsync(string resource1, string resource2, CancellationToken cancellationToken = default(CancellationToken)) + { + var v1cfg = GetConfigurationForMapping(resource1); + await (DropSchemaAsync(v1cfg, cancellationToken)); + _configurationToDrop = v1cfg; Tool.hbm2ddl.SchemaUpdate v1schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v1cfg); await (v1schemaUpdate.ExecuteAsync(true, true, cancellationToken)); @@ -46,9 +66,7 @@ public class MigrationFixtureAsync Assert.AreEqual(0, v1schemaUpdate.Exceptions.Count); - Configuration v2cfg = TestConfigurationHelper.GetDefaultConfiguration(); - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource2)) - v2cfg.AddInputStream(stream); + var v2cfg = GetConfigurationForMapping(resource2); Tool.hbm2ddl.SchemaUpdate v2schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v2cfg); await (v2schemaUpdate.ExecuteAsync(true, true, cancellationToken)); @@ -76,5 +94,90 @@ public async Task SimpleColumnReplaceAsync() await (MigrateSchemaAsync(resource1, resource2)); } + + [Test] + public async Task AutoUpdateFailuresAreThrownAsync() + { + var cfg1 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.1_Person.hbm.xml"); + var sf1 = cfg1.BuildSessionFactory(); + var dialect = Dialect.Dialect.GetDialect(cfg1.Properties); + if (!dialect.SupportsUnique) + Assert.Ignore("This test requires a dialect supporting unique constraints"); + + _configurationToDrop = cfg1; + await (CreateSchemaAsync(cfg1)); + + using (var s = sf1.OpenSession()) + using (var t = s.BeginTransaction()) + { + await (s.SaveAsync(new Person())); + await (s.SaveAsync(new Person())); + await (t.CommitAsync()); + } + + // This schema switches to not-nullable the person properties, which should fail due to an existing person with null properties. + var cfg2 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.3_Person.hbm.xml"); + cfg2.Properties[Environment.Hbm2ddlAuto] = SchemaAutoAction.Update.ToString(); + cfg2.Properties[Environment.Hbm2ddlThrowOnUpdate] = "true"; + Assert.That(() => cfg2.BuildSessionFactory(), Throws.InstanceOf()); + } + + private Configuration GetConfigurationForMapping(string resourcePath) + { + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath)) + cfg.AddInputStream(stream); + return cfg; + } + + private Task CreateSchemaAsync(Configuration cfg, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + // Firebird will pool each connection created during the test and will marked as used any table + // referenced by queries. It will at best delays those tables drop until connections are actually + // closed, or immediately fail dropping them. + // This results in other tests failing when they try to create tables with the same name. + // By clearing the connection pool the tables will get dropped. + _fireBirdDriver?.ClearPool(null); + + return new SchemaExport(cfg).CreateAsync(false, true, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + private Task DropSchemaAsync(Configuration cfg, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + // Firebird will pool each connection created during the test and will marked as used any table + // referenced by queries. It will at best delays those tables drop until connections are actually + // closed, or immediately fail dropping them. + // This results in other tests failing when they try to create tables with the same name. + // By clearing the connection pool the tables will get dropped. + _fireBirdDriver?.ClearPool(null); + + return new SchemaExport(cfg).DropAsync(false, true, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + private void DropSchema(Configuration cfg) + { + // Firebird will pool each connection created during the test and will marked as used any table + // referenced by queries. It will at best delays those tables drop until connections are actually + // closed, or immediately fail dropping them. + // This results in other tests failing when they try to create tables with the same name. + // By clearing the connection pool the tables will get dropped. + _fireBirdDriver?.ClearPool(null); + + new SchemaExport(cfg).Drop(false, true); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs b/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs index a4950e994ba..a8809777bba 100644 --- a/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs +++ b/src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs @@ -52,8 +52,7 @@ public async Task ShouldVerifySameTableAsync() await (validator.ValidateAsync()); } -#if NETFX - [Test, SetCulture("tr-TR"), SetUICulture("tr-TR")] + [Test, SetCulture("tr-TR"), SetUICulture("tr-TR"), NetFxOnly] public async Task ShouldVerifySameTableTurkishAsync() { //NH-3063 @@ -81,7 +80,6 @@ public async Task ShouldVerifySameTableTurkishAsync() await (export.DropAsync(true, true)); } } -#endif [Test] public void ShouldNotVerifyModifiedTableAsync() diff --git a/src/NHibernate.Test/Async/TransactionTest/TransactionFixture.cs b/src/NHibernate.Test/Async/TransactionTest/TransactionFixture.cs index ab4a0091a75..99edadc22b1 100644 --- a/src/NHibernate.Test/Async/TransactionTest/TransactionFixture.cs +++ b/src/NHibernate.Test/Async/TransactionTest/TransactionFixture.cs @@ -9,11 +9,10 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using NHibernate.Linq; using NUnit.Framework; +using NHibernate.Linq; namespace NHibernate.Test.TransactionTest { @@ -96,41 +95,30 @@ public async Task WasCommittedOrRolledBackAsync() { using (ITransaction t = s.BeginTransaction()) { - Assert.AreSame(t, s.Transaction); - Assert.IsFalse(s.Transaction.WasCommitted); - Assert.IsFalse(s.Transaction.WasRolledBack); + Assert.AreSame(t, s.GetCurrentTransaction()); + Assert.IsFalse(s.GetCurrentTransaction().WasCommitted); + Assert.IsFalse(s.GetCurrentTransaction().WasRolledBack); await (t.CommitAsync()); - // ISession.Transaction returns a new transaction - // if the previous one completed! - Assert.IsNotNull(s.Transaction); - Assert.IsFalse(t == s.Transaction); + // ISession.GetCurrentTransaction() returns null if the transaction is completed. + Assert.IsNull(s.GetCurrentTransaction()); Assert.IsTrue(t.WasCommitted); Assert.IsFalse(t.WasRolledBack); - Assert.IsFalse(s.Transaction.WasCommitted); - Assert.IsFalse(s.Transaction.WasRolledBack); Assert.IsFalse(t.IsActive); - Assert.IsFalse(s.Transaction.IsActive); } using (ITransaction t = s.BeginTransaction()) { await (t.RollbackAsync()); - // ISession.Transaction returns a new transaction - // if the previous one completed! - Assert.IsNotNull(s.Transaction); - Assert.IsFalse(t == s.Transaction); + // ISession.GetCurrentTransaction() returns null if the transaction is completed. + Assert.IsNull(s.GetCurrentTransaction()); Assert.IsTrue(t.WasRolledBack); Assert.IsFalse(t.WasCommitted); - Assert.IsFalse(s.Transaction.WasCommitted); - Assert.IsFalse(s.Transaction.WasRolledBack); - Assert.IsFalse(t.IsActive); - Assert.IsFalse(s.Transaction.IsActive); } } } @@ -145,7 +133,8 @@ public async Task FlushFromTransactionAppliesToSharingSessionAsync() using (var s1 = builder.Interceptor(new TestInterceptor(1, flushOrder)).OpenSession()) using (var s2 = builder.Interceptor(new TestInterceptor(2, flushOrder)).OpenSession()) - using (var s3 = s1.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)).OpenSession()) + using (var s3 = s1.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)) + .OpenSession()) using (var t = s.BeginTransaction()) { var p1 = new Person(); @@ -234,9 +223,115 @@ public async Task WhenCommittingItemsWillAddThemTo2ndLevelCacheAsync() using (var s = Sfi.WithOptions().Connection(connection).OpenSession()) { CacheablePerson person = null; - Assert.DoesNotThrowAsync(async () => person = await (s.LoadAsync(id)), "Failed loading entity from second level cache."); + Assert.DoesNotThrowAsync( + async () => person = await (s.LoadAsync(id)), + "Failed loading entity from second level cache."); Assert.That(person.NotNullData, Is.EqualTo(notNullData)); } } + + [Test] + public async Task CanCommitFromSessionTransactionAsync() + { + int id; + using (var s = OpenSession()) + { + Assert.That(s.GetCurrentTransaction(), Is.Null); + using (s.BeginTransaction()) + { + var person = new Person(); + await (s.SaveAsync(person)); + id = person.Id; + + await (s.GetCurrentTransaction().CommitAsync()); + } + Assert.That(s.GetCurrentTransaction(), Is.Null); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var person = await (s.GetAsync(id)); + Assert.That(person, Is.Not.Null); + await (t.CommitAsync()); + } + } + + [Test] + public async Task CanRollbackFromSessionTransactionAsync() + { + int id; + using (var s = OpenSession()) + { + Assert.That(s.GetCurrentTransaction(), Is.Null); + using (s.BeginTransaction()) + { + var person = new Person(); + await (s.SaveAsync(person)); + id = person.Id; + + await (s.GetCurrentTransaction().RollbackAsync()); + + // Need to check before leaving the current using, otherwise the rollback could be the result of the + // disposing. + using (var s2 = OpenSession()) + using (var t2 = s2.BeginTransaction()) + { + person = await (s2.GetAsync(id)); + Assert.That(person, Is.Null); + await (t2.CommitAsync()); + } + } + Assert.That(s.GetCurrentTransaction(), Is.Null); + } + } + + [Test, Obsolete] + public async Task CanCommitFromSessionObsoleteTransactionAsync() + { + int id; + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var person = new Person(); + await (s.SaveAsync(person)); + id = person.Id; + + await (s.Transaction.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var person = await (s.GetAsync(id)); + Assert.That(person, Is.Not.Null); + await (t.CommitAsync()); + } + } + + [Test, Obsolete] + public async Task CanRollbackFromSessionObsoleteTransactionAsync() + { + int id; + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var person = new Person(); + await (s.SaveAsync(person)); + id = person.Id; + + await (s.Transaction.RollbackAsync()); + + // Need to check before leaving the current using, otherwise the rollback could be the result of the + // disposing. + using (var s2 = OpenSession()) + using (var t2 = s2.BeginTransaction()) + { + person = await (s2.GetAsync(id)); + Assert.That(person, Is.Null); + await (t2.CommitAsync()); + } + } + } } } diff --git a/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs b/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs index 9403ebaac10..bf9a1f445a1 100644 --- a/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs @@ -11,6 +11,8 @@ using System; using System.Data; using System.Data.Common; +using NHibernate.Cfg; +using NHibernate.Mapping.ByCode; using NHibernate.Transaction; using NUnit.Framework; @@ -21,7 +23,27 @@ namespace NHibernate.Test.TransactionTest [TestFixture] public class TransactionNotificationFixtureAsync : TestCase { - protected override string[] Mappings => Array.Empty(); + public class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + protected override string[] Mappings => null; + + protected override void AddMappings(Configuration configuration) + { + var modelMapper = new ModelMapper(); + modelMapper.Class( + x => + { + x.Id(e => e.Id); + x.Property(e => e.Name); + x.Table(nameof(Entity)); + }); + + configuration.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities()); + } [Test] public async Task CommitAsync() diff --git a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs index fbe948fb8c1..9bf9afe7dd4 100644 --- a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -142,25 +142,21 @@ protected override string MappingsAssembly protected override void OnSetUp() { using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { s.Save(new Simple { Name = "Name1" }); s.Save(new Simple { Name = "Name2" }); - s.Transaction.Commit(); + t.Commit(); } - } } protected override void OnTearDown() { using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { s.Delete("from Simple"); - s.Transaction.Commit(); - } + t.Commit(); } } diff --git a/src/NHibernate.Test/Async/TypesTest/CurrencyTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/CurrencyTypeFixture.cs index 09d7e9b215c..fb97ec4384e 100644 --- a/src/NHibernate.Test/Async/TypesTest/CurrencyTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/CurrencyTypeFixture.cs @@ -43,6 +43,5 @@ public async Task ReadWriteAsync() await (s.FlushAsync()); s.Close(); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/TypesTest/EnumCharTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/EnumCharTypeFixture.cs index efbd66c3730..5cac8dea7d8 100644 --- a/src/NHibernate.Test/Async/TypesTest/EnumCharTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/EnumCharTypeFixture.cs @@ -25,7 +25,6 @@ protected override string TypeName protected override void OnSetUp() { - EnumCharClass basic = new EnumCharClass(); basic.Id = 1; basic.EnumValue = SampleCharEnum.Dimmed; @@ -154,4 +153,4 @@ public async Task CanBeUsedAsDiscriminatorAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/TypesTest/EnumStringTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/EnumStringTypeFixture.cs index 522c7ffa045..255951b0b7a 100644 --- a/src/NHibernate.Test/Async/TypesTest/EnumStringTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/EnumStringTypeFixture.cs @@ -48,7 +48,6 @@ protected override void OnTearDown() s.Close(); } - [Test] public async Task ReadFromLoadAsync() { @@ -99,4 +98,4 @@ public async Task ReadFromQueryAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/TypesTest/GenericEnumStringTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/GenericEnumStringTypeFixture.cs index 16106ed63fc..699ef01850b 100644 --- a/src/NHibernate.Test/Async/TypesTest/GenericEnumStringTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/GenericEnumStringTypeFixture.cs @@ -50,7 +50,6 @@ protected override void OnTearDown() s.Close(); } - [Test] public async Task ReadFromLoadAsync() { diff --git a/src/NHibernate.Test/Async/TypesTest/PersistentEnumTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/PersistentEnumTypeFixture.cs index e02924826b8..aa434f8532f 100644 --- a/src/NHibernate.Test/Async/TypesTest/PersistentEnumTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/PersistentEnumTypeFixture.cs @@ -95,4 +95,4 @@ public async Task CanWriteAndReadUsingBothHeuristicAndExplicitGenericDeclaration } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/TypesTest/UriTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/UriTypeFixture.cs index e71c4ea67ed..23ebe6fcc5d 100644 --- a/src/NHibernate.Test/Async/TypesTest/UriTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/UriTypeFixture.cs @@ -52,7 +52,6 @@ public async Task ReadWriteAsync() } } - [Test(Description = "NH-2887")] public async Task ReadWriteRelativeUriAsync() { @@ -101,6 +100,5 @@ public async Task InsertNullValueAsync() await (s.FlushAsync()); } } - } } diff --git a/src/NHibernate.Test/Async/Unconstrained/UnconstrainedNoLazyTest.cs b/src/NHibernate.Test/Async/Unconstrained/UnconstrainedNoLazyTest.cs index f4b2aac8310..e31c63dad31 100644 --- a/src/NHibernate.Test/Async/Unconstrained/UnconstrainedNoLazyTest.cs +++ b/src/NHibernate.Test/Async/Unconstrained/UnconstrainedNoLazyTest.cs @@ -8,9 +8,6 @@ //------------------------------------------------------------------------------ -using System; -using System.Collections; - using log4net; using NHibernate.Criterion; using NUnit.Framework; @@ -146,30 +143,39 @@ public async Task UnconstrainedAsync() [Test] public async Task ManyToOneUpdateFalseAsync() { - ISession session = OpenSession(); - ITransaction tx = session.BeginTransaction(); - Person p = new Person("gavin"); - p.EmployeeId = "123456"; - p.Unrelated = 10; - await (session.SaveAsync(p)); - await (tx.CommitAsync()); - - session.BeginTransaction(); - p.Employee = new Employee("456123"); - p.Unrelated = 235; // Force update of the object - await (session.SaveAsync(p.Employee)); - await (session.Transaction.CommitAsync()); - session.Close(); - - session = OpenSession(); - session.BeginTransaction(); - p = (Person) await (session.LoadAsync(typeof (Person), "gavin")); - // Should be null, not Employee#456123 - Assert.IsNull(p.Employee); - await (session.DeleteAsync(p)); - await (session.DeleteAsync("from Employee")); - await (session.Transaction.CommitAsync()); - session.Close(); + using (var session = OpenSession()) + { + Person p = new Person("gavin"); + using (var tx = session.BeginTransaction()) + { + p.EmployeeId = "123456"; + p.Unrelated = 10; + await (session.SaveAsync(p)); + await (tx.CommitAsync()); + } + + using (var tx = session.BeginTransaction()) + { + p.Employee = new Employee("456123"); + p.Unrelated = 235; // Force update of the object + await (session.SaveAsync(p.Employee)); + await (tx.CommitAsync()); + } + + session.Close(); + } + + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var p = (Person) await (session.LoadAsync(typeof(Person), "gavin")); + // Should be null, not Employee#456123 + Assert.IsNull(p.Employee); + await (session.DeleteAsync(p)); + await (session.DeleteAsync("from Employee")); + await (tx.CommitAsync()); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/Async/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs b/src/NHibernate.Test/Async/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs index 1b85a8dc996..63fa322ba71 100644 --- a/src/NHibernate.Test/Async/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs +++ b/src/NHibernate.Test/Async/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs @@ -48,7 +48,6 @@ public async Task QueryOverPersonTestAsync() await (s.DeleteAsync(result)); await (t.CommitAsync()); } - } } @@ -69,7 +68,6 @@ public async Task QueryOverCompanyTestAsync() var result = await (s.QueryOver().Where(p => p.Name == "Limited").SingleOrDefaultAsync()); Assert.NotNull(result); } - } } } diff --git a/src/NHibernate.Test/Async/UserCollection/UserCollectionTypeTest.cs b/src/NHibernate.Test/Async/UserCollection/UserCollectionTypeTest.cs index d75f7cc699a..5e2393d93d5 100644 --- a/src/NHibernate.Test/Async/UserCollection/UserCollectionTypeTest.cs +++ b/src/NHibernate.Test/Async/UserCollection/UserCollectionTypeTest.cs @@ -28,7 +28,6 @@ protected override string[] Mappings get { return new string[] {"UserCollection.UserPermissions.hbm.xml"}; } } - [Test] public async Task BasicOperationAsync() { @@ -51,4 +50,4 @@ public async Task BasicOperationAsync() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/UtilityTest/AsyncReaderWriterLockFixture.cs b/src/NHibernate.Test/Async/UtilityTest/AsyncReaderWriterLockFixture.cs new file mode 100644 index 00000000000..b22f20a3cd0 --- /dev/null +++ b/src/NHibernate.Test/Async/UtilityTest/AsyncReaderWriterLockFixture.cs @@ -0,0 +1,215 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Util; +using NUnit.Framework; + +namespace NHibernate.Test.UtilityTest +{ + public partial class AsyncReaderWriterLockFixture + { + + [Test, Explicit] + public async Task TestConcurrentReadWriteAsync() + { + var l = new AsyncReaderWriterLock(); + for (var i = 0; i < 2; i++) + { + var writeReleaser = await (l.WriteLockAsync()); + Assert.That(l.Writing, Is.True); + + var secondWriteSemaphore = new SemaphoreSlim(0); + var secondWriteReleaser = default(AsyncReaderWriterLock.Releaser); + var secondWriteThread = new Thread( + () => + { + secondWriteSemaphore.Wait(); + secondWriteReleaser = l.WriteLock(); + }); + secondWriteThread.Priority = ThreadPriority.Highest; + secondWriteThread.Start(); + await (AssertEqualValueAsync(() => secondWriteThread.ThreadState == ThreadState.WaitSleepJoin, true)); + + var secondReadThreads = new Thread[20]; + var secondReadReleasers = new AsyncReaderWriterLock.Releaser[secondReadThreads.Length]; + var secondReadSemaphore = new SemaphoreSlim(0); + for (var j = 0; j < secondReadReleasers.Length; j++) + { + var index = j; + var thread = new Thread( + () => + { + secondReadSemaphore.Wait(); + secondReadReleasers[index] = l.ReadLock(); + }); + thread.Priority = ThreadPriority.Highest; + secondReadThreads[j] = thread; + thread.Start(); + } + + await (AssertEqualValueAsync(() => secondReadThreads.All(o => o.ThreadState == ThreadState.WaitSleepJoin), true)); + + var firstReadReleaserTasks = new Task[30]; + var firstReadStopSemaphore = new SemaphoreSlim(0); + for (var j = 0; j < firstReadReleaserTasks.Length; j++) + { + firstReadReleaserTasks[j] = Task.Run(async () => + { + var releaser = await (l.ReadLockAsync()); + await (firstReadStopSemaphore.WaitAsync()); + releaser.Dispose(); + }); + } + + await (AssertEqualValueAsync(() => l.ReadersWaiting, firstReadReleaserTasks.Length, waitDelay: 60000)); + + writeReleaser.Dispose(); + secondWriteSemaphore.Release(); + secondReadSemaphore.Release(secondReadThreads.Length); + await (Task.Delay(1000)); + firstReadStopSemaphore.Release(firstReadReleaserTasks.Length); + + await (AssertEqualValueAsync(() => firstReadReleaserTasks.All(o => o.IsCompleted), true)); + Assert.That(l.ReadersWaiting, Is.EqualTo(secondReadThreads.Length)); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + await (AssertEqualValueAsync(() => secondWriteThread.IsAlive, false)); + await (AssertEqualValueAsync(() => secondReadThreads.All(o => o.IsAlive), true)); + + secondWriteReleaser.Dispose(); + await (AssertEqualValueAsync(() => secondReadThreads.All(o => !o.IsAlive), true)); + + Assert.That(l.ReadersWaiting, Is.EqualTo(0)); + Assert.That(l.CurrentReaders, Is.EqualTo(secondReadThreads.Length)); + + foreach (var secondReadReleaser in secondReadReleasers) + { + secondReadReleaser.Dispose(); + } + + Assert.That(l.ReadersWaiting, Is.EqualTo(0)); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + } + } + + [Test] + public async Task TestInvaildExitReadLockUsageAsync() + { + var l = new AsyncReaderWriterLock(); + var readReleaser = await (l.ReadLockAsync()); + var readReleaser2 = await (l.ReadLockAsync()); + + readReleaser.Dispose(); + readReleaser2.Dispose(); + Assert.Throws(() => readReleaser.Dispose()); + Assert.Throws(() => readReleaser2.Dispose()); + } + + [Test] + public void TestOperationAfterDisposeAsync() + { + var l = new AsyncReaderWriterLock(); + l.Dispose(); + + Assert.ThrowsAsync(() => l.ReadLockAsync()); + Assert.ThrowsAsync(() => l.WriteLockAsync()); + } + + [Test] + public async Task TestInvaildExitWriteLockUsageAsync() + { + var l = new AsyncReaderWriterLock(); + var writeReleaser = await (l.WriteLockAsync()); + + writeReleaser.Dispose(); + Assert.Throws(() => writeReleaser.Dispose()); + } + + private static async Task LockAsync( + AsyncReaderWriterLock readWriteLock, + Random random, + LockStatistics lockStatistics, + System.Action checkLockAction, + Func canContinue, CancellationToken cancellationToken = default(CancellationToken)) + { + while (canContinue()) + { + var isRead = random.Next(100) < 80; + var releaser = isRead ? await (readWriteLock.ReadLockAsync()) : await (readWriteLock.WriteLockAsync()); + lock (readWriteLock) + { + if (isRead) + { + lockStatistics.ReadLockCount++; + } + else + { + lockStatistics.WriteLockCount++; + } + + checkLockAction(); + } + + await (Task.Delay(10, cancellationToken)); + + lock (readWriteLock) + { + releaser.Dispose(); + if (isRead) + { + lockStatistics.ReadLockCount--; + } + else + { + lockStatistics.WriteLockCount--; + } + + checkLockAction(); + } + } + } + + private static async Task AssertEqualValueAsync(Func getValueFunc, T value, Task task = null, int waitDelay = 5000, CancellationToken cancellationToken = default(CancellationToken)) + { + var currentTime = 0; + var step = 5; + while (currentTime < waitDelay) + { + if (task != null) + { + task.Wait(step); + } + else + { + await (Task.Delay(step, cancellationToken)); + } + + currentTime += step; + if (getValueFunc().Equals(value)) + { + return; + } + + step *= 2; + } + + Assert.That(getValueFunc(), Is.EqualTo(value)); + } + + private static Task AssertTaskCompletedAsync(Task task, CancellationToken cancellationToken = default(CancellationToken)) + { + return AssertEqualValueAsync(() => task.IsCompleted, true, task, cancellationToken: cancellationToken); + } + } +} diff --git a/src/NHibernate.Test/Async/UtilityTest/LinkedHashMapFixture.cs b/src/NHibernate.Test/Async/UtilityTest/LinkedHashMapFixture.cs index 8d0477681f3..ff1e2011664 100644 --- a/src/NHibernate.Test/Async/UtilityTest/LinkedHashMapFixture.cs +++ b/src/NHibernate.Test/Async/UtilityTest/LinkedHashMapFixture.cs @@ -33,7 +33,6 @@ private static void Fill(IDictionary lhm) lhm.Add(player.Id, player); } - [Test, Explicit] public async Task ShowDiffAsync() { @@ -140,4 +139,3 @@ public async Task PerformanceAsync() } } } - diff --git a/src/NHibernate.Test/Async/UtilityTest/SequencedHashMapFixture.cs b/src/NHibernate.Test/Async/UtilityTest/SequencedHashMapFixture.cs index 077ff253646..1190cffab50 100644 --- a/src/NHibernate.Test/Async/UtilityTest/SequencedHashMapFixture.cs +++ b/src/NHibernate.Test/Async/UtilityTest/SequencedHashMapFixture.cs @@ -52,7 +52,6 @@ public void SetUp() } } - [Test, Explicit] public async Task PerformanceAsync() { @@ -143,7 +142,6 @@ public async Task PerformanceAsync() listItemTicks[runIndex] = DateTime.Now.Ticks - listStart; - list.Clear(); } @@ -181,4 +179,4 @@ public async Task PerformanceAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/VersionTest/Db/MsSQL/ComplexDomainFixture.cs b/src/NHibernate.Test/Async/VersionTest/Db/MsSQL/ComplexDomainFixture.cs index e1a80821d6a..b90f446e278 100644 --- a/src/NHibernate.Test/Async/VersionTest/Db/MsSQL/ComplexDomainFixture.cs +++ b/src/NHibernate.Test/Async/VersionTest/Db/MsSQL/ComplexDomainFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using NHibernate.Dialect; using NUnit.Framework; @@ -62,12 +61,12 @@ public async System.Threading.Tasks.Task NH1685Async() Assert.IsTrue(BinaryTimestamp.Equals(bar.Timestamp, retrievedBar.Timestamp), "Timestamps are different!"); } - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); await (session.DeleteAsync("from Bar")); - await (session.Transaction.CommitAsync()); + await (tran.CommitAsync()); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/BulkManipulation/NativeSQLBulkOperationsWithCache.cs b/src/NHibernate.Test/BulkManipulation/NativeSQLBulkOperationsWithCache.cs index 96ea66124c4..77849de3697 100644 --- a/src/NHibernate.Test/BulkManipulation/NativeSQLBulkOperationsWithCache.cs +++ b/src/NHibernate.Test/BulkManipulation/NativeSQLBulkOperationsWithCache.cs @@ -39,7 +39,6 @@ public void SimpleNativeSQLInsert_DoesNotEvictEntireCacheWhenQuerySpacesAreAdded using (var t = s.BeginTransaction()) { - s.CreateSQLQuery(ssql).ExecuteUpdate(); t.Commit(); diff --git a/src/NHibernate.Test/Bytecode/ActivatorObjectFactoryFixture.cs b/src/NHibernate.Test/Bytecode/ActivatorObjectFactoryFixture.cs index daba1084d9a..f4ecb052d81 100644 --- a/src/NHibernate.Test/Bytecode/ActivatorObjectFactoryFixture.cs +++ b/src/NHibernate.Test/Bytecode/ActivatorObjectFactoryFixture.cs @@ -43,8 +43,6 @@ public void CreateInstanceDefCtor() Assert.That(instance, Is.InstanceOf()); } - - [Test, Obsolete] public void CreateInstanceWithNoPublicCtor() { diff --git a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs index 093cddf61bf..e8df7dca2de 100644 --- a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs @@ -202,6 +202,351 @@ public void MultipleGetReadOnlyCollectionTest() } } + [Test] + public void GetManyReadWriteTest() + { + var persister = Sfi.GetEntityPersister(typeof(ReadWrite).FullName); + Assert.That(persister.Cache.Cache, Is.Not.Null); + Assert.That(persister.Cache.Cache, Is.TypeOf()); + int[] getIds; + int[] loadIds; + + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var items = s.Query().ToList(); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); + tx.Commit(); + } + + // Batch size 3 + var parentTestCases = new List>> + { + // When the cache is empty, GetMany will be called three times. First time in type + // DefaultLoadEventListener, the second time in BatchingEntityLoader and third in ReadWriteCache. + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {1, 2, 3}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 1, 2} // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 1, 2}, + null + ), + // When there are not enough uninitialized entities after the demanded one to fill the batch, + // the nearest before the demanded entity are added. + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3}, + new[] {5, 3, 2}, + new[] {4, 5, 3}, + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3}, + new[] {4, 3, 2}, + new[] {5, 4, 3}, + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds, + 1, + new[] + { + new[] {1, 2, 3}, // 2 gets assembled inside LoadFromSecondLevelCache + new[] {3, 4, 5}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3}, // 4 gets assembled inside LoadFromSecondLevelCache + new[] {3, 2, 1}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2}, // 1 gets assembled inside LoadFromSecondLevelCache + new[] {2, 3, 4}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3}, // 5 and 3 get assembled inside LoadFromSecondLevelCache + new[] {2, 1, 0}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {3, 4, 5}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 4, 5}, // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 4).ToArray(), + 4, + new[] + { + new[] {4, 5, 3}, + new[] {5, 3, 2}, + new[] {3, 4, 5} + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4}, // 4 gets assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ) + }; + + foreach (var tuple in parentTestCases) + { + AssertMultipleCacheCalls(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5); + } + } + + [Test] + public void GetManyReadWriteItemTest() + { + var persister = Sfi.GetEntityPersister(typeof(ReadWriteItem).FullName); + Assert.That(persister.Cache.Cache, Is.Not.Null); + Assert.That(persister.Cache.Cache, Is.TypeOf()); + int[] getIds; + int[] loadIds; + + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var items = s.Query().Take(6).ToList(); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); + tx.Commit(); + } + // Batch size 4 + var parentTestCases = new List>> + { + // When the cache is empty, GetMany will be called three times. First time in type + // DefaultLoadEventListener, the second time in BatchingEntityLoader and third in ReadWriteCache. + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2, 3}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {1, 2, 3, 4}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 1, 2, 3} // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 1, 2, 3}, + null + ), + // When there are not enough uninitialized entities after the demanded one to fill the batch, + // the nearest before the demanded entity are added. + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3, 2}, + new[] {5, 3, 2, 1}, + new[] {4, 5, 3, 2} + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3, 2}, + new[] {4, 3, 2, 1}, + new[] {5, 4, 3, 2} + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2, 3} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds, + 1, + new[] + { + new[] {1, 2, 3, 4}, // 2 and 4 get assembled inside LoadFromSecondLevelCache + new[] {3, 5, 0}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 5, + new[] + { + new[] {5, 4, 3, 2}, // 4 and 2 get assembled inside LoadFromSecondLevelCache + new[] {3, 1, 0}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + new Tuple>( + loadIds, + 0, + new[] + { + new[] {0, 1, 2, 3}, // 1 and 3 get assembled inside LoadFromSecondLevelCache + new[] {2, 4, 5}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + new Tuple>( + loadIds, + 4, + new[] + { + new[] {4, 5, 3, 2}, // 5 and 3 get assembled inside LoadFromSecondLevelCache + new[] {2, 1, 0}, + new[] {0, 2, 4} + }, + new[] {0, 2, 4}, + (i) => i % 2 != 0 + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3}, // Triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {5, 4, 3, 2}, // Triggered by Load method of BatchingEntityLoader type + new[] {0, 5, 4, 3}, // Triggered by PutMany method of ReadWriteCache type + }, + new[] {0, 5, 4, 3}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 5).ToArray(), + 5, + new[] + { + new[] {5, 4, 3, 2}, + new[] {4, 3, 2, 1}, + new[] {2, 3, 4, 5} + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4, 3}, // 4 get assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2, 0}, + new[] {1, 3, 5} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), + }; + + foreach (var tuple in parentTestCases) + { + AssertMultipleCacheCalls(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5); + } + } + [Test] public void MultipleGetReadOnlyTest() { @@ -1338,6 +1683,5 @@ private BatchableCache GetDefaultQueryCache() return cache; } - } } diff --git a/src/NHibernate.Test/CacheTest/CacheFixture.cs b/src/NHibernate.Test/CacheTest/CacheFixture.cs index 2c96ba341dd..5c86f6ced46 100644 --- a/src/NHibernate.Test/CacheTest/CacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/CacheFixture.cs @@ -18,7 +18,7 @@ public void TestSimpleCache() private CacheKey CreateCacheKey(string text) { - return new CacheKey(text, NHibernateUtil.String, "Foo", null); + return new CacheKey(text, NHibernateUtil.String, "Foo", null, null); } public void DoTestCache(ICacheProvider cacheProvider) diff --git a/src/NHibernate.Test/CacheTest/Caches/SerializingCache.cs b/src/NHibernate.Test/CacheTest/Caches/SerializingCache.cs new file mode 100644 index 00000000000..5930a2d443d --- /dev/null +++ b/src/NHibernate.Test/CacheTest/Caches/SerializingCache.cs @@ -0,0 +1,60 @@ +using System.Collections; +using NHibernate.Cache; + +namespace NHibernate.Test.CacheTest.Caches +{ + public partial class SerializingCache : CacheBase + { + private readonly IDictionary _hashtable; + + public SerializingCache(string regionName, IDictionary data) + { + RegionName = regionName; + _hashtable = data; + } + + public override object Get(object key) + { + return _hashtable[key]; + } + + public override void Put(object key, object value) + { + _hashtable[key] = value; + } + + public override void Remove(object key) + { + _hashtable.Remove(key); + } + + public override void Clear() + { + _hashtable.Clear(); + } + + public override void Destroy() + { + } + + public override object Lock(object key) + { + // local cache, no need to actually lock. + return null; + } + + public override void Unlock(object key, object lockValue) + { + // local cache, no need to actually lock. + } + + public override long NextTimestamp() + { + return Timestamper.Next(); + } + + public override int Timeout => Timestamper.OneMs * 60000; + + public override string RegionName { get; } + } +} diff --git a/src/NHibernate.Test/CacheTest/Caches/SerializingCacheProvider.cs b/src/NHibernate.Test/CacheTest/Caches/SerializingCacheProvider.cs new file mode 100644 index 00000000000..bd71a369b23 --- /dev/null +++ b/src/NHibernate.Test/CacheTest/Caches/SerializingCacheProvider.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using NHibernate.Cache; + +namespace NHibernate.Test.CacheTest.Caches +{ + public class SerializingCacheProvider : ICacheProvider + { + #region ICacheProvider Members + + // Since 5.2 + [Obsolete] + ICache ICacheProvider.BuildCache(string regionName, IDictionary properties) + { + return BuildCache(regionName, properties); + } + + public CacheBase BuildCache(string regionName, IDictionary properties) + { + if (!CacheData.TryGetValue(regionName ?? string.Empty, out var data)) + { + data = new Hashtable(); + CacheData.Add(regionName ?? string.Empty, data); + } + + return new SerializingCache(regionName, data); + } + + public long NextTimestamp() + { + return Timestamper.Next(); + } + + public void Start(IDictionary properties) + { + var serializer = new BinaryFormatter(); + foreach (var cache in SerializedCacheData) + { + using (var stream = new MemoryStream(cache.Value)) + { + CacheData.Add(cache.Key, (IDictionary) serializer.Deserialize(stream)); + } + } + } + + public void Stop() + { + var serializer = new BinaryFormatter(); + foreach (var cache in CacheData) + { + using (var stream = new MemoryStream()) + { + serializer.Serialize(stream, cache.Value); + SerializedCacheData[cache.Key] = stream.ToArray(); + } + } + } + + #endregion + + private readonly Dictionary CacheData = new Dictionary(); + private static readonly Dictionary SerializedCacheData = new Dictionary(); + } +} diff --git a/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs b/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs index dd62799eb28..6ed9fd8a584 100644 --- a/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs +++ b/src/NHibernate.Test/CacheTest/QueryKeyFixture.cs @@ -35,13 +35,13 @@ private void QueryKeyFilterDescLikeToCompare(out QueryKey qk, out QueryKey qk1, f.SetParameter("pLike", "so%"); var fk = new FilterKey(f); ISet fks = new HashSet { fk }; - qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); var f1 = new FilterImpl(Sfi.GetFilterDefinition(filterName)); f1.SetParameter("pLike", sameValue ? "so%" : "%ing"); var fk1 = new FilterKey(f1); fks = new HashSet { fk1 }; - qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); } private void QueryKeyFilterDescValueToCompare(out QueryKey qk, out QueryKey qk1, bool sameValue) @@ -52,13 +52,13 @@ private void QueryKeyFilterDescValueToCompare(out QueryKey qk, out QueryKey qk1, f.SetParameter("pDesc", "something").SetParameter("pValue", 10); var fk = new FilterKey(f); ISet fks = new HashSet { fk }; - qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); var f1 = new FilterImpl(Sfi.GetFilterDefinition(filterName)); f1.SetParameter("pDesc", "something").SetParameter("pValue", sameValue ? 10 : 11); var fk1 = new FilterKey(f1); fks = new HashSet { fk1 }; - qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk1 = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); } [Test] @@ -122,7 +122,7 @@ public void ToStringWithFilters() f.SetParameter("pLike", "so%"); var fk = new FilterKey(f); ISet fks = new HashSet { fk }; - var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); Assert.That(qk.ToString(), Does.Contain($"filters: ['{fk}']"), "Like"); filterName = "DescriptionEqualAndValueGT"; @@ -130,7 +130,7 @@ public void ToStringWithFilters() f.SetParameter("pDesc", "something").SetParameter("pValue", 10); fk = new FilterKey(f); fks = new HashSet { fk }; - qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); Assert.That(qk.ToString(), Does.Contain($"filters: ['{fk}']"), "Value"); } @@ -148,7 +148,7 @@ public void ToStringWithMoreFilters() var fvk = new FilterKey(fv); ISet fks = new HashSet { fk, fvk }; - var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null); + var qk = new QueryKey(Sfi, SqlAll, new QueryParameters(), fks, null, null); Assert.That(qk.ToString(), Does.Contain($"filters: ['{fk}', '{fvk}']")); } } diff --git a/src/NHibernate.Test/CacheTest/SerializationFixture.cs b/src/NHibernate.Test/CacheTest/SerializationFixture.cs index eb21721f45b..f62b5e87f5e 100644 --- a/src/NHibernate.Test/CacheTest/SerializationFixture.cs +++ b/src/NHibernate.Test/CacheTest/SerializationFixture.cs @@ -108,7 +108,6 @@ public void TestAnyTypeObjectTypeCacheEntrySerialization() CheckObjectTypeCacheEntry(item, copy); } - [Serializable] public class MyEntity { diff --git a/src/NHibernate.Test/CacheTest/SerializingCacheFixture.cs b/src/NHibernate.Test/CacheTest/SerializingCacheFixture.cs new file mode 100644 index 00000000000..b470dcb200c --- /dev/null +++ b/src/NHibernate.Test/CacheTest/SerializingCacheFixture.cs @@ -0,0 +1,82 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Linq; +using NHibernate.Test.CacheTest.Caches; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Test.CacheTest +{ + [TestFixture] + public class SerializingCacheFixture : TestCase + { + protected override string[] Mappings => new[] + { + "CacheTest.ReadOnly.hbm.xml" + }; + + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override string CacheConcurrencyStrategy => null; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.UseSecondLevelCache, "true"); + configuration.SetProperty(Environment.UseQueryCache, "true"); + configuration.SetProperty(Environment.CacheProvider, typeof(SerializingCacheProvider).AssemblyQualifiedName); + configuration.SetProperty(Environment.SessionFactoryName, "SerializingCacheFactory"); + } + + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var totalItems = 6; + for (var i = 1; i <= totalItems; i++) + { + var parent = new ReadOnly + { + Name = $"Name{i}" + }; + for (var j = 1; j <= totalItems; j++) + { + var child = new ReadOnlyItem + { + Parent = parent + }; + parent.Items.Add(child); + } + s.Save(parent); + } + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.CreateQuery("delete from ReadOnlyItem").ExecuteUpdate(); + s.CreateQuery("delete from ReadOnly").ExecuteUpdate(); + tx.Commit(); + } + } + + [Test] + public void CachedQueryTest() + { + // Put things in cache + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var items = s.Query().WithOptions(o => o.SetCacheable(true)).ToList(); + Assert.That(items, Has.Count.GreaterThan(0)); + tx.Commit(); + } + + RebuildSessionFactory(); + } + } +} diff --git a/src/NHibernate.Test/Cascade/Circle/MultiPathCircleCascadeTest.cs b/src/NHibernate.Test/Cascade/Circle/MultiPathCircleCascadeTest.cs index c32855efefd..06d190ba08c 100644 --- a/src/NHibernate.Test/Cascade/Circle/MultiPathCircleCascadeTest.cs +++ b/src/NHibernate.Test/Cascade/Circle/MultiPathCircleCascadeTest.cs @@ -4,7 +4,6 @@ namespace NHibernate.Test.Cascade.Circle { - /** * The test case uses the following model: * @@ -154,21 +153,21 @@ public void MergeRoute() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Merge(route); - s.Transaction.Commit(); + t.Commit(); } AssertInsertCount(4); AssertUpdateCount(1); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = s.Get(route.RouteId); CheckResults(route, true); - s.Transaction.Commit(); + t.Commit(); } } @@ -180,23 +179,23 @@ public void MergePickupNode() ClearCounts(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Node pickupNode = route.Nodes.First(n => n.Name == "pickupNodeB"); pickupNode = (Node) s.Merge(pickupNode); - s.Transaction.Commit(); + t.Commit(); } AssertInsertCount(4); AssertUpdateCount(0); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = s.Get(route.RouteId); CheckResults(route, false); - s.Transaction.Commit(); + t.Commit(); } } @@ -208,22 +207,22 @@ public void MergeDeliveryNode() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Node deliveryNode = route.Nodes.First(n => n.Name == "deliveryNodeB"); deliveryNode = (Node) s.Merge(deliveryNode); - s.Transaction.Commit(); + t.Commit(); } AssertInsertCount(4); AssertUpdateCount(0); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = s.Get(route.RouteId); CheckResults(route, false); - s.Transaction.Commit(); + t.Commit(); } } @@ -235,21 +234,21 @@ public void MergeTour() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Tour tour = s.Merge(route.Nodes.First().Tour); - s.Transaction.Commit(); + t.Commit(); } AssertInsertCount(4); AssertUpdateCount(0); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = s.Get(route.RouteId); CheckResults(route, false); - s.Transaction.Commit(); + t.Commit(); } } @@ -261,7 +260,7 @@ public void MergeTransport() ClearCounts(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Node node = route.Nodes.First(); Transport transport = null; @@ -273,18 +272,18 @@ public void MergeTransport() transport = (Transport) s.Merge(transport); - s.Transaction.Commit(); + t.Commit(); } AssertInsertCount(4); AssertUpdateCount(0); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route = s.Get(route.RouteId); CheckResults(route, false); - s.Transaction.Commit(); + t.Commit(); } } @@ -292,12 +291,12 @@ private Route GetUpdatedDetachedEntity() { Route route = new Route(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { route.Name = "routeA"; s.Save(route); - s.Transaction.Commit(); + t.Commit(); } route.Name = "new routeA"; route.TransientField = "sfnaouisrbn"; diff --git a/src/NHibernate.Test/Cascade/MultiPathCascadeTest.cs b/src/NHibernate.Test/Cascade/MultiPathCascadeTest.cs index 1ae1955c813..be721f19f67 100644 --- a/src/NHibernate.Test/Cascade/MultiPathCascadeTest.cs +++ b/src/NHibernate.Test/Cascade/MultiPathCascadeTest.cs @@ -24,21 +24,20 @@ public void MultiPathMergeModifiedDetached() A a = new A(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { - a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = s.Merge(a); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); @@ -50,22 +49,22 @@ public void MultiPathMergeModifiedDetachedIntoProxy() // persist a simple A in the database A a = new A(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { A aLoaded = s.Load(a.Id); Assert.That(aLoaded, Is.InstanceOf()); Assert.That(s.Merge(a), Is.SameAs(aLoaded)); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); @@ -78,21 +77,21 @@ public void MultiPathUpdateModifiedDetached() A a = new A(); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } // modify detached entity this.ModifyEntity(a); using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Update(a); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); } @@ -104,20 +103,20 @@ public void MultiPathGetAndModify() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { // retrieve the previously saved instance from the database, and update it a = s.Get(a.Id); this.ModifyEntity(a); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); } @@ -129,20 +128,20 @@ public void MultiPathMergeNonCascadedTransientEntityInCollection() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = s.Merge(a); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); @@ -156,7 +155,7 @@ public void MultiPathMergeNonCascadedTransientEntityInCollection() h.Gs.Add(gNew); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { try { @@ -178,20 +177,20 @@ public void MultiPathMergeNonCascadedTransientEntityInOneToOne() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = (A) s.Merge(a); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); @@ -227,20 +226,20 @@ public void MultiPathMergeNonCascadedTransientEntityInManyToOne() A a = new A(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a.Data = "Anna"; s.Save(a); - s.Transaction.Commit(); + t.Commit(); } // modify detached entity this.ModifyEntity(a); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { a = (A) s.Merge(a); - s.Transaction.Commit(); + t.Commit(); } this.VerifyModifications(a.Id); @@ -306,7 +305,7 @@ private void ModifyEntity(A a) private void VerifyModifications(long aId) { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { // retrieve the A object and check it A a = s.Get(aId); @@ -333,7 +332,7 @@ private void VerifyModifications(long aId) Assert.That(hFromA.Gs.Count, Is.EqualTo(1)); Assert.That(hFromA.Gs.First(), Is.SameAs(gFromA)); - s.Transaction.Commit(); + t.Commit(); } } } diff --git a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/DeleteOneToOneOrphansTest.cs b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/DeleteOneToOneOrphansTest.cs index d4b40bc9af8..a6aa17f6c90 100644 --- a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/DeleteOneToOneOrphansTest.cs +++ b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/DeleteOneToOneOrphansTest.cs @@ -97,7 +97,6 @@ protected override string[] Mappings get { return Array.Empty(); } } - protected override void AddMappings(Cfg.Configuration configuration) { var mapper = new ModelMapper(); diff --git a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/Model.cs b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/Model.cs index 6e51d129423..7e09eac540d 100644 --- a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/Model.cs +++ b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Composite/Model.cs @@ -10,7 +10,6 @@ public class Employee public Employee() { - } } @@ -23,7 +22,6 @@ public class Identifier public Identifier() { - } public Identifier(long companyId, long personId) @@ -32,7 +30,6 @@ public Identifier(long companyId, long personId) this.PersonId = personId; } - public override bool Equals(Object o) { if (this == o) @@ -43,7 +40,6 @@ public override bool Equals(Object o) var t = this.GetType(); var u = o.GetType(); - if (o == null || !t.IsAssignableFrom(u) || !u.IsAssignableFrom(t)) { return false; @@ -53,7 +49,6 @@ public override bool Equals(Object o) return CompanyId.Equals(id.CompanyId) && PersonId.Equals(id.PersonId); - } public override int GetHashCode() @@ -67,7 +62,6 @@ public override int GetHashCode() public EmployeeInfo() { - } public EmployeeInfo(long companyId, long personId) diff --git a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Reversed/Unidirectional/Model.cs b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Reversed/Unidirectional/Model.cs index cf3cedada54..a238a61ba64 100644 --- a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Reversed/Unidirectional/Model.cs +++ b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Fk/Reversed/Unidirectional/Model.cs @@ -8,7 +8,6 @@ public class Employee public Employee() { - } } @@ -18,7 +17,6 @@ public class EmployeeInfo public EmployeeInfo() { - } public EmployeeInfo(long id) diff --git a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Bidirectional/Model.cs b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Bidirectional/Model.cs index fb77ed67ec6..4383c6e8ea9 100644 --- a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Bidirectional/Model.cs +++ b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Bidirectional/Model.cs @@ -8,7 +8,6 @@ public class Employee public Employee() { - } } @@ -19,7 +18,6 @@ public class EmployeeInfo public EmployeeInfo() { - } public EmployeeInfo(Employee employee) diff --git a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/DeleteOneToOneOrphansTest.cs b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/DeleteOneToOneOrphansTest.cs index 0d54551499e..4cf8931e1bf 100644 --- a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/DeleteOneToOneOrphansTest.cs +++ b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/DeleteOneToOneOrphansTest.cs @@ -13,7 +13,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - protected override void OnSetUp() { base.OnSetUp(); diff --git a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/Model.cs b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/Model.cs index 54e04169711..d6c8a610b38 100644 --- a/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/Model.cs +++ b/src/NHibernate.Test/Cascade/OneToOneCascadeDelete/Pk/Unidirectional/Model.cs @@ -8,7 +8,6 @@ public class Employee public Employee() { - } } @@ -18,7 +17,6 @@ public class EmployeeInfo public EmployeeInfo() { - } public EmployeeInfo(long id) diff --git a/src/NHibernate.Test/Cascade/RefreshFixture.cs b/src/NHibernate.Test/Cascade/RefreshFixture.cs index 83601225f88..6e36a0721ea 100644 --- a/src/NHibernate.Test/Cascade/RefreshFixture.cs +++ b/src/NHibernate.Test/Cascade/RefreshFixture.cs @@ -1,7 +1,5 @@ using System; -using System.Collections; using System.Data; -using System.Data.Common; using NUnit.Framework; namespace NHibernate.Test.Cascade @@ -56,7 +54,7 @@ private void UpdateStatuses(ISession session) var cmd = conn.CreateCommand(); cmd.CommandText = "UPDATE T_JOB SET JOB_STATUS = 1"; cmd.CommandType = CommandType.Text; - session.Transaction.Enlist(cmd); + session.GetSessionImplementation().ConnectionManager.EnlistInTransaction(cmd); cmd.ExecuteNonQuery(); } @@ -83,7 +81,6 @@ public void RefreshIgnoringTransientInCollection() { using (ITransaction txn = session.BeginTransaction()) { - var batch = new JobBatch(DateTime.Now); batch.CreateJob().ProcessingInstructions = "Just do it!"; session.Persist(batch); @@ -116,6 +113,5 @@ public void RefreshNotIgnoringTransientByUnsavedValue() session.Close(); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/CfgTest/ConfigurationFixture.cs b/src/NHibernate.Test/CfgTest/ConfigurationFixture.cs index 73a2b9671f4..7fca6448984 100644 --- a/src/NHibernate.Test/CfgTest/ConfigurationFixture.cs +++ b/src/NHibernate.Test/CfgTest/ConfigurationFixture.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.IO; +using System.Linq; using System.Xml; using NHibernate.Cfg; using NHibernate.DomainModel; @@ -424,7 +425,15 @@ public class SampleQueryProvider : DefaultQueryProvider { public SampleQueryProvider(ISessionImplementor session) : base(session) { + } + + protected SampleQueryProvider(ISessionImplementor session, object collection, NhQueryableOptions options) : base(session, collection, options) + { + } + protected override IQueryProvider CreateWithOptions(NhQueryableOptions options) + { + return new SampleQueryProvider(Session, Collection, options); } } @@ -444,6 +453,12 @@ public void NH2890Standard() var query = session.Query(); Assert.IsInstanceOf(typeof(SampleQueryProvider), query.Provider); } + + using (var session = sessionFactory.OpenSession()) + { + var query = session.Query().WithOptions(x => x.SetReadOnly(true)); + Assert.IsInstanceOf(typeof(SampleQueryProvider), query.Provider); + } } } @@ -463,8 +478,6 @@ public void NH2890Xml() Assert.IsInstanceOf(typeof(SampleQueryProvider), query.Provider); } } - } - } } diff --git a/src/NHibernate.Test/CfgTest/ConfigurationSchemaFixture.cs b/src/NHibernate.Test/CfgTest/ConfigurationSchemaFixture.cs index 11c7a28d844..52c3f7b02de 100644 --- a/src/NHibernate.Test/CfgTest/ConfigurationSchemaFixture.cs +++ b/src/NHibernate.Test/CfgTest/ConfigurationSchemaFixture.cs @@ -1,4 +1,3 @@ -using System.Configuration; using NHibernate.Event; using NUnit.Framework; using NHibernate.Cfg; @@ -26,9 +25,7 @@ public void SessionFactoryIsRequiredWhenConfigurationIsNotLoadedFromAppConfig() [Test] public void FromAppConfigTest() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - - IHibernateConfiguration hc = ConfigurationManager.GetSection("hibernate-configuration") as IHibernateConfiguration; + IHibernateConfiguration hc = ConfigurationProvider.Current.GetConfiguration(); Assert.That(hc.ByteCodeProviderType, Is.EqualTo("lcg")); Assert.IsTrue(hc.UseReflectionOptimizer); Assert.AreEqual("NHibernate.Test", hc.SessionFactory.Name); @@ -37,8 +34,6 @@ public void FromAppConfigTest() [Test] public void ByteCodeProvider() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - var xml = @" @@ -54,9 +49,7 @@ public void ByteCodeProvider() [Test] public void IgnoreSystemOutOfAppConfig() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - - IHibernateConfiguration hc = ConfigurationManager.GetSection("hibernate-configuration") as IHibernateConfiguration; + IHibernateConfiguration hc = ConfigurationProvider.Current.GetConfiguration(); string xml = @" @@ -75,8 +68,6 @@ public void IgnoreSystemOutOfAppConfig() [Test] public void ObjectsFactory() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - var xml = @" diff --git a/src/NHibernate.Test/CfgTest/HbmOrderingFixture.cs b/src/NHibernate.Test/CfgTest/HbmOrderingFixture.cs index 1708979ac92..f18cba454d2 100644 --- a/src/NHibernate.Test/CfgTest/HbmOrderingFixture.cs +++ b/src/NHibernate.Test/CfgTest/HbmOrderingFixture.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.CfgTest { - #region Classes used by the mappings defined in the Dynamic Assembly public class A diff --git a/src/NHibernate.Test/CfgTest/Loquacious/ConfigurationFixture.cs b/src/NHibernate.Test/CfgTest/Loquacious/ConfigurationFixture.cs index 327b32faddd..6d613087b1e 100644 --- a/src/NHibernate.Test/CfgTest/Loquacious/ConfigurationFixture.cs +++ b/src/NHibernate.Test/CfgTest/Loquacious/ConfigurationFixture.cs @@ -63,8 +63,8 @@ public void CompleteConfiguration() .WithTimeout(10) .WithMaximumDepthOfOuterJoinFetching(11) .WithHqlToSqlSubstitutions("true 1, false 0, yes 'Y', no 'N'") - .Schema - .Validating(); + .Schema.Validating() + .Schema.ThrowOnSchemaUpdate(true); Assert.That(cfg.Properties[Environment.SessionFactoryName], Is.EqualTo("SomeName")); Assert.That(cfg.Properties[Environment.CacheProvider], Is.EqualTo(typeof(HashtableCacheProvider).AssemblyQualifiedName)); @@ -97,6 +97,7 @@ public void CompleteConfiguration() Assert.That(cfg.Properties[Environment.MaxFetchDepth], Is.EqualTo("11")); Assert.That(cfg.Properties[Environment.QuerySubstitutions], Is.EqualTo("true 1, false 0, yes 'Y', no 'N'")); Assert.That(cfg.Properties[Environment.Hbm2ddlAuto], Is.EqualTo("validate")); + Assert.That(cfg.Properties[Environment.Hbm2ddlThrowOnUpdate], Is.EqualTo("true")); // Keywords import and auto-validation require a valid connection string, disable them before checking // the session factory can be built. @@ -155,6 +156,5 @@ public void NH2890Loquacious() } } } - } } diff --git a/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs b/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs index d54c43d0763..373543bc103 100644 --- a/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs +++ b/src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs @@ -3,7 +3,6 @@ using NHibernate.Bytecode; using NHibernate.Cache; using NHibernate.Cfg; -using NHibernate.Cfg.Loquacious; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Hql.Ast.ANTLR; @@ -11,6 +10,7 @@ using NHibernate.Type; using NUnit.Framework; using NHibernate.Exceptions; +using NHibernate.MultiTenancy; namespace NHibernate.Test.CfgTest.Loquacious { @@ -63,6 +63,8 @@ public void FullConfiguration() db.MaximumDepthOfOuterJoinFetching = 11; db.HqlToSqlSubstitutions = "true 1, false 0, yes 'Y', no 'N'"; db.SchemaAction = SchemaAutoAction.Validate; + db.ThrowOnSchemaUpdate = true; + db.MultiTenancy = MultiTenancyStrategy.Database; }); Assert.That(configure.Properties[Environment.SessionFactoryName], Is.EqualTo("SomeName")); @@ -106,8 +108,10 @@ public void FullConfiguration() Assert.That(configure.Properties[Environment.MaxFetchDepth], Is.EqualTo("11")); Assert.That(configure.Properties[Environment.QuerySubstitutions], Is.EqualTo("true 1, false 0, yes 'Y', no 'N'")); Assert.That(configure.Properties[Environment.Hbm2ddlAuto], Is.EqualTo("validate")); + Assert.That(configure.Properties[Environment.Hbm2ddlThrowOnUpdate], Is.EqualTo("true")); Assert.That(configure.Properties[Environment.LinqToHqlGeneratorsRegistry], Is.EqualTo(typeof(DefaultLinqToHqlGeneratorsRegistry).AssemblyQualifiedName)); - + Assert.That(configure.Properties[Environment.MultiTenancy], Is.EqualTo(nameof(MultiTenancyStrategy.Database))); + // Keywords import and auto-validation require a valid connection string, disable them before checking // the session factory can be built. configure.SetProperty(Environment.Hbm2ddlKeyWords, "none"); diff --git a/src/NHibernate.Test/Classic/LifecycleFixture.cs b/src/NHibernate.Test/Classic/LifecycleFixture.cs index e9f87627c3f..b687e19bcbb 100644 --- a/src/NHibernate.Test/Classic/LifecycleFixture.cs +++ b/src/NHibernate.Test/Classic/LifecycleFixture.cs @@ -98,7 +98,6 @@ public void Merge() Assert.That(Sfi.Statistics.EntityInsertCount, Is.EqualTo(0)); Assert.That(Sfi.Statistics.EntityUpdateCount, Is.EqualTo(0)); - // cleanup using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) @@ -131,4 +130,4 @@ public void Delete() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Classic/ValidatableFixture.cs b/src/NHibernate.Test/Classic/ValidatableFixture.cs index ec657f017d6..a7a399a0de5 100644 --- a/src/NHibernate.Test/Classic/ValidatableFixture.cs +++ b/src/NHibernate.Test/Classic/ValidatableFixture.cs @@ -133,7 +133,6 @@ public void Merge() // Ok } - // cleanup using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) @@ -156,6 +155,5 @@ public void Delete() s.Flush(); } } - } } diff --git a/src/NHibernate.Test/CollectionTest/A.cs b/src/NHibernate.Test/CollectionTest/A.cs index b619008c4e0..c12b34faca1 100644 --- a/src/NHibernate.Test/CollectionTest/A.cs +++ b/src/NHibernate.Test/CollectionTest/A.cs @@ -28,6 +28,5 @@ public IList Items get { return _items; } set { _items = value; } } - } } diff --git a/src/NHibernate.Test/CollectionTest/PersistentCollectionsFixture.cs b/src/NHibernate.Test/CollectionTest/PersistentCollectionsFixture.cs index 5a7f9679773..03439d8a47a 100644 --- a/src/NHibernate.Test/CollectionTest/PersistentCollectionsFixture.cs +++ b/src/NHibernate.Test/CollectionTest/PersistentCollectionsFixture.cs @@ -36,5 +36,22 @@ public void AddRangeWorksCorrectly() Assert.That(items, Is.EqualTo(new[] {"A", "B", "C", "D", "E"})); } + + [Test] + public void SelectManyWorksCorrectlyWithIReadOnlyCollection() + { + var bags = new IReadOnlyCollection[] + { + new List {"A"}, + new PersistentGenericBag(null, new[] {"B"}), + new PersistentIdentifierBag(null, new[] {"C"}), + (IReadOnlyList)new PersistentGenericList(null, new[] {"D"}), + new PersistentGenericSet(null, new HashSet {"E"}) + }; + + var items = bags.SelectMany(b => b).ToArray(); + + Assert.That(items, Is.EqualTo(new[] {"A", "B", "C", "D", "E"})); + } } } diff --git a/src/NHibernate.Test/CompositeId/ClassWithCompositeIdFixture.cs b/src/NHibernate.Test/CompositeId/ClassWithCompositeIdFixture.cs index 333f0c375b6..56bf7713b79 100644 --- a/src/NHibernate.Test/CompositeId/ClassWithCompositeIdFixture.cs +++ b/src/NHibernate.Test/CompositeId/ClassWithCompositeIdFixture.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Linq; using NHibernate.Criterion; using NUnit.Framework; @@ -26,11 +27,6 @@ protected override string[] Mappings get { return new string[] {"CompositeId.ClassWithCompositeId.hbm.xml"}; } } - protected override bool AppliesTo(Dialect.Dialect dialect) - { - return !(dialect is Dialect.FirebirdDialect); // Firebird has no CommandTimeout, and locks up during the tear-down of this fixture - } - protected override void OnSetUp() { id = new Id("stringKey", 3, firstDateTime); @@ -40,13 +36,14 @@ protected override void OnSetUp() protected override void OnTearDown() { using (ISession s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) { s.Delete("from ClassWithCompositeId"); s.Flush(); + t.Commit(); } } - /// /// Test the basic CRUD operations for a class with a Composite Identifier /// @@ -228,5 +225,214 @@ public void Hql() Assert.AreEqual(1, results.Count); } } + + [Test] + public void HqlInClause() + { + var id1 = id; + var id2 = secondId; + var id3 = new Id(id.KeyString, id.GetKeyShort(), id2.KeyDateTime); + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(new ClassWithCompositeId(id1) {OneProperty = 5}); + s.Save(new ClassWithCompositeId(id2) {OneProperty = 10}); + s.Save(new ClassWithCompositeId(id3)); + + t.Commit(); + } + + using (var s = OpenSession()) + { + var results1 = s.CreateQuery("from ClassWithCompositeId x where x.Id in (:id1, :id2)") + .SetParameter("id1", id1) + .SetParameter("id2", id2) + .List(); + var results2 = s.CreateQuery("from ClassWithCompositeId x where x.Id in (:id1)") + .SetParameter("id1", id1) + .List(); + var results3 = s.CreateQuery("from ClassWithCompositeId x where x.Id not in (:id1)") + .SetParameter("id1", id1) + .List(); + var results4 = s.CreateQuery("from ClassWithCompositeId x where x.Id not in (:id1, :id2)") + .SetParameter("id1", id1) + .SetParameter("id2", id2) + .List(); + + Assert.Multiple( + () => + { + Assert.That(results1.Count, Is.EqualTo(2), "in multiple ids"); + Assert.That(results1.Select(x => x.Id), Is.EquivalentTo(new[] {id1, id2}), "in multiple ids"); + Assert.That(results2.Count, Is.EqualTo(1), "in single id"); + Assert.That(results2.Single().Id, Is.EqualTo(id1), "in single id"); + Assert.That(results3.Count, Is.EqualTo(2), "not in single id"); + Assert.That(results3.Select(x => x.Id), Is.EquivalentTo(new[] {id2, id3}), "not in single id"); + Assert.That(results4.Count, Is.EqualTo(1), "not in multiple ids"); + Assert.That(results4.Single().Id, Is.EqualTo(id3), "not in multiple ids"); + }); + } + } + + [Test] + public void QueryOverInClauseSubquery() + { + if (!TestDialect.SupportsRowValueConstructorSyntax) + { + Assert.Ignore(); + } + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(new ClassWithCompositeId(id) {OneProperty = 5}); + s.Save(new ClassWithCompositeId(secondId) {OneProperty = 10}); + s.Save(new ClassWithCompositeId(new Id(id.KeyString, id.GetKeyShort(), secondId.KeyDateTime))); + + t.Commit(); + } + + using (var s = OpenSession()) + { + var results = s.QueryOver().WithSubquery.WhereProperty(p => p.Id).In(QueryOver.Of().Where(p => p.Id.KeyString == id.KeyString).Select(p => p.Id)).List(); + Assert.That(results.Count, Is.EqualTo(2)); + } + } + + [Test] + public void HqlInClauseSubquery() + { + if (!TestDialect.SupportsRowValueConstructorSyntax) + Assert.Ignore(); + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(new ClassWithCompositeId(id) {OneProperty = 5}); + s.Save(new ClassWithCompositeId(secondId) {OneProperty = 10}); + s.Save(new ClassWithCompositeId(new Id(id.KeyString, id.GetKeyShort(), secondId.KeyDateTime))); + + t.Commit(); + } + + using (var s = OpenSession()) + { + var results = s.CreateQuery("from ClassWithCompositeId x where x.Id in (select s.Id from ClassWithCompositeId s where s.Id.KeyString = :keyString)") + .SetParameter("keyString", id.KeyString).List(); + Assert.That(results.Count, Is.EqualTo(2)); + } + } + + //GH-1376 + [Test] + public void HqlInClauseSubquery_ForEntity() + { + if (!TestDialect.SupportsRowValueConstructorSyntax) + Assert.Ignore(); + + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(new ClassWithCompositeId(id) {OneProperty = 5}); + s.Save(new ClassWithCompositeId(secondId) {OneProperty = 10}); + s.Save(new ClassWithCompositeId(new Id(id.KeyString, id.GetKeyShort(), secondId.KeyDateTime))); + + t.Commit(); + } + + using (var s = OpenSession()) + { + var results = s.CreateQuery("from ClassWithCompositeId x where x in (select s from ClassWithCompositeId s where s.Id.KeyString = :keyString)") + .SetParameter("keyString", id.KeyString).List(); + Assert.That(results.Count, Is.EqualTo(2)); + } + } + + //NH-2926 (GH-1103) + [Test] + public void QueryOverOrderByAndWhereWithIdProjectionDoesntThrow() + { + // insert the new objects + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + ClassWithCompositeId theClass = new ClassWithCompositeId(id); + theClass.OneProperty = 5; + + ClassWithCompositeId theSecondClass = new ClassWithCompositeId(secondId); + theSecondClass.OneProperty = 10; + + s.Save(theClass); + s.Save(theSecondClass); + + t.Commit(); + } + + using (ISession s = OpenSession()) + { + var results = s.QueryOver() + .Select(Projections.Id()) + .Where(Restrictions.Eq(Projections.Id(), id)) + .OrderBy(Projections.Id()).Desc.List(); + Assert.That(results.Count, Is.EqualTo(1)); + } + } + + [Test] + public void CriteriaGroupProjection() + { + using (ISession s = OpenSession()) + { + s.CreateCriteria() + .SetProjection(Projections.GroupProperty(Projections.Id())) + .Add(Restrictions.Eq(Projections.Id(), id)) + .List(); + } + } + + [Test] + public void QueryOverInClause() + { + // insert the new objects + var id1 = id; + var id2 = secondId; + var id3 = new Id(id1.KeyString, id1.GetKeyShort(), id2.KeyDateTime); + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(new ClassWithCompositeId(id1) {OneProperty = 5}); + s.Save(new ClassWithCompositeId(id2) {OneProperty = 10}); + s.Save(new ClassWithCompositeId(id3)); + + t.Commit(); + } + + using (var s = OpenSession()) + { + var results1 = s.QueryOver().WhereRestrictionOn(p => p.Id).IsIn(new[] {id1, id2}).List(); + var results2 = s.QueryOver().WhereRestrictionOn(p => p.Id).IsIn(new[] {id1}).List(); + var results3 = s.QueryOver().WhereRestrictionOn(p => p.Id).Not.IsIn(new[] {id1, id2}).List(); + var results4 = s.QueryOver().WhereRestrictionOn(p => p.Id).Not.IsIn(new[] {id1}).List(); + + Assert.Multiple( + () => + { + Assert.That(results1.Count, Is.EqualTo(2), "in multiple ids"); + Assert.That(results1.Select(r => r.Id), Is.EquivalentTo(new[] {id1, id2}), "in multiple ids"); + Assert.That(results2.Count, Is.EqualTo(1), "in single id"); + Assert.That(results2.Select(r => r.Id), Is.EquivalentTo(new[] {id1}), "in single id"); + Assert.That(results3.Count, Is.EqualTo(1), "not in multiple ids"); + Assert.That(results3.Select(r => r.Id), Is.EquivalentTo(new[] {id3}), "not in multiple ids"); + Assert.That(results4.Count, Is.EqualTo(2), "not in single id"); + Assert.That(results4.Select(r => r.Id), Is.EquivalentTo(new[] {id2, id3}), "not in single id"); + }); + } + } } } diff --git a/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs b/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs index aee299e06eb..e63fed37cb1 100644 --- a/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs +++ b/src/NHibernate.Test/CompositeId/CompositeIdFixture.cs @@ -127,7 +127,6 @@ public void CompositeIds() t.Commit(); } - using (s = OpenSession()) { t = s.BeginTransaction(); @@ -287,4 +286,4 @@ public void Query() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/CompositeId/Id.cs b/src/NHibernate.Test/CompositeId/Id.cs index 674bcb659d6..b3fa40d7f2d 100644 --- a/src/NHibernate.Test/CompositeId/Id.cs +++ b/src/NHibernate.Test/CompositeId/Id.cs @@ -28,15 +28,17 @@ public string KeyString set { _keyString = value; } } + //KeyShort is commented to test access attributes; So expose it as method instead + public short GetKeyShort() => _keyShort; // public short KeyShort { -// get { return _keyShort;} -// set {_keyShort = value;} +// get { return _keyShort; } +// set {_keyShort = value; } // } public DateTime KeyDateTime { get { return _keyDateTime; } -// set {_keyDateTime = value;} +// set {_keyDateTime = value; } } public override int GetHashCode() @@ -55,4 +57,4 @@ public override bool Equals(object obj) return false; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs b/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs index f44bc304e72..5933d5bb65d 100644 --- a/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs +++ b/src/NHibernate.Test/ConnectionStringTest/NamedConnectionStringFixture.cs @@ -35,8 +35,6 @@ public void ConnectionStringInSettingsOverrideNamedConnectionSTring() [Test] public void CanGetNamedConnectionStringFromConfiguration() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - Dictionary settings = new Dictionary(); settings.Add(Environment.ConnectionStringName, "DummyConnectionString"); MockConnectionProvider cp = new MockConnectionProvider(); @@ -48,7 +46,6 @@ public void CanGetNamedConnectionStringFromConfiguration() public partial class MockConnectionProvider : ConnectionProvider { - public string PublicConnectionString { get @@ -56,12 +53,12 @@ public string PublicConnectionString return base.ConnectionString; } } - + /// /// Get an open . /// /// An open . - public override DbConnection GetConnection() + public override DbConnection GetConnection(string connectionString) { throw new NotImplementedException(); } @@ -71,5 +68,4 @@ protected override void ConfigureDriver(IDictionary settings) return; } } - } diff --git a/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs b/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs index efb6d5488fc..bc0df5c5a65 100644 --- a/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs +++ b/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs @@ -218,30 +218,31 @@ public void ConnectionMaintanenceDuringFlush() { Prepare(); ISession s = GetSessionUnderTest(); - s.BeginTransaction(); - - IList entities = new List(); - for (int i = 0; i < 10; i++) + using (var t = s.BeginTransaction()) { - Other other = new Other("other-" + i); - Silly silly = new Silly("silly-" + i, other); - entities.Add(silly); - s.Save(silly); + IList entities = new List(); + for (int i = 0; i < 10; i++) + { + Other other = new Other("other-" + i); + Silly silly = new Silly("silly-" + i, other); + entities.Add(silly); + s.Save(silly); + } + s.Flush(); + + foreach (Silly silly in entities) + { + silly.Name = "new-" + silly.Name; + silly.Other.Name = "new-" + silly.Other.Name; + } + // long initialCount = sessions.Statistics.getConnectCount(); + s.Flush(); + //Assert.AreEqual(initialCount + 1, sessions.Statistics.getConnectCount(), "connection not maintained through Flush"); + + s.Delete("from Silly"); + s.Delete("from Other"); + t.Commit(); } - s.Flush(); - - foreach (Silly silly in entities) - { - silly.Name = "new-" + silly.Name; - silly.Other.Name = "new-" + silly.Other.Name; - } -// long initialCount = sessions.Statistics.getConnectCount(); - s.Flush(); -// Assert.AreEqual(initialCount + 1, sessions.Statistics.getConnectCount(), "connection not maintained through Flush"); - - s.Delete("from Silly"); - s.Delete("from Other"); - s.Transaction.Commit(); Release(s); Done(); } diff --git a/src/NHibernate.Test/ConnectionTest/ThreadLocalCurrentSessionTest.cs b/src/NHibernate.Test/ConnectionTest/ThreadLocalCurrentSessionTest.cs index 8452c2dd1b8..afd05fab790 100644 --- a/src/NHibernate.Test/ConnectionTest/ThreadLocalCurrentSessionTest.cs +++ b/src/NHibernate.Test/ConnectionTest/ThreadLocalCurrentSessionTest.cs @@ -25,7 +25,7 @@ protected override void Configure(Configuration configuration) protected override void Release(ISession session) { long initialCount = Sfi.Statistics.SessionCloseCount; - session.Transaction.Commit(); + session.GetCurrentTransaction()?.Commit(); long subsequentCount = Sfi.Statistics.SessionCloseCount; Assert.AreEqual(initialCount + 1, subsequentCount, "Session still open after commit"); // also make sure it was cleaned up from the internal ThreadLocal... @@ -37,8 +37,8 @@ protected override void Release(ISession session) public void ContextCleanup() { ISession session = Sfi.OpenSession(); - session.BeginTransaction(); - session.Transaction.Commit(); + using(var t = session.BeginTransaction()) + t.Commit(); Assert.IsFalse(session.IsOpen, "session open after txn completion"); Assert.IsFalse(TestableThreadLocalContext.IsSessionBound(session), "session still bound after txn completion"); diff --git a/src/NHibernate.Test/Criteria/AddNumberProjection.cs b/src/NHibernate.Test/Criteria/AddNumberProjection.cs index 1910585ec36..b40a931657c 100644 --- a/src/NHibernate.Test/Criteria/AddNumberProjection.cs +++ b/src/NHibernate.Test/Criteria/AddNumberProjection.cs @@ -35,7 +35,7 @@ public override SqlString ToSqlString(ICriteria criteria, int position, ICriteri .Add(" + ") .Add(criteriaQuery.NewQueryParameter(typedValue).Single()) .Add(") as ") - .Add(GetColumnAliases(0, criteria, criteriaQuery)[0]) + .Add(GetColumnAlias(0)) .ToSqlString(); } diff --git a/src/NHibernate.Test/Criteria/Animal.cs b/src/NHibernate.Test/Criteria/Animal.cs index 00739545bd4..efb8abb7b4c 100644 --- a/src/NHibernate.Test/Criteria/Animal.cs +++ b/src/NHibernate.Test/Criteria/Animal.cs @@ -61,9 +61,9 @@ public virtual string SerialNumber set { serialNumber = value; } } - private bool howHappyIsHe; + private string howHappyIsHe; - public virtual bool HowHappyIsHe + public virtual string HowHappyIsHe { get { return howHappyIsHe; } set { howHappyIsHe = value; } @@ -85,6 +85,8 @@ public virtual void AddOffspring(Animal offspring) { this.Offspring = new HashSet(); } + + offspring.Mother = this; this.Offspring.Add(offspring); } } diff --git a/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs b/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs index 367a89a3f6e..63a22533aa1 100644 --- a/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs +++ b/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs @@ -1,8 +1,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Dialect; using NHibernate.Criterion; +using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; using NHibernate.Type; @@ -300,122 +302,56 @@ public void SubselectWithComponent() t.Commit(); } - if (TestDialect.SupportsOperatorAll) + //Note: It might require separate test dialect flag like SupportsRowValueConstructorWithOperatorAll + if (TestDialect.SupportsOperatorAll && TestDialect.SupportsRowValueConstructorSyntax) { using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) { - try - { - session.CreateCriteria() + session.CreateCriteria() .Add(Subqueries.PropertyEqAll("CityState", dc)) .List(); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (QueryException) - { - // expected - } - t.Rollback(); } - } - if (TestDialect.SupportsOperatorAll) - { using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) { - try - { - session.CreateCriteria() + session.CreateCriteria() .Add(Property.ForName("CityState").EqAll(dc)) .List(); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (QueryException) - { - // expected - } - finally - { - t.Rollback(); - } } } - using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) + if (TestDialect.SupportsRowValueConstructorSyntax) { - try + using (ISession session = OpenSession()) { session.CreateCriteria() - .Add(Subqueries.In(odessaWa, dc)) - .List(); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (NHibernate.Exceptions.GenericADOException) - { - // expected - } - finally - { - t.Rollback(); + .Add(Subqueries.In(odessaWa, dc)) + .List(); } - } - - using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) - { - DetachedCriteria dc2 = DetachedCriteria.For("st1") - .Add(Property.ForName("st1.CityState").EqProperty("st2.CityState")) - .SetProjection(Property.ForName("CityState")); - - try + + using (ISession session = OpenSession()) { + DetachedCriteria dc2 = DetachedCriteria.For("st1") + .Add(Property.ForName("st1.CityState").EqProperty("st2.CityState")) + .SetProjection(Property.ForName("CityState")); session.CreateCriteria("st2") - .Add( Subqueries.Eq(odessaWa, dc2)) - .List(); - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (NHibernate.Exceptions.GenericADOException) - { - // expected - } - finally - { - t.Rollback(); + .Add( Subqueries.Eq(odessaWa, dc2)) + .List(); } - } - - using (ISession session = OpenSession()) - using (ITransaction t = session.BeginTransaction()) - { - DetachedCriteria dc3 = DetachedCriteria.For("st") - .CreateCriteria("Enrolments") - .CreateCriteria("Course") - .Add(Property.ForName("Description").Eq("Hibernate Training")) - .SetProjection(Property.ForName("st.CityState")); - try + + using (ISession session = OpenSession()) { + DetachedCriteria dc3 = DetachedCriteria.For("st") + .CreateCriteria("Enrolments") + .CreateCriteria("Course") + .Add(Property.ForName("Description").Eq("Hibernate Training")) + .SetProjection(Property.ForName("st.CityState")); session.CreateCriteria("e") - .Add(Subqueries.Eq(odessaWa, dc3)) - .List(); - - Assert.Fail("should have failed because cannot compare subquery results with multiple columns"); - } - catch (NHibernate.Exceptions.GenericADOException) - { - // expected - } - finally - { - t.Rollback(); + .Add(Subqueries.Eq(odessaWa, dc3)) + .List(); } } - + using (ISession session = OpenSession()) using (ITransaction t = session.BeginTransaction()) { @@ -455,7 +391,6 @@ public void CloningCriteria_AddCount_RemoveOrdering() .AddOrder(Order.Asc("a.bodyWeight")); ICriteria cloned = CriteriaTransformer.TransformToRowCount(c); - cloned.List(); t.Rollback(); s.Close(); @@ -828,7 +763,6 @@ public void NH_1155_ShouldNotLoadAllChildrenInPagedSubSelect() course.Description = "Hibernate Training"; s.Save(course); - Student gavin = new Student(); gavin.Name = "Gavin King"; gavin.StudentNumber = 667; @@ -839,7 +773,6 @@ public void NH_1155_ShouldNotLoadAllChildrenInPagedSubSelect() ayende.StudentNumber = 1337; s.Save(ayende); - Student xam = new Student(); xam.Name = "Max Rydahl Andersen"; xam.StudentNumber = 101; @@ -884,7 +817,7 @@ public void NH_1155_ShouldNotLoadAllChildrenInPagedSubSelect() Enrolment key = new Enrolment(); key.CourseCode = "HIB"; - key.StudentNumber = 101;// xam + key.StudentNumber = 101; // xam //since we didn't load xam's entrollments before (skipped by orderring) //it should not be already loaded Enrolment shouldNotBeLoaded = (Enrolment)s.Load(typeof(Enrolment), key); @@ -967,7 +900,6 @@ public void ProjectionsTest() Assert.AreEqual(101L, result[2]); Assert.AreEqual(384.0D, (Double)result[3], 0.01D); - IList resultWithMaps = s.CreateCriteria(typeof(Enrolment)) .SetProjection(Projections.Distinct(Projections.ProjectionList() .Add(Projections.Property("StudentNumber"), "stNumber") @@ -998,7 +930,6 @@ public void ProjectionsTest() Assert.AreEqual(101L, m1["stNumber"]); Assert.AreEqual(667L, m0["stNumber"]); - IList resultWithAliasedBean = s.CreateCriteria(typeof(Enrolment)) .CreateAlias("Student", "st") .CreateAlias("Course", "co") @@ -1055,7 +986,6 @@ public void ProjectionsTest() ProjectionList pp1 = Projections.ProjectionList().Add(Projections.RowCountInt64()); - object r = s.CreateCriteria(typeof(Enrolment)) .SetProjection(pp1) .UniqueResult(); @@ -1091,6 +1021,176 @@ public void ProjectionsTest() s.Close(); } + [Test] + public void TestSQLProjectionWithAliases() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + s.Save(course); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + s.Save(gavin); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + s.Save(xam); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + s.Save(enrolment); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + s.Save(enrolment); + t.Commit(); + } + + using (var s = OpenSession()) + { + Student studentSubquery = null; + var subquery = QueryOver.Of(() => studentSubquery) + .And( + Expression.Sql("{e}.studentId = 667 and {studentSubquery}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = s.CreateCriteria(typeof(Student)) + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResult(); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + s.Query().Delete(); + s.Query().Delete(); + s.Query().Delete(); + s.GetCurrentTransaction().Commit(); + } + } + + [Test] + public void TestSQLProjectionWithDuplicateAliases() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + s.Save(course); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + s.Save(gavin); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + s.Save(xam); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + s.Save(enrolment); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + s.Save(enrolment); + t.Commit(); + } + + using (var s = OpenSession()) + { + Student student = null; + var subquery = QueryOver.Of(() => student) + .And( + Expression.Sql("{e}.studentId = 667 and {student}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = s.CreateCriteria(typeof(Student), "student") + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResult(); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + s.Query().Delete(); + s.Query().Delete(); + s.Query().Delete(); + s.GetCurrentTransaction().Commit(); + } + } + [Test] public void CloningProjectionsTest() { @@ -1159,7 +1259,6 @@ public void CloningProjectionsTest() Assert.AreEqual(101L, result[2]); Assert.AreEqual(384.0D, (Double)result[3], 0.01D); - ICriteria criteriaToClone2 = s.CreateCriteria(typeof(Enrolment)) .SetProjection(Projections.Distinct(Projections.ProjectionList() .Add(Projections.Property("StudentNumber"), "stNumber") @@ -1192,7 +1291,6 @@ public void CloningProjectionsTest() Assert.AreEqual(101L, m1["stNumber"]); Assert.AreEqual(667L, m0["stNumber"]); - ICriteria criteriaToClone3 = s.CreateCriteria(typeof(Enrolment)) .CreateAlias("Student", "st") .CreateAlias("Course", "co") @@ -1943,7 +2041,6 @@ public void CloningProjectionsUsingProperty() Assert.AreEqual(101L, result[2]); Assert.AreEqual(384.0D, (double)result[3], 0.01D); - CriteriaTransformer.Clone( s.CreateCriteria(typeof(Enrolment)) .Add(Property.ForName("StudentNumber").Gt(665L)) @@ -1987,7 +2084,6 @@ public void CloningProjectionsUsingProperty() Assert.AreEqual(101L, m1["stNumber"]); Assert.AreEqual(667L, m0["stNumber"]); - IList resultWithAliasedBean = CriteriaTransformer.Clone(s.CreateCriteria(typeof(Enrolment)) .CreateAlias("Student", "st") .CreateAlias("Course", "co") @@ -2087,6 +2183,64 @@ public void RestrictionOnSubclassCollection() s.Close(); } + //NH-2239 (GH-1075) - Wrong OrderBy in generated SQL + [Test] + public void OrderByAndOrderedCollection() + { + var reptile1 = new Reptile { SerialNumber = "9" }; + var reptile2 = new Reptile { SerialNumber = "8" }; + var reptile3 = new Reptile { SerialNumber = "7" }; + var reptile4 = new Reptile { SerialNumber = "6" }; + var reptile5 = new Reptile { SerialNumber = "5" }; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(reptile5); + s.Save(reptile1); + s.Save(reptile2); + s.Save(reptile3); + s.Save(reptile4); + + reptile1.AddOffspring(reptile4); + reptile1.AddOffspring(reptile2); + reptile4.Father = reptile3; + reptile2.Father = reptile4; + reptile1.Father = reptile5; + reptile3.AddOffspring(reptile1); + + t.Commit(); + } + + using(var s = OpenSession()) + { + var list = s.CreateCriteria(typeof(Reptile)) + .Fetch(SelectMode.Fetch, "offspring") + .AddOrder(Order.Asc("serialNumber")) + .SetResultTransformer(Transformers.DistinctRootEntity) + .List(); + + var expectedList = list.OrderBy(x => x.SerialNumber).ToList(); + + Assert.That(list.Count, Is.EqualTo(5)); + CollectionAssert.AreEqual(expectedList, list); + + var mother = expectedList.Last(); + Assert.That(NHibernateUtil.IsInitialized(mother.Offspring), Is.True); + + var expectedAssociationList = mother.Offspring.OrderBy(r => r.Father.Id).ToList(); + Assert.That(expectedAssociationList.Count, Is.EqualTo(2)); + CollectionAssert.AreEqual(expectedAssociationList, mother.Offspring); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Reptile"); + t.Commit(); + } + } + [Test] public void ClassProperty() { @@ -2443,6 +2597,22 @@ public void SubcriteriaJoinTypes() session.Close(); } + public class NotMappedEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + [Test] + public void CriteriaOnNotMappedEntity() + { + using (ISession session = OpenSession()) + { + Assert.Throws( + () => session.CreateCriteria(typeof(NotMappedEntity)).List()); + } + } + [Test] public void TypeMismatch() { @@ -2535,7 +2705,6 @@ public void SameColumnAndAliasNames() .Add(Projections.Property("StudentNumber"), "StudentNumber") .Add(Projections.Property("Name"), "Name")); - ISession session = OpenSession(); ITransaction t = session.BeginTransaction(); @@ -2572,7 +2741,6 @@ public void SameColumnAndAliasNamesResultTransformer() .Add(Property.ForName("Name").Eq("Gavin King")) .AddOrder(Order.Asc("StudentNumber")); - ISession session = OpenSession(); ITransaction t = session.BeginTransaction(); @@ -2704,7 +2872,6 @@ public void TransformToRowCountTest() ICriteria subCriterium = crit.CreateCriteria("PreferredCourse"); subCriterium.Add(Property.ForName("CourseCode").Eq("PREFFERED_CODE")); - ICriteria countCriteria = CriteriaTransformer.TransformToRowCount(crit); countCriteria.List(); @@ -2740,7 +2907,6 @@ public void OrderProjectionAliasedTest() using (ISession session = OpenSession()) using (ITransaction t = session.BeginTransaction()) { - Course courseA = new Course(); courseA.CourseCode = "HIB-A"; courseA.Description = "Hibernate Training A"; diff --git a/src/NHibernate.Test/Criteria/DetachedCriteriaSerializable.cs b/src/NHibernate.Test/Criteria/DetachedCriteriaSerializable.cs index d0a8f39e65d..2ecad73a656 100644 --- a/src/NHibernate.Test/Criteria/DetachedCriteriaSerializable.cs +++ b/src/NHibernate.Test/Criteria/DetachedCriteriaSerializable.cs @@ -391,13 +391,16 @@ public void ExecutableCriteria() SerializeAndList(dc); // Subquery - dc = DetachedCriteria.For(typeof(Student)) - .Add(Property.ForName("StudentNumber").Eq(232L)) - .SetProjection(Property.ForName("Name")); + if (TestDialect.SupportsOperatorAll) + { + dc = DetachedCriteria.For(typeof(Student)) + .Add(Property.ForName("StudentNumber").Eq(232L)) + .SetProjection(Property.ForName("Name")); - DetachedCriteria dcs = DetachedCriteria.For(typeof(Student)) - .Add(Subqueries.PropertyEqAll("Name", dc)); - SerializeAndList(dc); + DetachedCriteria dcs = DetachedCriteria.For(typeof(Student)) + .Add(Subqueries.PropertyEqAll("Name", dc)); + SerializeAndList(dcs); + } // SQLCriterion dc = DetachedCriteria.For(typeof(Student)) diff --git a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs index 0ecebb750c6..f2c6360fee3 100644 --- a/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs +++ b/src/NHibernate.Test/Criteria/EntityJoinCriteriaTest.cs @@ -197,7 +197,6 @@ public void MixOfJoinsForAssociatedAndNotAssociatedEntities() using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - EntityComplex root = null; EntityComplex ejLevel1 = null; EntitySimpleChild customChildForEjLevel1 = null; @@ -282,7 +281,6 @@ public void NullLeftEntityJoinWithEntityProjection() [Test] public void EntityJoinForCustomEntityName() { - using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { @@ -303,7 +301,6 @@ public void EntityJoinForCustomEntityName() [Test] public void EntityJoinForCustomEntityName_Expression() { - using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { @@ -404,7 +401,6 @@ protected override HbmMapping GetMappings() m.Inverse(true); }, a => a.OneToMany()); - }); mapper.Class( @@ -454,7 +450,6 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Composite1Key1); rc.Property(e => e.Composite1Key2); rc.Property(e => e.CustomEntityNameId); - }); mapper.Class( diff --git a/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs b/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs index 9cbd8d892ee..9aa6d890243 100644 --- a/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs @@ -24,6 +24,7 @@ public class EntityComplex public virtual string Name { get; set; } public virtual string LazyProp { get; set; } + public virtual string LazyProp2 { get; set; } public virtual EntitySimpleChild Child1 { get; set; } public virtual EntitySimpleChild Child2 { get; set; } diff --git a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs index 98c67dcf547..25220eb157a 100644 --- a/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/EntityProjectionsTest.cs @@ -36,7 +36,17 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); - rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + rc.Property(ep => ep.LazyProp, m => + { + m.Lazy(true); + m.FetchGroup("LazyProp1"); + }); + + rc.Property(ep => ep.LazyProp2, m => + { + m.Lazy(true); + m.FetchGroup("LazyProp2"); + }); rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id")); rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id")); @@ -50,7 +60,6 @@ protected override HbmMapping GetMappings() m.Inverse(true); }, a => a.OneToMany()); - }); mapper.Class( @@ -181,7 +190,6 @@ public void RootEntityProjectionLazy() .Select(Projections.RootEntity().SetLazy(true)) .Take(1).SingleOrDefault(); - Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.False, "Object must be lazy loaded."); } } @@ -314,7 +322,6 @@ public void MultipleLazyEntityProjections() //make sure objects are populated from different aliases for the same types Assert.That(root.Id, Is.Not.EqualTo(sameTypeChild.Id), "Different objects are expected for root and sameTypeChild."); Assert.That(child1.Id, Is.Not.EqualTo(child2.Id), "Different objects are expected for child1 and child2."); - } } @@ -332,6 +339,26 @@ public void EntityProjectionWithLazyPropertiesFetched() Assert.That(entityRoot, Is.Not.Null); Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized"); Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.True, "Lazy property must be initialized"); + } + } + + [Test] + public void EntityProjectionWithLazyPropertiesSinglePropertyFetch() + { + using (var session = OpenSession()) + { + EntityComplex entityRoot; + entityRoot = session + .QueryOver() + .Where(ec => ec.LazyProp != null) + .Select(Projections.RootEntity().SetFetchLazyPropertyGroups(nameof(entityRoot.LazyProp))) + .Take(1).SingleOrDefault(); + + Assert.That(entityRoot, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized"); + Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.False, "Property must be lazy"); } } @@ -444,7 +471,6 @@ public void MultipleEntitiesProjectionsToResultTransformer() } } - [Test] public void ReadOnlyProjection() { @@ -476,7 +502,6 @@ public void EntityProjectionForCompositeKeyInitialized() Assert.That(composite, Is.EqualTo(_entityWithCompositeId).Using((EntityWithCompositeId x, EntityWithCompositeId y) => (Equals(x.Key, y.Key) && Equals(x.Name, y.Name)) ? 0 : 1)); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } - } [Test] diff --git a/src/NHibernate.Test/Criteria/Lambda/CriteriaAssertFixture.cs b/src/NHibernate.Test/Criteria/Lambda/CriteriaAssertFixture.cs index 8e092f9620e..63bbf4a06e2 100644 --- a/src/NHibernate.Test/Criteria/Lambda/CriteriaAssertFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/CriteriaAssertFixture.cs @@ -1,5 +1,4 @@ - -using System; +using System; using NUnit.Framework; @@ -8,35 +7,21 @@ namespace NHibernate.Test.Criteria.Lambda { - [TestFixture] public class CriteriaAssertFixture : LambdaFixtureBase { - private void AssertCriteriaAreNotEqual(ICriteria expected, ICriteria actual) { - try - { - AssertCriteriaAreEqual(expected, actual); - Assert.Fail("No exception thrown"); - } - catch - { - Assert.Pass(); - } + Assert.Throws( + () => { AssertCriteriaAreEqual(expected, actual); }, + "No exception thrown"); } private void AssertCriteriaAreNotEqual(DetachedCriteria expected, DetachedCriteria actual) { - try - { - AssertCriteriaAreEqual(expected, actual); - Assert.Fail("No exception thrown"); - } - catch - { - Assert.Pass(); - } + Assert.Throws( + () => { AssertCriteriaAreEqual(expected, actual); }, + "No exception thrown"); } [Test] @@ -236,7 +221,5 @@ public void DifferentSubquery() AssertCriteriaAreNotEqual(expected, actual); } - } - } diff --git a/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs b/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs index ed6ce2d9ae4..0d2410ac642 100644 --- a/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs +++ b/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs @@ -88,7 +88,6 @@ public void ManyAliases() .Where(p => p.Name == father.Name && p.Father.Id == 10 && child.Nickname == "nickname" && child.Age > person.Age).UnderlyingCriteria; BenchQuery(s, criteria); - } } diff --git a/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs b/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs index d98df133848..26b0a48cce4 100644 --- a/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFixture.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq; @@ -10,11 +9,9 @@ namespace NHibernate.Test.Criteria.Lambda { - [TestFixture] public class ExpressionProcessorFixture { - [Test] public void TestFindMemberExpressionReference() { @@ -353,7 +350,5 @@ public void TestSignatureQualifiedGeneric() Assert.That(ExpressionProcessor.Signature(genericMethodWithQualifiedType), Is.EqualTo("NHibernate.Test.Criteria.Lambda.ExpressionProcessorFixture:T GenericMethod[T](T)")); } - } - } diff --git a/src/NHibernate.Test/Criteria/Lambda/FunctionsIntegrationFixture.cs b/src/NHibernate.Test/Criteria/Lambda/FunctionsIntegrationFixture.cs index 0ad2fe3ed96..7d5b7d014cf 100644 --- a/src/NHibernate.Test/Criteria/Lambda/FunctionsIntegrationFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/FunctionsIntegrationFixture.cs @@ -51,7 +51,6 @@ public void SqrtSingleOrDefault() } } - [Test] public void RoundDoubleWithOneArgument() { diff --git a/src/NHibernate.Test/Criteria/Lambda/IntegrationFixture.cs b/src/NHibernate.Test/Criteria/Lambda/IntegrationFixture.cs index 0b3f46ba784..dd23c4b91b4 100644 --- a/src/NHibernate.Test/Criteria/Lambda/IntegrationFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/IntegrationFixture.cs @@ -498,5 +498,40 @@ public void StatelessSession() Assert.That(statelessPerson2.Id, Is.EqualTo(personId)); } } + + [Test] + public void QueryOverArithmetic() + { + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(new Person() {Name = "test person 1", Age = 20}); + s.Save(new Person() {Name = "test person 2", Age = 50}); + t.Commit(); + } + + using (var s = OpenSession()) + { + var persons1 = s.QueryOver().Where(p => ((p.Age * 2) / 2) + 20 - 20 == 20).List(); + var persons2 = s.QueryOver().Where(p => (-(-p.Age)) > 20).List(); + var persons3 = s.QueryOver().WhereRestrictionOn(p => ((p.Age * 2) / 2) + 20 - 20).IsBetween(19).And(21).List(); + var persons4 = s.QueryOver().WhereRestrictionOn(p => -(-p.Age)).IsBetween(19).And(21).List(); + var persons5 = s.QueryOver().WhereRestrictionOn(p => ((p.Age * 2) / 2) + 20 - 20).IsBetween(19).And(51).List(); + var persons6 = s.QueryOver().Where(p => ((p.Age * 2) / 2) + 20 - 20 == p.Age - p.Age + 20).List(); +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + var persons7 = s.QueryOver().Where(p => ((p.Age * 2) / 2) + 20 - 20 == null || p.Age * 2 == 20 * 1).List(); +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + var val1 = s.QueryOver().Select(p => p.Age * 2).Where(p => p.Age == 20).SingleOrDefault(); + + Assert.That(persons1.Count, Is.EqualTo(1)); + Assert.That(persons2.Count, Is.EqualTo(1)); + Assert.That(persons3.Count, Is.EqualTo(1)); + Assert.That(persons4.Count, Is.EqualTo(1)); + Assert.That(persons5.Count, Is.EqualTo(2)); + Assert.That(persons6.Count, Is.EqualTo(1)); + Assert.That(persons7.Count, Is.EqualTo(0)); + Assert.That(val1, Is.EqualTo(40)); + } + } } } diff --git a/src/NHibernate.Test/Criteria/Lambda/LambdaFixtureBase.cs b/src/NHibernate.Test/Criteria/Lambda/LambdaFixtureBase.cs index 8394d11fb66..19ad76bc17b 100644 --- a/src/NHibernate.Test/Criteria/Lambda/LambdaFixtureBase.cs +++ b/src/NHibernate.Test/Criteria/Lambda/LambdaFixtureBase.cs @@ -1,4 +1,3 @@ - using System; using System.Collections; using System.Collections.Generic; @@ -13,10 +12,8 @@ namespace NHibernate.Test.Criteria.Lambda { - public class LambdaFixtureBase { - private Hashtable _visitedObjects = new Hashtable(); private Stack _fieldPath = new Stack(); @@ -184,7 +181,5 @@ private void AssertObjectsAreEqual(object expected, object actual) _fieldPath.Clear(); AssertObjectsAreEqual(expected, actual, expected.GetType().Name); } - } - } diff --git a/src/NHibernate.Test/Criteria/Lambda/Mappings.hbm.xml b/src/NHibernate.Test/Criteria/Lambda/Mappings.hbm.xml index 623a0102196..867e993efe8 100644 --- a/src/NHibernate.Test/Criteria/Lambda/Mappings.hbm.xml +++ b/src/NHibernate.Test/Criteria/Lambda/Mappings.hbm.xml @@ -9,6 +9,7 @@ + diff --git a/src/NHibernate.Test/Criteria/Lambda/Model.cs b/src/NHibernate.Test/Criteria/Lambda/Model.cs index cdf004073fd..1ca4ddfae0b 100644 --- a/src/NHibernate.Test/Criteria/Lambda/Model.cs +++ b/src/NHibernate.Test/Criteria/Lambda/Model.cs @@ -1,10 +1,8 @@ - using System; using System.Collections.Generic; namespace NHibernate.Test.Criteria.Lambda { - public enum PersonGender { Male = 1, @@ -13,7 +11,6 @@ public enum PersonGender public class Person { - public Person() { Children = new List(); @@ -26,6 +23,7 @@ public Person() public virtual int Id { get; set; } public virtual string Name { get; set; } + public virtual string Nickname { get; set; } public virtual int Age { get; set; } public virtual PersonGender Gender { get; set; } public virtual int Height { get; set; } @@ -50,7 +48,6 @@ public virtual Person AddChild(Child child) (Children as IList).Add(child); return this; } - } public class PersonDetail @@ -75,7 +72,6 @@ public class Child public class Relation { - public virtual Relation Related1 { get; set; } public virtual Relation Related2 { get; set; } public virtual Relation Related3 { get; set; } @@ -87,7 +83,6 @@ public class Relation public virtual IEnumerable Collection4 { get; set; } public virtual IEnumerable People { get; set; } - } public class PersonSummary @@ -121,4 +116,3 @@ public class JoinedChild public virtual Parent Parent { get; set; } } } - diff --git a/src/NHibernate.Test/Criteria/Lambda/ProjectIntegrationFixture.cs b/src/NHibernate.Test/Criteria/Lambda/ProjectIntegrationFixture.cs index afecce3b193..b62e6ac98d0 100644 --- a/src/NHibernate.Test/Criteria/Lambda/ProjectIntegrationFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/ProjectIntegrationFixture.cs @@ -123,5 +123,43 @@ var actual Assert.That((int) actual[1], Is.EqualTo(2), "distinct count by name"); } } + + [Test] + public void ProjectionCanCoalesceInSelect() + { + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var actual + = s.QueryOver() + .Select(x => x.Age.Coalesce(0)) + .Where(x => x.Age == 20) + .List().FirstOrDefault(); + + Assert.That(actual, Is.EqualTo(20)); + } + } + + //NH-2983 + [Test] + public void ProjectionSelectSumOnCoalesce() + { + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var actual + = s.QueryOver() + .SelectList( + l => + l + .SelectSum(xx => xx.Age.Coalesce(0)) + .SelectSum(xx => xx.Age.Coalesce(1))) + .Where(x => x.Age == 20) + .List().FirstOrDefault(); + + Assert.That(actual[0], Is.EqualTo(20)); + Assert.That(actual[1], Is.EqualTo(20)); + } + } } } diff --git a/src/NHibernate.Test/Criteria/Lambda/ProjectionsFixture.cs b/src/NHibernate.Test/Criteria/Lambda/ProjectionsFixture.cs index 6b590fab678..2ae382b30a5 100644 --- a/src/NHibernate.Test/Criteria/Lambda/ProjectionsFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/ProjectionsFixture.cs @@ -7,11 +7,9 @@ namespace NHibernate.Test.Criteria.Lambda { - [TestFixture] public class ProjectionsFixture : LambdaFixtureBase { - private Child _subqueryChildAlias = null; private DetachedCriteria DetachedCriteriaAge @@ -202,7 +200,5 @@ public void SelectMultipleFunctionOfDateTimeOffset() AssertCriteriaAreEqual(expected, actual); } - } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs b/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs index a39b29c4747..ac1072bf475 100644 --- a/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/QueryOverFixture.cs @@ -1,21 +1,14 @@ using System; -using System.Collections; - using NUnit.Framework; - using NHibernate.Criterion; using NHibernate.SqlCommand; using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Test.Criteria.Lambda { - [TestFixture] public class QueryOverFixture : LambdaFixtureBase { - [Test] public void SimpleCriterion_NoAlias() { @@ -170,7 +163,6 @@ public void Negation() .Add(Restrictions.Not(Restrictions.Eq("personAlias.Name", "test name"))) .Add(Restrictions.Not(Restrictions.Eq("Name", "not test name"))); - Person personAlias = null; IQueryOver actual = CreateTestQueryOver(() => personAlias) @@ -931,6 +923,61 @@ public void Readonly() AssertCriteriaAreEqual(expected, actual); } + [Test] + public void SetTimeout() + { + var expected = + CreateTestCriteria(typeof(Person)) + .SetTimeout(3); + + var actual = + CreateTestQueryOver() + .SetTimeout(3); + + AssertCriteriaAreEqual(expected, actual); + } + + [Test] + public void SetFetchSize() + { + var expected = + CreateTestCriteria(typeof(Person)) + .SetFetchSize(3); + + var actual = + CreateTestQueryOver() + .SetFetchSize(3); + + AssertCriteriaAreEqual(expected, actual); + } + + [Test] + public void SetComment() + { + var expected = + CreateTestCriteria(typeof(Person)) + .SetComment("blah"); + + var actual = + CreateTestQueryOver() + .SetComment("blah"); + + AssertCriteriaAreEqual(expected, actual); + } + + [Test] + public void SetFlushMode( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] FlushMode flushMode) + { + var expected = + CreateTestCriteria(typeof(Person)) + .SetFlushMode(flushMode); + var actual = + CreateTestQueryOver() + .SetFlushMode(flushMode); + AssertCriteriaAreEqual(expected, actual); + } + [Test] public void DetachedQueryOver() { @@ -1039,7 +1086,5 @@ public void TransformQueryOverToRowCount64() AssertCriteriaAreEqual(expected.UnderlyingCriteria, actual); } - } - } diff --git a/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs b/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs index 6471c480301..6f6d89386c1 100644 --- a/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/RestrictionsFixture.cs @@ -7,11 +7,9 @@ namespace NHibernate.Test.Criteria.Lambda { - [TestFixture] public class RestrictionsFixture : LambdaFixtureBase { - [Test] public void ArbitraryCriterion() { @@ -284,11 +282,15 @@ public void FunctionExtensions() .Add(Restrictions.Eq(Projections.SqlFunction("length", NHibernateUtil.String, Projections.Property("Name")), 4)) .Add(Restrictions.Eq(Projections.SqlFunction("bit_length", NHibernateUtil.String, Projections.Property("Name")), 32)) .Add(Restrictions.Eq(Projections.SqlFunction("substring", NHibernateUtil.String, Projections.Property("Name"), Projections.Constant(1), Projections.Constant(2)), "te")) + .Add(Restrictions.Eq(Projections.SqlFunction("substring", NHibernateUtil.String, Projections.Property("Name"), Projections.Property("Age"), Projections.Constant(2)), "te")) .Add(Restrictions.Eq(Projections.SqlFunction("locate", NHibernateUtil.String, Projections.Constant("e"), Projections.Property("Name"), Projections.Constant(1)), 2)) - .Add(Restrictions.Eq(Projections.SqlFunction("coalesce", NHibernateUtil.Object, Projections.Property("Name"), Projections.Constant("not-null-val")), "test")) - .Add(Restrictions.Eq(Projections.SqlFunction("coalesce", NHibernateUtil.Object, Projections.Property("NullableIsParent"), Projections.Constant(true)), true)) + .Add(Restrictions.Eq(Projections.SqlFunction("locate", NHibernateUtil.String, Projections.Constant("e"), Projections.Property("Name"), Projections.Property("Age")), 2)) + .Add(Restrictions.Eq(new SqlFunctionProjection("coalesce", Projections.Property("Name"), Projections.Property("Name"), Projections.Constant("not-null-val")), "test")) + .Add(Restrictions.Eq(new SqlFunctionProjection("coalesce", Projections.Property("Name"), Projections.Property("Name"), Projections.Property("Nickname")), "test")) + .Add(Restrictions.Eq(new SqlFunctionProjection("coalesce", Projections.Property("NullableIsParent"), Projections.Property("NullableIsParent"), Projections.Constant(true)), true)) .Add(Restrictions.Eq(Projections.SqlFunction("concat", NHibernateUtil.String, Projections.Property("Name"), Projections.Constant(", "), Projections.Property("Name")), "test, test")) - .Add(Restrictions.Eq(Projections.SqlFunction("mod", NHibernateUtil.Int32, Projections.Property("Height"), Projections.Constant(10)), 0)); + .Add(Restrictions.Eq(Projections.SqlFunction("mod", NHibernateUtil.Int32, Projections.Property("Height"), Projections.Constant(10)), 0)) + .Add(Restrictions.Eq(Projections.SqlFunction("mod", NHibernateUtil.Int32, Projections.Property("Height"), Projections.Property("Age")), 0)); IQueryOver actual = CreateTestQueryOver() @@ -314,11 +316,15 @@ public void FunctionExtensions() .And(p => p.Name.StrLength() == 4) .And(p => p.Name.BitLength() == 32) .And(p => p.Name.Substr(1, 2) == "te") + .And(p => p.Name.Substr(p.Age, 2) == "te") .And(p => p.Name.CharIndex("e", 1) == 2) + .And(p => p.Name.CharIndex("e", p.Age) == 2) .And(p => p.Name.Coalesce("not-null-val") == "test") + .And(p => p.Name.Coalesce(p.Nickname) == "test") .And(p => p.NullableIsParent.Coalesce(true) == true) .And(p => Projections.Concat(p.Name, ", ", p.Name) == "test, test") - .And(p => p.Height.Mod(10) == 0); + .And(p => p.Height.Mod(10) == 0) + .And(p => p.Height.Mod(p.Age) == 0); AssertCriteriaAreEqual(expected, actual); } @@ -339,4 +345,4 @@ public void FunctionExtensionsProperty() AssertCriteriaAreEqual(expected, actual); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Criteria/Lambda/SubqueryFixture.cs b/src/NHibernate.Test/Criteria/Lambda/SubqueryFixture.cs index a2a1b653595..d28e9493e38 100644 --- a/src/NHibernate.Test/Criteria/Lambda/SubqueryFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/SubqueryFixture.cs @@ -11,11 +11,9 @@ namespace NHibernate.Test.Criteria.Lambda { - [TestFixture] public class SubqueryFixture : LambdaFixtureBase { - private Child _subqueryChildAlias = null; private DetachedCriteria DetachedCriteriaChild @@ -592,7 +590,5 @@ public void UntypedSubqueriesCriterion() AssertCriteriaAreEqual(expected, actual); } - } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Criteria/ProjectionsTest.cs b/src/NHibernate.Test/Criteria/ProjectionsTest.cs index 2d855f60bc2..0eb03b33a60 100644 --- a/src/NHibernate.Test/Criteria/ProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/ProjectionsTest.cs @@ -186,7 +186,6 @@ public void UsingConditionals() .UniqueResult(); Assert.AreEqual("yes", result); - result = session.CreateCriteria(typeof(Student)) .SetProjection( Projections.Conditional( @@ -211,7 +210,6 @@ public void UseInWithProjection() } } - [Test] public void UseLikeWithProjection() { @@ -260,7 +258,6 @@ public void UseEqWithProjection() } } - [Test] public void UseGtWithProjection() { diff --git a/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs new file mode 100644 index 00000000000..cd8cfd50fba --- /dev/null +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/CriteriaNorthwindReadonlyTestCase.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + public abstract class CriteriaNorthwindReadonlyTestCase : NHibernate.Test.Linq.ReadonlyTestCase + { + private ISession _session = null; + protected NorthwindQueryOver db; + + protected override string[] Mappings + { + get + { + return new[] + { + "Northwind.Mappings.Customer.hbm.xml", + "Northwind.Mappings.Employee.hbm.xml", + "Northwind.Mappings.Order.hbm.xml", + "Northwind.Mappings.OrderLine.hbm.xml", + "Northwind.Mappings.Product.hbm.xml", + "Northwind.Mappings.ProductCategory.hbm.xml", + "Northwind.Mappings.Region.hbm.xml", + "Northwind.Mappings.Shipper.hbm.xml", + "Northwind.Mappings.Supplier.hbm.xml", + "Northwind.Mappings.Territory.hbm.xml", + "Northwind.Mappings.AnotherEntity.hbm.xml", + "Northwind.Mappings.Role.hbm.xml", + "Northwind.Mappings.User.hbm.xml", + "Northwind.Mappings.TimeSheet.hbm.xml", + "Northwind.Mappings.Animal.hbm.xml", + "Northwind.Mappings.Patient.hbm.xml" + }; + } + } + + public ISession Session + { + get { return _session; } + } + + protected override void OnSetUp() + { + _session = OpenSession(); + db = new NorthwindQueryOver(_session); + base.OnSetUp(); + } + + protected override void OnTearDown() + { + if (_session.IsOpen) + { + _session.Close(); + } + } + + public static void AssertByIds(IEnumerable entities, TId[] expectedIds, Converter entityIdGetter) + { + Assert.That(entities.Select(x => entityIdGetter(x)), Is.EquivalentTo(expectedIds)); + } + + protected IQueryOver Customers => Session.QueryOver(); + } +} diff --git a/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs new file mode 100644 index 00000000000..749a5184354 --- /dev/null +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/QueryOverCacheableTests.cs @@ -0,0 +1,279 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.SqlCommand; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + [TestFixture] + public class QueryOverCacheableTests : CriteriaNorthwindReadonlyTestCase + { + //Just for discoverability + private class CriteriaCacheableTest{} + + protected override void Configure(Configuration config) + { + config.SetProperty(Environment.UseQueryCache, "true"); + config.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(config); + } + + [Test] + public void QueryIsCacheable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + db.Customers.Cacheable().Take(1).List(); + db.Customers.Cacheable().Take(1).List(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void QueryIsCacheable2() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + db.Customers.Cacheable().Take(1).List(); + db.Customers.Take(1).List(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "Unexpected cache hit count"); + } + + [Test] + public void QueryIsCacheableWithRegion() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + Sfi.EvictQueries("test"); + Sfi.EvictQueries("other"); + + db.Customers.Cacheable().Take(1).CacheRegion("test").List(); + db.Customers.Cacheable().Take(1).CacheRegion("test").List(); + db.Customers.Cacheable().Take(1).CacheRegion("other").List(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void CanBeCombinedWithFetch() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + db.Customers + .Cacheable() + .List(); + + db.Orders + .Cacheable() + .Take(1) + .List(); + + db.Customers + .Fetch(SelectMode.Fetch, x => x.Orders) + .Cacheable() + .Take(1) + .List(); + + db.Orders + .Fetch(SelectMode.Fetch, x => x.OrderLines) + .Cacheable() + .Take(1) + .List(); + + db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefault(); + + var customer = db.Customers + .Fetch(SelectMode.Fetch, x => x.Address) + .Where(x => x.CustomerId == "VINET") + .Cacheable() + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(customer.Address), Is.True, "Expected the fetched Address to be initialized"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(5), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void FetchIsCacheable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + var order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + Session.Clear(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.Customer, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void FetchIsCacheableForJoinAlias() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + Customer customer = null; + OrderLine orderLines = null; + Product product = null; + OrderLine prOrderLines = null; + + var order = db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + Session.Clear(); + + order = db.Orders + .JoinAlias(x => x.Customer, () => customer) + .JoinAlias(x => x.OrderLines, () => orderLines, JoinType.LeftOuterJoin) + .JoinAlias(() => orderLines.Product, () => product) + .JoinAlias(() => product.OrderLines, () => prOrderLines, JoinType.LeftOuterJoin) + .Where(x => x.OrderId == 10248) + .Cacheable() + .List() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void FutureFetchIsCacheable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + var multiQueries = Sfi.ConnectionProvider.Driver.SupportsMultipleQueries; + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + var order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(multiQueries ? 1 : 2), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(2), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + Session.Clear(); + + db.Orders + .Fetch(SelectMode.Fetch, x => x.Customer) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future(); + + order = db.Orders + .Fetch( + SelectMode.Fetch, + x => x.OrderLines, + x => x.OrderLines.First().Product, + x => x.OrderLines.First().Product.OrderLines) + .Where(x => x.OrderId == 10248) + .Cacheable() + .Future() + .ToList() + .First(); + + AssertFetchedOrder(order); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); + } + + private static void AssertFetchedOrder(Order order) + { + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items"); + var orderLine = order.OrderLines.First(); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized"); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); + } + } +} diff --git a/src/NHibernate.Test/Criteria/ReadonlyTests/ReadonlyFixtureSetUp.cs b/src/NHibernate.Test/Criteria/ReadonlyTests/ReadonlyFixtureSetUp.cs new file mode 100644 index 00000000000..b2ba33908d6 --- /dev/null +++ b/src/NHibernate.Test/Criteria/ReadonlyTests/ReadonlyFixtureSetUp.cs @@ -0,0 +1,12 @@ +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.ReadonlyTests +{ + /// + /// Single one-time fixture set up for all test fixtures in NHibernate.Test.Criteria.ReadonlyTests namespace + /// + [SetUpFixture] + public class ReadonlyFixtureSetUp : NHibernate.Test.Linq.LinqReadonlyTestsContext + { + } +} diff --git a/src/NHibernate.Test/Criteria/SelectModeTest/Entities.cs b/src/NHibernate.Test/Criteria/SelectModeTest/Entities.cs index 25732c89496..2508158d65a 100644 --- a/src/NHibernate.Test/Criteria/SelectModeTest/Entities.cs +++ b/src/NHibernate.Test/Criteria/SelectModeTest/Entities.cs @@ -12,6 +12,7 @@ public class EntityComplex public virtual string Name { get; set; } public virtual string LazyProp { get; set; } + public virtual string LazyProp2 { get; set; } public virtual EntitySimpleChild Child1 { get; set; } public virtual EntitySimpleChild Child2 { get; set; } diff --git a/src/NHibernate.Test/Criteria/SelectModeTest/SelectModeTest.cs b/src/NHibernate.Test/Criteria/SelectModeTest/SelectModeTest.cs index 595fa87baba..0fcaa767920 100644 --- a/src/NHibernate.Test/Criteria/SelectModeTest/SelectModeTest.cs +++ b/src/NHibernate.Test/Criteria/SelectModeTest/SelectModeTest.cs @@ -43,6 +43,27 @@ public void SelectModeJoinOnly() } } + [Test] + public void SelectModeDetachedQueryOver() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex root = null; + root = QueryOver.Of(() => root) + .Where(x => x.Id == _parentEntityComplexId) + .Fetch(SelectMode.Fetch, r => r.Child1) + .GetExecutableQueryOver(session) + .SingleOrDefault(); + + Assert.That(root, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(root.Child1, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root.Child1), Is.True, "Joined ManyToOne Child1 should not be fetched."); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + [Test] public void SelectModeFetch() { @@ -142,6 +163,53 @@ public void SelectModeFetchLazyProperties() Assert.That(NHibernateUtil.IsInitialized(root), Is.True); Assert.That(root.LazyProp, Is.Not.Null); Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched."); + + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void SelectModeFetchKeepLazyPropertiesUninitialized() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var root = session.QueryOver() + .Fetch(SelectMode.Fetch, ec => ec) + .Where(ec => ec.LazyProp != null) + .Take(1) + .SingleOrDefault(); + + Assert.That(root, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.False, "Property must be lazy."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.False, "Property must be lazy."); + + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void SelectModeFetchLazyPropertiesFetchGroup() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var root = session.QueryOver() + .Fetch(SelectMode.FetchLazyPropertyGroup, ec => ec.LazyProp, ec => ec.LazyProp2, ec => ec.SameTypeChild.LazyProp2) + .Where(ec => ec.Id == _parentEntityComplexId) + .SingleOrDefault(); + + Assert.That(root, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(root), Is.True); + Assert.That(root.LazyProp, Is.Not.Null); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsInitialized(root.SameTypeChild), Is.True, "Object must be initialized."); + Assert.That(root.SameTypeChild, Is.Not.Null); + Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched."); + Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp)), Is.False, "Property must be lazy."); Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); } @@ -443,6 +511,72 @@ public void OrderedInnerJoinFetch() } } + //GH-2440 + [Test] + public void FetchWithAliasedJoinFuture() + { + using (var session = OpenSession()) + { + EntityComplex alias = null; + EntitySimpleChild child1 = null; + var list = session.QueryOver(() => alias) + .Where(ec => ec.Id == _parentEntityComplexId) + .JoinQueryOver(() => alias.Child1, () => child1) + .Fetch(SelectMode.Fetch, () => alias.ChildrenList) + .TransformUsing(Transformers.DistinctRootEntity) + .Future() + .GetEnumerable() + .ToList(); + + var childList = list[0].ChildrenList; + Assert.That(list[0].ChildrenList.Count, Is.GreaterThan(1)); + Assert.That(list[0].ChildrenList, Is.EqualTo(list[0].ChildrenList.OrderByDescending(c => c.OrderIdx)), "wrong order"); + } + } + + //GH-2440 + [Test] + public void CacheableFetchWithAliasedJoinFuture() + { + using (var session = OpenSession()) + { + EntityComplex alias = null; + EntitySimpleChild child1 = null; + var list = session.QueryOver(() => alias) + .Where(ec => ec.Id == _parentEntityComplexId) + .JoinQueryOver(() => alias.Child1, () => child1) + .Fetch(SelectMode.Fetch, () => alias.ChildrenList) + .TransformUsing(Transformers.DistinctRootEntity) + .Cacheable() + .Future() + .GetEnumerable() + .ToList(); + EntityComplex value = null; + Assert.DoesNotThrow(() => value = list[0]); + Assert.That(value, Is.Not.Null); + } + + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex alias = null; + EntitySimpleChild child1 = null; + var list = session.QueryOver(() => alias) + .Where(ec => ec.Id == _parentEntityComplexId) + .JoinQueryOver(() => alias.Child1, () => child1) + .Fetch(SelectMode.Fetch, () => alias.ChildrenList) + .TransformUsing(Transformers.DistinctRootEntity) + .Cacheable() + .Future() + .ToList(); + EntityComplex value = null; + Assert.DoesNotThrow(() => value = list[0]); + Assert.That(value, Is.Not.Null); + + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(0), "Query is expected to be retrieved from cache"); + } + } + [Test, Obsolete] public void FetchModeEagerForLazy() { @@ -589,7 +723,16 @@ protected override HbmMapping GetMappings() rc.Property(x => x.Name); - rc.Property(ep => ep.LazyProp, m => m.Lazy(true)); + rc.Property(ep => ep.LazyProp, m => + { + m.Lazy(true); + m.FetchGroup("LazyGroup"); + }); + rc.Property(ep => ep.LazyProp2, m => + { + m.Lazy(true); + m.FetchGroup("LazyGroup2"); + }); rc.ManyToOne( ep => ep.Child1, @@ -665,7 +808,6 @@ private static void MapList(IClassMapper rc, Express ckm.Name("ParentId"); }); km.ForeignKey("none"); - }); m.Cascade(Mapping.ByCode.Cascade.All); if (fetchMode != null) @@ -762,9 +904,12 @@ protected override void OnSetUp() Child1 = child1, Child2 = child2, LazyProp = "SomeBigValue", + LazyProp2 = "SomeBigValue2", SameTypeChild = new EntityComplex() { - Name = "ComplexEntityChild" + Name = "ComplexEntityChild", + LazyProp = "LazyProp1", + LazyProp2 = "LazyProp2", }, ChildrenList = new List {child3, child1, child4 }, ChildrenListEmpty = new List { }, diff --git a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql index b9dbec1e05d..cb42443637d 100644 Binary files a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql and b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql differ diff --git a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql index 54136ab8381..766d9f34375 100644 Binary files a/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql and b/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyDropScript.sql differ diff --git a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql index 43e78e33fa0..ec1b580e231 100644 --- a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql +++ b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyCreateScript.sql @@ -54,6 +54,25 @@ PRIMARY KEY CLUSTERED )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO +CREATE TABLE [dbo].[NumericEntity]( + [Short] [smallint] IDENTITY(1,1) NOT NULL, + [NullableShort] [smallint] NULL, + [Integer] [int] NOT NULL, + [NullableInteger] [int] NULL, + [Long] [bigint] NOT NULL, + [NullableLong] [bigint] NULL, + [Decimal] [decimal](19, 5) NOT NULL, + [NullableDecimal] [decimal](19, 5) NULL, + [Single] [real] NOT NULL, + [NullableSingle] [real] NULL, + [Double] [float] NOT NULL, + [NullableDouble] [float] NULL, +PRIMARY KEY CLUSTERED +( + [Short] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +GO INSERT [dbo].[Suppliers] ([SupplierId], [CompanyName], [ContactName], [ContactTitle], [HomePage], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (1, N'Exotic Liquids', N'Charlotte Cooper', N'Purchasing Manager', N'', N'49 Gilbert St.', N'London', N'', N'EC1 4SD', N'UK', N'(171) 555-2222', N'') INSERT [dbo].[Suppliers] ([SupplierId], [CompanyName], [ContactName], [ContactTitle], [HomePage], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (2, N'New Orleans Cajun Delights', N'Shelley Burke', N'Order Administrator', N'#CAJUN.HTM#', N'P.O. Box 78934', N'New Orleans', N'LA', N'70117', N'USA', N'(100) 555-4822', N'') INSERT [dbo].[Suppliers] ([SupplierId], [CompanyName], [ContactName], [ContactTitle], [HomePage], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (3, N'Grandma Kelly''s Homestead', N'Regina Murphy', N'Sales Representative', N'', N'707 Oxford Rd.', N'Ann Arbor', N'MI', N'48104', N'USA', N'(313) 555-5735', N'(313) 555-3349') @@ -3774,6 +3793,8 @@ CREATE TABLE [dbo].[Users]( [Property1] [varchar](255) NULL, [Property2] [varchar](255) NULL, [OtherProperty1] [varchar](255) NULL, + [CreatedById] [int] NOT NULL, + [ModifiedById] [int] NULL PRIMARY KEY CLUSTERED ( [UserId] ASC @@ -3783,9 +3804,9 @@ GO SET ANSI_PADDING OFF GO SET IDENTITY_INSERT [dbo].[Users] ON -INSERT [dbo].[Users] ([UserId], [Name], [InvalidLoginAttempts], [RegisteredAt], [LastLoginDate], [Enum1], [Enum2], [RoleId], [Property1], [Property2], [OtherProperty1]) VALUES (1, N'ayende', 4, CAST(0x00009D9800000000 AS DateTime), NULL, N'Medium', 1, 1, N'test1', N'test2', N'othertest1') -INSERT [dbo].[Users] ([UserId], [Name], [InvalidLoginAttempts], [RegisteredAt], [LastLoginDate], [Enum1], [Enum2], [RoleId], [Property1], [Property2], [OtherProperty1]) VALUES (2, N'rahien', 5, CAST(0x00008D3E00000000 AS DateTime), NULL, N'Small', 0, 2, NULL, N'test2', NULL) -INSERT [dbo].[Users] ([UserId], [Name], [InvalidLoginAttempts], [RegisteredAt], [LastLoginDate], [Enum1], [Enum2], [Features], [RoleId], [Property1], [Property2], [OtherProperty1]) VALUES (3, N'nhibernate', 6, CAST(0x00008EAC00000000 AS DateTime), CAST(0x00009D970110B41C AS DateTime), N'Medium', 0, 8, NULL, NULL, NULL, NULL) +INSERT [dbo].[Users] ([UserId], [Name], [InvalidLoginAttempts], [RegisteredAt], [LastLoginDate], [Enum1], [Enum2], [RoleId], [Property1], [Property2], [OtherProperty1], [CreatedById], [ModifiedById]) VALUES (1, N'ayende', 4, CAST(0x00009D9800000000 AS DateTime), NULL, N'Medium', 1, 1, N'test1', N'test2', N'othertest1', 1, NULL) +INSERT [dbo].[Users] ([UserId], [Name], [InvalidLoginAttempts], [RegisteredAt], [LastLoginDate], [Enum1], [Enum2], [RoleId], [Property1], [Property2], [OtherProperty1], [CreatedById], [ModifiedById]) VALUES (2, N'rahien', 5, CAST(0x00008D3E00000000 AS DateTime), NULL, N'Small', 0, 2, NULL, N'test2', NULL, 1, NULL) +INSERT [dbo].[Users] ([UserId], [Name], [InvalidLoginAttempts], [RegisteredAt], [LastLoginDate], [Enum1], [Enum2], [Features], [RoleId], [Property1], [Property2], [OtherProperty1], [CreatedById], [ModifiedById]) VALUES (3, N'nhibernate', 6, CAST(0x00008EAC00000000 AS DateTime), CAST(0x00009D970110B41C AS DateTime), N'Medium', 0, 8, NULL, NULL, NULL, NULL, 1, NULL) SET IDENTITY_INSERT [dbo].[Users] OFF /****** Object: Table [dbo].[TimeSheetUsers] Script Date: 06/17/2010 13:08:54 ******/ SET ANSI_NULLS ON diff --git a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql index facae91e28c..bab84a61127 100644 --- a/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql +++ b/src/NHibernate.Test/DbScripts/MsSql2012DialectLinqReadonlyDropScript.sql @@ -53,3 +53,4 @@ DROP TABLE [dbo].[States] DROP TABLE [dbo].[Suppliers] DROP TABLE [dbo].[Region] DROP TABLE [dbo].[Physicians] +DROP TABLE [dbo].[NumericEntity] diff --git a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql index fa32b85f55b..0ac95140fc2 100644 Binary files a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql and b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyCreateScript.sql differ diff --git a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql index 27ff0b0e394..a9b1f2642ee 100644 Binary files a/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql and b/src/NHibernate.Test/DbScripts/PostgreSQL83DialectLinqReadonlyDropScript.sql differ diff --git a/src/NHibernate.Test/DebugSessionFactory.cs b/src/NHibernate.Test/DebugSessionFactory.cs index eb43b825eee..7643924119f 100644 --- a/src/NHibernate.Test/DebugSessionFactory.cs +++ b/src/NHibernate.Test/DebugSessionFactory.cs @@ -16,6 +16,7 @@ using NHibernate.Id; using NHibernate.Impl; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -400,7 +401,9 @@ public static ISessionCreationOptions GetCreationOptions(IStatelessSessionBuilde (ISessionCreationOptions)sessionBuilder; } - internal class SessionBuilder : ISessionBuilder + internal class SessionBuilder : ISessionBuilder, + //TODO 6.0: Remove interface with implementation (will be replaced TenantConfiguration ISessionBuilder method) + ISessionCreationOptionsWithMultiTenancy { private readonly ISessionBuilder _actualBuilder; private readonly DebugSessionFactory _debugFactory; @@ -465,9 +468,17 @@ ISessionBuilder ISessionBuilder.FlushMode(FlushMode flushMode) } #endregion + + TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration + { + get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration; + set => _actualBuilder.Tenant(value); + } } - internal class StatelessSessionBuilder : IStatelessSessionBuilder + internal class StatelessSessionBuilder : IStatelessSessionBuilder, + //TODO 6.0: Remove interface with implementation (will be replaced TenantConfiguration IStatelessSessionBuilder method) + ISessionCreationOptionsWithMultiTenancy { private readonly IStatelessSessionBuilder _actualBuilder; private readonly DebugSessionFactory _debugFactory; @@ -501,6 +512,12 @@ IStatelessSessionBuilder IStatelessSessionBuilder.AutoJoinTransaction(bool autoJ return this; } + TenantConfiguration ISessionCreationOptionsWithMultiTenancy.TenantConfiguration + { + get => (_actualBuilder as ISessionCreationOptionsWithMultiTenancy)?.TenantConfiguration; + set => _actualBuilder.Tenant(value); + } + #endregion } } diff --git a/src/NHibernate.Test/DialectTest/DB2DialectFixture.cs b/src/NHibernate.Test/DialectTest/DB2DialectFixture.cs index 3fee6c93d12..2ed1574657d 100644 --- a/src/NHibernate.Test/DialectTest/DB2DialectFixture.cs +++ b/src/NHibernate.Test/DialectTest/DB2DialectFixture.cs @@ -26,7 +26,7 @@ public void GetLimitString() SqlString limited = dialect.GetLimitString(sql, new SqlString("111"), new SqlString("222")); Assert.AreEqual( - "select * from (select rownumber() over(order by a, x) as rownum, a, b, c from d where X = ? and Z = ? order by a, x) as tempresult where rownum between 111+1 and 222", + "select a,b,c from (select rownumber() over(order by a, x) as rownum, a, b, c from d where X = ? and Z = ? order by a, x) as tempresult where rownum between 111+1 and 222", limited.ToString()); Assert.AreEqual(2, limited.GetParameterCount()); } @@ -58,4 +58,4 @@ public void GetLimitString_NoOffsetSpecified_UsesFetchFirstOnly() Assert.AreEqual(2, limited.GetParameterCount()); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DialectTest/DialectFixture.cs b/src/NHibernate.Test/DialectTest/DialectFixture.cs index 952e3e6c154..e80ef4d495a 100644 --- a/src/NHibernate.Test/DialectTest/DialectFixture.cs +++ b/src/NHibernate.Test/DialectTest/DialectFixture.cs @@ -33,7 +33,6 @@ public class DialectFixture // be. protected string[] tableThatNeedsToBeQuoted; - [SetUp] public virtual void SetUp() { @@ -92,7 +91,6 @@ public void QuoteTableNameAlreadyQuoted() d.QuoteForTableName(tableAlreadyQuoted[BeforeQuoteIndex])); } - /// /// Test that it does not matter if the name passed in has been quoted or not /// already. The UnQuote should take care of it and return the same result. diff --git a/src/NHibernate.Test/DialectTest/FunctionTests/SequenceSupportFixture.cs b/src/NHibernate.Test/DialectTest/FunctionTests/SequenceSupportFixture.cs index d96f03f438f..c93756b4f79 100644 --- a/src/NHibernate.Test/DialectTest/FunctionTests/SequenceSupportFixture.cs +++ b/src/NHibernate.Test/DialectTest/FunctionTests/SequenceSupportFixture.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.DialectTest.FunctionTests { /// @@ -25,7 +24,6 @@ public class SequenceSupportFixture .ToList(); } - [TestCaseSource(nameof(GetAllDialectTypes))] public void DialectSupportingSequencesMustFullfillSequenceContract(System.Type dialectType) { @@ -48,7 +46,6 @@ public void DialectSupportingSequencesMustFullfillSequenceContract(System.Type d dialect.GetSelectSequenceNextValString("foo"); dialect.GetSequenceNextValString("foo"); var sql = dialect.QuerySequencesString; - } } } diff --git a/src/NHibernate.Test/DialectTest/FunctionTests/SubstringSupportFixture.cs b/src/NHibernate.Test/DialectTest/FunctionTests/SubstringSupportFixture.cs index d80744439e2..84c83344946 100644 --- a/src/NHibernate.Test/DialectTest/FunctionTests/SubstringSupportFixture.cs +++ b/src/NHibernate.Test/DialectTest/FunctionTests/SubstringSupportFixture.cs @@ -5,7 +5,6 @@ using NHibernate.Dialect.Function; using NUnit.Framework; - namespace NHibernate.Test.DialectTest.FunctionTests { [TestFixture] @@ -23,7 +22,6 @@ public class SubstringSupportFixture .ToList(); } - [TestCaseSource(nameof(GetAllDialectTypes))] public void DialectShouldUseCorrectSubstringImplementation(System.Type dialectType) { diff --git a/src/NHibernate.Test/DialectTest/SQLiteDialectFixture.cs b/src/NHibernate.Test/DialectTest/SQLiteDialectFixture.cs index 838841e821f..1736282b48c 100644 --- a/src/NHibernate.Test/DialectTest/SQLiteDialectFixture.cs +++ b/src/NHibernate.Test/DialectTest/SQLiteDialectFixture.cs @@ -103,7 +103,6 @@ public void QuotedTableNameWithSqlLite() Assert.AreEqual("\"Group\"", tbl.GetQualifiedName(dialect)); } - [Test] public void SchemaNameWithSqlLite() { diff --git a/src/NHibernate.Test/DialectTest/SchemaTests/ColumnMetaDataFixture.cs b/src/NHibernate.Test/DialectTest/SchemaTests/ColumnMetaDataFixture.cs index a8f9d8efa2b..f8d496fd9c9 100644 --- a/src/NHibernate.Test/DialectTest/SchemaTests/ColumnMetaDataFixture.cs +++ b/src/NHibernate.Test/DialectTest/SchemaTests/ColumnMetaDataFixture.cs @@ -5,7 +5,6 @@ namespace NHibernate.Test.DialectTest.SchemaTests { - [TestFixture] public class ColumnMetaDataFixture { @@ -22,7 +21,7 @@ public TestableColumnMetaData(DataRow rs, object columnSizeValue, object numeric [TestCase(null, null, 0, 0)] // No size and no precision. [TestCase(null, "7", 0, 7)] // No size, but with numerical precision. [TestCase("13", null, 13, 0)] // Size, but no precision. - [TestCase("5000000000", null, int.MaxValue, 0)] // Oversize column should be bounded to int.MaxValue. + [TestCase("5000000000", null, int.MaxValue, 0)] // Oversize column should be bounded to int.MaxValue. [TestCase("13", "7", 13, 7)] // Can handle both size and precision together. public void SetColumnSizeAndNumericalPrecision(object columnSizeInput, object precisionInput, int expectedColumnSize, int expectedPrecision) diff --git a/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs b/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs index 7306e106840..1067f009224 100644 --- a/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs +++ b/src/NHibernate.Test/DriverTest/OracleClientDriverFixture.cs @@ -9,6 +9,8 @@ namespace NHibernate.Test.DriverTest /// Summary description for OracleClientDriverFixture. /// [TestFixture] + // Since v5.3 + [Obsolete] public class OracleClientDriverFixture { /// @@ -35,4 +37,4 @@ public void CommandClassName() Assert.AreEqual("System.Data.OracleClient.OracleCommand", cmd.GetType().FullName); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DriverTest/OracleDataClientDriverFixture.cs b/src/NHibernate.Test/DriverTest/OracleDataClientDriverFixture.cs index 808a1b68dea..93034e9dc3d 100644 --- a/src/NHibernate.Test/DriverTest/OracleDataClientDriverFixture.cs +++ b/src/NHibernate.Test/DriverTest/OracleDataClientDriverFixture.cs @@ -110,7 +110,6 @@ public void HasSameUnicodeDefaultThanDialect(bool managed) $"Default {nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} values mismatch between driver and dialect"); } - private static OracleDataClientDriverBase GetDriver(bool managed, IDictionary settings) { OracleDataClientDriverBase driver = null; @@ -151,4 +150,4 @@ private static object GetOracleParameterType(DbParameter dbParameter) return typeProperty.GetValue(dbParameter); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DriverTest/ReflectionBasedDriverTest.cs b/src/NHibernate.Test/DriverTest/ReflectionBasedDriverTest.cs index 89a017a1b25..9f57e6c0ac2 100644 --- a/src/NHibernate.Test/DriverTest/ReflectionBasedDriverTest.cs +++ b/src/NHibernate.Test/DriverTest/ReflectionBasedDriverTest.cs @@ -64,20 +64,19 @@ public override string NamedPrefix } } -#if NETFX - [Test] + [Test, NetFxOnly] public void WhenCreatedWithGoodDbProviderThenNotThrows() { Assert.That(() => new MyDriverWithWrongClassesAndGoodDbProviderFactory(), Throws.Nothing); } - [Test] + [Test, NetFxOnly] public void WhenCreatedWithNullAssemblyAndGoodDbProviderThenNotThrows() { Assert.That(() => new MyDriverWithWrongClassesAndGoodDbProviderFactory(null), Throws.Nothing); } - [Test] + [Test, NetFxOnly] public void WhenCreatedWithDbFactoryThenCanReturnConnection() { var provider = new MyDriverWithWrongClassesAndGoodDbProviderFactory(); @@ -87,7 +86,7 @@ public void WhenCreatedWithDbFactoryThenCanReturnConnection() } } - [Test] + [Test, NetFxOnly] public void WhenCreatedWithDbFactoryThenCanReturnCommand() { var provider = new MyDriverWithWrongClassesAndGoodDbProviderFactory(); @@ -96,7 +95,6 @@ public void WhenCreatedWithDbFactoryThenCanReturnCommand() Assert.That(command, Is.Not.Null); } } -#endif [Test] public void WhenCreatedWithNoDbProviderThenNotThrows() diff --git a/src/NHibernate.Test/DriverTest/Sql2008DateTime2Test.cs b/src/NHibernate.Test/DriverTest/Sql2008DateTime2Test.cs index 6958a8fc13d..a28f40eed48 100644 --- a/src/NHibernate.Test/DriverTest/Sql2008DateTime2Test.cs +++ b/src/NHibernate.Test/DriverTest/Sql2008DateTime2Test.cs @@ -69,6 +69,5 @@ public void Crud() t.Commit(); } } - } } diff --git a/src/NHibernate.Test/DynamicEntity/Address.cs b/src/NHibernate.Test/DynamicEntity/Address.cs index 160eab8ebcf..f074f51b0f9 100644 --- a/src/NHibernate.Test/DynamicEntity/Address.cs +++ b/src/NHibernate.Test/DynamicEntity/Address.cs @@ -2,9 +2,9 @@ namespace NHibernate.Test.DynamicEntity { public interface Address { - long Id { get; set;} - string Street { get; set;} - string City { get; set;} - string PostalCode { get; set;} + long Id { get; set; } + string Street { get; set; } + string City { get; set; } + string PostalCode { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DynamicEntity/Company.cs b/src/NHibernate.Test/DynamicEntity/Company.cs index 0f5f2337d21..1e6ee757628 100644 --- a/src/NHibernate.Test/DynamicEntity/Company.cs +++ b/src/NHibernate.Test/DynamicEntity/Company.cs @@ -2,7 +2,7 @@ namespace NHibernate.Test.DynamicEntity { public interface Company { - long Id { get; set;} - string Name { get; set;} + long Id { get; set; } + string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DynamicEntity/Customer.cs b/src/NHibernate.Test/DynamicEntity/Customer.cs index c1ec9b348fb..9c0e8fd4cff 100644 --- a/src/NHibernate.Test/DynamicEntity/Customer.cs +++ b/src/NHibernate.Test/DynamicEntity/Customer.cs @@ -2,6 +2,6 @@ namespace NHibernate.Test.DynamicEntity { public interface Customer:Person { - Company Company { get; set;} + Company Company { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DynamicEntity/IProxyMarker.cs b/src/NHibernate.Test/DynamicEntity/IProxyMarker.cs index 7d420fd1df9..f10da0d1ebf 100644 --- a/src/NHibernate.Test/DynamicEntity/IProxyMarker.cs +++ b/src/NHibernate.Test/DynamicEntity/IProxyMarker.cs @@ -5,6 +5,6 @@ namespace NHibernate.Test.DynamicEntity [Obsolete("Require dynamic proxies")] public interface IProxyMarker { - DataProxyHandler DataHandler { get;} + DataProxyHandler DataHandler { get; } } } diff --git a/src/NHibernate.Test/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs b/src/NHibernate.Test/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs index 4b747230db3..26055c5cba2 100644 --- a/src/NHibernate.Test/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs +++ b/src/NHibernate.Test/DynamicEntity/Interceptor/InterceptorDynamicEntity.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using NHibernate.Cfg; using NUnit.Framework; @@ -27,68 +26,78 @@ protected override void Configure(Configuration configuration) [Test] public void It() { + var company = ProxyHelper.NewCompanyProxy(); + var customer = ProxyHelper.NewCustomerProxy(); // Test saving these dyna-proxies - ISession session = OpenSession(); - session.BeginTransaction(); - Company company = ProxyHelper.NewCompanyProxy(); - company.Name = "acme"; - session.Save(company); - Customer customer = ProxyHelper.NewCustomerProxy(); - customer.Name = "Steve"; - customer.Company = company; - session.Save(customer); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + company.Name = "acme"; + session.Save(company); + customer.Name = "Steve"; + customer.Company = company; + session.Save(customer); + tran.Commit(); + session.Close(); + } Assert.IsNotNull(company.Id, "company id not assigned"); Assert.IsNotNull(customer.Id, "customer id not assigned"); // Test loading these dyna-proxies, along with flush processing - session = OpenSession(); - session.BeginTransaction(); - customer = session.Load(customer.Id); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + customer = session.Load(customer.Id); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); - customer.Name = "other"; - session.Flush(); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); + customer.Name = "other"; + session.Flush(); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); - session.Refresh(customer); - Assert.AreEqual("other", customer.Name, "name not updated"); - Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); + session.Refresh(customer); + Assert.AreEqual("other", customer.Name, "name not updated"); + Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); - session.Transaction.Commit(); - session.Close(); + tran.Commit(); + session.Close(); + } // Test detached entity re-attachment with these dyna-proxies customer.Name = "Steve"; - session = OpenSession(); - session.BeginTransaction(); - session.Update(customer); - session.Flush(); - session.Refresh(customer); - Assert.AreEqual("Steve", customer.Name, "name not updated"); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Update(customer); + session.Flush(); + session.Refresh(customer); + Assert.AreEqual("Steve", customer.Name, "name not updated"); + tran.Commit(); + session.Close(); + } // Test querying - session = OpenSession(); - session.BeginTransaction(); - int count = session.CreateQuery("from Customer").List().Count; - Assert.AreEqual(1, count, "querying dynamic entity"); - session.Clear(); - count = session.CreateQuery("from Person").List().Count; - Assert.AreEqual(1, count, "querying dynamic entity"); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + int count = session.CreateQuery("from Customer").List().Count; + Assert.AreEqual(1, count, "querying dynamic entity"); + session.Clear(); + count = session.CreateQuery("from Person").List().Count; + Assert.AreEqual(1, count, "querying dynamic entity"); + tran.Commit(); + session.Close(); + } // test deleteing - session = OpenSession(); - session.BeginTransaction(); - session.Delete(company); - session.Delete(customer); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Delete(company); + session.Delete(customer); + tran.Commit(); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/DynamicEntity/Person.cs b/src/NHibernate.Test/DynamicEntity/Person.cs index c87c73f08a8..95c86209bbb 100644 --- a/src/NHibernate.Test/DynamicEntity/Person.cs +++ b/src/NHibernate.Test/DynamicEntity/Person.cs @@ -4,9 +4,9 @@ namespace NHibernate.Test.DynamicEntity { public interface Person { - long Id { get; set;} - string Name { get; set;} - Address Address { get;set;} - ISet Family { get; set;} + long Id { get; set; } + string Name { get; set; } + Address Address { get; set; } + ISet Family { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DynamicEntity/ProxyHelper.cs b/src/NHibernate.Test/DynamicEntity/ProxyHelper.cs index 30f891e9417..b2b1745d52e 100644 --- a/src/NHibernate.Test/DynamicEntity/ProxyHelper.cs +++ b/src/NHibernate.Test/DynamicEntity/ProxyHelper.cs @@ -12,7 +12,6 @@ private static T NewProxy(object id) { return (T)proxyGenerator.CreateProxy(typeof(T), new DataProxyHandler(typeof (T).FullName, id), new[] {typeof (IProxyMarker), typeof (T)}); - } public static Person NewPersonProxy() diff --git a/src/NHibernate.Test/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs b/src/NHibernate.Test/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs index bc8eeba9584..bc12f6ccce3 100644 --- a/src/NHibernate.Test/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs +++ b/src/NHibernate.Test/DynamicEntity/Tuplizer/TuplizerDynamicEntity.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using NHibernate.Cfg; using NUnit.Framework; @@ -28,30 +27,33 @@ protected override void Configure(Configuration configuration) [Test] public void It() { + var company = ProxyHelper.NewCompanyProxy(); + var customer = ProxyHelper.NewCustomerProxy(); + var address = ProxyHelper.NewAddressProxy(); + var son = ProxyHelper.NewPersonProxy(); + var wife = ProxyHelper.NewPersonProxy(); + // Test saving these dyna-proxies - ISession session = OpenSession(); - session.BeginTransaction(); - Company company = ProxyHelper.NewCompanyProxy(); - company.Name = "acme"; - session.Save(company); - Customer customer = ProxyHelper.NewCustomerProxy(); - customer.Name = "Steve"; - customer.Company = company; - Address address = ProxyHelper.NewAddressProxy(); - address.Street = "somewhere over the rainbow"; - address.City = "lawerence, kansas"; - address.PostalCode = "toto"; - customer.Address = address; - customer.Family = new HashSet(); - Person son = ProxyHelper.NewPersonProxy(); - son.Name = "son"; - customer.Family.Add(son); - Person wife = ProxyHelper.NewPersonProxy(); - wife.Name = "wife"; - customer.Family.Add(wife); - session.Save(customer); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + company.Name = "acme"; + session.Save(company); + customer.Name = "Steve"; + customer.Company = company; + address.Street = "somewhere over the rainbow"; + address.City = "lawerence, kansas"; + address.PostalCode = "toto"; + customer.Address = address; + customer.Family = new HashSet(); + son.Name = "son"; + customer.Family.Add(son); + wife.Name = "wife"; + customer.Family.Add(wife); + session.Save(customer); + tran.Commit(); + session.Close(); + } Assert.IsNotNull(company.Id, "company id not assigned"); Assert.IsNotNull(customer.Id, "customer id not assigned"); @@ -60,51 +62,59 @@ public void It() Assert.IsNotNull(wife.Id, "wife:Person id not assigned"); // Test loading these dyna-proxies, along with flush processing - session = OpenSession(); - session.BeginTransaction(); - customer = session.Load(customer.Id); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + customer = session.Load(customer.Id); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer), "should-be-proxy was initialized"); - customer.Name = "other"; - session.Flush(); - Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); + customer.Name = "other"; + session.Flush(); + Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Company), "should-be-proxy was initialized"); - session.Refresh(customer); - Assert.AreEqual("other", customer.Name, "name not updated"); - Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); + session.Refresh(customer); + Assert.AreEqual("other", customer.Name, "name not updated"); + Assert.AreEqual("acme", customer.Company.Name, "company association not correct"); - session.Transaction.Commit(); - session.Close(); + tran.Commit(); + session.Close(); + } // Test detached entity re-attachment with these dyna-proxies customer.Name = "Steve"; - session = OpenSession(); - session.BeginTransaction(); - session.Update(customer); - session.Flush(); - session.Refresh(customer); - Assert.AreEqual("Steve", customer.Name, "name not updated"); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Update(customer); + session.Flush(); + session.Refresh(customer); + Assert.AreEqual("Steve", customer.Name, "name not updated"); + tran.Commit(); + session.Close(); + } // Test querying - session = OpenSession(); - session.BeginTransaction(); - int count = session.CreateQuery("from Customer").List().Count; - Assert.AreEqual(1, count, "querying dynamic entity"); - session.Clear(); - count = session.CreateQuery("from Person").List().Count; - Assert.AreEqual(3, count, "querying dynamic entity"); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + int count = session.CreateQuery("from Customer").List().Count; + Assert.AreEqual(1, count, "querying dynamic entity"); + session.Clear(); + count = session.CreateQuery("from Person").List().Count; + Assert.AreEqual(3, count, "querying dynamic entity"); + tran.Commit(); + session.Close(); + } // test deleteing - session = OpenSession(); - session.BeginTransaction(); - session.Delete(company); - session.Delete(customer); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Delete(company); + session.Delete(customer); + tran.Commit(); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/EngineTest/NativeSQLQuerySpecificationTest.cs b/src/NHibernate.Test/EngineTest/NativeSQLQuerySpecificationTest.cs index 6d49b7a63a1..0d83f648c7a 100644 --- a/src/NHibernate.Test/EngineTest/NativeSQLQuerySpecificationTest.cs +++ b/src/NHibernate.Test/EngineTest/NativeSQLQuerySpecificationTest.cs @@ -54,6 +54,5 @@ public void WhenChangeSpace_NotEqualHashCode() var sr2 = new NativeSQLQuerySpecification("SELECT * FROM SOMETHING", new INativeSQLQueryReturn[] { new NativeSQLQueryScalarReturn("myAlias", NHibernateUtil.Int32), new NativeSQLQueryScalarReturn("otherAlias", NHibernateUtil.Int32) }, new List { "ANOTHER" }); Assert.AreNotEqual(sr1.GetHashCode(), sr2.GetHashCode()); } - } } diff --git a/src/NHibernate.Test/EngineTest/ParameterParserFixture.cs b/src/NHibernate.Test/EngineTest/ParameterParserFixture.cs index 30d7ed2b0c8..28bf19e59a0 100644 --- a/src/NHibernate.Test/EngineTest/ParameterParserFixture.cs +++ b/src/NHibernate.Test/EngineTest/ParameterParserFixture.cs @@ -58,6 +58,5 @@ FROM tablea Assert.DoesNotThrow(() => p = recognizer.NamedParameterDescriptionMap["name"]); Assert.DoesNotThrow(() => p = recognizer.NamedParameterDescriptionMap["pizza"]); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/EngineTest/TypedValueFixture.cs b/src/NHibernate.Test/EngineTest/TypedValueFixture.cs index a82ddbdc3f2..aaa719dd8b5 100644 --- a/src/NHibernate.Test/EngineTest/TypedValueFixture.cs +++ b/src/NHibernate.Test/EngineTest/TypedValueFixture.cs @@ -48,7 +48,6 @@ public void WhenTheTypeIsAnArray_ChoseTheDefaultComparer() { byte[] value = new byte[]{1,2,3}; - var tv = new TypedValue(NHibernateUtil.BinaryBlob, value); Assert.That(tv.Comparer, Is.TypeOf()); diff --git a/src/NHibernate.Test/Events/Collections/AbstractCollectionEventFixture.cs b/src/NHibernate.Test/Events/Collections/AbstractCollectionEventFixture.cs index 3c6549f4c83..1f20c3130b7 100644 --- a/src/NHibernate.Test/Events/Collections/AbstractCollectionEventFixture.cs +++ b/src/NHibernate.Test/Events/Collections/AbstractCollectionEventFixture.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using NHibernate.Collection; @@ -26,30 +27,12 @@ protected override string MappingsAssembly protected override void OnTearDown() { - IParentWithCollection dummyParent = CreateParent("dummyParent"); - dummyParent.NewChildren(CreateCollection()); - IChild dummyChild = dummyParent.AddChild("dummyChild"); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) { - using (ITransaction tx = s.BeginTransaction()) - { - IList children = s.CreateCriteria(dummyChild.GetType()).List(); - IList parents = s.CreateCriteria(dummyParent.GetType()).List(); - foreach (IParentWithCollection parent in parents) - { - parent.ClearChildren(); - s.Delete(parent); - } - foreach (IChild child in children) - { - s.Delete(child); - } - - tx.Commit(); - } + s.Delete("from System.Object"); + tx.Commit(); } - base.OnTearDown(); } [Test] diff --git a/src/NHibernate.Test/Events/Collections/Association/Bidirectional/OneToMany/ParentWithBidirectionalOneToMany.cs b/src/NHibernate.Test/Events/Collections/Association/Bidirectional/OneToMany/ParentWithBidirectionalOneToMany.cs index e1f06a1a863..53760e5011d 100644 --- a/src/NHibernate.Test/Events/Collections/Association/Bidirectional/OneToMany/ParentWithBidirectionalOneToMany.cs +++ b/src/NHibernate.Test/Events/Collections/Association/Bidirectional/OneToMany/ParentWithBidirectionalOneToMany.cs @@ -50,8 +50,7 @@ public override void RemoveChild(IChild child) if (!Children.Contains(child)) { ((ChildWithManyToOne)child).Parent = null; - } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Events/Collections/IChild.cs b/src/NHibernate.Test/Events/Collections/IChild.cs index 9841fe6679c..b170245df92 100644 --- a/src/NHibernate.Test/Events/Collections/IChild.cs +++ b/src/NHibernate.Test/Events/Collections/IChild.cs @@ -2,6 +2,6 @@ namespace NHibernate.Test.Events.Collections { public interface IChild { - string Name { get; set;} + string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Events/Collections/IEntity.cs b/src/NHibernate.Test/Events/Collections/IEntity.cs index 6e93ff045cd..4e5857fb389 100644 --- a/src/NHibernate.Test/Events/Collections/IEntity.cs +++ b/src/NHibernate.Test/Events/Collections/IEntity.cs @@ -2,6 +2,6 @@ namespace NHibernate.Test.Events.Collections { public interface IEntity { - long Id { get; set;} + long Id { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs b/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs index 5203c7c737f..3f51f902fad 100644 --- a/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs +++ b/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs @@ -12,14 +12,22 @@ public class MSSQLExceptionConverterExample : ISQLExceptionConverter public Exception Convert(AdoExceptionContextInfo exInfo) { - SqlException sqle = ADOExceptionHelper.ExtractDbException(exInfo.SqlException) as SqlException; - if(sqle != null) + var dbEx = ADOExceptionHelper.ExtractDbException(exInfo.SqlException); + if (dbEx is SqlException sqle) { if (sqle.Number == 547) return new ConstraintViolationException(exInfo.Message, sqle.InnerException, exInfo.Sql, null); if (sqle.Number == 208) return new SQLGrammarException(exInfo.Message, sqle.InnerException, exInfo.Sql); } + + if(dbEx is Microsoft.Data.SqlClient.SqlException msSqle) + { + if (msSqle.Number == 547) + return new ConstraintViolationException(exInfo.Message, msSqle.InnerException, exInfo.Sql, null); + if (msSqle.Number == 208) + return new SQLGrammarException(exInfo.Message, msSqle.InnerException, exInfo.Sql); + } return SQLStateConverter.HandledNonSpecificException(exInfo.SqlException, exInfo.Message, exInfo.Sql); } diff --git a/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs b/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs index b8c59551c07..22a18760584 100644 --- a/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs +++ b/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Data; using System.Data.Common; using NHibernate.Dialect; @@ -69,65 +68,68 @@ protected override void Configure(Cfg.Configuration configuration) [Test] public void IntegrityViolation() { - //ISQLExceptionConverter converter = Dialect.BuildSQLExceptionConverter(); ISQLExceptionConverter converter = Sfi.Settings.SqlExceptionConverter; - ISession session = OpenSession(); - session.BeginTransaction(); - var connection = session.Connection; - - // Attempt to insert some bad values into the T_MEMBERSHIP table that should - // result in a constraint violation - DbCommand ps = null; - try + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - ps = connection.CreateCommand(); - ps.CommandType = CommandType.Text; - ps.CommandText = "INSERT INTO T_MEMBERSHIP (user_id, group_id) VALUES (@p1, @p2)"; - var pr = ps.CreateParameter(); - pr.ParameterName = "p1"; - pr.DbType = DbType.Int64; - pr.Value = 52134241L; // Non-existent user_id - ps.Parameters.Add(pr); - - pr = ps.CreateParameter(); - pr.ParameterName = "p2"; - pr.DbType = DbType.Int64; - pr.Value = 5342L; // Non-existent group_id - ps.Parameters.Add(pr); - - session.Transaction.Enlist(ps); - ps.ExecuteNonQuery(); + var connection = session.Connection; - Assert.Fail("INSERT should have failed"); - } - catch (Exception sqle) - { - ADOExceptionReporter.LogExceptions(sqle, "Just output!!!!"); - Exception adoException = converter.Convert(new AdoExceptionContextInfo{SqlException = sqle}); - Assert.AreEqual(typeof(ConstraintViolationException), adoException.GetType(), - "Bad conversion [" + sqle.Message + "]"); - ConstraintViolationException ex = (ConstraintViolationException)adoException; - Console.WriteLine("Violated constraint name: " + ex.ConstraintName); - } - finally - { - if (ps != null) + // Attempt to insert some bad values into the T_MEMBERSHIP table that should + // result in a constraint violation + DbCommand ps = null; + try { - try - { - ps.Dispose(); - } - catch (Exception) + ps = connection.CreateCommand(); + ps.CommandType = CommandType.Text; + ps.CommandText = "INSERT INTO T_MEMBERSHIP (user_id, group_id) VALUES (@p1, @p2)"; + var pr = ps.CreateParameter(); + pr.ParameterName = "p1"; + pr.DbType = DbType.Int64; + pr.Value = 52134241L; // Non-existent user_id + ps.Parameters.Add(pr); + + pr = ps.CreateParameter(); + pr.ParameterName = "p2"; + pr.DbType = DbType.Int64; + pr.Value = 5342L; // Non-existent group_id + ps.Parameters.Add(pr); + + tran.Enlist(ps); + ps.ExecuteNonQuery(); + + Assert.Fail("INSERT should have failed"); + } + catch (Exception sqle) + { + ADOExceptionReporter.LogExceptions(sqle, "Just output!!!!"); + Exception adoException = converter.Convert(new AdoExceptionContextInfo { SqlException = sqle }); + Assert.AreEqual( + typeof(ConstraintViolationException), + adoException.GetType(), + "Bad conversion [" + sqle.Message + "]"); + ConstraintViolationException ex = (ConstraintViolationException) adoException; + Console.WriteLine("Violated constraint name: " + ex.ConstraintName); + } + finally + { + if (ps != null) { - // ignore... + try + { + ps.Dispose(); + } + catch (Exception) + { + // ignore... + } } } - } - session.Transaction.Rollback(); - session.Close(); + tran.Rollback(); + session.Close(); + } } [Test] diff --git a/src/NHibernate.Test/ExpressionTest/Projection/ProjectionSqlFixture.cs b/src/NHibernate.Test/ExpressionTest/Projection/ProjectionSqlFixture.cs index 4738aff6e94..4867332b8c4 100644 --- a/src/NHibernate.Test/ExpressionTest/Projection/ProjectionSqlFixture.cs +++ b/src/NHibernate.Test/ExpressionTest/Projection/ProjectionSqlFixture.cs @@ -81,7 +81,7 @@ public void QueryTest1() .Add(Projections.Max("Pay")) .Add(Projections.Min("Pay"))) ; - IList result = c.List();// c.UniqueResult(); + IList result = c.List(); // c.UniqueResult(); Assert.IsTrue(result.Count == 1, "More than one record was found, while just one was expected"); Assert.IsTrue(result[0] is object[], "expected object[] as result, but found " + result[0].GetType().Name); @@ -105,7 +105,7 @@ public void SelectSqlProjectionTest() new string[] { "MyPay" }, new IType[] { NHibernateUtil.Double }))); - IList result = c.List();// c.UniqueResult(); + IList result = c.List(); // c.UniqueResult(); Assert.IsTrue(result.Count == 1); object results = result[0]; Assert.AreEqual(results, 2.5); diff --git a/src/NHibernate.Test/ExpressionTest/QueryByExampleTest.cs b/src/NHibernate.Test/ExpressionTest/QueryByExampleTest.cs index 4ac51e3ab9c..d8cbe01b60a 100644 --- a/src/NHibernate.Test/ExpressionTest/QueryByExampleTest.cs +++ b/src/NHibernate.Test/ExpressionTest/QueryByExampleTest.cs @@ -96,7 +96,6 @@ public void TestJunctionNotExpressionQBE() ICriteria crit = s.CreateCriteria(typeof(Componentizable)); Example ex = Example.Create(master).EnableLike(); - crit.Add(Expression.Or(Expression.Not(ex), ex)); IList result = crit.List(); @@ -188,4 +187,4 @@ private Componentizable GetMaster(String name, String subName, String subName1) return master; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/ExpressionTest/SubQueries/Classes.cs b/src/NHibernate.Test/ExpressionTest/SubQueries/Classes.cs index 57c4bd0417d..d02bc616dc9 100644 --- a/src/NHibernate.Test/ExpressionTest/SubQueries/Classes.cs +++ b/src/NHibernate.Test/ExpressionTest/SubQueries/Classes.cs @@ -130,7 +130,6 @@ public virtual string PostTitle set { post_title = value; } } - public virtual Blog Blog { get { return _blog; } @@ -218,4 +217,4 @@ public ISet Posts set { posts = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs b/src/NHibernate.Test/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs index 622c1f45783..4d630773e4a 100644 --- a/src/NHibernate.Test/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs +++ b/src/NHibernate.Test/ExpressionTest/SubQueries/SubQueriesSqlFixture.cs @@ -45,7 +45,6 @@ protected override void OnSetUp() comment.IndexInPost = 0; post1.Comments.Add(comment); - session.Save(category); session.Save(author); session.Save(commenter); @@ -105,4 +104,4 @@ public void ComplexSubQuery_QueryingByGrandChildren() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Extralazy/Company.cs b/src/NHibernate.Test/Extralazy/Company.cs new file mode 100644 index 00000000000..728a5f85e8d --- /dev/null +++ b/src/NHibernate.Test/Extralazy/Company.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Test.Extralazy +{ + public class Company + { + protected Company() { } + + public Company(string name, int index, User owner) + { + Name = name; + Owner = owner; + OriginalIndex = ListIndex = index; + } + + public virtual int Id { get; set; } + + public virtual int ListIndex { get; set; } + + public virtual int OriginalIndex { get; set; } + + public virtual string Name { get; set; } + + public virtual User Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/Extralazy/CreditCard.cs b/src/NHibernate.Test/Extralazy/CreditCard.cs new file mode 100644 index 00000000000..ffeb98a831b --- /dev/null +++ b/src/NHibernate.Test/Extralazy/CreditCard.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Test.Extralazy +{ + public class CreditCard + { + protected CreditCard() { } + + public CreditCard(string name, int index, User owner) + { + Name = name; + Owner = owner; + OriginalIndex = ListIndex = index; + } + + public virtual int Id { get; set; } + + public virtual int ListIndex { get; set; } + + public virtual int OriginalIndex { get; set; } + + public virtual string Name { get; set; } + + public virtual User Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/Extralazy/ExtraLazyFixture.cs b/src/NHibernate.Test/Extralazy/ExtraLazyFixture.cs index 248abdf63e8..8b11af550fc 100644 --- a/src/NHibernate.Test/Extralazy/ExtraLazyFixture.cs +++ b/src/NHibernate.Test/Extralazy/ExtraLazyFixture.cs @@ -1,7 +1,10 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using NHibernate.Cfg; using NUnit.Framework; +using NUnit.Framework.Constraints; namespace NHibernate.Test.Extralazy { @@ -23,12 +26,2257 @@ protected override string CacheConcurrencyStrategy get { return null; } } + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Cfg.Environment.GenerateStatistics, "true"); + } + protected override void OnTearDown() { using (var s = OpenSession()) using (var t = s.BeginTransaction()) { - s.Delete("from System.Object"); + s.Delete("from System.Object"); + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListAdd(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Gavin's companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after companies count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after count"); + + // Test adding companies with ICollection interface + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Test adding companies with IList interface + Sfi.Statistics.Clear(); + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + // Returned value is not valid with lazy list, no check for it. + ((IList) gavin.Companies).Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after adding through IList"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through IList"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through IList"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding through IList"); + + // Check existence of added companies + Sfi.Statistics.Clear(); + // Have to skip unloaded (non-queued indeed) elements to avoid triggering existence queries on them. + foreach (var item in addedItems.Skip(5)) + { + Assert.That(gavin.Companies.Contains(item), Is.True, "Company '{0}' existence", item.Name); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of non-flushed"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after checking existence of non-flushed"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after checking existence of non-flushed"); + + // Check existence of not loaded companies + Assert.That(gavin.Companies.Contains(addedItems[0]), Is.True, "First company existence"); + Assert.That(gavin.Companies.Contains(addedItems[1]), Is.True, "Second company existence"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of unloaded"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking existence of unloaded"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after checking existence of unloaded"); + + // Check existence of not existing companies + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Contains(new Company("test1", 15, gavin)), Is.False, "First non-existent test"); + Assert.That(gavin.Companies.Contains(new Company("test2", 16, gavin)), Is.False, "Second non-existent test"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking non-existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking non-existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after checking non-existence"); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [Explicit] + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public void ListAddDuplicated(bool initialize, bool flush) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count before flush"); + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statement count after count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after get"); + + // Re-add items + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + gavin.Companies.Add(addedItems[i]); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statement count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after re-adding"); + + if (flush) + { + s.Flush(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after second flush"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after second flush"); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumeration"); + Assert.That(gavin.Companies.Count, Is.EqualTo(flush ? 5 : 10), "Companies count after enumeration"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after loading Gavin again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading Gavin again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListInsert(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after get"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after get"); + + // Test inserting companies at the start + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(0, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after insert"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after insert"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after insert"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after insert"); + + // Test inserting companies at the end + Sfi.Statistics.Clear(); + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after tail insert"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after tail insert"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after tail insert"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after tail insert"); + + // Try insert invalid indexes + Assert.Throws( + () => gavin.Companies.Insert(-1, new Company("c-1", -1, gavin)), "inserting at -1"); + Assert.Throws( + () => gavin.Companies.Insert(20, new Company("c20", 20, gavin)), "inserting too far"); + + // Check existence of added companies + Sfi.Statistics.Clear(); + // Have to skip unloaded (non-queued indeed) elements to avoid triggering existence queries on them. + foreach (var item in addedItems.Skip(5)) + { + Assert.That(gavin.Companies.Contains(item), Is.True, "Company '{0}' existence", item.Name); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after existence check"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after existence check"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after existence check"); + + // Check existence of not loaded companies + Assert.That(gavin.Companies.Contains(addedItems[0]), Is.True, "First company existence"); + Assert.That(gavin.Companies.Contains(addedItems[1]), Is.True, "Second company existence"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after unloaded existence check"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after unloaded existence check"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after unloaded existence check"); + + // Check existence of not existing companies + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Contains(new Company("test1", 15, gavin)), Is.False, "First non-existence test"); + Assert.That(gavin.Companies.Contains(new Company("test2", 16, gavin)), Is.False, "Second non-existence test"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after non-existence check"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after non-existence check"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after non-existence check"); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumeration"); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after enumeration"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Companies count after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [Explicit] + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public void ListInsertDuplicated(bool initialize, bool flush) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count before flush"); + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after get"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after count"); + + // Re-add items + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + gavin.Companies.Insert(4 - i, addedItems[i]); + } + + Assert.That(gavin.Companies[0].ListIndex, Is.EqualTo(4), "Company at 0"); + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after re-insert"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-insert"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after re-insert"); + + if (flush) + { + s.Flush(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after flush"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after flush"); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumeration"); + Assert.That(gavin.Companies.Count, Is.EqualTo(flush ? 5 : 10), "Companies count after enumeration"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Companies count after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListRemoveAt(bool initialize) + { + User gavin; + var addedItems = new List(); + var finalIndexOrder = new List {0, 1, 2, 6, 8, 9}; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + // Add transient companies + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Remove transient companies + Sfi.Statistics.Clear(); + gavin.Companies.RemoveAt(5); + gavin.Companies.RemoveAt(6); + + Assert.That(gavin.Companies.Count, Is.EqualTo(8), "Gavin's companies count after removing 2 transient companies"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing transient companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing transient companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing transient companies"); + + // Remove persisted companies + Sfi.Statistics.Clear(); + gavin.Companies.RemoveAt(3); + gavin.Companies.RemoveAt(3); + + Assert.That(gavin.Companies.Count, Is.EqualTo(6), "Gavin's companies count after removing 2 persisted companies"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing persisted companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after removing persisted companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing persisted companies"); + + // Try remove invalid indexes + Assert.Throws(() => gavin.Companies.RemoveAt(-1), "Removing at -1"); + Assert.Throws(() => gavin.Companies.RemoveAt(8), "Removing too far"); + + // Check existence of companies + Sfi.Statistics.Clear(); + var removedIndexes = new HashSet {3, 4, 5, 7}; + for (var i = 0; i < addedItems.Count; i++) + { + Assert.That( + gavin.Companies.Contains(addedItems[i]), + removedIndexes.Contains(i) ? Is.False : (IResolveConstraint) Is.True, + $"Element at index {i} was {(removedIndexes.Contains(i) ? "not " : "")}removed"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Gavin's companies count after checking existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Flushes count after checking existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization after checking existence"); + + // Check existence of not existing companies + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Contains(new Company("test1", 15, gavin)), Is.False, "Checking existence of non-existence"); + Assert.That(gavin.Companies.Contains(new Company("test2", 16, gavin)), Is.False, "Checking existence of non-existence"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Gavin's companies count after checking non-existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Flushes count after checking non-existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization after checking non-existence"); + + gavin.UpdateCompaniesIndexes(); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(6), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(6), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListGetSet(bool initialize) + { + User gavin; + var addedItems = new List(); + var finalIndexOrder = new List {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + // Add transient companies + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding companies"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding companies"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Compare all items + Sfi.Statistics.Clear(); + for (var i = 0; i < 10; i++) + { + Assert.That(gavin.Companies[i], Is.EqualTo(addedItems[i]), "Comparing added company at index {0}", i); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding comparing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding comparing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after comparing"); + + // Try get invalid indexes + Assert.Throws(() => + { + var item = gavin.Companies[10]; + }, "Get too far"); + Assert.Throws(() => + { + var item = gavin.Companies[-1]; + }, "Get at -1"); + + // Try set invalid indexes + Assert.Throws(() => gavin.Companies[10] = addedItems[0], "Set too far"); + Assert.Throws(() => gavin.Companies[-1] = addedItems[0], "Set at -1"); + + // Swap transient and persisted indexes + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var hiIndex = 9 - i; + var tmp = gavin.Companies[i]; + gavin.Companies[i] = gavin.Companies[hiIndex]; + gavin.Companies[hiIndex] = tmp; + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after swapping"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after swapping"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(10), "Statements count after adding swapping"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after swapping"); + + // Check indexes + Sfi.Statistics.Clear(); + for (var i = 0; i < 10; i++) + { + Assert.That(gavin.Companies[i].ListIndex, Is.EqualTo(finalIndexOrder[i]), "Comparing company ListIndex at index {0}", i); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after comparing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after comparing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after comparing"); + + gavin.UpdateCompaniesIndexes(); + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListFlush(bool initialize) + { + User gavin; + var addedItems = new List(); + var finalIndexOrder = Enumerable.Range(0, 13).ToList(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + // Add transient companies with Add + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Add transient companies with Insert + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Insert(i, item); + } + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "INSERT \n INTO"), Is.EqualTo(5), "Statements count after inserting"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after inserting 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after inserting"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after inserting"); + + // Add transient companies with Add + Sfi.Statistics.Clear(); + for (var i = 15; i < 20; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems.Add(item); + gavin.Companies.Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(20), "Gavin's companies count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding"); + + // Remove last 5 transient companies + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + for (var i = 15; i < 20; i++) + { + Assert.That(gavin.Companies.Remove(addedItems[i]), Is.True, "Removing transient company at index {0}", i); + } + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "INSERT \n INTO"), Is.EqualTo(10), "Statements count after removing"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after removing 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing"); + + // Remove last 5 transient companies + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + for (var i = 10; i < 15; i++) + { + gavin.Companies.RemoveAt(10); + } + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "DELETE \n FROM"), Is.EqualTo(5), "Statements count after second removing"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(10), "Gavin's companies count after second removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after second removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(7), "Statements count after second removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after second removing"); + + // Add transient companies with Add + Sfi.Statistics.Clear(); + for (var i = 10; i < 15; i++) + { + var item = new Company($"c{i}", i, gavin); + addedItems[i] = item; + // NOTE: the returned index is currently invalid due to extra-lazy avoiding to query the count or initializing the collection + ((IList) gavin.Companies).Add(item); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(15), "Gavin's companies count after adding through IList"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through IList"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through IList"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after adding through IList"); + + // Remove last transient company + Sfi.Statistics.Clear(); + using (var sqlLog = new SqlLogSpy()) + { + Assert.That(gavin.Companies.Remove(addedItems[14]), Is.EqualTo(true), "Removing last transient company"); + var log = sqlLog.GetWholeLog(); + Assert.That(FindAllOccurrences(log, "DELETE \n FROM"), Is.EqualTo(5), "Delete statements count after removing last transient company"); + Assert.That(FindAllOccurrences(log, "INSERT \n INTO"), Is.EqualTo(5), "Insert statements count after removing last transient company"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(14), "Gavin's companies count after adding removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing"); + + // Test index getter + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies[0], Is.EqualTo(addedItems[0]), "Comparing first item with index getter"); + + Assert.That(gavin.Companies.Count, Is.EqualTo(14), "Gavin's companies count after adding comparing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after comparing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Statements count after comparing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after comparing"); + + // Remove last transient company + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Remove(addedItems[13]), Is.EqualTo(true), "Removing last transient company"); + + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Gavin's companies count after adding repeated removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after repeated removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after repeated removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after repeated removing"); + + // Test index setter + Sfi.Statistics.Clear(); + gavin.Companies[0] = addedItems[0]; + + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Gavin's companies count after setting first item"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after setting first item"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Statements count after setting first item"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after setting first item"); + + // Test manual flush after remove + Sfi.Statistics.Clear(); + gavin.Companies.RemoveAt(12); + using (var sqlLog = new SqlLogSpy()) + { + s.Flush(); + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "DELETE \n FROM"), Is.EqualTo(1), "Delete statements count after removing at 12 index"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(12), "Gavin's companies count after removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after removing"); + + // Test manual flush after insert + Sfi.Statistics.Clear(); + gavin.Companies.Add(new Company("c12", 12, gavin)); + using (var sqlLog = new SqlLogSpy()) + { + s.Flush(); + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "INSERT \n INTO"), Is.EqualTo(1), "Insert statements count after flushing"); + } + + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Gavin's companies count after flushing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(1), "Flushes count after flushing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after flushing"); + + for (var i = 0; i < gavin.Companies.Count; i++) + { + Assert.That(gavin.Companies[i].ListIndex, Is.EqualTo(i), "Comparing company ListIndex at index {0}", i); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.ListIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(13), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.ListIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListClear(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new CreditCard($"c{i}", i, gavin); + addedItems.Add(item); + gavin.CreditCards.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = FlushMode.Commit; + + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + var collection = gavin.CreditCards; + + // Add transient credit cards + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + var item = new CreditCard($"c{i}", i, gavin); + addedItems.Add(item); + collection.Insert(i, item); + } + + Assert.That(collection.Count, Is.EqualTo(10), "Gavin's credit cards count after inserting 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after inserting"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after inserting"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after inserting"); + + Sfi.Statistics.Clear(); + collection.Clear(); + + Assert.That(collection.Count, Is.EqualTo(0), "Gavin's credit cards count after clearing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after clearing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after clearing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after clearing"); + + // Re-add two not loaded and two transient credit cards + collection.Add(addedItems[0]); + collection.Add(addedItems[1]); + collection.Add(addedItems[5]); + collection.Add(addedItems[6]); + + Assert.That(collection.Count, Is.EqualTo(4), "Gavin's credit cards count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after re-adding"); + + // Remove one not loaded and one transient credit cards + Assert.That(collection.Remove(addedItems[1]), Is.True, "Removing not loaded credit card"); + Assert.That(collection.Remove(addedItems[6]), Is.True, "Removing transient credit card"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's credit cards count after removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after removing"); + + // Remove not existing items + Assert.That(collection.Remove(addedItems[1]), Is.False, "Removing not-existing credit card"); + Assert.That(collection.Remove(addedItems[6]), Is.False, "Removing not-existing credit card"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's credit cards count after not-existing removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after not-existing removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after not-existing removing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after not-existing removing"); + + if (initialize) + { + using (var e = collection.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.True, "Credit cards initialization status after enumerating"); + Assert.That(collection.Count, Is.EqualTo(2), "Credit cards count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + var collection = gavin.CreditCards; + // As the cascade option is set to all, the clear operation will only work on + // transient credit cards + Assert.That(collection.Count, Is.EqualTo(6), "Credit cards count after loading again Gavin"); + for (var i = 0; i < 10; i++) + { + Assert.That(collection.Contains(addedItems[i]), i < 6 ? Is.True : (IResolveConstraint) Is.False, "Checking existence for item at {0}", i); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Credit cards initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void ListIndexOperations(bool initialize) + { + User gavin; + var finalIndexOrder = new List {6, 0, 4}; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Company($"c{i}", i, gavin); + gavin.Companies.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Current tracker state: + // Indexes: 0,1,2,3,4 + // Queue: / + // RemoveDbIndexes: / + + Sfi.Statistics.Clear(); + Assert.That(gavin.Companies.Count, Is.EqualTo(5), "Gavin's companies count"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status"); + + Sfi.Statistics.Clear(); + gavin.Companies.Insert(1, new Company("c5", 5, gavin)); + // Current tracker state: + // Indexes: 0,5,1,2,3,4 + // Queue: {1, 5} + // RemoveDbIndexes: / + + gavin.Companies.Insert(0, new Company("c6", 6, gavin)); + // Current tracker state: + // Indexes: 6,0,5,1,2,3,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: / + + gavin.Companies.RemoveAt(4); + // Current tracker state: + // Indexes: 6,0,5,1,3,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: 2 + + gavin.Companies.RemoveAt(3); + // Current tracker state: + // Indexes: 6,0,5,3,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: 1,2 + + gavin.Companies.RemoveAt(3); + // Current tracker state: + // Indexes: 6,0,5,4 + // Queue: {0, 6}, {2, 5} + // RemoveDbIndexes: 1,2,3 + + gavin.Companies.RemoveAt(2); + // Current tracker state: + // Indexes: 6,0,4 + // Queue: {0, 6} + // RemoveDbIndexes: 1,2,3 + + Assert.That(gavin.Companies.Count, Is.EqualTo(3), "Gavin's companies count after remove/insert operations"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after remove/insert operations"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(3), "Statements count after remove/insert operations"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.False, "Companies initialization status after remove/insert operations"); + + gavin.UpdateCompaniesIndexes(); + + for (var i = 0; i < gavin.Companies.Count; i++) + { + Assert.That(gavin.Companies[i].OriginalIndex, Is.EqualTo(finalIndexOrder[i]), "Comparing company index at {0}", i); + } + + if (initialize) + { + using (var e = gavin.Companies.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after enumerating"); + Assert.That(gavin.Companies.Count, Is.EqualTo(3), "Companies count after enumerating"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Companies.Count, Is.EqualTo(3), "Companies count after loading again Gavin"); + Assert.That(gavin.Companies.Select(o => o.OriginalIndex), Is.EquivalentTo(finalIndexOrder), "Companies indexes after loading again"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Companies), Is.True, "Companies initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void SetAdd(bool initialize) + { + User gavin; + Document hia; + Document hia2; + var addedDocuments = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + hia = new Document("HiA", "blah blah blah", gavin); + hia2 = new Document("HiA2", "blah blah blah blah", gavin); + gavin.Documents.Add(hia); + gavin.Documents.Add(hia2); + s.Persist(gavin); + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Count, Is.EqualTo(2), "Gavin's documents count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding"); + + // Test adding documents with ISet interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"document{i}", $"content{i}", gavin); + addedDocuments.Add(document); + Assert.That(gavin.Documents.Add(document), Is.True, "Adding document through ISet"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(7), "Gavin's documents count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding"); + + // Test adding documents with ICollection interface + Sfi.Statistics.Clear(); + var documents = (ICollection) gavin.Documents; + for (var i = 0; i < 5; i++) + { + var document = new Document($"document2{i}", $"content{i}", gavin); + addedDocuments.Add(document); + documents.Add(document); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after adding through ICollection<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through ICollection<>"); + // In this case we cannot determine whether the entities are transient or not so + // we are forced to check the database + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding through ICollection<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding through ICollection<>"); + + // Test re-adding documents with ISet interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Add(addedDocuments[i]), Is.False, "Re-add document through ISet<>"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after re-adding"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after re-adding"); + + // Test re-adding documents with ICollection interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + documents.Add(addedDocuments[i]); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after re-adding through ICollection<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding through ICollection<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding through ICollection<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after re-adding through ICollection<>"); + + // Check existence of added documents + Sfi.Statistics.Clear(); + foreach (var document in addedDocuments) + { + Assert.That(gavin.Documents.Contains(document), Is.True, "Checking existence of an existing document"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after checking existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after checking existence"); + + // Check existence of not loaded documents + Assert.That(gavin.Documents.Contains(hia), Is.True, "Checking existence of not loaded document"); + Assert.That(gavin.Documents.Contains(hia2), Is.True, "Checking existence of not loaded document"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after checking existence"); + + // Check existence of not existing documents + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Contains(new Document("test1", "content", gavin)), Is.False, "Checking existence of not-existing document"); + Assert.That(gavin.Documents.Contains(new Document("test2", "content", gavin)), Is.False, "Checking existence of not-existing document"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking non-existence"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking non-existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after checking non-existence"); + + // Test adding not loaded documents + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Add(hia), Is.False, "Adding not loaded element"); + documents.Add(hia); + + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Gavin's documents count after adding not loaded element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding not loaded element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after adding not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding not loaded element"); + + if (initialize) + { + using (var e = gavin.Documents.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.True, "Documents initialization status after enumerating"); + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Documents count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Documents.Count, Is.EqualTo(12), "Documents count after loading again Gavin"); + Assert.That(gavin.Documents.Contains(hia2), Is.True, "Checking not loaded element"); + Assert.That(gavin.Documents.Contains(hia), Is.True, "Checking not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after loading again"); + + t.Commit(); + } + } + + [Test] + public void SetAddWithOverrideEquals() + { + User gavin; + User robert; + User tom; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + robert = new User("robert", "secret"); + tom = new User("tom", "secret"); + s.Persist(gavin); + s.Persist(robert); + s.Persist(tom); + + gavin.Followers.Add(new UserFollower(gavin, robert)); + gavin.Followers.Add(new UserFollower(gavin, tom)); + robert.Followers.Add(new UserFollower(robert, tom)); + + Assert.That(gavin.Followers.Count, Is.EqualTo(2), "Gavin's documents count after adding 2"); + Assert.That(robert.Followers.Count, Is.EqualTo(1), "Robert's followers count after adding one"); + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + robert = s.Get("robert"); + tom = s.Get("tom"); + + // Re-add + Assert.That(gavin.Followers.Add(new UserFollower(gavin, robert)), Is.False, "Re-adding element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after re-adding"); + Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Gavin's followers count after re-adding"); + + // Add new + Assert.That(robert.Followers.Add(new UserFollower(robert, gavin)), Is.True, "Adding element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after adding"); + Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Robert's followers count after re-adding"); + } + } + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public void SetAddDuplicated(bool initialize, bool flush) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new Document($"d{i}", $"c{i}", gavin); + addedItems.Add(item); + gavin.Documents.Add(item); + gavin.Documents.Add(item); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after adding 5"); + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Title); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after reload"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after reload"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after reload"); + + // Re-add items + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Add(addedItems[i]), Is.False, "Re-adding element"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after re-adding"); + + if (flush) + { + s.Flush(); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after flushing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after flushing"); + } + + if (initialize) + { + using (var e = gavin.Documents.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.True, "Documents initialization status after enumerating"); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Documents count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Documents count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void SetAddTransient(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p{i}", gavin); + addedItems.Add(item); + gavin.Permissions.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = FlushMode.Commit; + + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Permissions.Count, Is.EqualTo(5), "Gavin's permissions count after adding 5"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after adding"); + + // Test adding permissions with ICollection interface + Sfi.Statistics.Clear(); + var items = (ICollection) gavin.Permissions; + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p2{i}", gavin); + addedItems.Add(item); + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after adding through ICollection<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through ICollection<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through ICollection<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after adding through ICollection<>"); + + // Test re-adding permissions with ICollection interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Skip(5)) + { + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after re-adding"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding"); + + // Test adding not loaded permissions with ICollection interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Take(5)) + { + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after re-adding not loaded elements"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding not loaded elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after re-adding not loaded elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding not loaded elements"); + + // Test adding loaded permissions with ICollection interface + Sfi.Statistics.Clear(); + foreach (var item in s.Query()) + { + items.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(10), "Gavin's permissions count after re-adding loaded elements"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding loaded elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(6), "Statements count after re-adding loaded elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding loaded elements"); + + // Test adding permissions with ISet interface + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p3{i}", gavin); + addedItems.Add(item); + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after adding through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after adding through ISet<>"); + + // Test re-adding permissions with ISet interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Skip(10)) + { + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after re-adding through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding through ISet<>"); + + // Test adding not loaded permissions with ISet interface + Sfi.Statistics.Clear(); + foreach (var item in addedItems.Take(5)) + { + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after re-adding not loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding not loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after re-adding not loaded permissions through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding not loaded permissions through ISet<>"); + + // Test adding loaded permissions with ISet interface + Sfi.Statistics.Clear(); + foreach (var item in s.Query()) + { + gavin.Permissions.Add(item); + } + + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Gavin's permissions count after re-adding loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding loaded permissions through ISet<>"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(6), "Statements count after re-adding loaded permissions through ISet<>"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after re-adding loaded permissions through ISet<>"); + + if (initialize) + { + using (var e = gavin.Permissions.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.True, "Permissions initialization status after enumerating"); + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Permissions count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Permissions.Count, Is.EqualTo(15), "Permissions count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Permissions), Is.False, "Permissions initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void SetRemove(bool initialize) + { + User gavin; + var addedDocuments = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + for (var i = 0; i < 5; i++) + { + var document = new Document($"document{i}", $"content{i}", gavin); + addedDocuments.Add(document); + gavin.Documents.Add(document); + } + + s.Persist(gavin); + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedDocuments[i] = s.Get(addedDocuments[i].Title); + } + + Sfi.Statistics.Clear(); + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after refresh"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after refresh"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after refresh"); + + // Add new documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"document2{i}", $"content{i}", gavin); + addedDocuments.Add(document); + ((ICollection)gavin.Documents).Add(document); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(10), "Gavin's documents count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after adding"); + + // Test removing existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.True, "Removing existing document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after removing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after removing"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing"); + + // Test removing removed existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + Assert.That(gavin.Documents.Contains(addedDocuments[i]), Is.False, "Checking existence of a removed document"); + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.False, "Removing removed document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after removing removed documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing removed documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing removed documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing removed documents"); + + // Test removing not existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"test{i}", "content", gavin); + Assert.That(gavin.Documents.Remove(document), Is.False, "Removing not existing document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(5), "Gavin's documents count after removing not existing documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing not existing documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after removing not existing documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing not existing documents"); + + // Test removing newly added documents + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + Assert.That(gavin.Documents.Contains(addedDocuments[i]), Is.True, "Checking existence of an added document"); + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.True, "Removing added document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Gavin's documents count after removing added documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing added documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing added documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing added documents"); + + // Test removing removed newly added documents + Sfi.Statistics.Clear(); + for (var i = 5; i < 10; i++) + { + Assert.That(gavin.Documents.Contains(addedDocuments[i]), Is.False, "Checking existence of a removed document"); + Assert.That(gavin.Documents.Remove(addedDocuments[i]), Is.False, "Removing removed document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Gavin's documents count after removing removed documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing removed documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing removed documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing removed documents"); + + // Test removing not existing documents + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var document = new Document($"test{i}", "content", gavin); + Assert.That(gavin.Documents.Remove(document), Is.False, "Removing not existing document"); + } + + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Gavin's documents count after removing not existing documents"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing not existing documents"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after removing not existing documents"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after removing not existing documents"); + + if (initialize) + { + using (var e = gavin.Documents.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.True, "Documents initialization status after enumerating"); + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Documents count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Documents.Count, Is.EqualTo(0), "Documents count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Documents), Is.False, "Documents initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void SetClear(bool initialize) + { + User gavin; + var addedItems = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p{i}", gavin); + addedItems.Add(item); + gavin.Permissions.Add(item); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.FlushMode = FlushMode.Commit; + + gavin = s.Get("gavin"); + // Refresh added items + for (var i = 0; i < 5; i++) + { + addedItems[i] = s.Get(addedItems[i].Id); + } + + var collection = gavin.Permissions; + + Sfi.Statistics.Clear(); + Assert.That(collection.Count, Is.EqualTo(5), "Gavin's permissions count after refresh"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after refresh"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after refresh"); + + // Add transient permissions + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + var item = new UserPermission($"p2{i}", gavin); + addedItems.Add(item); + collection.Add(item); + } + + Assert.That(collection.Count, Is.EqualTo(10), "Gavin's permissions count after adding 5"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after adding"); + + Sfi.Statistics.Clear(); + collection.Clear(); + + Assert.That(collection.Count, Is.EqualTo(0), "Gavin's permissions count after flushing"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after flushing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after flushing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after flushing"); + + // Re-add two not loaded and two transient permissions + Assert.That(collection.Add(addedItems[0]), Is.True, "Re-adding not loaded element"); + Assert.That(collection.Add(addedItems[1]), Is.True, "Re-adding not loaded element"); + Assert.That(collection.Add(addedItems[5]), Is.True, "Re-adding transient element"); + Assert.That(collection.Add(addedItems[6]), Is.True, "Re-adding transient element"); + + Assert.That(collection.Count, Is.EqualTo(4), "Gavin's permissions count after re-adding"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after re-adding"); + + // Remove one not loaded and one transient permissions + Assert.That(collection.Remove(addedItems[1]), Is.True, "Removing not loaded element"); + Assert.That(collection.Remove(addedItems[6]), Is.True, "Removing transient element"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's permissions count after removing"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after removing"); + + // Remove not existing items + Assert.That(collection.Remove(addedItems[1]), Is.False, "Removing removed element"); + Assert.That(collection.Remove(addedItems[6]), Is.False, "Removing removed element"); + + Assert.That(collection.Count, Is.EqualTo(2), "Gavin's permissions count after removing removed elements"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing removed elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing removed elements"); + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after removing removed elements"); + + if (initialize) + { + using (var e = collection.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.True, "Permissions initialization status after enumerating"); + Assert.That(collection.Count, Is.EqualTo(2), "Permissions count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + var collection = gavin.Permissions; + // As the cascade option is set to all, the clear operation will only work on + // transient permissions + Assert.That(collection.Count, Is.EqualTo(6), "Permissions count after loading again Gavin"); + for (var i = 0; i < 10; i++) + { + Assert.That(collection.Contains(addedItems[i]), i < 6 ? Is.True : (IResolveConstraint) Is.False, + "Checking existence of added element at {0}", i); + } + + Assert.That(NHibernateUtil.IsInitialized(collection), Is.False, "Permissions initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void MapAdd(bool initialize) + { + User gavin; + UserSetting setting; + var addedSettings = new List(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s{i}", $"data{i}", gavin); + addedSettings.Add(setting); + gavin.Settings.Add(setting.Name, setting); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Count, Is.EqualTo(5), "Gavin's user settings count after load"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after load"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after load"); + + // Test adding settings with Add method + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + addedSettings.Add(setting); + gavin.Settings.Add(setting.Name, setting); + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding"); + + // Test adding settings with [] + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s3{i}", $"data{i}", gavin); + addedSettings.Add(setting); + + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(15), "Gavin's user settings count after adding 5 through indexer"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding through indexer"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding through indexer"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding through indexer"); + + // Check existence of added settings + Sfi.Statistics.Clear(); + foreach (var item in addedSettings.Skip(5)) + { + Assert.That(gavin.Settings.ContainsKey(item.Name), Is.True, "Checking existence of added element"); + Assert.That(gavin.Settings.Contains(new KeyValuePair(item.Name, item)), Is.True, "Checking existence of added element using KeyValuePair<,>"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of added elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after checking existence of added elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after checking existence of added elements"); + + // Check existence of not loaded settings + foreach (var item in addedSettings.Take(5)) + { + Assert.That(gavin.Settings.ContainsKey(item.Name), Is.True, "Checking key existence of not loaded elements"); + } + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of not loaded elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after checking existence of not loaded elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after checking existence of not loaded elements"); + + // Check existence of not existing settings + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.ContainsKey("test"), Is.False, "Checking existence of not existing element"); + Assert.That(gavin.Settings.ContainsKey("test2"), Is.False, "Checking existence of not existing element"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after checking existence of not existing elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after checking existence of not existing elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after checking existence of not existing elements"); + + // Try to add an existing setting + Assert.Throws(() => gavin.Settings.Add("s0", new UserSetting("s0", "data", gavin)), "Adding an existing key"); + Assert.Throws(() => gavin.Settings.Add("s20", new UserSetting("s20", "data", gavin)), "Adding an existing key"); + Assert.Throws(() => gavin.Settings.Add("s30", new UserSetting("s30", "data", gavin)), "Adding an existing key"); + + // Get values of not loaded keys + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.TryGetValue("s0", out setting), Is.True, "Getting value of not loaded key"); + Assert.That(setting.Id, Is.EqualTo(addedSettings[0].Id), "Comparing retrieved element id"); + Assert.That(gavin.Settings["s0"].Id, Is.EqualTo(addedSettings[0].Id), "Comparing retrieved element id by indexer"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after reading elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2), "Statements count after reading elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after reading elements"); + + // Get values of newly added keys + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.TryGetValue("s20", out setting), Is.True, "Getting value of a newly added key"); + Assert.That(setting, Is.EqualTo(addedSettings[5]), "Comparing retrieved element"); + Assert.That(gavin.Settings["s20"], Is.EqualTo(addedSettings[5]), "Comparing retrieved element by indexer"); + Assert.That(gavin.Settings.TryGetValue("s30", out setting), Is.True, "Getting value of a newly added key"); + Assert.That(setting, Is.EqualTo(addedSettings[10]), "Comparing retrieved element"); + Assert.That(gavin.Settings["s30"], Is.EqualTo(addedSettings[10]), "Getting value of a newly added key"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after reading elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after reading elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after reading elements"); + + // Try to get a non existing setting + Assert.That(gavin.Settings.TryGetValue("test", out setting), Is.False, "Try to get a not existing key"); + Assert.That(gavin.Settings.TryGetValue("test2", out setting), Is.False, "Try to get a not existing key"); + Assert.Throws(() => + { + setting = gavin.Settings["test"]; + }, "Getting a not existing key"); + Assert.Throws(() => + { + setting = gavin.Settings["test2"]; + }, "Getting a not existing key"); + + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after reading not existing elements"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(4), "Statements count after reading not existing elements"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after reading not existing elements"); + + if (initialize) + { + using (var e = gavin.Settings.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.True, "User settings initialization status after enumerating"); + Assert.That(gavin.Settings.Count, Is.EqualTo(15), "User settings count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Settings.Count, Is.EqualTo(15), "User settings count after loading again Gavin"); + Assert.That(gavin.Settings.ContainsKey(addedSettings[0].Name), Is.True, "Checking key existence"); + Assert.That(gavin.Settings.ContainsKey(addedSettings[5].Name), Is.True, "Checking key existence"); + Assert.That(gavin.Settings.ContainsKey(addedSettings[10].Name), Is.True, "Checking key existence"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void MapSet(bool initialize) + { + User gavin; + UserSetting setting; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s{i}", $"data{i}", gavin); + gavin.Settings.Add(setting.Name, setting); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Count, Is.EqualTo(5), "Gavin's user settings count after load"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after load"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after load"); + + // Set a key that does not exist in db and is not in the queue + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding"); + + // Set a key that does not exist in db and it is in the queue + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after re-adding existing keys"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding existing keys"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding existing keys"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after re-adding existing keys"); + + // Set a key that exists in db and is not in the queue + Sfi.Statistics.Clear(); + gavin.Settings["s0"] = new UserSetting("s0", "s0", gavin); + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after re-adding a not loaded key"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding a not loaded key"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after re-adding a not loaded key"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after re-adding a not loaded key"); + + // Set a key that exists in db and it is in the queue + Sfi.Statistics.Clear(); + gavin.Settings["s0"] = new UserSetting("s0", "s0", gavin); + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after re-adding a loaded key"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after re-adding a loaded key"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after re-adding a loaded key"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after re-adding a loaded key"); + + // Set a key that exists in db and it is in the removal queue + Assert.That(gavin.Settings.Remove("s1"), Is.True, "Removing an existing key"); + Sfi.Statistics.Clear(); + gavin.Settings["s1"] = new UserSetting("s1", "s1", gavin); + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after removing an existing key"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing an existing key"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing an existing key"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing an existing key"); + + if (initialize) + { + using (var e = gavin.Settings.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.True, "User settings initialization status after enumerating"); + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "User settings count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "User settings count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading again"); + + t.Commit(); + } + } + + [TestCase(false)] + [TestCase(true)] + public void MapRemove(bool initialize) + { + User gavin; + UserSetting setting; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = new User("gavin", "secret"); + s.Persist(gavin); + + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s{i}", $"data{i}", gavin); + gavin.Settings.Add(setting.Name, setting); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Count, Is.EqualTo(5), "Gavin's user settings count after loading"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after loading"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading"); + + Sfi.Statistics.Clear(); + for (var i = 0; i < 5; i++) + { + setting = new UserSetting($"s2{i}", $"data{i}", gavin); + gavin.Settings[setting.Name] = setting; + } + + Assert.That(gavin.Settings.Count, Is.EqualTo(10), "Gavin's user settings count after adding 5"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after adding"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(5), "Statements count after adding"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after adding"); + + // Remove a key that exists in db and is not in the queue and removal queue + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s0"), Is.True, "Removing an existing element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(9), "Gavin's user settings count after removing a not loaded element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing a not loaded element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after removing a not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing a not loaded element"); + + // Remove a key that exists in db and it is in the queue + var item = gavin.Settings["s1"]; + Assert.That(gavin.Settings.Remove("s1"), Is.True, "Removing an existing element"); + gavin.Settings.Add(item.Name, item); + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s1"), Is.True, "Removing a re-added element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(8), "Gavin's user settings count after removing a re-added element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing a re-added element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing a re-added element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing a re-added element"); + + // Remove a key that does not exist in db and is not in the queue + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("test"), Is.False, "Removing not existing element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(8), "Gavin's user settings count after removing not existing element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing not existing element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1), "Statements count after removing not existing element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing not existing element"); + + // Remove a key that does not exist in db and it is in the queue + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s20"), Is.True); + + Assert.That(gavin.Settings.Count, Is.EqualTo(7), "Gavin's user settings count after removing an existing element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing an existing element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing an existing element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing an existing element"); + + // Remove a key that exists in db and it is in the removal queue + Assert.That(gavin.Settings.Remove("s2"), Is.True, "Removing not loaded element"); + Sfi.Statistics.Clear(); + Assert.That(gavin.Settings.Remove("s2"), Is.False, "Removing removed element"); + + Assert.That(gavin.Settings.Count, Is.EqualTo(6), "Gavin's user settings count after removing a not loaded element"); + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(0), "Flushes count after removing a not loaded element"); + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0), "Statements count after removing a not loaded element"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after removing a not loaded element"); + + if (initialize) + { + using (var e = gavin.Settings.GetEnumerator()) + { + e.MoveNext(); + } + + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.True, "User settings initialization status after enumerating"); + Assert.That(gavin.Settings.Count, Is.EqualTo(6), "User settings count after enumerating"); + } + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gavin = s.Get("gavin"); + Assert.That(gavin.Settings.Count, Is.EqualTo(6), "User settings count after loading again Gavin"); + Assert.That(NHibernateUtil.IsInitialized(gavin.Settings), Is.False, "User settings initialization status after loading again"); + t.Commit(); } } @@ -384,5 +2632,20 @@ public void AddToUninitializedSetWithLaterLazyLoad() t.Commit(); } } + + private int FindAllOccurrences(string source, string substring) + { + if (source == null) + { + return 0; + } + int n = 0, count = 0; + while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1) + { + n += substring.Length; + ++count; + } + return count; + } } } diff --git a/src/NHibernate.Test/Extralazy/Photo.cs b/src/NHibernate.Test/Extralazy/Photo.cs index b18bc390c02..38562cae427 100644 --- a/src/NHibernate.Test/Extralazy/Photo.cs +++ b/src/NHibernate.Test/Extralazy/Photo.cs @@ -6,7 +6,6 @@ namespace NHibernate.Test.Extralazy { public class Photo { - protected Photo() {} public Photo(string title, User owner) { diff --git a/src/NHibernate.Test/Extralazy/User.cs b/src/NHibernate.Test/Extralazy/User.cs index 5279d6d0dd6..b4f174e3243 100644 --- a/src/NHibernate.Test/Extralazy/User.cs +++ b/src/NHibernate.Test/Extralazy/User.cs @@ -40,11 +40,28 @@ public virtual ISet Documents set { documents = value; } } - public virtual ISet Photos { get { return photos; } set { photos = value; } } + + public virtual IDictionary Settings { get; set; } = new Dictionary(); + + public virtual ISet Permissions { get; set; } = new HashSet(); + + public virtual ISet Followers { get; set; } = new HashSet(); + + public virtual IList Companies { get; set; } = new List(); + + public virtual IList CreditCards { get; set; } = new List(); + + public virtual void UpdateCompaniesIndexes() + { + for (var i = 0; i < Companies.Count; i++) + { + Companies[i].ListIndex = i; + } + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Extralazy/UserFollower.cs b/src/NHibernate.Test/Extralazy/UserFollower.cs new file mode 100644 index 00000000000..8c3ba8f48b4 --- /dev/null +++ b/src/NHibernate.Test/Extralazy/UserFollower.cs @@ -0,0 +1,46 @@ +using System; + +namespace NHibernate.Test.Extralazy +{ + public class UserFollower : IEquatable + { + public UserFollower(User user, User follower) + { + User = user; + Follower = follower; + } + + protected UserFollower() + { + } + + public virtual int Id { get; set; } + + public virtual User User { get; set; } + + public virtual User Follower { get; set; } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((UserFollower) obj); + } + + public virtual bool Equals(UserFollower other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(User.Name, other.User.Name) && Equals(Follower.Name, other.Follower.Name); + } + + public override int GetHashCode() + { + unchecked + { + return (User.Name.GetHashCode() * 397) ^ Follower.Name.GetHashCode(); + } + } + } +} diff --git a/src/NHibernate.Test/Extralazy/UserGroup.hbm.xml b/src/NHibernate.Test/Extralazy/UserGroup.hbm.xml index 19d201f2bd4..2dd3fb6433d 100644 --- a/src/NHibernate.Test/Extralazy/UserGroup.hbm.xml +++ b/src/NHibernate.Test/Extralazy/UserGroup.hbm.xml @@ -21,19 +21,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/Extralazy/UserPermission.cs b/src/NHibernate.Test/Extralazy/UserPermission.cs new file mode 100644 index 00000000000..3c4820c0c72 --- /dev/null +++ b/src/NHibernate.Test/Extralazy/UserPermission.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Test.Extralazy +{ + public class UserPermission + { + protected UserPermission() { } + + public UserPermission(string name, User owner) + { + Name = name; + Owner = owner; + } + + public virtual int Id { get; set; } + + public virtual string Name { get; set; } + + public virtual User Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/Extralazy/UserSetting.cs b/src/NHibernate.Test/Extralazy/UserSetting.cs new file mode 100644 index 00000000000..da86331a3f3 --- /dev/null +++ b/src/NHibernate.Test/Extralazy/UserSetting.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Test.Extralazy +{ + public class UserSetting + { + protected UserSetting() { } + public UserSetting(string name, string data, User owner) + { + Name = name; + Data = data; + Owner = owner; + } + + public virtual int Id { get; set; } + + public virtual string Name { get; set; } + + public virtual string Data { get; set; } + + public virtual User Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/FetchLazyProperties/Animal.cs b/src/NHibernate.Test/FetchLazyProperties/Animal.cs index f18a8b76ffe..a4b9dde5a05 100644 --- a/src/NHibernate.Test/FetchLazyProperties/Animal.cs +++ b/src/NHibernate.Test/FetchLazyProperties/Animal.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.FetchLazyProperties +namespace NHibernate.Test.FetchLazyProperties { public abstract class Animal { diff --git a/src/NHibernate.Test/FetchLazyProperties/Cat.cs b/src/NHibernate.Test/FetchLazyProperties/Cat.cs index 790b230983d..7a95f212af8 100644 --- a/src/NHibernate.Test/FetchLazyProperties/Cat.cs +++ b/src/NHibernate.Test/FetchLazyProperties/Cat.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.FetchLazyProperties +namespace NHibernate.Test.FetchLazyProperties { public class Cat : Animal { diff --git a/src/NHibernate.Test/FetchLazyProperties/Dog.cs b/src/NHibernate.Test/FetchLazyProperties/Dog.cs index 1019be4df3b..82d0dc5ecfa 100644 --- a/src/NHibernate.Test/FetchLazyProperties/Dog.cs +++ b/src/NHibernate.Test/FetchLazyProperties/Dog.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.FetchLazyProperties +namespace NHibernate.Test.FetchLazyProperties { public class Dog : Animal { diff --git a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 2f5bf5442d3..f1a81fb7e61 100644 --- a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Linq; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Hql.Ast.ANTLR; using NHibernate.Linq; using NUnit.Framework; +using NUnit.Framework.Constraints; using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.FetchLazyProperties @@ -184,7 +186,6 @@ private static void AssertFetchProperty(Person person) #endregion - #region FetchComponentAndFormulaTwoQueryReadOnly [TestCase(true)] @@ -331,7 +332,6 @@ public void TestLinqFetchFormulaAndManyToOneComponent() .Fetch(o => o.Formula) .Fetch(o => o.BestFriend).ThenFetch(o => o.Address) .FirstOrDefault(o => o.Id == 1); - } AssertFetchFormulaAndManyToOneComponent(person); @@ -655,6 +655,40 @@ private static void AssertFetchComponentManyToOne(Person person) #endregion + #region TestHqlFetchManyToOneAndComponentManyToOne + + [Test] + public void TestHqlFetchManyToOneAndComponentManyToOne() + { + Person person; + using (var s = OpenSession()) + { + person = s.CreateQuery("from Person p fetch p.Address left join fetch p.Address.Continent left join fetch p.BestFriend where p.Id = 1").UniqueResult(); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + [Test] + public void TestLinqFetchManyToOneAndComponentManyToOne() + { + Person person; + using (var s = OpenSession()) + { + person = s.Query().Fetch(o => o.BestFriend).Fetch(o => o.Address).ThenFetch(o => o.Continent).FirstOrDefault(o => o.Id == 1); + } + + AssertFetchManyToOneAndComponentManyToOne(person); + } + + private static void AssertFetchManyToOneAndComponentManyToOne(Person person) + { + AssertFetchComponentManyToOne(person); + Assert.That(NHibernateUtil.IsInitialized(person.BestFriend), Is.True); + } + + #endregion + #region FetchSubClassFormula [Test] @@ -934,6 +968,62 @@ public void TestFetchAfterEntityIsInitialized(bool readOnly) Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Formula"), Is.True); } + [TestCase(true)] + [TestCase(false)] + public void TestFetchAllPropertiesAfterEntityIsInitialized(bool readOnly) + { + Person person; + using(var s = OpenSession()) + using(var tx = s.BeginTransaction()) + { + person = s.CreateQuery("from Person where Id = 1").SetReadOnly(readOnly).UniqueResult(); + var image = person.Image; + person = s.CreateQuery("from Person fetch all properties where Id = 1").SetReadOnly(readOnly).UniqueResult(); + + tx.Commit(); + } + + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Formula"), Is.True); + } + + [Test] + public void TestHqlCrossJoinFetchFormula() + { + if (!Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + var persons = new List(); + var bestFriends = new List(); + using (var sqlSpy = new SqlLogSpy()) + using (var s = OpenSession()) + { + var list = s.CreateQuery("select p, bf from Person p cross join Person bf fetch bf.Formula where bf.Id = p.BestFriend.Id").List(); + foreach (var arr in list) + { + persons.Add((Person) arr[0]); + bestFriends.Add((Person) arr[1]); + } + } + + AssertPersons(persons, false); + AssertPersons(bestFriends, true); + + void AssertPersons(List results, bool fetched) + { + foreach (var person in results) + { + Assert.That(person, Is.Not.Null); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False); + Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Formula"), fetched ? Is.True : (IResolveConstraint) Is.False); + } + } + } + private static Person GeneratePerson(int i, Person bestFriend) { return new Person diff --git a/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs b/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs index 5e247ef72e1..5830894d526 100644 --- a/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs +++ b/src/NHibernate.Test/FilterTest/DynamicFilterTest.cs @@ -33,7 +33,7 @@ public void SecondLevelCachedCollectionsFiltering() var persister = Sfi .GetCollectionPersister(typeof(Salesperson).FullName + ".Orders"); var cacheKey = - new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi); + new CacheKey(testData.steveId, persister.KeyType, persister.Role, Sfi, null); CollectionCacheEntry cachedData; using (var session = OpenSession()) @@ -187,7 +187,6 @@ public void CriteriaQueryFilters() } } - [Test] public void CriteriaControl() { @@ -282,7 +281,6 @@ public void CriteriaSubqueryWithFilters() Assert.That(orders.Count, Is.EqualTo(1), "Incorrect orders count"); - log.Info("query against Order with a subquery for line items with a subquery line items where the product name is Acme Hair Gel and the quantity is greater than 1 in a given region and the product is effective as of 4 months ago"); session.EnableFilter("region").SetParameter("region", "APAC"); session.EnableFilter("effectiveDate").SetParameter("asOfDate", testData.fourMonthsAgo.Date); @@ -298,7 +296,6 @@ public void CriteriaSubqueryWithFilters() } } - [Test] public void GetFilters() { @@ -710,7 +707,6 @@ public void Release() using (var session = outer.OpenSession()) using (var transaction = session.BeginTransaction()) { - foreach (var obj in entitiesToCleanUp) { session.Delete(obj); diff --git a/src/NHibernate.Test/FilterTest/FilterConfig.cs b/src/NHibernate.Test/FilterTest/FilterConfig.cs index 4d70dc3136e..26c5d590aeb 100644 --- a/src/NHibernate.Test/FilterTest/FilterConfig.cs +++ b/src/NHibernate.Test/FilterTest/FilterConfig.cs @@ -53,7 +53,6 @@ public void FiltersLoaded() filter2.Validate(); } - [Test] public void TestFilterThrowsWithNoParameterSet() { @@ -67,4 +66,4 @@ public void TestFilterThrowsWithNoParameterSet() Assert.Throws(() => filter.Validate()); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Futures/Entities.cs b/src/NHibernate.Test/Futures/Entities.cs index 3c866f497fd..8d042d4a808 100644 --- a/src/NHibernate.Test/Futures/Entities.cs +++ b/src/NHibernate.Test/Futures/Entities.cs @@ -19,7 +19,6 @@ public class EntityComplex public virtual IList ChildrenList { get; set; } = new List(); public virtual IList ChildrenListEmpty { get; set; } = new List(); - } public class EntitySimpleChild @@ -36,11 +35,18 @@ public class EntitySubselectChild public virtual EntityEager Parent { get; set; } } + public class EntityEagerChild + { + public Guid Id { get; set; } + public string Name { get; set; } + } + public class EntityEager { public Guid Id { get; set; } public string Name { get; set; } + public EntityEagerChild EagerEntity { get; set; } public IList ChildrenListSubselect { get; set; } public IList ChildrenListEager { get; set; } //= new HashSet(); } diff --git a/src/NHibernate.Test/Futures/FutureCriteriaFixture.cs b/src/NHibernate.Test/Futures/FutureCriteriaFixture.cs index e557c6538a9..5e23ef745ef 100644 --- a/src/NHibernate.Test/Futures/FutureCriteriaFixture.cs +++ b/src/NHibernate.Test/Futures/FutureCriteriaFixture.cs @@ -39,12 +39,10 @@ public void CanUseFutureCriteria() { foreach (var person in persons5.GetEnumerable()) { - } foreach (var person in persons10.GetEnumerable()) { - } var events = logSpy.Appender.GetEvents(); @@ -101,7 +99,6 @@ public void CanCombineSingleFutureValueWithEnumerableFutures() foreach (var person in persons.GetEnumerable()) { - } var events = logSpy.Appender.GetEvents(); diff --git a/src/NHibernate.Test/Futures/FutureQueryFixture.cs b/src/NHibernate.Test/Futures/FutureQueryFixture.cs index fb71dcda52f..8c06b87a94b 100644 --- a/src/NHibernate.Test/Futures/FutureQueryFixture.cs +++ b/src/NHibernate.Test/Futures/FutureQueryFixture.cs @@ -52,12 +52,10 @@ public void CanUseFutureQuery() { foreach (var person in persons5.GetEnumerable()) { - } foreach (var person in persons10.GetEnumerable()) { - } var events = logSpy.Appender.GetEvents(); diff --git a/src/NHibernate.Test/Futures/FutureQueryOverFixture.cs b/src/NHibernate.Test/Futures/FutureQueryOverFixture.cs index bcc51d60714..721ccade26c 100644 --- a/src/NHibernate.Test/Futures/FutureQueryOverFixture.cs +++ b/src/NHibernate.Test/Futures/FutureQueryOverFixture.cs @@ -6,7 +6,6 @@ namespace NHibernate.Test.Futures [TestFixture] public class FutureQueryOverFixture : FutureFixture { - protected override void OnSetUp() { base.OnSetUp(); @@ -130,7 +129,6 @@ public void CanCombineSingleFutureValueWithEnumerableFutures() foreach (var person in persons.GetEnumerable()) { - } var events = logSpy.Appender.GetEvents(); diff --git a/src/NHibernate.Test/Futures/LinqFutureFixture.cs b/src/NHibernate.Test/Futures/LinqFutureFixture.cs index dd38fe4a8f0..b9b0177ee21 100644 --- a/src/NHibernate.Test/Futures/LinqFutureFixture.cs +++ b/src/NHibernate.Test/Futures/LinqFutureFixture.cs @@ -249,7 +249,6 @@ public void CanUseFutureFetchQuery() using (var logSpy = new SqlLogSpy()) { - Assert.That(persons.GetEnumerable().Any(x => x.Children.Any()), "No children found"); Assert.That(persons10.GetEnumerable().Any(x => x.Children.Any()), "No children found"); diff --git a/src/NHibernate.Test/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Futures/QueryBatchFixture.cs index cb79faedae1..0d8220500b7 100644 --- a/src/NHibernate.Test/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Futures/QueryBatchFixture.cs @@ -193,7 +193,6 @@ public void SameCollectionFetches() Assert.That(NHibernateUtil.IsInitialized(parent), Is.True); Assert.That(NHibernateUtil.IsInitialized(parent.ChildrenList), Is.True); Assert.That(parent.ChildrenList.Count, Is.EqualTo(2)); - } } @@ -486,6 +485,35 @@ public void CacheModeWorksWithFuture() } } + //GH-2173 + [Test] + public void CanFetchNonLazyEntitiesInSubsequentQuery() + { + Sfi.Statistics.IsStatisticsEnabled = true; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save( + new EntityEager + { + Name = "EagerManyToOneAssociation", + EagerEntity = new EntityEagerChild {Name = "association"} + }); + t.Commit(); + } + + using (var s = OpenSession()) + { + Sfi.Statistics.Clear(); + //EntityEager.EagerEntity is lazy initialized instead of being loaded by the second query + s.QueryOver().Fetch(SelectMode.Skip, x => x.EagerEntity).Future(); + s.QueryOver().Fetch(SelectMode.Fetch, x => x.EagerEntity).Future().GetEnumerable(); + + if(SupportsMultipleQueries) + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -531,6 +559,10 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); + rc.ManyToOne(x => x.EagerEntity, m => + { + m.Cascade(Mapping.ByCode.Cascade.Persist); + }); rc.Bag(ep => ep.ChildrenListSubselect, m => { @@ -548,6 +580,14 @@ protected override HbmMapping GetMappings() }, a => a.OneToMany()); }); + mapper.Class( + rc => + { + rc.Lazy(false); + + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); mapper.Class( rc => { diff --git a/src/NHibernate.Test/GeneratedTest/PartiallyGeneratedComponentTest.cs b/src/NHibernate.Test/GeneratedTest/PartiallyGeneratedComponentTest.cs index 7724cf002c1..55ed101e45a 100644 --- a/src/NHibernate.Test/GeneratedTest/PartiallyGeneratedComponentTest.cs +++ b/src/NHibernate.Test/GeneratedTest/PartiallyGeneratedComponentTest.cs @@ -1,7 +1,4 @@ -using System; - using NHibernate.Dialect; - using NUnit.Framework; namespace NHibernate.Test.GeneratedTest @@ -16,7 +13,7 @@ protected override string MappingsAssembly protected override string[] Mappings { - get { return new string[] { "GeneratedTest.ComponentOwner.hbm.xml" }; } + get { return new [] { "GeneratedTest.ComponentOwner.hbm.xml" }; } } protected override bool AppliesTo(Dialect.Dialect dialect) @@ -28,34 +25,40 @@ protected override bool AppliesTo(Dialect.Dialect dialect) public void PartialComponentGeneration() { ComponentOwner owner = new ComponentOwner("initial"); - ISession s = OpenSession(); - s.BeginTransaction(); - s.Save(owner); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(owner); + t.Commit(); + s.Close(); + } Assert.IsNotNull(owner.Component, "expecting insert value generation"); int previousValue = owner.Component.Generated; Assert.AreNotEqual(0, previousValue, "expecting insert value generation"); - s = OpenSession(); - s.BeginTransaction(); - owner = (ComponentOwner) s.Get(typeof(ComponentOwner), owner.Id); - Assert.AreEqual(previousValue, owner.Component.Generated, "expecting insert value generation"); - owner.Name = "subsequent"; - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + owner = (ComponentOwner) s.Get(typeof(ComponentOwner), owner.Id); + Assert.AreEqual(previousValue, owner.Component.Generated, "expecting insert value generation"); + owner.Name = "subsequent"; + t.Commit(); + s.Close(); + } Assert.IsNotNull(owner.Component); previousValue = owner.Component.Generated; - s = OpenSession(); - s.BeginTransaction(); - owner = (ComponentOwner) s.Get(typeof(ComponentOwner), owner.Id); - Assert.AreEqual(previousValue, owner.Component.Generated, "expecting update value generation"); - s.Delete(owner); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + owner = (ComponentOwner) s.Get(typeof(ComponentOwner), owner.Id); + Assert.AreEqual(previousValue, owner.Component.Generated, "expecting update value generation"); + s.Delete(owner); + t.Commit(); + s.Close(); + } } } } diff --git a/src/NHibernate.Test/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs b/src/NHibernate.Test/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs index 30b1d5f4829..47b8b64128d 100644 --- a/src/NHibernate.Test/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs +++ b/src/NHibernate.Test/Generatedkeys/ByTrigger/GeneratedIdentityFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.ByTrigger @@ -24,18 +23,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public void GetGeneratedKeysSupport() { - ISession session = OpenSession(); - session.BeginTransaction(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntity { Name = "entity-1" }; + session.Save(e); - var e = new MyEntity { Name = "entity-1" }; - session.Save(e); + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - session.Delete(e); - session.Transaction.Commit(); - session.Close(); + session.Delete(e); + tran.Commit(); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs b/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs index 83f7fb14771..3eaf6de36ca 100644 --- a/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs +++ b/src/NHibernate.Test/Generatedkeys/Identity/IdentityGeneratedKeysTest.cs @@ -1,4 +1,3 @@ -using System.Collections; using NHibernate.Cfg; using NUnit.Framework; @@ -31,137 +30,188 @@ protected override void Configure(Configuration configuration) [Test] public void IdentityColumnGeneratedIds() { - ISession s = OpenSession(); - s.BeginTransaction(); - MyEntity myEntity = new MyEntity("test"); - long id = (long)s.Save(myEntity); - Assert.IsNotNull(id,"identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity.Id); - s.Delete(myEntity); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + MyEntity myEntity = new MyEntity("test"); + long id = (long) s.Save(myEntity); + Assert.IsNotNull(id, "identity column did not force immediate insert"); + Assert.AreEqual(id, myEntity.Id); + s.Delete(myEntity); + t.Commit(); + s.Close(); + } } [Test, Ignore("Not supported yet.")] public void PersistOutsideTransaction() { - ISession s = OpenSession(); - - // first test save() which should force an immediate insert... - MyEntity myEntity1 = new MyEntity("test-save"); - long id = (long)s.Save(myEntity1); - Assert.IsNotNull(id, "identity column did not force immediate insert"); - Assert.AreEqual(id, myEntity1.Id); - - // next test persist() which should cause a delayed insert... - long initialInsertCount = Sfi.Statistics.EntityInsertCount; - MyEntity myEntity2 = new MyEntity("test-persist"); - s.Persist(myEntity2); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0,myEntity2.Id); - - // an explicit flush should cause execution of the delayed insertion - s.Flush(); - Assert.AreEqual(initialInsertCount + 1, Sfi.Statistics.EntityInsertCount, "delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - s.Delete(myEntity1); - s.Delete(myEntity2); - s.Transaction.Commit(); - s.Close(); + var myEntity1 = new MyEntity("test-save"); + var myEntity2 = new MyEntity("test-persist"); + using (var s = OpenSession()) + { + // first test save() which should force an immediate insert... + long id = (long) s.Save(myEntity1); + Assert.IsNotNull(id, "identity column did not force immediate insert"); + Assert.AreEqual(id, myEntity1.Id); + + // next test persist() which should cause a delayed insert... + long initialInsertCount = Sfi.Statistics.EntityInsertCount; + s.Persist(myEntity2); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity2.Id); + + // an explicit flush should cause execution of the delayed insertion + s.Flush(); + Assert.AreEqual( + initialInsertCount + 1, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(myEntity1); + s.Delete(myEntity2); + t.Commit(); + s.Close(); + } } [Test, Ignore("Not supported yet.")] public void PersistOutsideTransactionCascadedToNonInverseCollection() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity = new MyEntity("test-persist"); - myEntity.NonInverseChildren.Add(new MyChild("test-child-persist-non-inverse")); - s.Persist(myEntity); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0, myEntity.Id); - s.Flush(); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount,"delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from MyChild"); - s.Delete("from MyEntity"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + { + MyEntity myEntity = new MyEntity("test-persist"); + myEntity.NonInverseChildren.Add(new MyChild("test-child-persist-non-inverse")); + s.Persist(myEntity); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity.Id); + s.Flush(); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from MyChild"); + s.Delete("from MyEntity"); + t.Commit(); + s.Close(); + } } [Test, Ignore("Not supported yet.")] public void PersistOutsideTransactionCascadedToInverseCollection() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity2 = new MyEntity("test-persist-2"); - MyChild child = new MyChild("test-child-persist-inverse"); - myEntity2.InverseChildren.Add(child); - child.InverseParent= myEntity2; - s.Persist(myEntity2); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount,"persist on identity column not delayed"); - Assert.AreEqual(0, myEntity2.Id); - s.Flush(); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount,"delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from MyChild"); - s.Delete("from MyEntity"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + { + MyEntity myEntity2 = new MyEntity("test-persist-2"); + MyChild child = new MyChild("test-child-persist-inverse"); + myEntity2.InverseChildren.Add(child); + child.InverseParent = myEntity2; + s.Persist(myEntity2); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity2.Id); + s.Flush(); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from MyChild"); + s.Delete("from MyEntity"); + t.Commit(); + s.Close(); + } } [Test, Ignore("Not supported yet.")] public void PersistOutsideTransactionCascadedToManyToOne() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity = new MyEntity("test-persist"); - myEntity.Sibling=new MySibling("test-persist-sibling-out"); - s.Persist(myEntity); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0, myEntity.Id); - s.Flush(); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount, "delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from MyEntity"); - s.Delete("from MySibling"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + { + MyEntity myEntity = new MyEntity("test-persist"); + myEntity.Sibling = new MySibling("test-persist-sibling-out"); + s.Persist(myEntity); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity.Id); + s.Flush(); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from MyEntity"); + s.Delete("from MySibling"); + t.Commit(); + s.Close(); + } } [Test, Ignore("Not supported yet.")] public void PersistOutsideTransactionCascadedFromManyToOne() { long initialInsertCount = Sfi.Statistics.EntityInsertCount; - ISession s = OpenSession(); - MyEntity myEntity2 = new MyEntity("test-persist-2"); - MySibling sibling = new MySibling("test-persist-sibling-in"); - sibling.Entity= myEntity2; - s.Persist(sibling); - Assert.AreEqual(initialInsertCount, Sfi.Statistics.EntityInsertCount, "persist on identity column not delayed"); - Assert.AreEqual(0, myEntity2.Id); - s.Flush(); - Assert.AreEqual(initialInsertCount + 2, Sfi.Statistics.EntityInsertCount, "delayed persist insert not executed on flush"); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from MySibling"); - s.Delete("from MyEntity"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + { + MyEntity myEntity2 = new MyEntity("test-persist-2"); + MySibling sibling = new MySibling("test-persist-sibling-in"); + sibling.Entity = myEntity2; + s.Persist(sibling); + Assert.AreEqual( + initialInsertCount, + Sfi.Statistics.EntityInsertCount, + "persist on identity column not delayed"); + Assert.AreEqual(0, myEntity2.Id); + s.Flush(); + Assert.AreEqual( + initialInsertCount + 2, + Sfi.Statistics.EntityInsertCount, + "delayed persist insert not executed on flush"); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from MySibling"); + s.Delete("from MyEntity"); + t.Commit(); + s.Close(); + } } } } diff --git a/src/NHibernate.Test/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs b/src/NHibernate.Test/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs index de3433ee45c..ca6fc6d8279 100644 --- a/src/NHibernate.Test/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs +++ b/src/NHibernate.Test/Generatedkeys/Identity/SimpleIdentityGeneratedFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Identity @@ -22,18 +21,19 @@ protected override string MappingsAssembly [Test] public void SequenceIdentityGenerator() { - ISession session = OpenSession(); - session.BeginTransaction(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntityIdentity { Name = "entity-1" }; + session.Save(e); - var e = new MyEntityIdentity { Name = "entity-1" }; - session.Save(e); + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - session.Delete(e); - session.Transaction.Commit(); - session.Close(); + session.Delete(e); + tran.Commit(); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Generatedkeys/Select/SelectGeneratorTest.cs b/src/NHibernate.Test/Generatedkeys/Select/SelectGeneratorTest.cs index eaa04d884d1..bbc75a6359d 100644 --- a/src/NHibernate.Test/Generatedkeys/Select/SelectGeneratorTest.cs +++ b/src/NHibernate.Test/Generatedkeys/Select/SelectGeneratorTest.cs @@ -1,4 +1,3 @@ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Select @@ -24,19 +23,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public void GetGeneratedKeysSupport() { - ISession session = OpenSession(); - session.BeginTransaction(); - - MyEntity e = new MyEntity("entity-1"); - session.Save(e); - - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - session.Delete(e); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + MyEntity e = new MyEntity("entity-1"); + session.Save(e); + + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); + + session.Delete(e); + tran.Commit(); + session.Close(); + } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs b/src/NHibernate.Test/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs index 03fb23be34b..ad390e92802 100644 --- a/src/NHibernate.Test/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs +++ b/src/NHibernate.Test/Generatedkeys/Seqidentity/SequenceIdentityFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NUnit.Framework; namespace NHibernate.Test.Generatedkeys.Seqidentity @@ -30,18 +29,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public void SequenceIdentityGenerator() { - ISession session = OpenSession(); - session.BeginTransaction(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntity { Name = "entity-1" }; + session.Save(e); - var e = new MyEntity{Name="entity-1"}; - session.Save(e); + // this insert should happen immediately! + Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - // this insert should happen immediately! - Assert.AreEqual(1, e.Id, "id not generated through forced insertion"); - - session.Delete(e); - session.Transaction.Commit(); - session.Close(); + session.Delete(e); + tran.Commit(); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/GenericTest/BagGeneric/A.cs b/src/NHibernate.Test/GenericTest/BagGeneric/A.cs index 6feda251b06..a30a5b9e04f 100644 --- a/src/NHibernate.Test/GenericTest/BagGeneric/A.cs +++ b/src/NHibernate.Test/GenericTest/BagGeneric/A.cs @@ -29,6 +29,5 @@ public IList Items get { return _items; } set { _items = value; } } - } } diff --git a/src/NHibernate.Test/GenericTest/BagGeneric/BagGenericFixture.cs b/src/NHibernate.Test/GenericTest/BagGeneric/BagGenericFixture.cs index b754ffee503..d70aadf34eb 100644 --- a/src/NHibernate.Test/GenericTest/BagGeneric/BagGenericFixture.cs +++ b/src/NHibernate.Test/GenericTest/BagGeneric/BagGenericFixture.cs @@ -1,6 +1,7 @@ -using System; using System.Collections.Generic; -using System.Text; +using System.Linq; +using NHibernate.Collection; +using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.GenericTest.BagGeneric @@ -8,7 +9,6 @@ namespace NHibernate.Test.GenericTest.BagGeneric [TestFixture] public class BagGenericFixture : TestCase { - protected override string[] Mappings { get { return new string[] { "GenericTest.BagGeneric.BagGenericFixture.hbm.xml" }; } @@ -64,6 +64,51 @@ public void Simple() s.Close(); } + [Test] + public void EqualsSnapshot() + { + var a = new A {Name = "first generic type"}; + var i0 = new B {Name = "1"}; + var i4 = new B {Name = "4"}; + a.Items = new List + { + i0, + i0, + new B {Name = "2"}, + new B {Name = "3"}, + i4, + i4, + }; + var lastIdx = a.Items.Count - 1; + using (var s = OpenSession()) + { + s.Save(a); + s.Flush(); + var collection = (IPersistentCollection) a.Items; + var collectionPersister = Sfi.GetCollectionPersister(collection.Role); + + a.Items[0] = i4; + Assert.Multiple( + () => + { + Assert.That(collection.EqualsSnapshot(collectionPersister), Is.False, "modify first collection element"); + + a.Items[lastIdx] = i0; + Assert.That(collection.EqualsSnapshot(collectionPersister), Is.True, "swap elements in collection"); + + a.Items[0] = i0; + a.Items[lastIdx] = i0; + Assert.That(collection.EqualsSnapshot(collectionPersister), Is.False, "modify last collection element"); + + a.Items[lastIdx] = i4; + var reversed = a.Items.Reverse().ToArray(); + a.Items.Clear(); + ArrayHelper.AddAll(a.Items, reversed); + Assert.That(collection.EqualsSnapshot(collectionPersister), Is.True, "reverse collection elements"); + }); + } + } + [Test] public void Copy() { diff --git a/src/NHibernate.Test/GenericTest/IdBagGeneric/A.cs b/src/NHibernate.Test/GenericTest/IdBagGeneric/A.cs index 567c9965045..ef0f69eeb35 100644 --- a/src/NHibernate.Test/GenericTest/IdBagGeneric/A.cs +++ b/src/NHibernate.Test/GenericTest/IdBagGeneric/A.cs @@ -29,6 +29,5 @@ public IList Items get { return _items; } set { _items = value; } } - } } diff --git a/src/NHibernate.Test/GenericTest/ListGeneric/A.cs b/src/NHibernate.Test/GenericTest/ListGeneric/A.cs index d4dfb2a3ebd..4ae1cb8c6aa 100644 --- a/src/NHibernate.Test/GenericTest/ListGeneric/A.cs +++ b/src/NHibernate.Test/GenericTest/ListGeneric/A.cs @@ -29,6 +29,5 @@ public IList Items get { return _items; } set { _items = value; } } - } } diff --git a/src/NHibernate.Test/GenericTest/ListGeneric/ListGenericFixture.cs b/src/NHibernate.Test/GenericTest/ListGeneric/ListGenericFixture.cs index 305b998c81f..cf4327ae8fd 100644 --- a/src/NHibernate.Test/GenericTest/ListGeneric/ListGenericFixture.cs +++ b/src/NHibernate.Test/GenericTest/ListGeneric/ListGenericFixture.cs @@ -9,7 +9,6 @@ namespace NHibernate.Test.GenericTest.ListGeneric [TestFixture] public class ListGenericFixture : TestCase { - protected override string[] Mappings { get { return new string[] { "GenericTest.ListGeneric.ListGenericFixture.hbm.xml" }; } diff --git a/src/NHibernate.Test/GenericTest/Overall/Fixture.cs b/src/NHibernate.Test/GenericTest/Overall/Fixture.cs index 9374883c24d..8097060cb30 100644 --- a/src/NHibernate.Test/GenericTest/Overall/Fixture.cs +++ b/src/NHibernate.Test/GenericTest/Overall/Fixture.cs @@ -68,5 +68,4 @@ public void CRUDAB() } } } - } diff --git a/src/NHibernate.Test/GenericTest/SetGeneric/A.cs b/src/NHibernate.Test/GenericTest/SetGeneric/A.cs index 70cf884e727..cfc1b21e6b6 100644 --- a/src/NHibernate.Test/GenericTest/SetGeneric/A.cs +++ b/src/NHibernate.Test/GenericTest/SetGeneric/A.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text; - namespace NHibernate.Test.GenericTest.SetGeneric { public class A @@ -30,6 +29,5 @@ public ISet Items get { return _items; } set { _items = value; } } - } } diff --git a/src/NHibernate.Test/GenericTest/SetGeneric/SetGenericFixture.cs b/src/NHibernate.Test/GenericTest/SetGeneric/SetGenericFixture.cs index 58e2598cfcb..d930f504644 100644 --- a/src/NHibernate.Test/GenericTest/SetGeneric/SetGenericFixture.cs +++ b/src/NHibernate.Test/GenericTest/SetGeneric/SetGenericFixture.cs @@ -9,7 +9,6 @@ namespace NHibernate.Test.GenericTest.SetGeneric [TestFixture] public class SetGenericFixture : TestCase { - protected override string[] Mappings { get { return new string[] { "GenericTest.SetGeneric.SetGenericFixture.hbm.xml" }; } diff --git a/src/NHibernate.Test/GhostProperty/GhostPropertyFixture.cs b/src/NHibernate.Test/GhostProperty/GhostPropertyFixture.cs index b8fccac67c1..13a3c6e0e7b 100644 --- a/src/NHibernate.Test/GhostProperty/GhostPropertyFixture.cs +++ b/src/NHibernate.Test/GhostProperty/GhostPropertyFixture.cs @@ -48,7 +48,6 @@ protected override void OnSetUp() }); tx.Commit(); } - } protected override void OnTearDown() diff --git a/src/NHibernate.Test/Hql/AggregateFunctionsWithSubSelectTest.cs b/src/NHibernate.Test/Hql/AggregateFunctionsWithSubSelectTest.cs new file mode 100644 index 00000000000..349e5444bba --- /dev/null +++ b/src/NHibernate.Test/Hql/AggregateFunctionsWithSubSelectTest.cs @@ -0,0 +1,109 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.Hql +{ + [TestFixture] + public class AggregateFunctionsWithSubSelectTest : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.Name); + rc.Map(x => x.Localized, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.Element()); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.Name); + rc.Map(x => x.Contacts, cm => cm.Key(k => k.Column("position")), x => x.OneToMany()); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var document = new Document(); + var p1 = new Person(); + var p2 = new Person(); + + p1.Localized.Add(1, "p1.1"); + p1.Localized.Add(2, "p1.2"); + p2.Localized.Add(1, "p2.1"); + p2.Localized.Add(2, "p2.2"); + + document.Contacts.Add(1, p1); + document.Contacts.Add(2, p2); + + session.Persist(p1); + session.Persist(p2); + session.Persist(document); + + transaction.Commit(); + } + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return TestDialect.SupportsAggregateInSubSelect; + } + + [TestCase("SUM", 4)] + [TestCase("MIN", 2)] + [TestCase("MAX", 2)] + [TestCase("AVG", 2d)] + public void TestAggregateFunction(string functionName, object result) + { + var query = "SELECT " + + " d.Id, " + + $" {functionName}(" + + " (" + + " SELECT COUNT(localized) " + + " FROM Person p " + + " LEFT JOIN p.Localized localized " + + " WHERE p.Id = c.Id" + + " )" + + " ) AS LocalizedCount " + + "FROM Document d " + + "LEFT JOIN d.Contacts c " + + "GROUP BY d.Id"; + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var results = session.CreateQuery(query).List(); + + Assert.That(results, Has.Count.EqualTo(1)); + var tuple = results[0] as object[]; + Assert.That(tuple, Is.Not.Null); + Assert.That(tuple, Has.Length.EqualTo(2)); + Assert.That(tuple[1], Is.EqualTo(result)); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/Hql/AggregateFunctionsWithSubSelectTestEntities.cs b/src/NHibernate.Test/Hql/AggregateFunctionsWithSubSelectTestEntities.cs new file mode 100644 index 00000000000..31f628cec2b --- /dev/null +++ b/src/NHibernate.Test/Hql/AggregateFunctionsWithSubSelectTestEntities.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.Hql +{ + public class Person + { + public virtual int Id { get; set; } + + public virtual string Name { get; set; } + + public virtual IDictionary Localized { get; set; } = new Dictionary(); + } + + public class Document + { + public virtual int Id { get; set; } + + public virtual string Name { get; set; } + + public virtual IDictionary Contacts { get; set; } = new Dictionary(); + } +} diff --git a/src/NHibernate.Test/Hql/Ast/BulkManipulation.cs b/src/NHibernate.Test/Hql/Ast/BulkManipulation.cs index 8d8bfe625e2..bb24ff0c228 100644 --- a/src/NHibernate.Test/Hql/Ast/BulkManipulation.cs +++ b/src/NHibernate.Test/Hql/Ast/BulkManipulation.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Threading; -using NHibernate.Dialect; using NHibernate.Hql.Ast.ANTLR; using NHibernate.Id; using NHibernate.Persister.Entity; @@ -92,7 +91,6 @@ public void SimpleInsertFromAggregate() data.Cleanup(); } - [Test] public void InsertWithManyToOne() { @@ -214,6 +212,7 @@ public void InsertAcrossMappedJoinFails() data.Cleanup(); } + [Test] public void InsertWithGeneratedId() { // Make sure the env supports bulk inserts with generated ids... @@ -363,14 +362,16 @@ public void InsertWithGeneratedTimestampVersion() public void InsertWithSelectListUsingJoins() { // this is just checking parsing and syntax... - ISession s = OpenSession(); - s.BeginTransaction(); - s.CreateQuery( - "insert into Animal (description, bodyWeight) select h.description, h.bodyWeight from Human h where h.mother.mother is not null") - .ExecuteUpdate(); - s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery( + "insert into Animal (description, bodyWeight) select h.description, h.bodyWeight from Human h where h.mother.mother is not null") + .ExecuteUpdate(); + s.CreateQuery("delete from Animal").ExecuteUpdate(); + t.Commit(); + s.Close(); + } } #endregion @@ -826,6 +827,31 @@ public void UpdateSetNullOnJoinedSubclass() data.Cleanup(); } + [Test] + public void UpdateMultitableWithWhereExistsSubquery() + { + if (!Dialect.SupportsTemporaryTables) + Assert.Ignore("Cannot perform multi-table updates using dialect not supporting temp tables."); + + if(!Dialect.SupportsScalarSubSelects) + Assert.Ignore("Dialect does not support scalar sub-select"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + String updateQryString = "update Human h " + + "set h.description = 'updated' " + + "where exists (" + + " select f.id " + + " from h.friends f " + + " where f.name.last = 'Public' " + + ")"; + s.CreateQuery(updateQryString).ExecuteUpdate(); + + t.Commit(); + } + } + #endregion #region DELETES @@ -834,41 +860,23 @@ public void UpdateSetNullOnJoinedSubclass() public void DeleteWithSubquery() { // setup the test data... - ISession s = OpenSession(); - s.BeginTransaction(); - var owner = new SimpleEntityWithAssociation {Name = "myEntity-1"}; - owner.AddAssociation("assoc-1"); - owner.AddAssociation("assoc-2"); - owner.AddAssociation("assoc-3"); - s.Save(owner); - var owner2 = new SimpleEntityWithAssociation {Name = "myEntity-2"}; - owner2.AddAssociation("assoc-1"); - owner2.AddAssociation("assoc-2"); - owner2.AddAssociation("assoc-3"); - owner2.AddAssociation("assoc-4"); - s.Save(owner2); - var owner3 = new SimpleEntityWithAssociation {Name = "myEntity-3"}; - s.Save(owner3); - s.Transaction.Commit(); - s.Close(); + CreateSimpleEntityWithAssociationData(); // now try the bulk delete - s = OpenSession(); - s.BeginTransaction(); - int count = - s.CreateQuery("delete SimpleEntityWithAssociation e where size(e.AssociatedEntities ) = 0 and e.Name like '%'"). - ExecuteUpdate(); - Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + int count = + s.CreateQuery( + "delete SimpleEntityWithAssociation e where size(e.AssociatedEntities ) = 0 and e.Name like '%'") + .ExecuteUpdate(); + Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); + t.Commit(); + s.Close(); + } // finally, clean up - s = OpenSession(); - s.BeginTransaction(); - s.CreateQuery("delete SimpleAssociatedEntity").ExecuteUpdate(); - s.CreateQuery("delete SimpleEntityWithAssociation").ExecuteUpdate(); - s.Transaction.Commit(); - s.Close(); + CleanSimpleEntityWithAssociationData(); } [Test] @@ -1072,6 +1080,56 @@ public void DeleteSyntaxWithCompositeId() s.Close(); } + [Test] + public void DeleteSubQueryReferencingTargetPropert() + { + CreateSimpleEntityWithAssociationData(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var count = s.CreateQuery("delete from SimpleEntityWithAssociation m where not exists (select d from SimpleAssociatedEntity d where d.Owner = m) and m.Name like 'myEntity%'").ExecuteUpdate(); + Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); + t.Commit(); + } + + CleanSimpleEntityWithAssociationData(); + } + + private void CreateSimpleEntityWithAssociationData() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var owner = new SimpleEntityWithAssociation {Name = "myEntity-1"}; + owner.AddAssociation("assoc-1"); + owner.AddAssociation("assoc-2"); + owner.AddAssociation("assoc-3"); + s.Save(owner); + var owner2 = new SimpleEntityWithAssociation {Name = "myEntity-2"}; + owner2.AddAssociation("assoc-1"); + owner2.AddAssociation("assoc-2"); + owner2.AddAssociation("assoc-3"); + owner2.AddAssociation("assoc-4"); + s.Save(owner2); + var owner3 = new SimpleEntityWithAssociation {Name = "myEntity-3"}; + s.Save(owner3); + t.Commit(); + s.Close(); + } + } + + private void CleanSimpleEntityWithAssociationData() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete SimpleAssociatedEntity").ExecuteUpdate(); + s.CreateQuery("delete SimpleEntityWithAssociation").ExecuteUpdate(); + t.Commit(); + s.Close(); + } + } #endregion private class TestData diff --git a/src/NHibernate.Test/Hql/Ast/HqlFixture.cs b/src/NHibernate.Test/Hql/Ast/HqlFixture.cs index fea98840262..6b3175242d2 100644 --- a/src/NHibernate.Test/Hql/Ast/HqlFixture.cs +++ b/src/NHibernate.Test/Hql/Ast/HqlFixture.cs @@ -88,10 +88,10 @@ public void CaseClauseInSelect() { // NH-322 using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Save(new Animal {BodyWeight = 12, Description = "Polliwog"}); - s.Transaction.Commit(); + t.Commit(); } using (ISession s = OpenSession()) @@ -107,10 +107,10 @@ public void CaseClauseInSelect() } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } @@ -118,10 +118,10 @@ public void CaseClauseInSelect() public void MultipleParametersInCaseStatement() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Save(new Animal { BodyWeight = 12, Description = "Polliwog" }); - s.Transaction.Commit(); + t.Commit(); } try @@ -139,10 +139,10 @@ public void MultipleParametersInCaseStatement() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } } @@ -151,10 +151,10 @@ public void MultipleParametersInCaseStatement() public void ParameterInCaseThenClause() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Save(new Animal { BodyWeight = 12, Description = "Polliwog" }); - s.Transaction.Commit(); + t.Commit(); } try @@ -170,10 +170,10 @@ public void ParameterInCaseThenClause() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } } @@ -182,10 +182,10 @@ public void ParameterInCaseThenClause() public void ParameterInCaseThenAndElseClausesWithCast() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Save(new Animal { BodyWeight = 12, Description = "Polliwog" }); - s.Transaction.Commit(); + t.Commit(); } try @@ -202,10 +202,10 @@ public void ParameterInCaseThenAndElseClausesWithCast() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } } @@ -214,10 +214,10 @@ public void ParameterInCaseThenAndElseClausesWithCast() public void SubselectAddition() { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Save(new Animal { BodyWeight = 12, Description = "Polliwog" }); - s.Transaction.Commit(); + t.Commit(); } try @@ -232,10 +232,10 @@ public void SubselectAddition() finally { using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } } @@ -245,10 +245,10 @@ public void SumShouldReturnDouble() { // NH-1734 using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Save(new Human{ IntValue = 11, BodyWeight = 12.5f, Description = "Polliwog" }); - s.Transaction.Commit(); + t.Commit(); } using (ISession s = OpenSession()) @@ -258,10 +258,10 @@ public void SumShouldReturnDouble() } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } @@ -298,29 +298,26 @@ public void InvalidJoinOnProperty() public void InsertIntoFromSelect_WithSelectClauseParameters() { using (ISession s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { - // arrange - s.Save(new Animal() {Description = "cat1", BodyWeight = 2.1f}); - s.Save(new Animal() {Description = "cat2", BodyWeight = 2.5f}); - s.Save(new Animal() {Description = "cat3", BodyWeight = 2.7f}); + // arrange + s.Save(new Animal() {Description = "cat1", BodyWeight = 2.1f}); + s.Save(new Animal() {Description = "cat2", BodyWeight = 2.5f}); + s.Save(new Animal() {Description = "cat3", BodyWeight = 2.7f}); - // act - s.CreateQuery("insert into Animal (description, bodyWeight) select a.description, :weight from Animal a where a.bodyWeight < :weight") - .SetParameter("weight", 5.7f).ExecuteUpdate(); + // act + s.CreateQuery("insert into Animal (description, bodyWeight) select a.description, :weight from Animal a where a.bodyWeight < :weight") + .SetParameter("weight", 5.7f).ExecuteUpdate(); - // assert - Assert.AreEqual(3, s.CreateCriteria().SetProjection(Projections.RowCount()) - .Add(Restrictions.Gt("bodyWeight", 5.5f)).UniqueResult()); + // assert + Assert.AreEqual(3, s.CreateCriteria().SetProjection(Projections.RowCount()) + .Add(Restrictions.Gt("bodyWeight", 5.5f)).UniqueResult()); - s.CreateQuery("delete from Animal").ExecuteUpdate(); - s.Transaction.Commit(); - } + s.CreateQuery("delete from Animal").ExecuteUpdate(); + t.Commit(); } } - [Test] public void UnaryMinusBeforeParenthesesHandledCorrectly() { @@ -340,5 +337,14 @@ public void UnaryMinusBeforeParenthesesHandledCorrectly() Assert.That(actualWorkaround, Is.EqualTo(-2)); } } + + //NH-3249 (GH-1285) + [Test] + public void CountDistinctOnFunction() + { + var hql = @"SELECT COUNT(DISTINCT DATE(m.birthdate)) FROM Mammal m"; + using(var s = OpenSession()) + s.CreateQuery(hql).List(); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Hql/Ast/KeyManyToOneEntity.cs b/src/NHibernate.Test/Hql/Ast/KeyManyToOneEntity.cs index 22d76cd5521..1a9f8008fe1 100644 --- a/src/NHibernate.Test/Hql/Ast/KeyManyToOneEntity.cs +++ b/src/NHibernate.Test/Hql/Ast/KeyManyToOneEntity.cs @@ -37,7 +37,6 @@ public Id(KeyManyToOneKeyEntity key1, string key2) this.key1 = key1; this.key2 = key2; - } public virtual KeyManyToOneKeyEntity Key1 @@ -83,4 +82,4 @@ public override int GetHashCode() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Hql/Ast/OrderByTest.cs b/src/NHibernate.Test/Hql/Ast/OrderByTest.cs index 03bb8220491..4fb2a345355 100644 --- a/src/NHibernate.Test/Hql/Ast/OrderByTest.cs +++ b/src/NHibernate.Test/Hql/Ast/OrderByTest.cs @@ -355,7 +355,6 @@ private void CheckTestOrderByResult(object result, Zoo zooExpected, HashSet zoosUnordered) { - Assert.IsInstanceOf(result); var resultArray = (object[]) result; Assert.AreEqual(2, resultArray.Length); @@ -376,7 +375,6 @@ private void CheckTestOrderByResult(object result, } } - private class TestData { private readonly OrderByTest tc; @@ -540,4 +538,4 @@ public void Cleanup() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs b/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs index 040ba037b9a..45be4789f1c 100644 --- a/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs +++ b/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs @@ -74,8 +74,6 @@ namespace NHibernate.Test.Hql.Ast // [Test] // public void BasicQuery() // { - // XmlConfigurator.Configure(); - // string input = "select o.id, li.id from NHibernate.Test.CompositeId.Order o join o.LineItems li";// join o.LineItems li"; // ISessionFactoryImplementor sfi = SetupSFI(); @@ -175,4 +173,4 @@ namespace NHibernate.Test.Hql.Ast // } // } //} -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs b/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs index 2e2cd4cddff..ab734057d4f 100644 --- a/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs +++ b/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs @@ -23,6 +23,16 @@ public void CaseClauseWithMath() Assert.DoesNotThrow(() => GetSql(queryWithoutParen)); } + [Test] + public void SimpleCaseClauseWithMath() + { + const string query = "from SimpleClass s where (case (cast(s.IntValue as long) * :pAValue) when (cast(s.IntValue as long) * :pAValue) then (cast(s.IntValue as long) * :pAValue) else 1 end) > 0"; + Assert.DoesNotThrow(() => GetSql(query)); + + const string queryWithoutParen = "from SimpleClass s where (case cast(s.IntValue as long) * :pAValue when cast(s.IntValue as long) * :pAValue then cast(s.IntValue as long) * :pAValue else 1 end) > 0"; + Assert.DoesNotThrow(() => GetSql(queryWithoutParen)); + } + [Test] public void Union() { diff --git a/src/NHibernate.Test/Hql/Ast/WithClauseFixture.cs b/src/NHibernate.Test/Hql/Ast/WithClauseFixture.cs index b3e4a44f571..540fa631a84 100644 --- a/src/NHibernate.Test/Hql/Ast/WithClauseFixture.cs +++ b/src/NHibernate.Test/Hql/Ast/WithClauseFixture.cs @@ -1,6 +1,4 @@ -using System; using System.Collections; -using NHibernate.Exceptions; using NHibernate.Hql.Ast.ANTLR; using NUnit.Framework; @@ -42,40 +40,21 @@ public void WithClauseFailsWithFetch() } [Test] - public void ValidWithSemantics() + public void WithClauseOnSubclasses() { using (var s = OpenSession()) { s.CreateQuery( "from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1").List(); - } - } - [Test] - public void InvalidWithSemantics() - { - using (ISession s = OpenSession()) - { - // PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f' - // alias relates to the Human.friends collection which the aonther Human entity. The issue - // here is the way JoinSequence and Joinable (the persister) interact to generate the - // joins relating to the sublcass/superclass tables - Assert.Throws( - () => - s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).List()); - - //The query below is no longer throw InvalidWithClauseException but generates "invalid" SQL to better support complex with join clauses. - //Invalid SQL means that additional joins for "o.mother.father" are currently added after "offspring" join. Some DBs can process such query and some can't. - try - { - s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin") - .SetInt32("cousin", 123) - .List(); - } - catch (GenericADOException) - { - //Apparently SQLite can process queries with wrong join orders - } + // f.bodyWeight is a reference to a column on the Animal table; however, the 'f' + // alias relates to the Human.friends collection which the aonther Human entity. + // Group join allows us to use such constructs + s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).List(); + + s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin") + .SetInt32("cousin", 123) + .List(); } } diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs index 18e14f1a435..1fc649d6302 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs @@ -142,6 +142,124 @@ public void EntityJoinFoSubquery() } } + [Test] + public void EntityJoinWithNullableOneToOneEntityComparisonInWithClausShouldAddJoin() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "left join OneToOneEntity st with st = ex.OneToOne " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(2)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void NullableOneToOneWhereEntityIsNotNull() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.OneToOne is not null " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void NullableOneToOneWhereIdIsNotNull() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.OneToOne.Id is not null " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void NullablePropRefWhereIdEntityNotNullShouldAddJoin() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.PropRef is not null " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "PropRefEntity").Count, Is.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void NullableOneToOneFetchQueryIsNotAffected() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex left join fetch ex.OneToOne o " + + "where o is null " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + } + } + + [Test] + public void NullableOneToOneFetchQueryIsNotAffected2() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex left join fetch ex.OneToOne o " + + "where o.Id is null " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); + } + } + [Test] public void EntityJoinWithEntityComparisonInWithClausShouldNotAddJoin() { @@ -202,7 +320,6 @@ public void EntityJoinWithEntityAssociationComparison2ShouldAddJoin() } } - [Test] public void WithClauseOnOtherAssociation() { @@ -274,12 +391,57 @@ public void CrossJoinAndWithClause() { session.CreateQuery( "SELECT s " + - "FROM EntityComplex s, EntityComplex q " + + "FROM EntityComplex s CROSS JOIN EntityComplex q " + "LEFT JOIN s.SameTypeChild AS sa WITH sa.SameTypeChild.Id = q.SameTypeChild.Id" ).List(); } } + [Test] + public void WithImpliedJoinOnAssociation() + { + using (var session = OpenSession()) + { + var result = session.CreateQuery( + "SELECT s " + + "FROM EntityComplex s left join s.SameTypeChild q on q.SameTypeChild.SameTypeChild.Name = s.Name" + ).List(); + } + } + + [Test] + public void WithImpliedEntityJoin() + { + using (var session = OpenSession()) + { + var result = session.CreateQuery( + "SELECT s " + + "FROM EntityComplex s left join EntityComplex q on q.SameTypeChild.SameTypeChild.Name = s.Name" + ).List(); + } + } + + [Test] + public void CrossJoinAndWhereClause() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = session.CreateQuery( + "SELECT s " + + "FROM EntityComplex s cross join EntityComplex q " + + "where s.SameTypeChild.Id = q.SameTypeChild.Id" + ).List(); + + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + if (Dialect.SupportsCrossJoin) + { + Assert.That(sqlLog.GetWholeLog(), Does.Contain("cross join"), "A cross join is expected in the SQL select"); + } + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -299,10 +461,8 @@ protected override HbmMapping GetMappings() rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId")); rc.ManyToOne(ep => ep.SameTypeChild2, m => m.Column("SameTypeChild2Id")); - }); - mapper.Class( rc => { @@ -316,7 +476,7 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Name); }); - + mapper.Class( rc => { @@ -343,7 +503,6 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Composite1Key1); rc.Property(e => e.Composite1Key2); rc.Property(e => e.CustomEntityNameId); - }); mapper.Class( @@ -355,6 +514,39 @@ protected override HbmMapping GetMappings() rc.Property(e => e.Name); }); + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + rc.Property(e => e.PropertyRef, m => m.Column("EntityPropertyRef")); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + rc.OneToOne(e => e.OneToOne, m => m.Constrained(false)); + rc.ManyToOne( + e => e.PropRef, + m => + { + m.Column("OwnerPropertyRef"); + m.PropertyRef(nameof(PropRefEntity.PropertyRef)); + m.ForeignKey("none"); + m.NotFound(NotFoundMode.Ignore); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); } @@ -387,7 +579,6 @@ protected override void OnSetUp() { Name = "ComplexEntityChild2" } - }; _entityWithCompositeId = new EntityWithCompositeId @@ -420,7 +611,6 @@ protected override void OnSetUp() Composite1Key2 = _entityWithCompositeId.Key.Id2, CustomEntityNameId = _entityWithCustomEntityName.Id }; - session.Save(_noAssociation); session.Flush(); diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs index 51304d8643f..277b0ea706b 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs @@ -5,16 +5,32 @@ namespace NHibernate.Test.Hql.EntityJoinHqlTestEntities public class EntityComplex { public virtual Guid Id { get; set; } - public virtual int Version { get; set; } - public virtual string Name { get; set; } - public virtual string LazyProp { get; set; } - public virtual EntityComplex SameTypeChild { get; set; } public virtual EntityComplex SameTypeChild2 { get; set; } + } + + public class OneToOneEntity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public class PropRefEntity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual string PropertyRef { get; set; } + } + public class NullableOwner + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual OneToOneEntity OneToOne { get; set; } + public virtual PropRefEntity PropRef { get; set; } } public class EntityWithCompositeId @@ -22,6 +38,7 @@ public class EntityWithCompositeId public virtual CompositeKey Key { get; set; } public virtual string Name { get; set; } } + public class CompositeKey { public int Id1 { get; set; } diff --git a/src/NHibernate.Test/Hql/HQLFunctions.cs b/src/NHibernate.Test/Hql/HQLFunctions.cs index 3c84a3d2050..1a2b80b0efd 100644 --- a/src/NHibernate.Test/Hql/HQLFunctions.cs +++ b/src/NHibernate.Test/Hql/HQLFunctions.cs @@ -204,8 +204,7 @@ public void AggregatesAndMathNH959() Assert.DoesNotThrow(() => s.CreateQuery("select a.Id, sum(BodyWeight)/avg(BodyWeight) from Animal a group by a.Id having sum(BodyWeight)>0").List()); } } - - + [Test] public void SubStringTwoParameters() { @@ -246,8 +245,7 @@ public void SubStringTwoParameters() Assert.AreEqual("abcdef", result.Description); } } - - + [Test] public void SubString() { @@ -272,7 +270,6 @@ public void SubString() .UniqueResult(); Assert.AreEqual("abcdef", result.Description); - // Following tests verify that parameters can be used. hql = "from Animal a where substring(a.Description, 2, ?) = 'bcd'"; @@ -281,7 +278,6 @@ public void SubString() .UniqueResult(); Assert.AreEqual("abcdef", result.Description); - hql = "from Animal a where substring(a.Description, ?, ?) = ?"; result = (Animal)s.CreateQuery(hql) .SetParameter(0, 2) @@ -303,7 +299,6 @@ public void SubString() [Test] public void Locate() { - AssumeFunctionSupported("locate"); using (ISession s = OpenSession()) { Animal a1 = new Animal("abcdef", 20); diff --git a/src/NHibernate.Test/Hql/Human.cs b/src/NHibernate.Test/Hql/Human.cs index 92935ebf084..535406ec806 100644 --- a/src/NHibernate.Test/Hql/Human.cs +++ b/src/NHibernate.Test/Hql/Human.cs @@ -24,6 +24,5 @@ public virtual DateTime Birthdate get { return _birthdate; } set { _birthdate = value; } } - } } diff --git a/src/NHibernate.Test/Hql/MaterialResource.cs b/src/NHibernate.Test/Hql/MaterialResource.cs index 536cd7ddbf9..24cab7d0ac0 100644 --- a/src/NHibernate.Test/Hql/MaterialResource.cs +++ b/src/NHibernate.Test/Hql/MaterialResource.cs @@ -11,7 +11,6 @@ public enum MaterialState : int public MaterialResource() { - } public MaterialResource(string description, string serialNumber, MaterialState state) diff --git a/src/NHibernate.Test/Hql/Name.cs b/src/NHibernate.Test/Hql/Name.cs index 353ee14a88a..61ffe2225c0 100644 --- a/src/NHibernate.Test/Hql/Name.cs +++ b/src/NHibernate.Test/Hql/Name.cs @@ -4,7 +4,6 @@ namespace NHibernate.Test.Hql { public class Name { - protected Name() { } public Name(string first, char initial, string last) diff --git a/src/NHibernate.Test/Hql/SubQueryTest.cs b/src/NHibernate.Test/Hql/SubQueryTest.cs new file mode 100644 index 00000000000..caf665de998 --- /dev/null +++ b/src/NHibernate.Test/Hql/SubQueryTest.cs @@ -0,0 +1,59 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.Hql +{ + [TestFixture] + public class SubQueryTest : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.RootName); + rc.ManyToOne(x => x.Branch); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.BranchName); + rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany()); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.LeafName); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + [TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")] + [TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")] + public void TestSubQuery(string query) + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // Simple syntax check + session.CreateQuery(query).List(); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/Hql/SubQueryTestEntities.cs b/src/NHibernate.Test/Hql/SubQueryTestEntities.cs new file mode 100644 index 00000000000..2b431eeacf7 --- /dev/null +++ b/src/NHibernate.Test/Hql/SubQueryTestEntities.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.Hql +{ + public class Root + { + public virtual int Id { get; set; } + + public virtual string RootName { get; set; } + + public virtual Branch Branch { get; set; } + } + + public class Branch + { + public virtual int Id { get; set; } + + public virtual string BranchName { get; set; } + + public virtual IList Leafs { get; set; } + } + + public class Leaf + { + public virtual int Id { get; set; } + + public virtual string LeafName { get; set; } + } +} diff --git a/src/NHibernate.Test/IdGen/Enhanced/Forcedtable/Entity.cs b/src/NHibernate.Test/IdGen/Enhanced/Forcedtable/Entity.cs index e3c6871816d..49794820b49 100644 --- a/src/NHibernate.Test/IdGen/Enhanced/Forcedtable/Entity.cs +++ b/src/NHibernate.Test/IdGen/Enhanced/Forcedtable/Entity.cs @@ -2,7 +2,6 @@ { public class Entity { - // Assigned by reflection #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value private long _id; @@ -25,4 +24,4 @@ public Entity(string name) Name = name; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/IdGen/Enhanced/OptimizerTests.cs b/src/NHibernate.Test/IdGen/Enhanced/OptimizerTests.cs index 1952612cbb7..8f0fd01986f 100644 --- a/src/NHibernate.Test/IdGen/Enhanced/OptimizerTests.cs +++ b/src/NHibernate.Test/IdGen/Enhanced/OptimizerTests.cs @@ -28,7 +28,7 @@ public void TestBasicNoOptimizerUsage() long next = (long)optimizer.Generate(sequence); Assert.That(next, Is.EqualTo(i)); } - Assert.That(sequence.TimesCalled, Is.EqualTo(11)); // an extra time to get to 1 initially + Assert.That(sequence.TimesCalled, Is.EqualTo(11)); // an extra time to get to 1 initially Assert.That(sequence.CurrentValue, Is.EqualTo(10)); } @@ -55,7 +55,6 @@ public void TestBasicHiLoOptimizerUsage() Assert.That(sequence.TimesCalled, Is.EqualTo(2)); Assert.That(sequence.CurrentValue, Is.EqualTo(2)); - // test historic table behavior, where the initial values started at 0 (we now force 1 to be the first used id value) sequence = new SourceMock(0); optimizer = OptimizerFactory.BuildOptimizer(OptimizerFactory.HiLo, typeof(long), increment, -1); @@ -239,4 +238,4 @@ public void TestRecoveredPooledLoOptimizerUsage() Assert.That(sequence.CurrentValue, Is.EqualTo(4)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/IdGen/Enhanced/Table/BasicTableTest.cs b/src/NHibernate.Test/IdGen/Enhanced/Table/BasicTableTest.cs index 54d2ffe7497..16b8e768641 100644 --- a/src/NHibernate.Test/IdGen/Enhanced/Table/BasicTableTest.cs +++ b/src/NHibernate.Test/IdGen/Enhanced/Table/BasicTableTest.cs @@ -17,7 +17,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - [Test] public void TestNormalBoundary() { @@ -41,12 +40,10 @@ public void TestNormalBoundary() Assert.That(entities[i].Id, Is.EqualTo(expectedId)); Assert.That(generator.TableAccessCount, Is.EqualTo(expectedId)); Assert.That(generator.Optimizer.LastSourceValue, Is.EqualTo(expectedId)); - } transaction.Commit(); } - using (ITransaction transaction = s.BeginTransaction()) { for (int i = 0; i < count; i++) diff --git a/src/NHibernate.Test/IdGen/Enhanced/Table/HiLoTableTest.cs b/src/NHibernate.Test/IdGen/Enhanced/Table/HiLoTableTest.cs index 28b0b72d2bc..878252ce3dd 100644 --- a/src/NHibernate.Test/IdGen/Enhanced/Table/HiLoTableTest.cs +++ b/src/NHibernate.Test/IdGen/Enhanced/Table/HiLoTableTest.cs @@ -54,7 +54,6 @@ public void TestNormalBoundary() transaction.Commit(); } - using (ITransaction transaction = s.BeginTransaction()) { for (int i = 0; i < entities.Length; i++) diff --git a/src/NHibernate.Test/IdGen/Enhanced/Table/PooledLoTableTest.cs b/src/NHibernate.Test/IdGen/Enhanced/Table/PooledLoTableTest.cs index 14b1a4fbb3d..bd5245dbe9a 100644 --- a/src/NHibernate.Test/IdGen/Enhanced/Table/PooledLoTableTest.cs +++ b/src/NHibernate.Test/IdGen/Enhanced/Table/PooledLoTableTest.cs @@ -17,7 +17,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - [Test] public void TestNormalBoundary() { @@ -40,7 +39,7 @@ public void TestNormalBoundary() entities[i] = new Entity("" + (i + 1)); s.Save(entities[i]); Assert.That(generator.TableAccessCount, Is.EqualTo(1)); // initialization - Assert.That(optimizer.LastSourceValue, Is.EqualTo(1)); // initialization + Assert.That(optimizer.LastSourceValue, Is.EqualTo(1)); // initialization Assert.That(optimizer.LastValue, Is.EqualTo(i + 1)); } diff --git a/src/NHibernate.Test/IdGen/Enhanced/Table/PooledTableTest.cs b/src/NHibernate.Test/IdGen/Enhanced/Table/PooledTableTest.cs index 9cdc166b0d7..3a7ca042514 100644 --- a/src/NHibernate.Test/IdGen/Enhanced/Table/PooledTableTest.cs +++ b/src/NHibernate.Test/IdGen/Enhanced/Table/PooledTableTest.cs @@ -17,7 +17,6 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } - [Test] public void TestNormalBoundary() { @@ -40,7 +39,7 @@ public void TestNormalBoundary() entities[i] = new Entity("" + (i + 1)); s.Save(entities[i]); Assert.That(generator.TableAccessCount, Is.EqualTo(2)); // initialization calls seq twice - Assert.That(optimizer.LastSourceValue, Is.EqualTo(increment + 1)); // initialization calls seq twice + Assert.That(optimizer.LastSourceValue, Is.EqualTo(increment + 1)); // initialization calls seq twice Assert.That(optimizer.LastValue, Is.EqualTo(i + 1)); } diff --git a/src/NHibernate.Test/IdTest/AssignedFixture.cs b/src/NHibernate.Test/IdTest/AssignedFixture.cs index 137c3e7ac9d..07a483cb8d5 100644 --- a/src/NHibernate.Test/IdTest/AssignedFixture.cs +++ b/src/NHibernate.Test/IdTest/AssignedFixture.cs @@ -6,11 +6,9 @@ namespace NHibernate.Test.IdTest { - [TestFixture] public class AssignedFixture : IdFixtureBase { - private string[] GetAssignedIdentifierWarnings(LogSpy ls) { List warnings = new List(); @@ -249,7 +247,5 @@ public void InsertCascadeNoWarning() Assert.That(warnings.Length, Is.EqualTo(0)); } } - } - } diff --git a/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs b/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs index 0fa40be3831..1e77060ab44 100644 --- a/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs +++ b/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs @@ -485,7 +485,7 @@ public void CreateWithNonEmptyManyToManyCollectionMergeWithNewElement() s.Close(); AssertInsertCount(1); - AssertUpdateCount(isContractVersioned && isPlanVersioned ? 1 : 0); // NH-specific: Hibernate issues a separate UPDATE for the version number + AssertUpdateCount(isContractVersioned && isPlanVersioned ? 1 : 0); // NH-specific: Hibernate issues a separate UPDATE for the version number ClearCounts(); s = OpenSession(); diff --git a/src/NHibernate.Test/Immutable/ImmutableTest.cs b/src/NHibernate.Test/Immutable/ImmutableTest.cs index 0d0f8b76cd5..214252925b1 100644 --- a/src/NHibernate.Test/Immutable/ImmutableTest.cs +++ b/src/NHibernate.Test/Immutable/ImmutableTest.cs @@ -1038,7 +1038,6 @@ public void ImmutableParentEntityWithMerge() AssertUpdateCount(0); AssertDeleteCount(3); - } [Test] diff --git a/src/NHibernate.Test/Insertordering/AnimalModel/Fixture.cs b/src/NHibernate.Test/Insertordering/AnimalModel/Fixture.cs index beca3285dd9..2b1c55ca4ed 100644 --- a/src/NHibernate.Test/Insertordering/AnimalModel/Fixture.cs +++ b/src/NHibernate.Test/Insertordering/AnimalModel/Fixture.cs @@ -153,5 +153,29 @@ public void InsertShouldNotInitializeOneToManyProxy() // instead.) } } + + // #2141 + [Test] + public void InsertShouldNotDependOnEntityEqualsImplementation() + { + var person = new PersonWithWrongEquals { Name = "AnimalOwner" }; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(person); + t.Commit(); + } + Sfi.Evict(typeof(PersonWithWrongEquals)); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var personProxy = s.Get(person.Id); + + s.Save(new Cat { Name = "Felix", Owner = personProxy }); + s.Save(new Cat { Name = "Loustic", Owner = personProxy }); + Assert.DoesNotThrow(() => { t.Commit(); }); + } + } } } diff --git a/src/NHibernate.Test/Insertordering/AnimalModel/Mappings.hbm.xml b/src/NHibernate.Test/Insertordering/AnimalModel/Mappings.hbm.xml index 1f2b3d055c3..1d83891c2a1 100644 --- a/src/NHibernate.Test/Insertordering/AnimalModel/Mappings.hbm.xml +++ b/src/NHibernate.Test/Insertordering/AnimalModel/Mappings.hbm.xml @@ -61,5 +61,11 @@ + + + + + + - \ No newline at end of file + diff --git a/src/NHibernate.Test/Insertordering/AnimalModel/PersonWithSivasKangals.cs b/src/NHibernate.Test/Insertordering/AnimalModel/PersonWithSivasKangals.cs index 676dde2d302..b9d4e719c6a 100644 --- a/src/NHibernate.Test/Insertordering/AnimalModel/PersonWithSivasKangals.cs +++ b/src/NHibernate.Test/Insertordering/AnimalModel/PersonWithSivasKangals.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace NHibernate.Test.Insertordering.AnimalModel { @@ -6,4 +7,18 @@ public class PersonWithSivasKangals : Person { public virtual IList SivasKangalsGeneric { get; set; } = new List(); } + + public class PersonWithWrongEquals : Person + { + public override bool Equals(object obj) + { + throw new ApplicationException("Equals call is unexpected."); + } + public override int GetHashCode() + { + throw new ApplicationException("GetHashCode call is unexpected."); + } + + public virtual IList SivasKangalsGeneric { get; set; } = new List(); + } } diff --git a/src/NHibernate.Test/Insertordering/InsertOrderingFixture.cs b/src/NHibernate.Test/Insertordering/InsertOrderingFixture.cs index a669948da7d..4a2363ad5c6 100644 --- a/src/NHibernate.Test/Insertordering/InsertOrderingFixture.cs +++ b/src/NHibernate.Test/Insertordering/InsertOrderingFixture.cs @@ -1,10 +1,8 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; -using System.Threading; using NHibernate.AdoNet; using NHibernate.Cfg; using NHibernate.Dialect; @@ -78,8 +76,8 @@ protected override void OnTearDown() [Test] public void BatchOrdering() { - using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { for (int i = 0; i < instancesPerEach; i++) { @@ -90,7 +88,7 @@ public void BatchOrdering() user.AddMembership(group); } StatsBatcher.Reset(); - s.Transaction.Commit(); + t.Commit(); } int expectedBatchesPerEntity = (instancesPerEach / batchSize) + ((instancesPerEach % batchSize) == 0 ? 0 : 1); diff --git a/src/NHibernate.Test/Join/Customer.cs b/src/NHibernate.Test/Join/Customer.cs index fe57cc30d70..d5c07e43401 100644 --- a/src/NHibernate.Test/Join/Customer.cs +++ b/src/NHibernate.Test/Join/Customer.cs @@ -17,6 +17,5 @@ public virtual string Comments get { return _Comments; } set { _Comments = value; } } - } } diff --git a/src/NHibernate.Test/Join/JoinTest.cs b/src/NHibernate.Test/Join/JoinTest.cs index 463cd0184b9..50d2e6d1306 100644 --- a/src/NHibernate.Test/Join/JoinTest.cs +++ b/src/NHibernate.Test/Join/JoinTest.cs @@ -431,7 +431,6 @@ private Employee[] CreateAndInsertEmployees(ISession s, int count) return result; } - [Test] public void TestSimpleInsertAndRetrieveEmployee() { @@ -574,4 +573,4 @@ public void PolymorphicGetByTypeofSuperclass() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Join/User.cs b/src/NHibernate.Test/Join/User.cs index 2726c5eb851..39875a727ba 100644 --- a/src/NHibernate.Test/Join/User.cs +++ b/src/NHibernate.Test/Join/User.cs @@ -14,6 +14,5 @@ public virtual string Login get { return _Login; } set { _Login = value; } } - } } diff --git a/src/NHibernate.Test/JoinedSubclass/JoinedSubclassFixture.cs b/src/NHibernate.Test/JoinedSubclass/JoinedSubclassFixture.cs index ba5b88d1455..2a516e5e5b4 100644 --- a/src/NHibernate.Test/JoinedSubclass/JoinedSubclassFixture.cs +++ b/src/NHibernate.Test/JoinedSubclass/JoinedSubclassFixture.cs @@ -21,7 +21,6 @@ protected override string[] Mappings get { return new string[] {"JoinedSubclass.JoinedSubclass.hbm.xml"}; } } - private DateTime testDateTime = new DateTime(2003, 8, 16); private DateTime updateDateTime = new DateTime(2003, 8, 17); diff --git a/src/NHibernate.Test/LazyGroup/LazyGroupFixture.cs b/src/NHibernate.Test/LazyGroup/LazyGroupFixture.cs index 82b51aa0c4a..e7be24d19db 100644 --- a/src/NHibernate.Test/LazyGroup/LazyGroupFixture.cs +++ b/src/NHibernate.Test/LazyGroup/LazyGroupFixture.cs @@ -213,7 +213,6 @@ private static Person GeneratePerson(int i) Image = new byte[i], NickName = $"NickName{i}" }; - } } } diff --git a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs index af65b929e69..8a2b2d6939a 100644 --- a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs @@ -61,7 +61,6 @@ protected override void OnSetUp() }); tx.Commit(); } - } protected override void OnTearDown() @@ -319,7 +318,6 @@ public void CacheShouldNotContainLazyProperties() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - book = s.CreateQuery("from Book b fetch all properties where b.Id = :id") .SetParameter("id", 1) .UniqueResult(); @@ -332,7 +330,6 @@ public void CacheShouldNotContainLazyProperties() using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { - book = s.Get(1); tx.Commit(); } @@ -366,7 +363,6 @@ public void CanMergeTransientWithLazyPropertyInCollection() Assert.That(book, Is.Not.Null); Assert.That(book.Name, Is.EqualTo("some name two")); Assert.That(book.ALotOfText, Is.EqualTo("a lot of text two...")); - } using (var s = OpenSession()) using (var tx = s.BeginTransaction()) diff --git a/src/NHibernate.Test/Legacy/CriteriaTest.cs b/src/NHibernate.Test/Legacy/CriteriaTest.cs index 2158acbc1e0..d0ad5b3d2f3 100644 --- a/src/NHibernate.Test/Legacy/CriteriaTest.cs +++ b/src/NHibernate.Test/Legacy/CriteriaTest.cs @@ -187,6 +187,5 @@ public void DetachedCriteria_can_get_query_entity_type() DetachedCriteria.For().GetRootEntityTypeIfAvailable() ); } - } } diff --git a/src/NHibernate.Test/Legacy/FooBarTest.cs b/src/NHibernate.Test/Legacy/FooBarTest.cs index 628ed9dd212..9704b5dc737 100644 --- a/src/NHibernate.Test/Legacy/FooBarTest.cs +++ b/src/NHibernate.Test/Legacy/FooBarTest.cs @@ -440,7 +440,6 @@ public void Query() list = s.CreateQuery("select foo, bar from Foo foo left outer join foo.TheFoo bar where foo = ?") .SetEntity(0, foo).List(); - object[] row1 = (object[]) list[0]; Assert.IsTrue(row1[0] == foo && row1[1] == foo2); @@ -599,7 +598,6 @@ public void Query() enumerable = s.CreateQuery( "select foo.Component.Name, elements(foo.Component.ImportantDates) from foo in class Foo where foo.TheFoo.id=?"). SetString(0, foo.TheFoo.Key).Enumerable(); - int i = 0; foreach (object[] row in enumerable) @@ -1005,7 +1003,6 @@ public void NonlazyCollections() } } - [Test] public void ReuseDeletedCollection() { @@ -1254,7 +1251,6 @@ public void FetchInitializedCollection() s.Close(); } - [Test] public void LateCollectionAdd() { @@ -1317,7 +1313,6 @@ public void Update() s.Close(); } - [Test] public void ListRemove() { @@ -1463,7 +1458,6 @@ public void Sortables() s.Close(); } - [Test] public void FetchList() { @@ -1491,7 +1485,6 @@ public void FetchList() s.Close(); } - [Test] public void BagOneToMany() { @@ -1515,7 +1508,6 @@ public void BagOneToMany() s.Close(); } - [Test] public void QueryLockMode() { @@ -1590,7 +1582,6 @@ public void QueryLockMode() s.Close(); } - [Test] public void ManyToManyBag() { @@ -1617,7 +1608,6 @@ public void ManyToManyBag() s.Close(); } - [Test] public void IdBag() { @@ -1677,7 +1667,6 @@ public void IdBag() s.Close(); } - [Test] public void ForceOuterJoin() { @@ -1714,7 +1703,6 @@ public void ForceOuterJoin() s.Close(); } - [Test] public void EmptyCollection() { @@ -1735,7 +1723,6 @@ public void EmptyCollection() s.Close(); } - [Test] public void OneToOneGenerator() { @@ -1756,7 +1743,6 @@ public void OneToOneGenerator() Assert.AreEqual(x.Id, y.Id); - s = OpenSession(); x = new X(); y = new Y(); @@ -1772,7 +1758,6 @@ public void OneToOneGenerator() Assert.AreEqual(x.Id, y.Id); - s = OpenSession(); x = new X(); y = new Y(); @@ -1804,7 +1789,6 @@ public void OneToOneGenerator() s.Close(); } - [Test] public void Limit() { @@ -1834,7 +1818,6 @@ public void Limit() s.Close(); } - [Test] public void Custom() { @@ -1867,7 +1850,6 @@ public void Custom() s.Close(); } - [Test] public void SaveAddDelete() { @@ -1883,7 +1865,6 @@ public void SaveAddDelete() s.Close(); } - [Test] public void NamedParams() { @@ -2077,7 +2058,6 @@ public void FindByCriteria() Assert.IsTrue(list.Count == 1 && list[0] == f); - list = s.CreateCriteria(typeof(Foo)) .SetMaxResults(5) .AddOrder(Order.Asc("Date")) @@ -2125,7 +2105,6 @@ public void FindByCriteria() s.Close(); } - [Test] public void AfterDelete() { @@ -2140,7 +2119,6 @@ public void AfterDelete() s.Close(); } - [Test] public void CollectionWhere() { @@ -2179,7 +2157,6 @@ public void CollectionWhere() s.Close(); } - [Test] public void ComponentParent() { @@ -2211,7 +2188,6 @@ public void ComponentParent() s.Close(); } - [Test] public void CollectionCache() { @@ -2234,7 +2210,6 @@ public void CollectionCache() s.Close(); } - [Test] //[Ignore("TimeZone Portions commented out - http://nhibernate.jira.com/browse/NH-88")] public void AssociationId() @@ -2280,7 +2255,6 @@ public void AssociationId() NHibernateUtil.String }; - //IList results = s.List( hqlString, values, types ); IQuery q = s.CreateQuery(hqlString); for (int i = 0; i < values.Length; i++) @@ -2307,7 +2281,6 @@ public void AssociationId() results = q.List(); Assert.AreEqual(1, results.Count); - hqlString = "from s in class Stuff where s.Foo.String is not null"; s.CreateQuery(hqlString).List(); @@ -2350,7 +2323,6 @@ public void AssociationId() } } - [Test] public void CascadeSave() { @@ -2377,7 +2349,6 @@ public void CascadeSave() s.Close(); } - [Test] public void CompositeKeyPathExpressions() { @@ -2408,7 +2379,6 @@ public void CompositeKeyPathExpressions() s.Close(); } - [Test] public void CollectionsInSelect() { @@ -2445,7 +2415,6 @@ public void CollectionsInSelect() Assert.AreEqual(1, r.Count); Assert.AreEqual(foos[1].Long, r.Amount); - list = s.CreateQuery( "select new Result( baz.Name, max(foo.Long), count(foo) ) from Baz baz join baz.FooArray foo group by baz.Name"). @@ -2545,7 +2514,6 @@ public void CollectionsInSelect() s.Close(); } - [Test] public void NewFlushing() { @@ -2628,7 +2596,6 @@ public void NewFlushing() s.Close(); } - [Test] public void PersistCollections() { @@ -2676,7 +2643,6 @@ public void PersistCollections() list = s.CreateQuery( "select foo from foo in class NHibernate.DomainModel.Foo, baz in class NHibernate.DomainModel.Baz where foo in elements(baz.FooArray) and 3 = some elements(baz.IntArray) and 4 > all indices(baz.IntArray)") .List(); - Assert.AreEqual(2, list.Count, "collection.elements find"); } @@ -2859,7 +2825,7 @@ public void PersistCollections() s.Delete(baz.TopGlarchez['H']); var cmd = s.Connection.CreateCommand(); - s.Transaction.Enlist(cmd); + txn.Enlist(cmd); cmd.CommandText = "update " + Dialect.QuoteForTableName("glarchez") + " set baz_map_id=null where baz_map_index='a'"; int rows = cmd.ExecuteNonQuery(); Assert.AreEqual(1, rows); @@ -2904,7 +2870,6 @@ public void PersistCollections() s.Close(); } - [Test] public void SaveFlush() { @@ -2924,7 +2889,6 @@ public void SaveFlush() s.Close(); } - [Test] public void CreateUpdate() { @@ -2958,7 +2922,6 @@ public void CreateUpdate() s.Close(); } - [Test] public void UpdateCollections() { @@ -3026,7 +2989,6 @@ public void UpdateCollections() s.Close(); } - [Test] public void Load() { @@ -3055,7 +3017,6 @@ public void Load() s.Close(); } - [Test] public void Create() { @@ -3075,7 +3036,6 @@ public void Create() s.Close(); } - [Test] public void Callback() { @@ -3113,7 +3073,6 @@ public void Callback() s.Close(); } - [Test] public void Polymorphism() { @@ -3133,7 +3092,6 @@ public void Polymorphism() s.Close(); } - [Test] public void RemoveContains() { @@ -3156,7 +3114,6 @@ public void RemoveContains() s.Close(); } - [Test] public void CollectionOfSelf() { @@ -3197,7 +3154,6 @@ public void CollectionOfSelf() s.Close(); } - [Test] public void Find() { @@ -3272,8 +3228,7 @@ public void Find() txn.Commit(); s.Close(); } - - + [Test] public void DeleteRecursive() { @@ -3290,8 +3245,7 @@ public void DeleteRecursive() s.Flush(); s.Close(); } - - + [Test] public void Reachability() { @@ -3414,8 +3368,7 @@ public void Reachability() s.Flush(); s.Close(); } - - + [Test] public void PersistentLifecycle() { @@ -3441,8 +3394,7 @@ public void PersistentLifecycle() s.Flush(); s.Close(); } - - + [Test] public void Enumerable() { @@ -3779,7 +3731,6 @@ public void VersionedCollections() s.Close(); } - [Test] public void RecursiveLoad() { @@ -3982,7 +3933,6 @@ public void MultiColumnQueries() s.Close(); } - [Test] public void DeleteTransient() { @@ -4207,7 +4157,6 @@ public void UpdateFromTransient() s.Close(); } - [Test] public void ArraysOfTimes() { @@ -4285,7 +4234,6 @@ public void Components() s.Close(); } - [Test] public void Enum() { @@ -4351,8 +4299,7 @@ public void Enum() s.Flush(); s.Close(); } - - + [Test] public void NoForeignKeyViolations() { @@ -4921,7 +4868,6 @@ public void AutoFlushCollections() e = s.CreateQuery("select elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").Enumerable(). GetEnumerator(); - bool found = false; while (e.MoveNext()) @@ -5127,7 +5073,6 @@ public void LoadAfterDelete() s.Close(); } - [Test] public void ObjectType() { @@ -5154,7 +5099,6 @@ public void ObjectType() } } - [Test] public void Any() { @@ -5465,6 +5409,20 @@ public void PSCache() } } + // NH-2329 (GH-1218) + [Test] + public void JoinOverJoin() + { + using (var s = OpenSession()) + { + s.CreateCriteria(typeof(Stuff), "s1") + .CreateAlias("s1.MoreStuff", "m1") + .CreateAlias("m1.Stuffs", "s2") + .Add(Expression.Eq("s2.Id", 2L)) + .List(); + } + } + #region NHibernate specific tests [Test] diff --git a/src/NHibernate.Test/Legacy/FumTest.cs b/src/NHibernate.Test/Legacy/FumTest.cs index ebd83bb9ed0..8cefd0d38e1 100644 --- a/src/NHibernate.Test/Legacy/FumTest.cs +++ b/src/NHibernate.Test/Legacy/FumTest.cs @@ -536,7 +536,6 @@ public void CompositeIDs() s.Close(); } - [Test] public void KeyManyToOne() { @@ -588,7 +587,6 @@ public void KeyManyToOne() s.Close(); } - [Test] public void CompositeKeyPathExpressions() { diff --git a/src/NHibernate.Test/Legacy/MultiTableTest.cs b/src/NHibernate.Test/Legacy/MultiTableTest.cs index 32ecfca5e4d..fcdd25f93a1 100644 --- a/src/NHibernate.Test/Legacy/MultiTableTest.cs +++ b/src/NHibernate.Test/Legacy/MultiTableTest.cs @@ -29,7 +29,6 @@ public void FetchManyToOne() s.Close(); } - [Test] public void Joins() { @@ -51,7 +50,6 @@ public void Joins() s.Close(); } - [Test] public void JoinOpenBug() { diff --git a/src/NHibernate.Test/Legacy/SQLFunctionsTest.cs b/src/NHibernate.Test/Legacy/SQLFunctionsTest.cs index f96677909ea..2ee617dbbd3 100644 --- a/src/NHibernate.Test/Legacy/SQLFunctionsTest.cs +++ b/src/NHibernate.Test/Legacy/SQLFunctionsTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using log4net; using NHibernate.Dialect; using NHibernate.Dialect.Function; @@ -104,19 +105,30 @@ public void DialectSQLFunctions() s.Close(); } + //NH-3893 (GH-1349) left and right functions are broken [Test] - [Ignore("NH-3893 is not fixed")] public void LeftAndRight() { - // As of NH-3893, left and right functions are broken. Seemed confused with join keyword, and not - // supported on Hibernate side. + //left or right functions are supported by most dialects but not registered. + AssumeFunctionSupported("left"); + AssumeFunctionSupported("right"); + using (var s = OpenSession()) using (var t = s.BeginTransaction()) { + Simple simple = new Simple(); + simple.Name = "Simple Dialect Function Test"; + simple.Address = "Simple Address"; + simple.Pay = 45.8f; + simple.Count = 2; + s.Save(simple, 10L); + var rset = s.CreateQuery("select left('abc', 2), right('abc', 2) from s in class Simple").List(); var row = rset[0]; Assert.AreEqual("ab", row[0], "Left function is broken."); Assert.AreEqual("bc", row[1], "Right function is broken."); + + s.Delete(simple); t.Commit(); } } @@ -137,6 +149,112 @@ public void SetProperties() s.Close(); } + [Test] + public void SetParametersWithDictionary() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + s.Save(simple, 10L); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and s.Count = :Count"); + var parameters = new Dictionary + { + { nameof(simple.Name), simple.Name }, + { nameof(simple.Count), simple.Count }, + }; + q.SetProperties(parameters); + var results = q.List(); + Assert.That(results, Has.One.EqualTo(simple)); + s.Delete(simple); + t.Commit(); + } + } + + [Test] + public void SetParametersWithHashtable() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + s.Save(simple, 10L); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and (s.Address = :Address or :Address is null and s.Address is null)"); + var parameters = new Hashtable + { + { nameof(simple.Name), simple.Name }, + { nameof(simple.Address), simple.Address }, + }; + q.SetProperties(parameters); + var results = q.List(); + Assert.That(results, Has.One.EqualTo(simple)); + s.Delete(simple); + t.Commit(); + } + } + + [Test] + public void SetParametersWithDynamic() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + s.Save(simple, 10L); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and s.Count = :Count"); + dynamic parameters = new ExpandoObject(); + parameters.Name = simple.Name; + parameters.Count = simple.Count; + q.SetProperties(parameters); + var results = q.List(); + Assert.That(results, Has.One.EqualTo(simple)); + s.Delete(simple); + t.Commit(); + } + } + + [Test] + public void SetNullParameterWithDictionary() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + s.Save(simple, 10L); + var q = s.CreateQuery("from s in class Simple where s.Name = :Name and (s.Address = :Address or :Address is null and s.Address is null)"); + var parameters = new Dictionary + { + { nameof(simple.Name), simple.Name }, + { nameof(simple.Address), null }, + }; + q.SetProperties(parameters); + var results = q.List(); + Assert.That(results, Has.One.EqualTo(simple)); + s.Delete(simple); + t.Commit(); + } + } + + [Test] + public void SetParameterListWithDictionary() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var simple = new Simple { Name = "Simple 1" }; + s.Save(simple, 10L); + var q = s.CreateQuery("from s in class Simple where s.Name in (:Name)"); + var parameters = new Dictionary + { + { nameof(simple.Name), new [] {simple.Name} } + }; + q.SetProperties(parameters); + var results = q.List(); + Assert.That(results, Has.One.EqualTo(simple)); + s.Delete(simple); + t.Commit(); + } + } [Test] public void Broken() @@ -200,7 +318,6 @@ public void NothingToUpdate() s.Close(); } - [Test] public void CachedQuery() { diff --git a/src/NHibernate.Test/Legacy/SQLLoaderTest.cs b/src/NHibernate.Test/Legacy/SQLLoaderTest.cs index f7960c7192b..5818a4f9909 100644 --- a/src/NHibernate.Test/Legacy/SQLLoaderTest.cs +++ b/src/NHibernate.Test/Legacy/SQLLoaderTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using NHibernate.Dialect; using NHibernate.DomainModel; using NUnit.Framework; @@ -140,6 +141,64 @@ public void FindBySQLProperties() session.Close(); } + [Test] + public void FindBySQLDictionary() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var s = new Category { Name = nextLong.ToString() }; + nextLong++; + session.Save(s); + + s = new Category { Name = "WannaBeFound" }; + session.Flush(); + + var query = + session.CreateSQLQuery("select {category.*} from Category {category} where {category}.Name = :Name") + .AddEntity("category", typeof(Category)); + var parameters = new Dictionary + { + { nameof(s.Name), s.Name } + }; + query.SetProperties(parameters); + var results = query.List(); + Assert.That(results, Is.Empty); + + session.Delete("from Category"); + tran.Commit(); + } + } + + [Test] + public void FindBySQLDynamic() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var s = new Category { Name = nextLong.ToString() }; + nextLong++; + session.Save(s); + + s = new Category { Name = "WannaBeFound" }; + session.Flush(); + + var query = + session.CreateSQLQuery("select {category.*} from Category {category} where {category}.Name = :Name") + .AddEntity("category", typeof(Category)); + dynamic parameters = new ExpandoObject(); + parameters.Name = s.Name; + // dynamic does not work on inherited interface method calls. https://stackoverflow.com/q/3071634 + IQuery q = query; + q.SetProperties(parameters); + var results = query.List(); + Assert.That(results, Is.Empty); + + session.Delete("from Category"); + tran.Commit(); + } + } + [Test] public void FindBySQLAssociatedObject() { @@ -409,7 +468,6 @@ public void EmbeddedCompositeProperties() Assert.IsTrue(list.Count == 1); - session.Clear(); query = diff --git a/src/NHibernate.Test/Legacy/SimpleTest.cs b/src/NHibernate.Test/Legacy/SimpleTest.cs index 459d76c576f..50f6797a199 100644 --- a/src/NHibernate.Test/Legacy/SimpleTest.cs +++ b/src/NHibernate.Test/Legacy/SimpleTest.cs @@ -100,7 +100,6 @@ public void TestCRUD() s4.Close(); } - [Test] public void SetPropertiesOnQuery() { @@ -138,4 +137,4 @@ public void SetPropertiesOnQuery() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs b/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs index d220cfece5f..e576587cc01 100644 --- a/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/AverageTests.cs @@ -10,32 +10,10 @@ public class AverageTests : LinqTestCase [Test] public void CanGetAverageOfIntegersAsDouble() { - if (Dialect is Oracle8iDialect) - { - // The point of this test is to verify that LINQ's Average over an - // integer columns yields a non-integer result, even on databases - // where the corresponding avg() will yield a return type equal to - // the input type. This means the LINQ provider must generate HQL - // that cast the input to double inside the call to avg(). This works - // fine on most databases, but Oracle causes trouble. - // - // The dialect maps double to "DOUBLE PRECISION" on Oracle, which - // on Oracle has larger precision than C#'s double. When such - // values are returned, ODP.NET will convert it to .Net decimal, which - // has lower precision and thus causes an overflow exception. - // - // Some argue that this is a flaw in ODP.NET, others have created - // local dialects that use e.g. BINARY_DOUBLE instead, which more - // closely matches C#'s IEEE 745 double, see e.g. HHH-1961 and - // serveral blogs. - - Assert.Ignore("Not supported on Oracle due to casting/overflow issues."); - } - //NH-2429 var average = db.Products.Average(x => x.UnitsOnOrder); Assert.AreEqual(average, 10.129870d, 0.000001d); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Linq/ByMethod/CountTests.cs b/src/NHibernate.Test/Linq/ByMethod/CountTests.cs index 3bb93c7a083..e1ced74bd04 100644 --- a/src/NHibernate.Test/Linq/ByMethod/CountTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/CountTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NHibernate.Cfg; +using NHibernate.Dialect; using NUnit.Framework; namespace NHibernate.Test.Linq.ByMethod @@ -27,6 +28,21 @@ public void CountDistinctProperty_ReturnsNumberOfDistinctEntriesForThatProperty( Assert.That(result, Is.EqualTo(387)); } + //NH-3249 (GH-1285) + [Test] + public void CountDistinctFunc_ReturnsNumberOfDistinctEntriesForThatFunc() + { + if (!TestDialect.SupportsCountDistinct) + Assert.Ignore("Dialect does not support count distinct"); + + var result = db.Orders + .Select(x => x.ShippingDate.Value.Date) + .Distinct() + .Count(); + + Assert.That(result, Is.EqualTo(387)); + } + [Test] public void CountProperty_ReturnsNumberOfNonNullEntriesForThatProperty() { @@ -98,5 +114,50 @@ into temp Assert.That(result.Count, Is.EqualTo(77)); } + + [Test] + public void CheckSqlFunctionNameLongCount() + { + var name = Dialect is MsSql2000Dialect ? "count_big" : "count"; + using (var sqlLog = new SqlLogSpy()) + { + var result = db.Orders.LongCount(); + Assert.That(result, Is.EqualTo(830)); + + var log = sqlLog.GetWholeLog(); + Assert.That(log, Does.Contain($"{name}(")); + } + } + + [Test] + public void CheckSqlFunctionNameForCount() + { + using (var sqlLog = new SqlLogSpy()) + { + var result = db.Orders.Count(); + Assert.That(result, Is.EqualTo(830)); + + var log = sqlLog.GetWholeLog(); + Assert.That(log, Does.Contain("count(")); + } + } + + [Test] + public void CheckMssqlCountCast() + { + if (!(Dialect is MsSql2000Dialect)) + { + Assert.Ignore(); + } + + using (var sqlLog = new SqlLogSpy()) + { + var result = db.Orders.Count(); + Assert.That(result, Is.EqualTo(830)); + + var log = sqlLog.GetWholeLog(); + Assert.That(log, Does.Not.Contain("cast(")); + } + } } } diff --git a/src/NHibernate.Test/Linq/ByMethod/DistinctTests.cs b/src/NHibernate.Test/Linq/ByMethod/DistinctTests.cs index a6a5e73927c..daa3758201d 100644 --- a/src/NHibernate.Test/Linq/ByMethod/DistinctTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/DistinctTests.cs @@ -32,6 +32,25 @@ public void DistinctOnAnonymousTypeProjection() Assert.That(result.Length, Is.EqualTo(388)); } + [Test] + public void DistinctAndOrderByOnAnonymousTypeProjection() + { + var result = db.Orders + .Where(x => x.ShippingDate != null) + .Select(x => new { x.ShippingDate }) + .OrderByDescending(x => x.ShippingDate) + .Distinct() + .ToArray(); + + var expectedResults = result + .OrderByDescending(x => x.ShippingDate) + .Distinct() + .ToArray(); + + Assert.That(result.Length, Is.EqualTo(387)); + CollectionAssert.AreEqual(expectedResults, result); + } + [Test] public void DistinctOnComplexAnonymousTypeProjection() { @@ -120,7 +139,6 @@ public void DistinctOnTypeProjectionWithCustomProjectionMethodsIsBlocked1() "Cannot use distinct on result that depends on methods for which no SQL equivalent exist.")); } - [Test] public void DistinctOnTypeProjectionWithCustomProjectionMethodsIsBlocked2() { diff --git a/src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs b/src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs index 4601013f556..626dc619692 100644 --- a/src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs @@ -128,7 +128,6 @@ public void SingleKeyPropertyGroupAndOrderByProjectedCount() AssertOrderedBy.Descending(orderCounts, oc => oc.OrderCount); } - [Test] public void SingleKeyPropertyGroupAndOrderByCountBeforeProjection() { @@ -891,7 +890,6 @@ public override int GetHashCode() } } - [Test(Description = "NH-3446"), KnownBug("NH-3446", "NHibernate.HibernateException")] public void GroupByOrderByKeySelectToClass() { @@ -901,7 +899,6 @@ public void GroupByOrderByKeySelectToClass() .ToList(); } - [Test(Description = "NH-3743")] public void FetchBeforeGroupBy() { @@ -915,6 +912,48 @@ public void FetchBeforeGroupBy() Assert.True(result.Any()); } + [Test] + public void SelectArrayIndexBeforeGroupBy() + { + var result = db.Orders + .SelectMany(o => o.OrderLines.Select(c => c.Id).DefaultIfEmpty().Select(c => new object[] {c, o})) + .GroupBy(g => g[0], g => (Order) g[1]) + .Select(g => new[] {g.Key, g.Count(), g.Max(x => x.OrderDate)}); + + Assert.True(result.Any()); + } + + [Test] + public void SelectMemberInitBeforeGroupBy() + { + var result = db.Orders + .Select(o => new OrderGroup {OrderId = o.OrderId, OrderDate = o.OrderDate}) + .GroupBy(o => o.OrderId) + .Select(g => new OrderGroup {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)}) + .ToList(); + + Assert.True(result.Any()); + } + + [Test] + public void SelectNewBeforeGroupBy() + { + var result = db.Orders + .Select(o => new {o.OrderId, o.OrderDate}) + .GroupBy(o => o.OrderId) + .Select(g => new {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)}) + .ToList(); + + Assert.True(result.Any()); + } + + private class OrderGroup + { + public int OrderId { get; set; } + + public DateTime? OrderDate { get; set; } + } + private class GroupInfo { public object Key { get; set; } diff --git a/src/NHibernate.Test/Linq/ByMethod/JoinTests.cs b/src/NHibernate.Test/Linq/ByMethod/JoinTests.cs new file mode 100644 index 00000000000..a7a4750b87a --- /dev/null +++ b/src/NHibernate.Test/Linq/ByMethod/JoinTests.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Engine.Query; +using NHibernate.Linq; +using NHibernate.Util; +using NSubstitute; +using NUnit.Framework; + +namespace NHibernate.Test.Linq.ByMethod +{ + [TestFixture] + public class JoinTests : LinqTestCase + { + [Test] + public void MultipleLinqJoinsWithSameProjectionNames() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = db.Orders + .Join(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 }) + .Select(x => new { First = x.order, Second = x.order1 }) + .Join(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 }) + .Select(x => new { FirstId = x.order.First.OrderId, SecondId = x.order.Second.OrderId, ThirdId = x.order1.OrderId }) + .ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(828)); + Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + } + } + + [Test] + public void MultipleLinqJoinsWithSameProjectionNamesWithLeftJoin() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = db.Orders + .GroupJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 }) + .SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new { First = x.order, Second = order1 }) + .GroupJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 }) + .SelectMany(x => x.order1.DefaultIfEmpty(), (x, order1) => new + { + FirstId = x.order.First.OrderId, + SecondId = (int?) x.order.Second.OrderId, + ThirdId = (int?) order1.OrderId + }) + .ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(830)); + Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue) + .All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public void MultipleLinqJoinsWithSameProjectionNamesWithLeftJoinExtensionMethod() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = db.Orders + .LeftJoin(db.Orders, x => x.OrderId, x => x.OrderId - 1, (order, order1) => new { order, order1 }) + .Select(x => new { First = x.order, Second = x.order1 }) + .LeftJoin(db.Orders, x => x.First.OrderId, x => x.OrderId - 2, (order, order1) => new { order, order1 }) + .Select(x => new + { + FirstId = x.order.First.OrderId, + SecondId = (int?) x.order.Second.OrderId, + ThirdId = (int?) x.order1.OrderId + }) + .ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(830)); + Assert.IsTrue(orders.Where(x => x.SecondId.HasValue && x.ThirdId.HasValue) + .All(x => x.FirstId == x.SecondId - 1 && x.SecondId == x.ThirdId - 1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public void LeftJoinExtensionMethodWithMultipleKeyProperties() + { + using (var sqlSpy = new SqlLogSpy()) + { + var orders = db.Orders + .LeftJoin( + db.Orders, + x => new {x.OrderId, x.Customer.CustomerId}, + x => new {x.OrderId, x.Customer.CustomerId}, + (order, order1) => new {order, order1}) + .Select(x => new {FirstId = x.order.OrderId, SecondId = x.order1.OrderId}) + .ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(orders.Count, Is.EqualTo(830)); + Assert.IsTrue(orders.All(x => x.FirstId == x.SecondId)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } + } + + [TestCase(false)] + [TestCase(true)] + public void CrossJoinWithPredicateInWhereStatement(bool useCrossJoin) + { + if (useCrossJoin && !Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + using (var substitute = SubstituteDialect()) + using (var sqlSpy = new SqlLogSpy()) + { + ClearQueryPlanCache(); + substitute.Value.SupportsCrossJoin.Returns(useCrossJoin); + + var result = (from o in db.Orders + from o2 in db.Orders.Where(x => x.Freight > 50) + where (o.OrderId == o2.OrderId + 1) || (o.OrderId == o2.OrderId - 1) + select new { o.OrderId, OrderId2 = o2.OrderId }).ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(result.Count, Is.EqualTo(720)); + Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join")); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(useCrossJoin ? 0 : 1)); + } + } + + [Test] + public void CanJoinOnEntityWithSubclasses() + { + var result = (from o in db.Animals + from o2 in db.Animals.Where(x => x.BodyWeight > 50) + select new {o, o2}).Take(1).ToList(); + } + + [Test(Description = "GH-2580")] + public void CanInnerJoinOnSubclassWithBaseTableReferenceInOnClause() + { + var result = (from o in db.Animals + join o2 in db.Mammals on o.BodyWeight equals o2.BodyWeight + select new { o, o2 }).Take(1).ToList(); + } + } +} diff --git a/src/NHibernate.Test/Linq/ByMethod/WithOptionsTests.cs b/src/NHibernate.Test/Linq/ByMethod/WithOptionsTests.cs index b32a55f287a..13cba8d2061 100644 --- a/src/NHibernate.Test/Linq/ByMethod/WithOptionsTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/WithOptionsTests.cs @@ -23,6 +23,7 @@ public void AppliesOptionsToQuery() var query = Substitute.For(); query.List().Returns(new List()); + query.List().Returns(new List()); session.CreateQuery(Arg.Any()).Returns(query); @@ -56,6 +57,7 @@ public void DoNotContaminateQueryWithOptions() var query = Substitute.For(); query.List().Returns(new List()); + query.List().Returns(new List()); session.CreateQuery(Arg.Any()).Returns(query); diff --git a/src/NHibernate.Test/Linq/ConstantTest.cs b/src/NHibernate.Test/Linq/ConstantTest.cs index 2b4be96a912..74b20f13afb 100644 --- a/src/NHibernate.Test/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Linq/ConstantTest.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using NHibernate.Criterion; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Engine.Query; +using NHibernate.Linq; using NHibernate.Linq.Visitors; using NHibernate.Util; using NUnit.Framework; @@ -118,6 +120,22 @@ public void ConstantNonCachedInMemberInitExpression() Assert.That(s2, Has.All.Property("Name").EqualTo("shipper2"), "s2 Names"); } + [Test] + public void ConstantNonCachedInMemberInitExpressionWithCondition() + { + var shipper1 = GetShipper(1); + var shipper2 = GetShipper(2); + + Assert.That(shipper1.Number, Is.EqualTo(1)); + Assert.That(shipper2.Number, Is.EqualTo(2)); + } + + private ShipperDto GetShipper(int id) + { + return db.Shippers.Where(o => o.ShipperId == id) + .Select(o => new ShipperDto {Number = id, CompanyName = o.CompanyName}).Single(); + } + [Test] public void ConstantInNewArrayExpression() { @@ -213,10 +231,14 @@ public void ConstantInWhereDoesNotCauseManyKeys() var q2 = (from c in db.Customers where c.CustomerId == "ANATR" select c); - var parameters1 = ExpressionParameterVisitor.Visit(q1.Expression, Sfi); - var k1 = ExpressionKeyVisitor.Visit(q1.Expression, parameters1); - var parameters2 = ExpressionParameterVisitor.Visit(q2.Expression, Sfi); - var k2 = ExpressionKeyVisitor.Visit(q2.Expression, parameters2); + var preTransformParameters = new PreTransformationParameters(QueryMode.Select, Sfi); + var preTransformResult = NhRelinqQueryParser.PreTransform(q1.Expression, preTransformParameters); + var parameters1 = ExpressionParameterVisitor.Visit(preTransformResult); + var k1 = ExpressionKeyVisitor.Visit(preTransformResult.Expression, parameters1, Sfi); + + var preTransformResult2 = NhRelinqQueryParser.PreTransform(q2.Expression, preTransformParameters); + var parameters2 = ExpressionParameterVisitor.Visit(preTransformResult2); + var k2 = ExpressionKeyVisitor.Visit(preTransformResult2.Expression, parameters2, Sfi); Assert.That(parameters1, Has.Count.GreaterThan(0), "parameters1"); Assert.That(parameters2, Has.Count.GreaterThan(0), "parameters2"); @@ -257,6 +279,66 @@ public void PlansAreCached() } } + [Test] + public void DmlPlansAreCached() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + using (session.BeginTransaction()) + { + db.Customers.Where(c => c.CustomerId == "UNKNOWN").Update(x => new Customer {CompanyName = "Constant1"}); + db.Customers.Where(c => c.CustomerId == "ALFKI").Update(x => new Customer {CompanyName = x.CompanyName}); + db.Customers.Where(c => c.CustomerId == "UNKNOWN").Update(x => new Customer {ContactName = "Constant1"}); + Assert.That( + cache, + Has.Count.EqualTo(3), + "Query plans should be cached."); + + using (var spy = new LogSpy(queryPlanCacheType)) + { + //Queries below should hit plan cache. + using (var sqlSpy = new SqlLogSpy()) + { + db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {CompanyName = x.CompanyName}); + db.Customers.Where(c => c.CustomerId == "UNKNOWN").Update(x => new Customer {CompanyName = "Constant2"}); + db.Customers.Where(c => c.CustomerId == "UNKNOWN").Update(x => new Customer {ContactName = "Constant2"}); + + var sqlEvents = sqlSpy.Appender.GetEvents(); + Assert.That( + sqlEvents[0].RenderedMessage, + Does.Contain("ANATR").And.Not.Contain("UNKNOWN").And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + Assert.That( + sqlEvents[1].RenderedMessage, + Does.Contain("UNKNOWN").And.Contain("Constant2").And.Contain("CompanyName").IgnoreCase + .And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + Assert.That( + sqlEvents[2].RenderedMessage, + Does.Contain("UNKNOWN").And.Contain("Constant2").And.Contain("ContactName").IgnoreCase + .And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + } + + Assert.That(cache, Has.Count.EqualTo(3), "Additional queries should not cause a plan to be cached."); + Assert.That( + spy.GetWholeLog(), + Does + .Contain("located HQL query plan in cache") + .And.Not.Contain("unable to locate HQL query plan in cache")); + + db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {ContactName = x.ContactName}); + Assert.That(cache, Has.Count.EqualTo(4), "Query should be cached"); + } + } + } + [Test] public void PlansWithNonParameterizedConstantsAreNotCached() { @@ -276,5 +358,62 @@ public void PlansWithNonParameterizedConstantsAreNotCached() Has.Count.EqualTo(0), "Query plan should not be cached."); } + + [Test] + public void PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + var ids = new[] {"ANATR", "UNKNOWN"}.ToList(); + db.Customers.Where(x => ids.Contains(x.CustomerId)).Select( + c => new {c.CustomerId, c.ContactName, Constant = 1}).First(); + + Assert.That( + cache, + Has.Count.EqualTo(0), + "Query plan should not be cached."); + } + + //GH-2298 - Different Update queries - same query cache plan + [Test] + public void DmlPlansForExpandedQuery() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + using (session.BeginTransaction()) + { + var list = new[] {"UNKNOWN", "UNKNOWN2"}.ToList(); + db.Customers.Where(x => list.Contains(x.CustomerId)).Update( + x => new Customer + { + CompanyName = "Constant1" + }); + + db.Customers.Where(x => list.Contains(x.CustomerId)) + .Update( + x => new Customer + { + ContactName = "Constant1" + }); + + Assert.That( + cache.Count, + //2 original queries + 2 expanded queries are expected in cache + Is.EqualTo(0).Or.EqualTo(4), + "Query plans should either be cached separately or not cached at all."); + } + } } } diff --git a/src/NHibernate.Test/Linq/CustomExpressionTransformerRegistrarTests.cs b/src/NHibernate.Test/Linq/CustomExpressionTransformerRegistrarTests.cs new file mode 100644 index 00000000000..0cfacd1a5a2 --- /dev/null +++ b/src/NHibernate.Test/Linq/CustomExpressionTransformerRegistrarTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Linq; +using NHibernate.Linq.Visitors; +using NHibernate.Util; +using NUnit.Framework; +using Remotion.Linq.Parsing.ExpressionVisitors.Transformation; + +namespace NHibernate.Test.Linq +{ + [TestFixture] + public class CustomPreTransformRegistrarTests : LinqTestCase + { + protected override void Configure(Cfg.Configuration configuration) + { + configuration.Properties[Cfg.Environment.PreTransformerRegistrar] = typeof(PreTransformerRegistrar).AssemblyQualifiedName; + } + + [Test] + public void RewriteLike() + { + // This example shows how to use the pre-transformer registrar to rewrite the + // query so that StartsWith, EndsWith and Contains methods will generate the same sql. + var queryPlanCache = GetQueryPlanCache(); + queryPlanCache.Clear(); + db.Customers.Where(o => o.ContactName.StartsWith("A")).ToList(); + db.Customers.Where(o => o.ContactName.EndsWith("A")).ToList(); + db.Customers.Where(o => o.ContactName.Contains("A")).ToList(); + + Assert.That(queryPlanCache.Count, Is.EqualTo(1)); + } + + [Serializable] + public class PreTransformerRegistrar : IExpressionTransformerRegistrar + { + public void Register(ExpressionTransformerRegistry expressionTransformerRegistry) + { + expressionTransformerRegistry.Register(new LikeTransformer()); + } + } + + private class LikeTransformer : IExpressionTransformer + { + private static readonly MethodInfo Like = ReflectHelper.GetMethodDefinition(() => SqlMethods.Like(null, null)); + private static readonly MethodInfo EndsWith = ReflectHelper.GetMethodDefinition(x => x.EndsWith(null)); + private static readonly MethodInfo StartsWith = ReflectHelper.GetMethodDefinition(x => x.StartsWith(null)); + private static readonly MethodInfo Contains = ReflectHelper.GetMethodDefinition(x => x.Contains(null)); + private static readonly Dictionary> ValueTransformers = + new Dictionary> + { + {StartsWith, s => $"{s}%"}, + {EndsWith, s => $"%{s}"}, + {Contains, s => $"%{s}%"}, + }; + + public Expression Transform(MethodCallExpression expression) + { + if (ValueTransformers.TryGetValue(expression.Method, out var valueTransformer) && + expression.Arguments[0] is ConstantExpression constantExpression) + { + return Expression.Call( + Like, + expression.Object, + Expression.Constant(valueTransformer(constantExpression.Value)) + ); + } + + return expression; + } + + public ExpressionType[] SupportedExpressionTypes { get; } = {ExpressionType.Call}; + } + } +} diff --git a/src/NHibernate.Test/Linq/EagerLoadTests.cs b/src/NHibernate.Test/Linq/EagerLoadTests.cs index d813e76a92f..927dd1d3e1f 100644 --- a/src/NHibernate.Test/Linq/EagerLoadTests.cs +++ b/src/NHibernate.Test/Linq/EagerLoadTests.cs @@ -23,6 +23,48 @@ public void CanSelectAndFetch() Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].Orders)); } + [Test] + public void CanSelectAndFetchMany() + { + var result = db.OrderLines + .Select(o => o.Product) + .FetchMany(o => o.OrderLines) + .ToList(); + + session.Close(); + + Assert.IsNotEmpty(result); + Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].OrderLines)); + } + + [Test] + public void CanSelectManyAndFetch() + { + var result = db.Orders + .SelectMany(o => o.OrderLines) + .Fetch(o => o.Product) + .ToList(); + + session.Close(); + + Assert.IsNotEmpty(result); + Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].Product)); + } + + [Test] + public void CanSelectManyAndFetchMany() + { + var result = db.Employees + .SelectMany(o => o.Orders) + .FetchMany(o => o.OrderLines) + .ToList(); + + session.Close(); + + Assert.IsNotEmpty(result); + Assert.IsTrue(NHibernateUtil.IsInitialized(result[0].OrderLines)); + } + [Test] public void CanSelectAndFetchHql() { @@ -328,5 +370,231 @@ public void WhereAfterFetchAndSingleOrDefault() Assert.IsTrue(NHibernateUtil.IsInitialized(order.Shipper)); } + + [Test] + public void WhereReuseJoins() + { + OrderLine orderLine; + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .Fetch(o => o.Order) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.False); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.OrderLines.Any(l => l.Product.Name == "Tofu")) + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + sql = sql.Substring(0, sql.IndexOf("where")); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + using (var logSpy = new SqlLogSpy()) + { + db.Employees + .Where(o => o.Superior.Superior.Superior.FirstName != null) + .Fetch(o => o.Superior) + .ToList() + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, ","), Is.EqualTo(31), "Only the first level should be fetched."); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(3)); + } + + using (var logSpy = new SqlLogSpy()) + { + db.Employees + .Where(o => o.Superior.FirstName != null) + .Fetch(o => o.Superior).ThenFetch(o => o.Superior).ThenFetch(o => o.Superior) + .ToList() + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public void OrderByReuseJoins() + { + OrderLine orderLine; + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.OrderId == 10248) + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.OrderId == 10248) + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.False); + } + + using (var logSpy = new SqlLogSpy()) + { + db.Employees + .OrderBy(o => o.Superior.Superior.Superior.FirstName) + .Fetch(o => o.Superior) + .ToList() + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, ","), Is.EqualTo(31), "Only the first level should be fetched."); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(3)); + } + + using (var logSpy = new SqlLogSpy()) + { + db.Employees + .OrderBy(o => o.Superior.FirstName) + .Fetch(o => o.Superior).ThenFetch(o => o.Superior).ThenFetch(o => o.Superior) + .ToList() + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(3)); + } + } + + [Test] + public void WhereAndOrderByReuseJoins() + { + OrderLine orderLine; + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order).ThenFetch(o => o.Customer) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.True); + } + + session.Clear(); + using (var logSpy = new SqlLogSpy()) + { + orderLine = db.OrderLines + .Where(o => o.Order.Customer.ContactName == "Maria Anders") + .OrderBy(o => o.Order.Customer.ContactName) + .Fetch(o => o.Order) + .ToList() + .First(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order), Is.True); + Assert.That(NHibernateUtil.IsInitialized(orderLine.Order.Customer), Is.False); + } + + using (var logSpy = new SqlLogSpy()) + { + db.Employees + .Where(o => o.Superior.Superior.Superior.FirstName != null) + .OrderBy(o => o.Superior.Superior.Superior.FirstName) + .Fetch(o => o.Superior) + .ToList() + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, ","), Is.EqualTo(31), "Only the first level should be fetched."); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(3)); + } + + using (var logSpy = new SqlLogSpy()) + { + db.Employees + .Where(o => o.Superior.FirstName != null) + .OrderBy(o => o.Superior.FirstName) + .Fetch(o => o.Superior).ThenFetch(o => o.Superior).ThenFetch(o => o.Superior) + .ToList() + .FirstOrDefault(); + + var sql = logSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + + [Test] + public void FetchBeforeSelect() + { + var result = db.Orders + .Where(o => o.OrderId == 10248) + .Fetch(x => x.Customer) + .Select(x => new {x.Customer.ContactName}) + .ToList(); + + Assert.True(result.Any()); + } } } diff --git a/src/NHibernate.Test/Linq/EnumTests.cs b/src/NHibernate.Test/Linq/EnumTests.cs index 83507fc93d8..1b01c1706d7 100644 --- a/src/NHibernate.Test/Linq/EnumTests.cs +++ b/src/NHibernate.Test/Linq/EnumTests.cs @@ -1,55 +1,214 @@ -using System.Linq; -using NHibernate.DomainModel.Northwind.Entities; +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlTypes; +using NHibernate.Type; using NUnit.Framework; namespace NHibernate.Test.Linq { - [TestFixture] - public class EnumTests : LinqTestCase + [TestFixture(typeof(EnumType), "0")] + [TestFixture(typeof(EnumStringType), "'Unspecified'")] + [TestFixture(typeof(EnumAnsiStringType), "'Unspecified'")] + public class EnumTests : TestCaseMappingByCode { + private IType _enumType; + private string _unspecifiedValue; + + public EnumTests(System.Type enumType, string unspecifiedValue) + { + _enumType = (IType) Activator.CreateInstance(enumType); + _unspecifiedValue = unspecifiedValue; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Table("EnumEntity"); + rc.Id(x => x.Id, m => m.Generator(Generators.Guid)); + rc.Property(x => x.Name); + rc.Property(x => x.Enum1, m => m.Type(_enumType)); + rc.Property(x => x.NullableEnum1, m => + { + m.Type(_enumType); + m.Formula($"(case when Enum1 = {_unspecifiedValue} then null else Enum1 end)"); + }); + rc.ManyToOne(x => x.Other, m => m.Cascade(Mapping.ByCode.Cascade.All)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + base.OnSetUp(); + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + session.Save(new EnumEntity { Enum1 = TestEnum.Unspecified }); + session.Save(new EnumEntity { Enum1 = TestEnum.Small }); + session.Save(new EnumEntity { Enum1 = TestEnum.Small }); + session.Save(new EnumEntity { Enum1 = TestEnum.Medium }); + session.Save(new EnumEntity { Enum1 = TestEnum.Medium }); + session.Save(new EnumEntity { Enum1 = TestEnum.Medium }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + session.Save(new EnumEntity { Enum1 = TestEnum.Large }); + trans.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + [Test] - public void CanQueryOnEnumStoredAsInt32_High_1() + public void CanQueryOnEnum_Large_4() { - CanQueryOnEnumStoredAsInt32(EnumStoredAsInt32.High, 1); + CanQueryOnEnum(TestEnum.Large, 4); } [Test] - public void CanQueryOnEnumStoredAsInt32_Unspecified_2() + public void CanQueryOnEnum_Medium_3() { - CanQueryOnEnumStoredAsInt32(EnumStoredAsInt32.Unspecified, 2); + CanQueryOnEnum(TestEnum.Medium, 3); } + [Test] + public void CanQueryOnEnum_Small_2() + { + CanQueryOnEnum(TestEnum.Small, 2); + } + + [Test] + public void CanQueryOnEnum_Unspecified_1() + { + CanQueryOnEnum(TestEnum.Unspecified, 1); + } - public void CanQueryOnEnumStoredAsInt32(EnumStoredAsInt32 type, int expectedCount) + private void CanQueryOnEnum(TestEnum type, int expectedCount) { - var query = (from user in db.Users - where user.Enum2 == type - select user).ToList(); + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var query = session.Query().Where(x => x.Enum1 == type).ToList(); - Assert.AreEqual(expectedCount, query.Count); + Assert.AreEqual(expectedCount, query.Count); + } } [Test] - public void CanQueryOnEnumStoredAsString_Meduim_2() + public void CanQueryWithContainsOnTestEnum_Small_1() { - CanQueryOnEnumStoredAsString(EnumStoredAsString.Medium, 2); + var values = new[] { TestEnum.Small, TestEnum.Medium }; + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var query = session.Query().Where(x => values.Contains(x.Enum1)).ToList(); + Assert.AreEqual(5, query.Count); + } } [Test] - public void CanQueryOnEnumStoredAsString_Small_1() + public void ConditionalNavigationProperty() { - CanQueryOnEnumStoredAsString(EnumStoredAsString.Small, 1); + TestEnum? type = null; + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var entities = session.Query(); + entities.Where(o => o.Enum1 == TestEnum.Large).ToList(); + entities.Where(o => TestEnum.Large != o.Enum1).ToList(); + entities.Where(o => (o.NullableEnum1 ?? TestEnum.Large) == TestEnum.Medium).ToList(); + entities.Where(o => ((o.NullableEnum1 ?? type) ?? o.Enum1) == TestEnum.Medium).ToList(); + entities.Where(o => (o.NullableEnum1.HasValue ? o.Enum1 : TestEnum.Unspecified) == TestEnum.Medium).ToList(); + entities.Where(o => (o.Enum1 != TestEnum.Large + ? (o.NullableEnum1.HasValue ? o.Enum1 : TestEnum.Unspecified) + : TestEnum.Small) == TestEnum.Medium).ToList(); + + entities.Where(o => (o.Enum1 == TestEnum.Large ? o.Other : o.Other).Name == "test").ToList(); + } } - public void CanQueryOnEnumStoredAsString(EnumStoredAsString type, int expectedCount) + [Test] + public void CanQueryComplexExpressionOnTestEnum() { - var query = (from user in db.Users - where user.Enum1 == type - select user).ToList(); + //TODO: Fix issue on SQLite with type set to TestEnum.Unspecified + TestEnum? type = null; + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var entities = session.Query(); + + var query = (from user in entities + where (user.NullableEnum1 == TestEnum.Large + ? TestEnum.Medium + : user.NullableEnum1 ?? user.Enum1 + ) == type + select new + { + user, + simple = user.Enum1, + condition = user.Enum1 == TestEnum.Large ? TestEnum.Medium : user.Enum1, + coalesce = user.NullableEnum1 ?? TestEnum.Medium + }).ToList(); - Assert.AreEqual(expectedCount, query.Count); + Assert.That(query.Count, Is.EqualTo(0)); + } } } + + public class EnumEntity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual TestEnum Enum1 { get; set; } + public virtual TestEnum? NullableEnum1 { get; set; } + + public virtual EnumEntity Other { get; set; } + } + + public enum TestEnum + { + Unspecified, + Small, + Medium, + Large + } + + [Serializable] + public class EnumAnsiStringType : EnumStringType + { + private readonly string typeName; + + public EnumAnsiStringType() + : base(typeof(T)) + { + System.Type type = GetType(); + typeName = type.FullName + ", " + type.Assembly.GetName().Name; + } + + public override string Name + { + get { return typeName; } + } + + public override SqlType SqlType => SqlTypeFactory.GetAnsiString(255); + } } diff --git a/src/NHibernate.Test/Linq/FunctionTests.cs b/src/NHibernate.Test/Linq/FunctionTests.cs index b4ad91c53cb..b92e7b3ece9 100644 --- a/src/NHibernate.Test/Linq/FunctionTests.cs +++ b/src/NHibernate.Test/Linq/FunctionTests.cs @@ -3,7 +3,6 @@ using System.Text.RegularExpressions; using NHibernate.DomainModel; using NHibernate.DomainModel.Northwind.Entities; -using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -36,7 +35,6 @@ public void LikeFunctionWithEscapeCharacter() session.Save(new Employee { FirstName = employeeName, LastName = "LastName" }); session.Flush(); - var query = (from e in db.Employees where NHibernate.Linq.SqlMethods.Like(e.FirstName, employeeNameEscaped, escapeChar) select e).ToList(); @@ -185,9 +183,6 @@ where e.FirstName.StartsWith("An") [Test] public void CharIndexFunction() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = (from e in db.Employees select e.FirstName).ToList(); var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf('a') == 0).ToList(); @@ -204,9 +199,6 @@ where lowerName.IndexOf('a') == 0 [Test] public void CharIndexOffsetNegativeFunction() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = (from e in db.Employees select e.FirstName).ToList(); var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf('a', 2) == -1).ToList(); @@ -223,9 +215,6 @@ where lowerName.IndexOf('a', 2) == -1 [Test] public void IndexOfFunctionExpression() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = (from e in db.Employees select e.FirstName).ToList(); var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf("an") == 0).ToList(); @@ -242,9 +231,6 @@ where lowerName.IndexOf("an") == 0 [Test] public void IndexOfFunctionProjection() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var raw = (from e in db.Employees select e.FirstName).ToList(); var expected = raw.Select(x => x.ToLower()).Where(x => x.Contains("a")).Select(x => x.IndexOf("a", 1)).ToList(); @@ -261,9 +247,6 @@ where lowerName.Contains("a") [Test] public void TwoFunctionExpression() { - if (!TestDialect.SupportsLocate) - Assert.Ignore("Locate function not supported."); - var query = from e in db.Employees where e.FirstName.IndexOf("A") == e.BirthDate.Value.Month select e.FirstName; @@ -373,6 +356,7 @@ where item.Id.Equals(-1) } [Test] + [Ignore("Not mapped entity")] public void WhereShortEqual() { var query = from item in session.Query() @@ -465,6 +449,7 @@ where item.BodyWeight.Equals(-1) } [Test] + [Ignore("Not mapped entity")] public void WhereFloatEqual() { var query = from item in session.Query() @@ -475,6 +460,7 @@ where item.Float.Equals(-1) } [Test] + [Ignore("Not mapped entity")] public void WhereCharEqual() { var query = from item in session.Query() @@ -485,6 +471,7 @@ where item.Char.Equals('A') } [Test] + [Ignore("Not mapped entity")] public void WhereByteEqual() { var query = from item in session.Query() diff --git a/src/NHibernate.Test/Linq/LinqQuerySamples.cs b/src/NHibernate.Test/Linq/LinqQuerySamples.cs index d70b0f89b37..4a924507d38 100755 --- a/src/NHibernate.Test/Linq/LinqQuerySamples.cs +++ b/src/NHibernate.Test/Linq/LinqQuerySamples.cs @@ -1,7 +1,11 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Hql.Ast.ANTLR; +using NHibernate.Linq; +using NSubstitute; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -9,6 +13,42 @@ namespace NHibernate.Test.Linq [TestFixture] public class LinqQuerySamples : LinqTestCase { + class NotMappedEntity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + [Test] + public void ShouldThrowForQueryOnNotMappedEntity() + { + var querySyntaxException = Assert.Throws(() => session.Query().Select(x => x.Id).ToList()); + Assert.That(querySyntaxException.Message, Does.Contain(nameof(NotMappedEntity))); + } + + [Test] + public void ShouldThrowForQueryOnNotMappedEntityName() + { + var entityName = "SomeNamespace.NotMappedEntityName"; + var querySyntaxException = Assert.Throws(() => session.Query(entityName).ToList()); + Assert.That(querySyntaxException.Message, Does.Contain(entityName)); + } + + [Test] + public void ShouldThrowForDmlQueryOnNotMappedEntity() + { + Assert.Multiple( + () => + { + var querySyntaxException = Assert.Throws(() => session.Query().Delete()); + Assert.That(querySyntaxException.Message, Does.Contain(nameof(NotMappedEntity))); + + var entityName = "SomeNamespace.NotMappedEntityName"; + querySyntaxException = Assert.Throws(() => session.Delete($"from {entityName}")); + Assert.That(querySyntaxException.Message, Does.Contain(entityName)); + }); + } + [Test] public void GroupTwoQueriesAndSum() { @@ -289,7 +329,7 @@ public void DLinq17() var dbOrders3 = s.CreateQuery("select o.OrderId from Order o").List(); //var q3 = dbOrders3.SubQueryBatcher(orderId => orderId, - // ids => from subO in db.Orders.ToList() // Note that ToList is just because current group by code is incorrent in our linq provider + // ids => from subO in db.Orders.ToList() // Note that ToList is just because current group by code is incorrent in our linq provider // where ids.Contains(subO.OrderId) // from orderLine in subO.OrderLines // group new {orderLine, FreeShippingDiscount = subO.Freight} @@ -332,7 +372,6 @@ from ol in o.OrderLines DiscountedProducts = input.Batcher.GetData(index) }); - foreach (var x in q3) { Console.WriteLine(x.OrderId); @@ -740,7 +779,6 @@ from o in db.Orders ObjectDumper.Write(q); } - [Category("ORDER BY")] [Test(Description = "This sample uses Orderby, Max and Group By to find the Products that have " + "the highest unit price in each category, and sorts the group by category id.")] @@ -1303,7 +1341,33 @@ from o in c.Orders where c.Address.City == "London" select o; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample uses foreign key navigation in the " + + "from clause to select all orders for customers in London.")] + public void DLinqJoin1LeftJoin() + { + IQueryable q = + from c in db.Customers + from o in c.Orders.DefaultIfEmpty() + where c.Address.City == "London" + select o; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1409,7 +1473,13 @@ from p in db.Products where p.Supplier.Address.Country == "USA" && p.UnitsInStock == 0 select p; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1425,7 +1495,16 @@ from et in e.Territories where e.Address.City == "Seattle" select new {e.FirstName, e.LastName, et.Region.Description}; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + // EmployeeTerritories and Territories + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + // Region + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1449,7 +1528,13 @@ from e2 in e1.Subordinates e1.Address.City }; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1464,7 +1549,13 @@ from c in db.Customers join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders select new {c.ContactName, OrderCount = orders.Average(x => x.Freight)}; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(0)); + } } [Category("JOIN")] @@ -1476,7 +1567,33 @@ from c in db.Customers join o in db.Orders on c.CustomerId equals o.Customer.CustomerId select new { c.ContactName, o.OrderId }; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample explictly joins two tables and projects results from both tables.")] + public void DLinqJoin5aLeftJoin() + { + var q = + from c in db.Customers + join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders + from o in orders.DefaultIfEmpty() + where o != null + select new { c.ContactName, o.OrderId }; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1510,12 +1627,119 @@ public void DLinqJoin5d() { var q = from c in db.Customers - join o in db.Orders on new {c.CustomerId, HasContractTitle = c.ContactTitle != null} equals new {o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } + join o in db.Orders on + new {c.CustomerId, HasContractTitle = c.ContactTitle != null} equals + new {o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } select new { c.ContactName, o.OrderId }; + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(0)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "cross join"), Is.EqualTo(0)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")] + public void DLinqJoin5dLeftJoin() + { + var q = + from c in db.Customers + join o in db.Orders on + new {c.CustomerId, HasContractTitle = c.ContactTitle != null} equals + new {o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null} into orders + from o in orders.DefaultIfEmpty() + select new {c.ContactName, OrderId = (int?) o.OrderId}; + ObjectDumper.Write(q); } + [Category("JOIN")] + [Test(Description = "This sample joins two tables and projects results from the first table.")] + public void DLinqJoin5e() + { + var q = + from c in db.Customers + join o in db.Orders on c.CustomerId equals o.Customer.CustomerId + where c.ContactName != null + select o; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample joins two tables and projects results from the first table.")] + public void DLinqJoin5eLeftJoin() + { + var q = + from c in db.Customers + join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders + from o in orders.DefaultIfEmpty() + where c.ContactName != null + select o; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } + } + + [Category("JOIN")] + [TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")] + public void DLinqJoin5f() + { + var q = + from o in db.Orders + join c in db.Customers on + new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals + new { c.CustomerId, HasContractTitle = c.ContactTitle != null } + select new { c.ContactName, o.OrderId }; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(0)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + } + } + + [Category("JOIN")] + [TestCase(Description = "This sample explictly joins two tables with a composite key and projects results from both tables.")] + public void DLinqJoin5fLeftJoin() + { + var q = + from o in db.Orders + join c in db.Customers on + new { o.Customer.CustomerId, HasContractTitle = o.Customer.ContactTitle != null } equals + new { c.CustomerId, HasContractTitle = c.ContactTitle != null } into customers + from c in customers.DefaultIfEmpty() + select new { c.ContactName, o.OrderId }; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(0)); + } + } + [Category("JOIN")] [Test(Description = "This sample explictly joins three tables and projects results from each of them.")] public void DLinqJoin6() @@ -1529,7 +1753,13 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into ords join e in db.Employees on c.Address.City equals e.Address.City into emps select new {c.ContactName, ords = ords.Count(), emps = emps.Count()}; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "join"), Is.EqualTo(0)); + } } [Category("JOIN")] @@ -1537,7 +1767,6 @@ join e in db.Employees on c.Address.City equals e.Address.City into emps Description = "This sample shows how to get LEFT OUTER JOIN by using DefaultIfEmpty(). The DefaultIfEmpty() method returns null when there is no Order for the Employee." )] - [Ignore("TODO left outer join")] public void DLinqJoin7() { var q = @@ -1546,7 +1775,13 @@ join o in db.Orders on e equals o.Employee into ords from o in ords.DefaultIfEmpty() select new {e.FirstName, e.LastName, Order = o}; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1560,32 +1795,99 @@ join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into ords from o in ords select new {c.ContactName, o.OrderId, z}; - ObjectDumper.Write(q); + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(1)); + } } [Category("JOIN")] - [Test(Description = "This sample shows a group join with a composite key.")] - public void DLinqJoin9() - { - var expected = - (from o in db.Orders.ToList() - from p in db.Products.ToList() - join d in db.OrderLines.ToList() - on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} - into details - from d in details - select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList(); - - var actual = - (from o in db.Orders - from p in db.Products - join d in db.OrderLines - on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} - into details - from d in details - select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList(); + [TestCase(true, Description = "This sample shows a group join with a composite key.")] + [TestCase(false, Description = "This sample shows a group join with a composite key.")] + public void DLinqJoin9(bool useCrossJoin) + { + if (useCrossJoin && !Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } - Assert.AreEqual(expected.Count, actual.Count); + // The expected collection can be obtained from the below Linq to Objects query. + //var expected = + // (from o in db.Orders.ToList() + // from p in db.Products.ToList() + // join d in db.OrderLines.ToList() + // on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} + // into details + // from d in details + // select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList(); + + using (var substitute = SubstituteDialect()) + using (var sqlSpy = new SqlLogSpy()) + { + ClearQueryPlanCache(); + substitute.Value.SupportsCrossJoin.Returns(useCrossJoin); + + var actual = + (from o in db.Orders + from p in db.Products + join d in db.OrderLines + on new { o.OrderId, p.ProductId } equals new { d.Order.OrderId, d.Product.ProductId } + into details + from d in details + select new { o.OrderId, p.ProductId, d.UnitPrice }).ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(actual.Count, Is.EqualTo(2155)); + Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join")); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(useCrossJoin ? 1 : 2)); + } + } + + [Category("JOIN")] + [TestCase(true, Description = "This sample shows a group left join with a composite key.")] + [TestCase(false, Description = "This sample shows a group left join with a composite key.")] + public void DLinqJoin9LeftJoin(bool useCrossJoin) + { + if (useCrossJoin && !Dialect.SupportsCrossJoin) + { + Assert.Ignore("Dialect does not support cross join."); + } + + // The expected collection can be obtained from the below Linq to Objects query. + //var expected = + // (from o in db.Orders.ToList() + // from p in db.Products.ToList() + // join d in db.OrderLines.ToList() + // on new { o.OrderId, p.ProductId } equals new { d.Order.OrderId, d.Product.ProductId } + // into details + // from d in details.DefaultIfEmpty() + // where d != null && d.UnitPrice > 50 + // select new { o.OrderId, p.ProductId, d.UnitPrice }).ToList(); + + using (var substitute = SubstituteDialect()) + using (var sqlSpy = new SqlLogSpy()) + { + ClearQueryPlanCache(); + substitute.Value.SupportsCrossJoin.Returns(useCrossJoin); + + var actual = + (from o in db.Orders + from p in db.Products + join d in db.OrderLines + on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} + into details + from d in details.DefaultIfEmpty() + where d != null && d.UnitPrice > 50 + select new {o.OrderId, p.ProductId, d.UnitPrice}).ToList(); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(actual.Count, Is.EqualTo(163)); + Assert.That(sql, Does.Contain(useCrossJoin ? "cross join" : "inner join")); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(1)); + } } [Category("JOIN")] @@ -1600,6 +1902,46 @@ group o by c into x ObjectDumper.Write(q); } + [Category("JOIN")] + [Test(Description = "This sample shows how to join multiple tables.")] + public void DLinqJoin10a() + { + var q = + from e in db.Employees + join s in db.Employees on e.Superior.EmployeeId equals s.EmployeeId + join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId + select new { e.FirstName, SuperiorName = s.FirstName, Superior2Name = s2.FirstName }; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "inner join"), Is.EqualTo(2)); + } + } + + [Category("JOIN")] + [Test(Description = "This sample shows how to join multiple tables using a left join.")] + public void DLinqJoin10aLeftJoin() + { + var q = + from e in db.Employees + join s in db.Employees on e.Superior.EmployeeId equals s.EmployeeId into sup + from s in sup.DefaultIfEmpty() + join s2 in db.Employees on s.Superior.EmployeeId equals s2.EmployeeId into sup2 + from s2 in sup2.DefaultIfEmpty() + select new { e.FirstName, SuperiorName = s.FirstName, Superior2Name = s2.FirstName }; + + using (var sqlSpy = new SqlLogSpy()) + { + ObjectDumper.Write(q); + + var sql = sqlSpy.GetWholeLog(); + Assert.That(GetTotalOccurrences(sql, "left outer join"), Is.EqualTo(2)); + } + } + [Category("WHERE")] [Test(Description = "This sample uses WHERE to filter for orders with shipping date equals to null.")] public void DLinq2B() @@ -1628,7 +1970,6 @@ public void DLinq2C() where o.ShippingDate != null select o.OrderId).ToArray(); - var withNullShippingDate = new[] { @@ -1731,4 +2072,4 @@ public class Name public string FirstName; public string LastName; } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs b/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs index 399eec6df39..d5d0a6bd71f 100644 --- a/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs +++ b/src/NHibernate.Test/Linq/LinqReadonlyTestsContext.cs @@ -43,7 +43,8 @@ private IEnumerable Mappings "Northwind.Mappings.User.hbm.xml", "Northwind.Mappings.TimeSheet.hbm.xml", "Northwind.Mappings.Animal.hbm.xml", - "Northwind.Mappings.Patient.hbm.xml" + "Northwind.Mappings.Patient.hbm.xml", + "Northwind.Mappings.NumericEntity.hbm.xml" }; } } diff --git a/src/NHibernate.Test/Linq/LinqTestCase.cs b/src/NHibernate.Test/Linq/LinqTestCase.cs index 26503786bb0..ecd9c7690e3 100755 --- a/src/NHibernate.Test/Linq/LinqTestCase.cs +++ b/src/NHibernate.Test/Linq/LinqTestCase.cs @@ -29,11 +29,14 @@ protected override string[] Mappings "Northwind.Mappings.Supplier.hbm.xml", "Northwind.Mappings.Territory.hbm.xml", "Northwind.Mappings.AnotherEntity.hbm.xml", + "Northwind.Mappings.AnotherEntityRequired.hbm.xml", "Northwind.Mappings.Role.hbm.xml", "Northwind.Mappings.User.hbm.xml", "Northwind.Mappings.TimeSheet.hbm.xml", "Northwind.Mappings.Animal.hbm.xml", - "Northwind.Mappings.Patient.hbm.xml" + "Northwind.Mappings.Patient.hbm.xml", + "Northwind.Mappings.DynamicUser.hbm.xml", + "Northwind.Mappings.NumericEntity.hbm.xml" }; } } @@ -69,4 +72,4 @@ public static void AssertByIds(IEnumerable entities, TId[ Assert.That(entities.Select(x => entityIdGetter(x)), Is.EquivalentTo(expectedIds)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Linq/LoggingTests.cs b/src/NHibernate.Test/Linq/LoggingTests.cs index b89ef0f49fd..8d6924e2144 100644 --- a/src/NHibernate.Test/Linq/LoggingTests.cs +++ b/src/NHibernate.Test/Linq/LoggingTests.cs @@ -27,7 +27,6 @@ public void PageBetweenProjections() } } - [Test] public void CanLogLinqExpressionWithoutInitializingContainedProxy() { diff --git a/src/NHibernate.Test/Linq/MathFTests.cs b/src/NHibernate.Test/Linq/MathFTests.cs index 40a3e759f0d..8dd21f8de24 100644 --- a/src/NHibernate.Test/Linq/MathFTests.cs +++ b/src/NHibernate.Test/Linq/MathFTests.cs @@ -7,7 +7,9 @@ namespace NHibernate.Test.Linq { - [TestFixture] + // [TestFixture] is omitted on purpose: it is not required by NUnit. It is used by the async generator + // to generated async tests. Async tests cannot works for this fixture due to using a non NHibernate + // queryable which will not be supported by NHibernate ToListAsync. public class MathFTests : LinqTestCase { private IQueryable _orderLines; @@ -135,4 +137,4 @@ private void Test(Expression> selector) } } } -#endif \ No newline at end of file +#endif diff --git a/src/NHibernate.Test/Linq/MiscellaneousTextFixture.cs b/src/NHibernate.Test/Linq/MiscellaneousTextFixture.cs index 9eada444639..983919e2914 100644 --- a/src/NHibernate.Test/Linq/MiscellaneousTextFixture.cs +++ b/src/NHibernate.Test/Linq/MiscellaneousTextFixture.cs @@ -27,7 +27,8 @@ from s in db.Shippers [Test(Description = "This sample uses Count to find the number of Orders placed before yesterday in the database.")] public void CountWithWhereClause() { - var q = from o in db.Orders where o.OrderDate <= DateTime.Today.AddDays(-1) select o; + var yesterday = DateTime.Today.AddDays(-1); + var q = from o in db.Orders where o.OrderDate <= yesterday select o; var count = q.Count(); diff --git a/src/NHibernate.Test/Linq/NestedSelectsTests.cs b/src/NHibernate.Test/Linq/NestedSelectsTests.cs index c2b0436ff01..8aee94797df 100644 --- a/src/NHibernate.Test/Linq/NestedSelectsTests.cs +++ b/src/NHibernate.Test/Linq/NestedSelectsTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using System.Linq.Expressions; +using NHibernate.DomainModel.Northwind.Entities; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -364,5 +366,100 @@ public void OrdersIdWithOrderLinesNestedWhereId() Assert.That(orders.Count, Is.EqualTo(830)); Assert.That(orders[0].OrderLinesIds, Is.Empty); } + + [Test] + public void NoNestedSelects_AnyOnGroupBySubquery() + { + var subQuery = from vms in db.Animals + group vms by vms.Father + into vmReqs + select vmReqs.Max(x => x.Id); + + var outerQuery = from vm in db.Animals + where subQuery.Any(x => vm.Id == x) + select vm; + var animals = outerQuery.ToList(); + Assert.That(animals.Count, Is.EqualTo(2)); + } + + //NH-3155 + [Test] + public void NoNestedSelects_ContainsOnGroupBySubquery() + { + var subQuery = from vms in db.Animals + where vms.BodyWeight > 0 + group vms by vms.Father + into vmReqs + select vmReqs.Max(x => x.Id); + + var outerQuery = from vm in db.Animals + where subQuery.Contains(vm.Id) + select vm; + + var animals = outerQuery.ToList(); + Assert.That(animals.Count, Is.EqualTo(2)); + } + + [Test] + public void CanSelectWithWhereSubQuery() + { + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.Where(e => e.NumberOfHours >= 0).ToList() + }; + + var list = query.ToList(); + + Assert.AreEqual(3, list.Count); + } + + [Test(Description = "GH-2540")] + public void CanSelectWithAsQueryableAndWhereSubQuery() + { + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.AsQueryable().Where(e => e.NumberOfHours >= 0).ToList() + }; + + var list = query.ToList(); + + Assert.AreEqual(3, list.Count); + } + + [Test(Description = "GH-2540")] + public void CanSelectWithAsQueryableAndWhereSubQueryToArray() + { + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.AsQueryable().Where(e => e.NumberOfHours >= 0).ToArray() + }; + + var list = query.ToList(); + + Assert.AreEqual(3, list.Count); + } + + [Test(Description = "GH-2540")] + public void CanSelectWithAsQueryableAndWhereSubQueryWithExternalPredicate() + { + Expression> predicate = e => e.NumberOfHours >= 0; + + var query = from timesheet in db.Timesheets + select new + { + timesheet.Id, + Entries = timesheet.Entries.AsQueryable().Where(predicate).ToList() + }; + + var list = query.ToList(); + + Assert.AreEqual(3, list.Count); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Linq/NorthwindDbCreator.cs b/src/NHibernate.Test/Linq/NorthwindDbCreator.cs index dbb34f93bd2..fa484be3a92 100644 --- a/src/NHibernate.Test/Linq/NorthwindDbCreator.cs +++ b/src/NHibernate.Test/Linq/NorthwindDbCreator.cs @@ -70,6 +70,11 @@ public static void CreateMiscTestData(ISession session) } }; + foreach (var user in users) + { + user.CreatedBy = users[0]; + } + var timesheets = new[] { new Timesheet @@ -3707,4 +3712,4 @@ static void CreateOrderLines22(IStatelessSession session, IDictionary().Where(o => o.Input != null); + Expect(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothSame, BothDifferent); + + q = session.Query().Where(o => null != o.Input); + Expect(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothSame, BothDifferent); + + q = session.Query().Where(o => o.InputNullability != AnotherEntityNullability.True); + Expect(q, Does.Not.Contain("end is null").IgnoreCase, InputSet, BothSame, BothDifferent); + + q = session.Query().Where(o => AnotherEntityNullability.True != o.InputNullability); + Expect(q, Does.Not.Contain("end is null").IgnoreCase, InputSet, BothSame, BothDifferent); + + q = session.Query().Where(o => "input" != o.Input); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Input != "input"); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Input != o.Output); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent); + + q = session.Query().Where(o => o.Output != o.Input); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent); + + q = session.Query().Where(o => o.Input != o.NullableOutput); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull); + + q = session.Query().Where(o => o.NullableOutput != o.Input); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Output != o.Input); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull); + + q = session.Query().Where(o => o.Input != o.NullableAnotherEntityRequired.Output); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothDifferent, InputSet, BothNull); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Input != o.Output); + Expect(q, Does.Contain("Input is null").IgnoreCase, BothDifferent, OutputSet, BothNull); + + q = session.Query().Where(o => o.Output != o.NullableAnotherEntityRequired.Input); + Expect(q, Does.Contain("Input is null").IgnoreCase, BothDifferent, OutputSet, BothNull); + + q = session.Query().Where(o => 3 != o.NullableOutput.Length); + Expect(q, Does.Contain("is null").IgnoreCase, InputSet, BothDifferent, BothNull, OutputSet); + + q = session.Query().Where(o => o.NullableOutput.Length != 3); + Expect(q, Does.Contain("is null").IgnoreCase, InputSet, BothDifferent, BothNull, OutputSet); + + q = session.Query().Where(o => 3 != o.Input.Length); + Expect(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent); + + q = session.Query().Where(o => o.Input.Length != 3); + Expect(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequiredId ?? 0) != (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0)); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0) != (o.NullableAnotherEntityRequiredId ?? 0)); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.GetValueOrDefault() != o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault()); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault() != o.NullableAnotherEntityRequiredId.GetValueOrDefault()); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value != o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value != o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value && o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value != 0); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value != 0 && o.NullableAnotherEntityRequiredId.HasValue); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue || o.NullableAnotherEntityRequiredId.Value != 0); + ExpectAll(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value != 0 || o.NullableAnotherEntityRequiredId.HasValue); + ExpectAll(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableOutput != null && o.NullableOutput != "test"); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet); + + q = session.Query().Where(o => o.NullableOutput != "test" && o.NullableOutput != null); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet); + + q = session.Query().Where(o => o.NullableOutput != null || o.NullableOutput != "test"); + ExpectAll(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableOutput != "test" || o.NullableOutput != null); + ExpectAll(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableOutput != "test" && (o.NullableAnotherEntityRequiredId > 0 && o.NullableOutput != null)); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet); + + q = session.Query().Where(o => o.NullableOutput != null && (o.NullableAnotherEntityRequiredId > 0 && o.NullableOutput != "test")); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent, BothSame, OutputSet); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value != o.NullableAnotherEntityRequiredId.Value); + Expect(q, Does.Contain("or case").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.Any(r => r.Output != o.Input)); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase.And.Contain("Output is null").IgnoreCase, BothDifferent, InputSet, BothNull); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != o.Input)); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase.And.Contain("Output is null").IgnoreCase, InputSet, OutputSet, BothDifferent, BothNull); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null && r.Output != o.Input)); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase.And.Not.Contain("Output is null").IgnoreCase, BothDifferent, OutputSet); + + q = session.Query().Where(o => (o.NullableOutput + o.Output) != o.Output); + ExpectAll(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => (o.Input + o.Output) != o.Output); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame, BothDifferent); + + q = session.Query().Where(o => o.Address.Street != o.Output); + Expect(q, Does.Contain("Input is null").IgnoreCase, BothDifferent, OutputSet, BothNull); + + q = session.Query().Where(o => o.Address.City != o.Output); + Expect(q, Does.Contain("Output is null").IgnoreCase, InputSet, BothNull); + + q = session.Query().Where(o => o.Address.City != null && o.Address.City != o.Output); + Expect(q, Does.Not.Contain("Output is null").IgnoreCase); + + q = session.Query().Where(o => o.Address.Street != null && o.Address.Street != o.NullableOutput); + Expect(q, Does.Contain("Output is null").IgnoreCase, InputSet, BothDifferent); + + Expect(session.Query().Where(o => o.CustomerId != null), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => null != o.CustomerId), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CustomerId != "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.CustomerId), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.Order.Customer.CustomerId != "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.Order.Customer.CustomerId), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.Order.Customer.CompanyName != "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.Order.Customer.CompanyName), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.CreatedBy.CreatedBy.Name != "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.CreatedBy.CreatedBy.CreatedBy.Name), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.CreatedBy.Id != 5), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => 5 != o.CreatedBy.CreatedBy.Id), Does.Not.Contain("is null").IgnoreCase); + } + + [Test] + public void NullInequalityWithNotNullSubSelect() + { + if (!Dialect.SupportsScalarSubSelects) + { + Assert.Ignore("Dialect does not support scalar subselects"); + } + + var q = session.Query().Where(o => o.RelatedItems.Count != 1); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.Max(r => r.Id) != 0); + ExpectAll(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null) != o.NullableBool); + ExpectAll(q, Does.Not.Contain("or case").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.Where(r => r.Id == 0).Sum(r => r.Input.Length) != 5); + ExpectAll(q, Does.Contain("or (").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null) != (o.NullableOutput.Length > 0)); + Expect(q, Does.Not.Contain("or case").IgnoreCase); + } + + [Test] + public void NullEqualityWithNotNull() + { + var q = session.Query().Where(o => o.Input == null); + Expect(q, Does.Contain("is null").IgnoreCase, OutputSet, BothNull); + + q = session.Query().Where(o => null == o.Input); + Expect(q, Does.Contain("is null").IgnoreCase, OutputSet, BothNull); + + q = session.Query().Where(o => o.InputNullability == AnotherEntityNullability.True); + Expect(q, Does.Not.Contain("end is null").IgnoreCase, BothNull, OutputSet); + + q = session.Query().Where(o => AnotherEntityNullability.True == o.InputNullability); + Expect(q, Does.Not.Contain("end is null").IgnoreCase, BothNull, OutputSet); + + q = session.Query().Where(o => "input" == o.Input); + Expect(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent); + + q = session.Query().Where(o => o.Input == "input"); + Expect(q, Does.Not.Contain("is null").IgnoreCase, InputSet, BothDifferent); + + q = session.Query().Where(o => o.Input == o.Output); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Output == o.Input); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Input == o.NullableOutput); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.NullableOutput == o.Input); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Output == o.Input); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Input == o.NullableAnotherEntityRequired.Output); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.Input == o.Output); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Output == o.NullableAnotherEntityRequired.Input); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => 3 == o.Input.Length); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.Input.Length == 3); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequiredId ?? 0) == (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0)); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => (o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId ?? 0) == (o.NullableAnotherEntityRequiredId ?? 0)); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.GetValueOrDefault() == o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault()); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.GetValueOrDefault() == o.NullableAnotherEntityRequiredId.GetValueOrDefault()); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value == o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value == o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value && o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.HasValue); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue && o.NullableAnotherEntityRequiredId.Value == 0); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value == 0 && o.NullableAnotherEntityRequiredId.HasValue); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.HasValue || o.NullableAnotherEntityRequiredId.Value == 0); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableAnotherEntityRequiredId.Value == 0 || o.NullableAnotherEntityRequiredId.HasValue); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableOutput == "test"); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.NullableOutput != null || o.NullableOutput == "test"); + Expect(q, Does.Not.Contain("is null").IgnoreCase, OutputSet, BothDifferent, BothSame); + + q = session.Query().Where(o => o.NullableAnotherEntityRequired.NullableAnotherEntityRequiredId.Value == o.NullableAnotherEntityRequiredId.Value); + ExpectAll(q, Does.Contain("Id is null").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.Any(r => r.Output == o.Input)); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase.And.Not.Contain("Output is null").IgnoreCase, BothSame); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output == o.Input)); + Expect(q, Does.Not.Contain("Input is null").IgnoreCase.And.Not.Contain("Output is null").IgnoreCase, BothSame, BothNull, InputSet, OutputSet); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output == o.NullableOutput)); + ExpectAll(q, Does.Contain("Output is null").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.All(r => r.Output != null && o.NullableOutput != null && r.Output == o.NullableOutput)); + Expect(q, Does.Not.Contain("Output is null").IgnoreCase, BothSame, BothDifferent, OutputSet); + + q = session.Query().Where(o => (o.NullableOutput + o.Output) == o.Output); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => (o.Output + o.Output) == o.Output); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => !o.Input.Equals(o.Output)); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent); + + q = session.Query().Where(o => !o.Output.Equals(o.Input)); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent); + + q = session.Query().Where(o => !o.Input.Equals(o.NullableOutput)); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent); + + q = session.Query().Where(o => !o.NullableOutput.Equals(o.Input)); + Expect(q, Does.Not.Contain("is null").IgnoreCase, BothDifferent); + + q = session.Query().Where(o => !o.NullableOutput.Equals(o.NullableOutput)); + Expect(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => !o.NullableOutput.Equals(o.NullableOutput)); + Expect(q, Does.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.Address.City == o.NullableOutput); + ExpectAll(q, Does.Contain("Output is null").IgnoreCase); + + q = session.Query().Where(o => o.Address.Street != null && o.Address.Street == o.NullableOutput); + Expect(q, Does.Not.Contain("Output is null").IgnoreCase, BothSame); + + Expect(session.Query().Where(o => o.CustomerId == null), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => null == o.CustomerId), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CustomerId == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.CustomerId), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.Order.Customer.CustomerId == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.Order.Customer.CustomerId), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.Order.Customer.CompanyName == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.Order.Customer.CompanyName), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.CreatedBy.CreatedBy.Name == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.CreatedBy.CreatedBy.CreatedBy.Name), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.CreatedBy.Id == 5), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => 5 == o.CreatedBy.CreatedBy.Id), Does.Not.Contain("is null").IgnoreCase); + } + + [Test] + public void NullEqualityWithNotNullSubSelect() + { + if (!Dialect.SupportsScalarSubSelects) + { + Assert.Ignore("Dialect does not support scalar subselects"); + } + + var q = session.Query().Where(o => o.RelatedItems.Count == 1); + ExpectAll(q, Does.Not.Contain("is null").IgnoreCase); + + q = session.Query().Where(o => o.RelatedItems.Max(r => r.Id) == 0); + Expect(q, Does.Not.Contain("is null").IgnoreCase); + } + [Test] public void NullEquality() { @@ -85,6 +431,63 @@ public void NullEquality() // Columns against columns q = from x in session.Query() where x.Input == x.Output select x; Expect(q, BothSame, BothNull); + + Expect(session.Query().Where(o => o.Order.Customer.ContactName == null), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => null == o.Order.Customer.ContactName), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.Order.Customer.ContactName == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.Order.Customer.ContactName), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => null == o.Component.Property1), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.Property1 == null), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => "test" == o.Component.Property1), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.Property1 == "test"), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => null == o.Component.OtherComponent.OtherProperty1), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 == null), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => "test" == o.Component.OtherComponent.OtherProperty1), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 == "test"), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.ModifiedBy.CreatedBy.Name == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.CreatedBy.ModifiedBy.CreatedBy.Name), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1 == "test"), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" == o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.ModifiedBy.CreatedBy.Id == 5), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => 5 == o.ModifiedBy.CreatedBy.Id), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.ModifiedBy.Id == 5), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => 5 == o.CreatedBy.ModifiedBy.Id), Does.Not.Contain("is null").IgnoreCase); + + if (Sfi.Dialect is FirebirdDialect) + { + return; + } + + Expect(db.NumericEntities.Where(o => o.NullableShort == o.NullableShort), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short == o.Short), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.NullableShort == o.Short), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short == o.NullableShort), WithoutIsNullAndWithoutCast()); + + short value = 3; + Expect(db.NumericEntities.Where(o => o.NullableShort == value), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => value == o.NullableShort), WithoutIsNullAndWithoutCast()); + + Expect(db.NumericEntities.Where(o => o.NullableShort.Value == value), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => value == o.NullableShort.Value), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short == value), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => value == o.Short), WithoutIsNullAndWithoutCast()); + + Expect(db.NumericEntities.Where(o => o.NullableShort == 3L), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => 3L == o.NullableShort), WithoutIsNullAndWithoutCast()); + + Expect(db.NumericEntities.Where(o => o.NullableShort.Value == 3L), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => 3L == o.NullableShort.Value), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short == 3L), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => 3L == o.Short), WithoutIsNullAndWithoutCast()); } [Test] @@ -143,6 +546,73 @@ public void NullInequality() // Columns against columns q = from x in session.Query() where x.Input != x.Output select x; Expect(q, BothDifferent, InputSet, OutputSet); + + Expect(session.Query().Where(o => o.Order.Customer.ContactName != null), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => null != o.Order.Customer.ContactName), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.Order.Customer.ContactName != "test"), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.Order.Customer.ContactName), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => null != o.Component.Property1), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.Property1 != null), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => "test" != o.Component.Property1), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.Property1 != "test"), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => null != o.Component.OtherComponent.OtherProperty1), Does.Not.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 != null), Does.Not.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => "test" != o.Component.OtherComponent.OtherProperty1), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => o.Component.OtherComponent.OtherProperty1 != "test"), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.ModifiedBy.CreatedBy.Name != "test"), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.CreatedBy.ModifiedBy.CreatedBy.Name), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1 != "test"), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => "test" != o.CreatedBy.CreatedBy.Component.OtherComponent.OtherProperty1), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.ModifiedBy.CreatedBy.Id != 5), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => 5 != o.ModifiedBy.CreatedBy.Id), Does.Contain("is null").IgnoreCase); + + Expect(session.Query().Where(o => o.CreatedBy.ModifiedBy.Id != 5), Does.Contain("is null").IgnoreCase); + Expect(session.Query().Where(o => 5 != o.CreatedBy.ModifiedBy.Id), Does.Contain("is null").IgnoreCase); + + if (Sfi.Dialect is FirebirdDialect) + { + return; + } + + Expect(db.NumericEntities.Where(o => o.NullableShort != o.NullableShort), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short != o.Short), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.NullableShort != o.Short), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short != o.NullableShort), WithIsNullAndWithoutCast()); + + short value = 3; + Expect(db.NumericEntities.Where(o => o.NullableShort != value), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => value != o.NullableShort), WithIsNullAndWithoutCast()); + + Expect(db.NumericEntities.Where(o => o.NullableShort.Value != value), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => value != o.NullableShort.Value), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short != value), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => value != o.Short), WithoutIsNullAndWithoutCast()); + + Expect(db.NumericEntities.Where(o => o.NullableShort != 3L), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => 3 != o.NullableShort), WithIsNullAndWithoutCast()); + + Expect(db.NumericEntities.Where(o => o.NullableShort.Value != 3L), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => 3L != o.NullableShort.Value), WithIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => o.Short != 3L), WithoutIsNullAndWithoutCast()); + Expect(db.NumericEntities.Where(o => 3L != o.Short), WithoutIsNullAndWithoutCast()); + } + + private IResolveConstraint WithIsNullAndWithoutCast() + { + return Does.Contain("is null").IgnoreCase.And.Not.Contain("cast").IgnoreCase; + } + + private IResolveConstraint WithoutIsNullAndWithoutCast() + { + return Does.Not.Contain("is null").IgnoreCase.And.Not.Contain("cast").IgnoreCase; } [Test] @@ -305,5 +775,54 @@ private string Key(AnotherEntity e) { return "Input=" + (e.Input ?? "NULL") + ", Output=" + (e.Output ?? "NULL"); } + + private void ExpectAll(IQueryable q, IResolveConstraint sqlConstraint) + { + Expect(q, sqlConstraint, BothNull, BothSame, BothDifferent, InputSet, OutputSet); + } + + private void Expect(IQueryable q, IResolveConstraint sqlConstraint, params AnotherEntity[] entities) + { + IList results; + if (sqlConstraint == null) + { + results = GetResults(q); + } + else + { + using (var sqlLog = new SqlLogSpy()) + { + results = GetResults(q); + Assert.That(sqlLog.GetWholeLog(), sqlConstraint); + } + } + + IList check = entities.OrderBy(Key).ToList(); + + Assert.That(results.Count, Is.EqualTo(check.Count)); + for (var i = 0; i < check.Count; i++) + { + Assert.That(Key(results[i]), Is.EqualTo(Key(check[i]))); + } + } + + private IList GetResults(IQueryable q) + { + return q.ToList().OrderBy(Key).ToList(); + } + + private static void Expect(IQueryable query, IResolveConstraint sqlConstraint) + { + using (var sqlLog = new SqlLogSpy()) + { + var list = query.ToList(); + Assert.That(sqlLog.GetWholeLog(), sqlConstraint); + } + } + + private static string Key(AnotherEntityRequired e) + { + return "Input=" + (e.Input ?? "NULL") + ", Output=" + (e.Output ?? "NULL"); + } } } diff --git a/src/NHibernate.Test/Linq/ODataTests.cs b/src/NHibernate.Test/Linq/ODataTests.cs new file mode 100644 index 00000000000..bdb1ee7c592 --- /dev/null +++ b/src/NHibernate.Test/Linq/ODataTests.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.AspNetCore.Http; +using Microsoft.OData.Edm; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; + +namespace NHibernate.Test.Linq +{ + [TestFixture] + public class ODataTests : LinqTestCase + { + private IEdmModel _edmModel; + + protected override void OnSetUp() + { + base.OnSetUp(); + + _edmModel = CreatEdmModel(); + } + + [TestCase("$expand=Customer", 830, "Customer")] + [TestCase("$expand=OrderLines", 830, "OrderLines")] + public void Expand(string queryString, int expectedRows, string expandedProperty) + { + var query = ApplyFilter(session.Query(), queryString); + Assert.That(query, Is.AssignableTo>()); + + var results = ((IQueryable) query).ToList(); + Assert.That(results, Has.Count.EqualTo(expectedRows)); + + var dict = results[0].ToDictionary(); + Assert.That(dict.TryGetValue(expandedProperty, out var value), Is.True); + Assert.That(value, Is.Not.Null); + } + + [TestCase("$apply=groupby((Customer/CustomerId))", 89)] + [TestCase("$apply=groupby((Customer/CustomerId))&$orderby=Customer/CustomerId", 89)] + [TestCase("$apply=groupby((Customer/CustomerId, ShippingAddress/PostalCode), aggregate(OrderId with average as Average, Employee/EmployeeId with max as Max))", 89)] + [TestCase("$apply=groupby((Customer/CustomerId), aggregate(OrderId with sum as Total))&$skip=2", 87)] + public void OrderGroupBy(string queryString, int expectedRows) + { + var query = ApplyFilter(session.Query(), queryString); + Assert.That(query, Is.AssignableTo>()); + + var results = ((IQueryable) query).ToList(); + Assert.That(results, Has.Count.EqualTo(expectedRows)); + } + + private class CustomerVm : BaseCustomerVm + { + } + + private class BaseCustomerVm + { + public string Id { get; set; } + + public string Name { get; set; } + } + + [TestCase("$filter=Name eq 'Maria Anders'", 1)] + public void BasePropertyFilter(string queryString, int expectedRows) + { + var query = ApplyFilter( + session.Query().Select(o => new CustomerVm {Name = o.ContactName, Id = o.CustomerId}), + queryString); + + var results = ((IQueryable) query).ToList(); + Assert.That(results, Has.Count.EqualTo(expectedRows)); + } + + //GH-2362 + [TestCase("$filter=CustomerId le 'ANATR'", 2)] + [TestCase("$filter=startswith(CustomerId, 'ANATR')", 1)] + [TestCase("$filter=endswith(CustomerId, 'ANATR')", 1)] + [TestCase("$filter=indexof(CustomerId, 'ANATR') eq 0", 1)] + public void StringFilter(string queryString, int expectedCount) + { + Assert.That( + ApplyFilter(session.Query(), queryString).Cast().ToList(), + Has.Count.EqualTo(expectedCount)); + } + + private IQueryable ApplyFilter(IQueryable query, string queryString) + { + var context = new ODataQueryContext(CreatEdmModel(), typeof(T), null) { }; + var dataQuerySettings = new ODataQuerySettings {HandleNullPropagation = HandleNullPropagationOption.False}; + var serviceProvider = new ODataServiceProvider( + new Dictionary() + { + {typeof(DefaultQuerySettings), new DefaultQuerySettings()}, + {typeof(ODataOptions), new ODataOptions()}, + {typeof(IEdmModel), _edmModel}, + {typeof(ODataQuerySettings), dataQuerySettings}, + }); + + HttpContext httpContext = new DefaultHttpContext(); + httpContext.ODataFeature().RequestContainer = serviceProvider; + httpContext.RequestServices = serviceProvider; + var request = httpContext.Request; + Uri requestUri = new Uri($"http://localhost/?{queryString}"); + request.Method = HttpMethods.Get; + request.Scheme = requestUri.Scheme; + request.Host = new HostString(requestUri.Host); + request.QueryString = new QueryString(requestUri.Query); + request.Path = new PathString(requestUri.AbsolutePath); + var options = new ODataQueryOptions(context, request); + + return options.ApplyTo(query, dataQuerySettings); + } + + private static IEdmModel CreatEdmModel() + { + var builder = new ODataConventionModelBuilder(); + + var addressModel = builder.ComplexType
(); + addressModel.Property(o => o.City); + addressModel.Property(o => o.Country); + addressModel.Property(o => o.Fax); + addressModel.Property(o => o.PhoneNumber); + addressModel.Property(o => o.PostalCode); + addressModel.Property(o => o.Region); + addressModel.Property(o => o.Street); + + var customerModel = builder.EntitySet(nameof(Customer)); + customerModel.EntityType.HasKey(o => o.CustomerId); + customerModel.EntityType.Property(o => o.CompanyName); + customerModel.EntityType.Property(o => o.ContactTitle); + customerModel.EntityType.ComplexProperty(o => o.Address); + customerModel.EntityType.HasMany(o => o.Orders); + + var orderLineModel = builder.EntitySet(nameof(OrderLine)); + orderLineModel.EntityType.HasKey(o => o.Id); + orderLineModel.EntityType.Property(o => o.Discount); + orderLineModel.EntityType.Property(o => o.Quantity); + orderLineModel.EntityType.Property(o => o.UnitPrice); + orderLineModel.EntityType.HasRequired(o => o.Order); + + var orderModel = builder.EntitySet(nameof(Order)); + orderModel.EntityType.HasKey(o => o.OrderId); + orderModel.EntityType.Property(o => o.Freight); + orderModel.EntityType.Property(o => o.OrderDate); + orderModel.EntityType.Property(o => o.RequiredDate); + orderModel.EntityType.Property(o => o.ShippedTo); + orderModel.EntityType.Property(o => o.ShippingDate); + orderModel.EntityType.ComplexProperty(o => o.ShippingAddress); + orderModel.EntityType.HasRequired(o => o.Customer); + orderModel.EntityType.HasOptional(o => o.Employee); + orderModel.EntityType.HasMany(o => o.OrderLines); + + var employeeModel = builder.EntitySet(nameof(Employee)); + employeeModel.EntityType.HasKey(o => o.EmployeeId); + employeeModel.EntityType.Property(o => o.BirthDate); + employeeModel.EntityType.Property(o => o.Extension); + employeeModel.EntityType.Property(o => o.FirstName); + employeeModel.EntityType.Property(o => o.HireDate); + employeeModel.EntityType.Property(o => o.LastName); + employeeModel.EntityType.Property(o => o.Notes); + employeeModel.EntityType.Property(o => o.Title); + employeeModel.EntityType.HasMany(o => o.Orders); + + builder.EntitySet(nameof(CustomerVm)); + + return builder.GetEdmModel(); + } + + private class ODataServiceProvider : IServiceProvider + { + private readonly Dictionary _singletonObjects = new Dictionary(); + + public ODataServiceProvider(Dictionary singletonObjects) + { + _singletonObjects = singletonObjects; + } + + public object GetService(System.Type serviceType) + { + if (_singletonObjects.TryGetValue(serviceType, out var service)) + { + return service; + } + + var ctor = serviceType.GetConstructor(new System.Type[0]); + if (ctor != null) + { + return ctor.Invoke(new object[0]); + } + + ctor = serviceType.GetConstructor(new[] { typeof(DefaultQuerySettings) }); + if (ctor != null) + { + return ctor.Invoke(new object[] { GetService(typeof(DefaultQuerySettings)) }); + } + + ctor = serviceType.GetConstructor(new[] { typeof(IServiceProvider) }); + if (ctor != null) + { + return ctor.Invoke(new object[] { this }); + } + + return null; + } + } + } +} diff --git a/src/NHibernate.Test/Linq/ParameterTests.cs b/src/NHibernate.Test/Linq/ParameterTests.cs new file mode 100644 index 00000000000..fdbb1a73275 --- /dev/null +++ b/src/NHibernate.Test/Linq/ParameterTests.cs @@ -0,0 +1,999 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text.RegularExpressions; +using NHibernate.Dialect; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Driver; +using NHibernate.Engine.Query; +using NHibernate.Linq; +using NHibernate.Util; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace NHibernate.Test.Linq +{ + [TestFixture] + public class ParameterTests : LinqTestCase + { + [Test] + public void UsingArrayParameterTwice() + { + var ids = new[] {11008, 11019, 11039}; + AssertTotalParameters( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)), + ids.Length, + 1); + } + + [Test] + public void UsingTwoArrayParameters() + { + var ids = new[] {11008, 11019, 11039}; + var ids2 = new[] {11008, 11019, 11039}; + AssertTotalParameters( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)), + ids.Length + ids2.Length, + 2); + } + + [Test] + public void UsingListParameterTwice() + { + var ids = new List {11008, 11019, 11039}; + AssertTotalParameters( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids.Contains(o.OrderId)), + ids.Count, + 1); + } + + [Test] + public void UsingTwoListParameters() + { + var ids = new List {11008, 11019, 11039}; + var ids2 = new List {11008, 11019, 11039}; + AssertTotalParameters( + db.Orders.Where(o => ids.Contains(o.OrderId) && ids2.Contains(o.OrderId)), + ids.Count + ids2.Count, + 2); + } + + [Test] + public void UsingEntityParameterTwice() + { + var order = db.Orders.First(); + AssertTotalParameters( + db.Orders.Where(o => o == order && o != order), + 1); + } + + [Test] + public void UsingEntityParameterForCollection() + { + var item = db.OrderLines.First(); + AssertTotalParameters( + db.Orders.Where(o => o.OrderLines.Contains(item)), + 1); + } + + [Test] + public void UsingProxyParameterForCollection() + { + var item = session.Load(10248); + Assert.That(NHibernateUtil.IsInitialized(item), Is.False); + AssertTotalParameters( + db.Customers.Where(o => o.Orders.Contains(item)), + 1); + } + + [Test] + public void UsingFieldProxyParameterForCollection() + { + var item = session.Query().First(); + AssertTotalParameters( + session.Query().Where(o => o.RequiredRelatedItems.Contains(item)), + 1); + } + + [Test] + public void UsingEntityParameterInSubQuery() + { + var item = db.Customers.First(); + var subQuery = db.Orders.Select(o => o.Customer).Where(o => o == item); + AssertTotalParameters( + db.Orders.Where(o => subQuery.Contains(o.Customer)), + 1); + } + + [Test] + public void UsingEntityParameterForCollectionSelection() + { + var item = db.OrderLines.First(); + AssertTotalParameters( + db.Orders.SelectMany(o => o.OrderLines).Where(o => o == item), + 1); + } + + [Test] + public void UsingFieldProxyParameterForCollectionSelection() + { + var item = session.Query().First(); + AssertTotalParameters( + session.Query().SelectMany(o => o.RequiredRelatedItems).Where(o => o == item), + 1); + } + + [Test] + public void UsingEntityListParameterForCollectionSelection() + { + var items = new[] {db.OrderLines.First()}; + AssertTotalParameters( + db.Orders.SelectMany(o => o.OrderLines).Where(o => items.Contains(o)), + 1); + } + + [Test] + public void UsingFieldProxyListParameterForCollectionSelection() + { + var items = new[] {session.Query().First()}; + AssertTotalParameters( + session.Query().SelectMany(o => o.RequiredRelatedItems).Where(o => items.Contains(o)), + 1); + } + + [Test] + public void UsingTwoEntityParameters() + { + var order = db.Orders.First(); + var order2 = db.Orders.First(); + AssertTotalParameters( + db.Orders.Where(o => o == order && o != order2), + 2); + } + + [Test] + public void UsingEntityEnumerableParameterTwice() + { + if (!Dialect.SupportsSubSelects) + { + Assert.Ignore(); + } + + var enumerable = db.DynamicUsers.First(); + AssertTotalParameters( + db.DynamicUsers.Where(o => o == enumerable && o != enumerable), + 1); + } + + [Test] + public void UsingEntityEnumerableListParameterTwice() + { + if (!Dialect.SupportsSubSelects) + { + Assert.Ignore(); + } + + var enumerable = new[] {db.DynamicUsers.First()}; + AssertTotalParameters( + db.DynamicUsers.Where(o => enumerable.Contains(o) && enumerable.Contains(o)), + 1); + } + + [Test] + public void UsingValueTypeParameterTwice() + { + var value = 1; + AssertTotalParameters( + db.Orders.Where(o => o.OrderId == value && o.OrderId != value), + 1); + } + + [Test] + public void CompareIntegralParametersAndColumns() + { + short shortParam = 1; + var intParam = 2; + var longParam = 3L; + short? nullShortParam = 1; + int? nullIntParam = 2; + long? nullLongParam = 3L; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Short == shortParam || o.Short < intParam || o.Short > longParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == shortParam || o.NullableShort <= intParam || o.NullableShort != longParam), "Int16"}, + {db.NumericEntities.Where(o => o.Short == nullShortParam || o.Short < nullIntParam || o.Short > nullLongParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == nullShortParam || o.NullableShort <= nullIntParam || o.NullableShort != nullLongParam), "Int16"}, + + {db.NumericEntities.Where(o => o.Integer == shortParam || o.Integer < intParam || o.Integer > longParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == shortParam || o.NullableInteger <= intParam || o.NullableInteger != longParam), "Int32"}, + {db.NumericEntities.Where(o => o.Integer == nullShortParam || o.Integer < nullIntParam || o.Integer > nullLongParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == nullShortParam || o.NullableInteger <= nullIntParam || o.NullableInteger != nullLongParam), "Int32"}, + + {db.NumericEntities.Where(o => o.Long == shortParam || o.Long < intParam || o.Long > longParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == shortParam || o.NullableLong <= intParam || o.NullableLong != longParam), "Int64"}, + {db.NumericEntities.Where(o => o.Long == nullShortParam || o.Long < nullIntParam || o.Long > nullLongParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == nullShortParam || o.NullableLong <= nullIntParam || o.NullableLong != nullLongParam), "Int64"} + }; + + foreach (var pair in queriables) + { + // Parameters should be pre-evaluated + AssertTotalParameters( + pair.Key, + 3, + sql => + { + Assert.That(sql, Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(3)); + }); + } + } + + [Test] + public void CompareIntegralParametersWithFloatingPointColumns() + { + short shortParam = 1; + var intParam = 2; + var longParam = 3L; + short? nullShortParam = 1; + int? nullIntParam = 2; + long? nullLongParam = 3L; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Decimal == shortParam || o.Decimal < intParam || o.Decimal > longParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == shortParam || o.NullableDecimal <= intParam || o.NullableDecimal != longParam), "Decimal"}, + {db.NumericEntities.Where(o => o.Decimal == nullShortParam || o.Decimal < nullIntParam || o.Decimal > nullLongParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == nullShortParam || o.NullableDecimal <= nullIntParam || o.NullableDecimal != nullLongParam), "Decimal"}, + + {db.NumericEntities.Where(o => o.Single == shortParam || o.Single < intParam || o.Single > longParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == shortParam || o.NullableSingle <= intParam || o.NullableSingle != longParam), "Single"}, + {db.NumericEntities.Where(o => o.Single == nullShortParam || o.Single < nullIntParam || o.Single > nullLongParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == nullShortParam || o.NullableSingle <= nullIntParam || o.NullableSingle != nullLongParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double == shortParam || o.Double < intParam || o.Double > longParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == shortParam || o.NullableDouble <= intParam || o.NullableDouble != longParam), "Double"}, + {db.NumericEntities.Where(o => o.Double == nullShortParam || o.Double < nullIntParam || o.Double > nullLongParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == nullShortParam || o.NullableDouble <= nullIntParam || o.NullableDouble != nullLongParam), "Double"}, + }; + + foreach (var pair in queriables) + { + // Parameters should be pre-evaluated + AssertTotalParameters( + pair.Key, + 3, + sql => + { + Assert.That(sql, Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(3)); + }); + } + } + + [Test] + public void CompareFloatingPointParametersAndColumns() + { + var decimalParam = 1.1m; + var singleParam = 2.2f; + var doubleParam = 3.3d; + decimal? nullDecimalParam = 1.1m; + float? nullSingleParam = 2.2f; + double? nullDoubleParam = 3.3d; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Decimal == decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.Decimal == nullDecimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == nullDecimalParam), "Decimal"}, + + {db.NumericEntities.Where(o => o.Single <= singleParam || o.Single >= doubleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.NullableSingle != doubleParam), "Single"}, + {db.NumericEntities.Where(o => o.Single <= nullSingleParam || o.Single >= nullDoubleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == nullSingleParam || o.NullableSingle != nullDoubleParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double <= singleParam || o.Double >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == singleParam || o.NullableDouble != doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Double <= nullSingleParam || o.Double >= nullDoubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == nullSingleParam || o.NullableDouble != nullDoubleParam), "Double"}, + }; + + foreach (var pair in queriables) + { + var totalParameters = pair.Value == "Decimal" ? 1 : 2; + // Parameters should be pre-evaluated + AssertTotalParameters( + pair.Key, + totalParameters, + sql => + { + Assert.That(sql, Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(totalParameters)); + }); + } + } + + [Test] + public void CompareFloatingPointParametersWithIntegralColumns() + { + var decimalParam = 1.1m; + var singleParam = 2.2f; + var doubleParam = 3.3d; + decimal? nullDecimalParam = 1.1m; + float? nullSingleParam = 2.2f; + double? nullDoubleParam = 3.3d; + var queriables = new List> + { + db.NumericEntities.Where(o => o.Short == decimalParam || o.Short != singleParam || o.Short <= doubleParam), + db.NumericEntities.Where(o => o.NullableShort <= decimalParam || o.NullableShort == singleParam || o.NullableShort >= doubleParam), + db.NumericEntities.Where(o => o.Short == nullDecimalParam || o.Short != nullSingleParam || o.Short <= nullDoubleParam), + db.NumericEntities.Where(o => o.NullableShort <= nullDecimalParam || o.NullableShort == nullSingleParam || o.NullableShort >= nullDoubleParam), + + db.NumericEntities.Where(o => o.Integer == decimalParam || o.Integer != singleParam || o.Integer <= doubleParam), + db.NumericEntities.Where(o => o.NullableInteger <= decimalParam || o.NullableInteger == singleParam || o.NullableInteger >= doubleParam), + db.NumericEntities.Where(o => o.Integer == nullDecimalParam || o.Integer != nullSingleParam || o.Integer <= nullDoubleParam), + db.NumericEntities.Where(o => o.NullableInteger <= nullDecimalParam || o.NullableInteger == nullSingleParam || o.NullableInteger >= nullDoubleParam), + + db.NumericEntities.Where(o => o.Long == decimalParam || o.Long != singleParam || o.Long <= doubleParam), + db.NumericEntities.Where(o => o.NullableLong <= decimalParam || o.NullableLong == singleParam || o.NullableLong >= doubleParam), + db.NumericEntities.Where(o => o.Long == nullDecimalParam || o.Long != nullSingleParam || o.Long <= nullDoubleParam), + db.NumericEntities.Where(o => o.NullableLong <= nullDecimalParam || o.NullableLong == nullSingleParam || o.NullableLong >= nullDoubleParam), + }; + + foreach (var query in queriables) + { + // Columns should be casted + AssertTotalParameters( + query, + 3, + sql => + { + var matches = Regex.Matches(sql, @"cast\([\w\d]+\..+\)"); + Assert.That(matches.Count, Is.EqualTo(3)); + Assert.That(GetTotalOccurrences(sql, $"Type: Decimal"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: Single"), Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: Double"), Is.EqualTo(1)); + }); + } + } + + [Test] + public void CompareFloatingPointParameterWithIntegralAndFloatingPointColumns() + { + var decimalParam = 1.1m; + var singleParam = 2.2f; + var doubleParam = 3.3d; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Decimal == decimalParam || o.NullableShort >= decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.Decimal == decimalParam || o.Long >= decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == decimalParam || o.Integer != decimalParam), "Decimal"}, + {db.NumericEntities.Where(o => o.NullableDecimal == decimalParam || o.NullableInteger == decimalParam), "Decimal"}, + + {db.NumericEntities.Where(o => o.Single == singleParam || o.NullableShort >= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.Single == singleParam || o.Long >= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.Integer != singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.NullableInteger == singleParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double == doubleParam || o.NullableShort >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Double == doubleParam || o.Long >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.Integer != doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.NullableInteger == doubleParam), "Double"} + }; + var odbcDriver = Sfi.ConnectionProvider.Driver is OdbcDriver; + + foreach (var pair in queriables) + { + // Integral columns should be casted + AssertTotalParameters( + pair.Key, + 1, + sql => + { + var matches = Regex.Matches(sql, @"cast\([\w\d]+\..+\)"); + Assert.That(matches.Count, Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(odbcDriver ? 2 : 1)); + }); + } + } + + [Test] + public void CompareFloatingPointParameterWithDifferentFloatingPointColumns() + { + if (Sfi.Dialect is FirebirdDialect) + { + Assert.Ignore("Due to the regex hack in FirebirdClientDriver, the parameters can be casted twice."); + } + + var singleParam = 2.2f; + var doubleParam = 3.3d; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Single == singleParam || o.Double >= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.Double >= singleParam || o.Single == singleParam), "Single"}, + {db.NumericEntities.Where(o => o.Single == singleParam || o.NullableDouble <= singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.Double != singleParam), "Single"}, + {db.NumericEntities.Where(o => o.NullableSingle == singleParam || o.NullableDouble == singleParam), "Single"}, + + {db.NumericEntities.Where(o => o.Double == doubleParam || o.Single >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Single >= doubleParam || o.Double == doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.Double == doubleParam || o.NullableSingle >= doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.Single != doubleParam), "Double"}, + {db.NumericEntities.Where(o => o.NullableDouble == doubleParam || o.NullableSingle == doubleParam), "Double"} + }; + var sameType = Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Single.SqlType, out var singleCast) && + Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Double.SqlType, out var doubleCast) && + singleCast == doubleCast; + var odbcDriver = Sfi.ConnectionProvider.Driver is OdbcDriver; + + foreach (var pair in queriables) + { + // Columns should be casted for Double parameter and parameters for Single parameter + AssertTotalParameters( + pair.Key, + 1, + sql => + { + var matches = pair.Value == "Double" + ? Regex.Matches(sql, @"cast\([\w\d]+\..+\)") + : Regex.Matches(sql, @"cast\(((@|\?|:)p\d+|\?)\s+as.*\)"); + // SQLiteDialect uses sql cast for transparentcast method + Assert.That(matches.Count, Is.EqualTo(sameType && !(Sfi.Dialect is SQLiteDialect) ? 0 : 1)); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(odbcDriver ? 2 : 1)); + }); + } + } + + [Test] + public void CompareIntegralParameterWithIntegralAndFloatingPointColumns() + { + if (Sfi.Dialect is FirebirdDialect) + { + Assert.Ignore("Due to the regex hack in FirebirdClientDriver, the parameters can be casted twice."); + } + + short shortParam = 1; + var intParam = 2; + var longParam = 3L; + var queriables = new Dictionary, string> + { + {db.NumericEntities.Where(o => o.Short == shortParam || o.Double >= shortParam), "Int16"}, + {db.NumericEntities.Where(o => o.Short == shortParam || o.NullableDouble >= shortParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == shortParam || o.Decimal != shortParam), "Int16"}, + {db.NumericEntities.Where(o => o.NullableShort == shortParam || o.NullableSingle > shortParam), "Int16"}, + + {db.NumericEntities.Where(o => o.Integer == intParam || o.Double >= intParam), "Int32"}, + {db.NumericEntities.Where(o => o.Integer == intParam || o.NullableDouble >= intParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == intParam || o.Decimal != intParam), "Int32"}, + {db.NumericEntities.Where(o => o.NullableInteger == intParam || o.NullableSingle > intParam), "Int32"}, + + {db.NumericEntities.Where(o => o.Long == longParam || o.Double >= longParam), "Int64"}, + {db.NumericEntities.Where(o => o.Long == longParam || o.NullableDouble >= longParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == longParam || o.Decimal != longParam), "Int64"}, + {db.NumericEntities.Where(o => o.NullableLong == longParam || o.NullableSingle > longParam), "Int64"} + }; + var odbcDriver = Sfi.ConnectionProvider.Driver is OdbcDriver; + + foreach (var pair in queriables) + { + // Parameters should be casted + AssertTotalParameters( + pair.Key, + 1, + sql => + { + var matches = Regex.Matches(sql, @"cast\(((@|\?|:)p\d+|\?)\s+as.*\)"); + Assert.That(matches.Count, Is.EqualTo(1)); + Assert.That(GetTotalOccurrences(sql, $"Type: {pair.Value}"), Is.EqualTo(odbcDriver ? 2 : 1)); + }); + } + } + + [Test] + public void UsingValueTypeParameterOfDifferentType() + { + var value = 1; + var queriables = new List> + { + db.NumericEntities.Where(o => o.Short == value), + db.NumericEntities.Where(o => o.Short != value), + db.NumericEntities.Where(o => o.Short >= value), + db.NumericEntities.Where(o => o.Short <= value), + db.NumericEntities.Where(o => o.Short > value), + db.NumericEntities.Where(o => o.Short < value), + + db.NumericEntities.Where(o => o.NullableShort == value), + db.NumericEntities.Where(o => o.NullableShort != value), + db.NumericEntities.Where(o => o.NullableShort >= value), + db.NumericEntities.Where(o => o.NullableShort <= value), + db.NumericEntities.Where(o => o.NullableShort > value), + db.NumericEntities.Where(o => o.NullableShort < value), + + db.NumericEntities.Where(o => o.NullableShort.Value == value), + db.NumericEntities.Where(o => o.NullableShort.Value != value), + db.NumericEntities.Where(o => o.NullableShort.Value >= value), + db.NumericEntities.Where(o => o.NullableShort.Value <= value), + db.NumericEntities.Where(o => o.NullableShort.Value > value), + db.NumericEntities.Where(o => o.NullableShort.Value < value) + }; + + foreach (var query in queriables) + { + AssertTotalParameters( + query, + 1, + sql => Assert.That(sql, Does.Not.Contain("cast"))); + } + + if (Sfi.Dialect is FirebirdDialect) + { + Assert.Ignore("Due to the regex bug in FirebirdClientDriver, the parameters are not casted."); + } + + queriables = new List> + { + db.NumericEntities.Where(o => o.Short + value > value), + db.NumericEntities.Where(o => o.Short - value > value), + db.NumericEntities.Where(o => o.Short * value > value), + + db.NumericEntities.Where(o => o.NullableShort + value > value), + db.NumericEntities.Where(o => o.NullableShort - value > value), + db.NumericEntities.Where(o => o.NullableShort * value > value), + + db.NumericEntities.Where(o => o.NullableShort.Value + value > value), + db.NumericEntities.Where(o => o.NullableShort.Value - value > value), + db.NumericEntities.Where(o => o.NullableShort.Value * value > value), + }; + + var sameType = Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Int16.SqlType, out var shortCast) && + Sfi.Dialect.TryGetCastTypeName(NHibernateUtil.Int32.SqlType, out var intCast) && + shortCast == intCast; + foreach (var query in queriables) + { + AssertTotalParameters( + query, + 1, + sql => { + // SQLiteDialect uses sql cast for transparentcast method + Assert.That(sql, !sameType || Sfi.Dialect is SQLiteDialect ? Does.Match("where\\s+cast") : (IResolveConstraint)Does.Not.Contain("cast")); + Assert.That(GetTotalOccurrences(sql, "cast"), Is.EqualTo(!sameType || Sfi.Dialect is SQLiteDialect ? 1 : 0)); + }); + } + } + + [Test] + public void UsingValueTypeParameterTwiceOnNullableProperty() + { + short value = 1; + AssertTotalParameters( + db.NumericEntities.Where(o => o.NullableShort == value && o.NullableShort != value && o.Short == value), + 1, sql => { + + Assert.That(GetTotalOccurrences(sql, "cast"), Is.EqualTo(0)); + }); + } + + [Test] + public void UsingValueTypeParameterOnDifferentProperties() + { + int value = 1; + AssertTotalParameters( + db.NumericEntities.Where(o => o.NullableShort == value && o.NullableShort != value && o.Integer == value), + 1); + + AssertTotalParameters( + db.NumericEntities.Where(o => o.Integer == value && o.NullableShort == value && o.NullableShort != value), + 1); + } + + [Test] + public void UsingParameterInEvaluatableExpression() + { + var value = "test"; + db.Orders.Where(o => string.Format("{0}", value) != o.ShippedTo).ToList(); + db.Orders.Where(o => $"{value}_" != o.ShippedTo).ToList(); + db.Orders.Where(o => string.Copy(value) != o.ShippedTo).ToList(); + + var guid = Guid.Parse("2D7E6EB3-BD08-4A40-A4E7-5150F7895821"); + db.Orders.Where(o => o.ShippedTo.Contains($"VALUE {guid}")).ToList(); + + var names = new[] {"name"}; + db.Users.Where(x => names.Length == 0 || names.Contains(x.Name)).ToList(); + names = new string[] { }; + db.Users.Where(x => names.Length == 0 || names.Contains(x.Name)).ToList(); + } + + [Test] + public void UsingParameterOnSelectors() + { + var user = new User() {Id = 1}; + db.Users.Where(o => o == user).ToList(); + db.Users.FirstOrDefault(o => o == user); + db.Timesheets.Where(o => o.Users.Any(u => u == user)).ToList(); + + var users = new[] {new User() {Id = 1}}; + db.Users.Where(o => users.Contains(o)).ToList(); + db.Users.FirstOrDefault(o => users.Contains(o)); + db.Timesheets.Where(o => o.Users.Any(u => users.Contains(u))).ToList(); + } + + [Test] + public void ValidateMixingTwoParametersCacheKeys() + { + var value = 1; + var value2 = 1; + var expression1 = GetLinqExpression(db.Orders.Where(o => o.OrderId == value && o.OrderId != value)); + var expression2 = GetLinqExpression(db.Orders.Where(o => o.OrderId == value && o.OrderId != value2)); + var expression3 = GetLinqExpression(db.Orders.Where(o => o.OrderId == value2 && o.OrderId != value)); + var expression4 = GetLinqExpression(db.Orders.Where(o => o.OrderId == value2 && o.OrderId != value2)); + + Assert.That(expression1.Key, Is.Not.EqualTo(expression2.Key)); + Assert.That(expression1.Key, Is.Not.EqualTo(expression3.Key)); + Assert.That(expression1.Key, Is.EqualTo(expression4.Key)); + + Assert.That(expression2.Key, Is.EqualTo(expression3.Key)); + Assert.That(expression2.Key, Is.Not.EqualTo(expression4.Key)); + + Assert.That(expression3.Key, Is.Not.EqualTo(expression4.Key)); + } + + [Test] + public void UsingNegateValueTypeParameterTwice() + { + var value = 1; + AssertTotalParameters( + db.Orders.Where(o => o.OrderId == -value && o.OrderId != -value), + 1); + } + + [Test] + public void UsingNegateValueTypeParameter() + { + var value = 1; + AssertTotalParameters( + db.Orders.Where(o => o.OrderId == value && o.OrderId != -value), + 1); + } + + [Test] + public void UsingValueTypeParameterInArray() + { + var id = 11008; + AssertTotalParameters( + db.Orders.Where(o => new[] {id, 11019}.Contains(o.OrderId) && new[] {id, 11019}.Contains(o.OrderId)), + 4, + 2); + } + + [Test] + public void UsingTwoValueTypeParameters() + { + var value = 1; + var value2 = 1; + AssertTotalParameters( + db.Orders.Where(o => o.OrderId == value && o.OrderId != value2), + 2); + } + + [Test] + public void UsingStringParameterTwice() + { + var value = "test"; + AssertTotalParameters( + db.Products.Where(o => o.Name == value && o.Name != value), + 1); + } + + [Test] + public void UsingTwoStringParameters() + { + var value = "test"; + var value2 = "test"; + AssertTotalParameters( + db.Products.Where(o => o.Name == value && o.Name != value2), + 2); + } + + [Test] + public void UsingObjectPropertyParameterTwice() + { + var value = new Product {Name = "test"}; + AssertTotalParameters( + db.Products.Where(o => o.Name == value.Name && o.Name != value.Name), + 1); + } + + [Test] + public void UsingTwoObjectPropertyParameters() + { + var value = new Product {Name = "test"}; + var value2 = new Product {Name = "test"}; + AssertTotalParameters( + db.Products.Where(o => o.Name == value.Name && o.Name != value2.Name), + 2); + } + + [Test] + public void UsingParameterInWhereSkipTake() + { + var value3 = 1; + var q1 = db.Products.Where(o => o.ProductId < value3).Take(value3).Skip(value3); + AssertTotalParameters(q1, 3); + } + + [Test] + public void UsingParameterInTwoWhere() + { + var value3 = 1; + var q1 = db.Products.Where(o => o.ProductId < value3).Where(o => o.ProductId < value3); + AssertTotalParameters(q1, 1); + } + + [Test] + public void UsingObjectNestedPropertyParameterTwice() + { + var value = new Employee {Superior = new Employee {Superior = new Employee {FirstName = "test"}}}; + AssertTotalParameters( + db.Employees.Where(o => o.FirstName == value.Superior.Superior.FirstName && o.FirstName != value.Superior.Superior.FirstName), + 1); + } + + [Test] + public void UsingDifferentObjectNestedPropertyParameter() + { + var value = new Employee {Superior = new Employee {FirstName = "test", Superior = new Employee {FirstName = "test"}}}; + AssertTotalParameters( + db.Employees.Where(o => o.FirstName == value.Superior.FirstName && o.FirstName != value.Superior.Superior.FirstName), + 2); + } + + [Test] + public void UsingMethodObjectPropertyParameterTwice() + { + var value = new Product {Name = "test"}; + AssertTotalParameters( + db.Products.Where(o => o.Name == value.Name.Trim() && o.Name != value.Name.Trim()), + 2); + } + + [Test] + public void UsingStaticMethodObjectPropertyParameterTwice() + { + var value = new Product {Name = "test"}; + AssertTotalParameters( + db.Products.Where(o => o.Name == string.Copy(value.Name) && o.Name != string.Copy(value.Name)), + 2); + } + + [Test] + public void UsingObjectPropertyParameterWithSecondLevelClosure() + { + var value = new Product {Name = "test"}; + Expression> predicate = o => o.Name == value.Name && o.Name != value.Name; + AssertTotalParameters( + db.Products.Where(predicate), + 1); + } + + [Test] + public void UsingObjectPropertyParameterWithThirdLevelClosure() + { + var value = new Product {Name = "test"}; + Expression> orderLinePredicate = o => o.Order.ShippedTo == value.Name && o.Order.ShippedTo != value.Name; + Expression> predicate = o => o.Name == value.Name && o.OrderLines.AsQueryable().Any(orderLinePredicate); + AssertTotalParameters( + db.Products.Where(predicate), + 1); + } + + [Test] + public void UsingParameterInDMLInsertIntoFourTimes() + { + var value = "test"; + AssertTotalParameters( + QueryMode.Insert, + db.Customers.Where(c => c.CustomerId == value), + x => new Customer {CustomerId = value, ContactName = value, CompanyName = value}, + 4); + } + + [Test] + public void UsingFourParametersInDMLInsertInto() + { + var value = "test"; + var value2 = "test"; + var value3 = "test"; + var value4 = "test"; + AssertTotalParameters( + QueryMode.Insert, + db.Customers.Where(c => c.CustomerId == value3), + x => new Customer {CustomerId = value4, ContactName = value2, CompanyName = value}, + 4); + } + + [Test] + public void DMLInsertIntoShouldHaveSameCacheKeys() + { + var value = "test"; + var value2 = "test"; + var value3 = "test"; + var value4 = "test"; + var expression1 = GetLinqExpression( + QueryMode.Insert, + db.Customers.Where(c => c.CustomerId == value), + x => new Customer {CustomerId = value, ContactName = value, CompanyName = value}); + var expression2 = GetLinqExpression( + QueryMode.Insert, + db.Customers.Where(c => c.CustomerId == value3), + x => new Customer {CustomerId = value4, ContactName = value2, CompanyName = value}); + + Assert.That(expression1.Key, Is.EqualTo(expression2.Key)); + } + + [Test] + public void UsingParameterInDMLUpdateThreeTimes() + { + var value = "test"; + AssertTotalParameters( + QueryMode.Update, + db.Customers.Where(c => c.CustomerId == value), + x => new Customer {ContactName = value, CompanyName = value}, + 3); + } + + [Test] + public void UsingThreeParametersInDMLUpdate() + { + var value = "test"; + var value2 = "test"; + var value3 = "test"; + AssertTotalParameters( + QueryMode.Update, + db.Customers.Where(c => c.CustomerId == value3), + x => new Customer { ContactName = value2, CompanyName = value }, + 3); + } + + [TestCase(QueryMode.Update)] + [TestCase(QueryMode.UpdateVersioned)] + public void DMLUpdateIntoShouldHaveSameCacheKeys(QueryMode queryMode) + { + var value = "test"; + var value2 = "test"; + var value3 = "test"; + var expression1 = GetLinqExpression( + queryMode, + db.Customers.Where(c => c.CustomerId == value), + x => new Customer {ContactName = value, CompanyName = value}); + var expression2 = GetLinqExpression( + queryMode, + db.Customers.Where(c => c.CustomerId == value3), + x => new Customer {ContactName = value2, CompanyName = value}); + + Assert.That(expression1.Key, Is.EqualTo(expression2.Key)); + } + + [Test] + public void UsingParameterInDMLDeleteTwice() + { + var value = "test"; + AssertTotalParameters( + QueryMode.Delete, + db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value), + 2); + } + + [Test] + public void UsingTwoParametersInDMLDelete() + { + var value = "test"; + var value2 = "test"; + AssertTotalParameters( + QueryMode.Delete, + db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value2), + 2); + } + + [Test] + public void DMLDeleteShouldHaveSameCacheKeys() + { + var value = "test"; + var value2 = "test"; + var expression1 = GetLinqExpression( + QueryMode.Delete, + db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value)); + var expression2 = GetLinqExpression( + QueryMode.Delete, + db.Customers.Where(c => c.CustomerId == value && c.CompanyName == value2)); + + Assert.That(expression1.Key, Is.EqualTo(expression2.Key)); + } + + private void AssertTotalParameters(IQueryable query, int parameterNumber, Action sqlAction) + { + AssertTotalParameters(query, parameterNumber, null, sqlAction); + } + + private void AssertTotalParameters(IQueryable query, int parameterNumber, int? linqParameterNumber = null, Action sqlAction = null) + { + using (var sqlSpy = new SqlLogSpy()) + { + // In case of arrays linqParameterNumber and parameterNumber will be different + Assert.That( + GetLinqExpression(query).ParameterValuesByName.Count, + Is.EqualTo(linqParameterNumber ?? parameterNumber), + "Linq expression has different number of parameters"); + + var queryPlanCacheType = typeof(QueryPlanCache); + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + query.ToList(); + + sqlAction?.Invoke(sqlSpy.GetWholeLog()); + + // In case of arrays two query plans will be stored, one with an one without expended parameters + Assert.That(cache, Has.Count.EqualTo(linqParameterNumber.HasValue ? 2 : 1), "Query should be cacheable"); + + AssertParameters(sqlSpy, parameterNumber); + } + } + + private static void AssertTotalParameters(QueryMode queryMode, IQueryable query, int parameterNumber) + { + AssertTotalParameters(queryMode, query, null, parameterNumber); + } + + private static void AssertTotalParameters(QueryMode queryMode, IQueryable query, Expression> expression, int parameterNumber) + { + var provider = query.Provider as INhQueryProvider; + Assert.That(provider, Is.Not.Null); + + var dmlExpression = expression != null + ? DmlExpressionRewriter.PrepareExpression(query.Expression, expression) + : query.Expression; + + using (var sqlSpy = new SqlLogSpy()) + { + Assert.That(provider.ExecuteDml(queryMode, dmlExpression), Is.EqualTo(0), "The DML query updated the data"); // Avoid updating the data + AssertParameters(sqlSpy, parameterNumber); + } + } + + private static void AssertParameters(SqlLogSpy sqlSpy, int parameterNumber) + { + var sqlParameters = sqlSpy.GetWholeLog().Split(';')[1]; + var matches = Regex.Matches(sqlParameters, @"([\d\w]+)[\s]+\=", RegexOptions.IgnoreCase); + + // Due to ODBC drivers not supporting parameter names, we have to do a distinct of parameter names. + var distinctParameters = matches.OfType().Select(m => m.Groups[1].Value.Trim()).Distinct().ToList(); + Assert.That(distinctParameters, Has.Count.EqualTo(parameterNumber)); + } + + private NhLinqExpression GetLinqExpression(QueryMode queryMode, IQueryable query, Expression> expression) + { + return GetLinqExpression(queryMode, DmlExpressionRewriter.PrepareExpression(query.Expression, expression)); + } + + private NhLinqExpression GetLinqExpression(QueryMode queryMode, IQueryable query) + { + return GetLinqExpression(queryMode, query.Expression); + } + + private NhLinqExpression GetLinqExpression(IQueryable query) + { + return GetLinqExpression(QueryMode.Select, query.Expression); + } + + private NhLinqExpression GetLinqExpression(QueryMode queryMode, Expression expression) + { + return queryMode == QueryMode.Select + ? new NhLinqExpression(expression, Sfi) + : new NhLinqDmlExpression(queryMode, expression, Sfi); + } + } +} diff --git a/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs b/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs new file mode 100644 index 00000000000..48cc2e14048 --- /dev/null +++ b/src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs @@ -0,0 +1,612 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Linq.Expressions; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Engine.Query; +using NHibernate.Linq; +using NHibernate.Linq.Visitors; +using NHibernate.Type; +using NUnit.Framework; +using Remotion.Linq.Clauses; + +namespace NHibernate.Test.Linq +{ + public class ParameterTypeLocatorTests : LinqTestCase + { + [Test] + public void AddIntegerTest() + { + AssertResults( + new Dictionary> + { + {"2.1", o => o is DoubleType}, + {"5", o => o is Int32Type}, + }, + db.Users.Where(o => o.Id + 5 > 2.1), + db.Users.Where(o => 2.1 < 5 + o.Id) + ); + } + + [Test] + public void AddDecimalTest() + { + AssertResults( + new Dictionary> + { + {"2.1", o => o is DecimalType}, + {"5.2", o => o is DecimalType}, + }, + db.Users.Where(o => o.Id + 5.2m > 2.1m), + db.Users.Where(o => 2.1m < 5.2m + o.Id) + ); + } + + [Test] + public void SubtractFloatTest() + { + AssertResults( + new Dictionary> + { + {"2.1", o => o is DoubleType}, + {"5.2", o => o is SingleType}, + }, + db.Users.Where(o => o.Id - 5.2f > 2.1), + db.Users.Where(o => 2.1 < 5.2f - o.Id) + ); + } + + [Test] + public void GreaterThanTest() + { + AssertResults( + new Dictionary> + { + {"2.1", o => o is DoubleType} + }, + db.Users.Where(o => o.Id > 2.1), + db.Users.Where(o => 2.1 > o.Id) + ); + } + + [Test] + public void EqualStringEnumTest() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => o.Enum1 == EnumStoredAsString.Large), + db.Users.Where(o => EnumStoredAsString.Large == o.Enum1) + ); + } + + [Test] + public void EqualsMethodStringTest() + { + AssertResults( + new Dictionary> + { + {"\"London\"", o => o is StringType stringType && stringType.SqlType.Length == 15} + }, + db.Orders.Where(o => o.ShippingAddress.City.Equals("London")), + db.Orders.Where(o => "London".Equals(o.ShippingAddress.City)), + db.Orders.Where(o => string.Equals("London", o.ShippingAddress.City)), + db.Orders.Where(o => string.Equals(o.ShippingAddress.City, "London")) + ); + } + + [Test] + public void BinaryIntShortTest() + { + AssertResults( + new Dictionary> + { + {"3", o => o is Int16Type} + }, + db.NumericEntities.Where(o => o.Short == 3), + db.NumericEntities.Where(o => 3 == o.Short), + db.NumericEntities.Where(o => o.Short < 3), + db.NumericEntities.Where(o => 3 < o.Short), + db.NumericEntities.Where(o => o.Short > 3), + db.NumericEntities.Where(o => 3 > o.Short), + db.NumericEntities.Where(o => o.Short >= 3), + db.NumericEntities.Where(o => 3 >= o.Short), + db.NumericEntities.Where(o => o.Short <= 3), + db.NumericEntities.Where(o => 3 <= o.Short), + db.NumericEntities.Where(o => o.Short != 3), + db.NumericEntities.Where(o => 3 != o.Short), + + db.NumericEntities.Where(o => o.NullableShort == 3), + db.NumericEntities.Where(o => 3 == o.NullableShort), + db.NumericEntities.Where(o => o.NullableShort < 3), + db.NumericEntities.Where(o => 3 < o.NullableShort), + db.NumericEntities.Where(o => o.NullableShort > 3), + db.NumericEntities.Where(o => 3 > o.NullableShort), + db.NumericEntities.Where(o => o.NullableShort >= 3), + db.NumericEntities.Where(o => 3 >= o.NullableShort), + db.NumericEntities.Where(o => o.NullableShort <= 3), + db.NumericEntities.Where(o => 3 <= o.NullableShort), + db.NumericEntities.Where(o => o.NullableShort != 3), + db.NumericEntities.Where(o => 3 != o.NullableShort), + + db.NumericEntities.Where(o => o.NullableShort.Value == 3), + db.NumericEntities.Where(o => 3 == o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value < 3), + db.NumericEntities.Where(o => 3 < o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value > 3), + db.NumericEntities.Where(o => 3 > o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value >= 3), + db.NumericEntities.Where(o => 3 >= o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value <= 3), + db.NumericEntities.Where(o => 3 <= o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value != 3), + db.NumericEntities.Where(o => 3 != o.NullableShort.Value) + ); + } + + [Test] + public void BinaryNullableIntShortTest() + { + int? value = 3; + AssertResults( + new Dictionary> + { + {"3", o => o is Int16Type} + }, + db.NumericEntities.Where(o => o.Short == value), + db.NumericEntities.Where(o => value == o.Short), + db.NumericEntities.Where(o => o.Short < value), + db.NumericEntities.Where(o => value < o.Short), + + db.NumericEntities.Where(o => o.NullableShort == value), + db.NumericEntities.Where(o => value == o.NullableShort), + db.NumericEntities.Where(o => o.NullableShort < value), + db.NumericEntities.Where(o => value < o.NullableShort), + + db.NumericEntities.Where(o => o.NullableShort.Value == value), + db.NumericEntities.Where(o => value == o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value < value), + db.NumericEntities.Where(o => value < o.NullableShort.Value), + db.NumericEntities.Where(o => o.NullableShort.Value > value) + ); + } + + [Test] + public void ContainsStringEnumTest() + { + var values = new[] {EnumStoredAsString.Small}; + AssertResults( + new Dictionary> + { + {"value(NHibernate.DomainModel.Northwind.Entities.EnumStoredAsString[])", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => values.Contains(o.Enum1)), + db.Users.Where(o => values.Contains(o.NullableEnum1.Value)), + db.Users.Where(o => values.Contains(o.Name == o.Name ? o.Enum1 : o.NullableEnum1.Value)), + db.Timesheets.Where(o => o.Users.Any(u => values.Contains(u.Enum1))) + ); + } + + [Test] + public void EqualStringEnumTestWithFetch() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType} + }, + db.Users.Fetch(o => o.Role).ThenFetch(o => o.ParentRole).Where(o => o.Enum1 == EnumStoredAsString.Large), + db.Users.Fetch(o => o.Role).ThenFetch(o => o.ParentRole).Where(o => EnumStoredAsString.Large == o.Enum1), + db.Timesheets.SelectMany(o => o.Users).Fetch(o => o.Role).Where(o => EnumStoredAsString.Large == o.Enum1), + db.Timesheets.FetchMany(o => o.Users).SelectMany(o => o.Users).Where(o => EnumStoredAsString.Large == o.Enum1), + db.Timesheets.FetchMany(o => o.Users).Where(o => o.Users.Any(u => EnumStoredAsString.Large == u.Enum1)) + ); + } + + [Test] + public void EqualStringEnumTestWithSubQuery() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => db.Users.Any(u => u.Enum1 == EnumStoredAsString.Large)), + db.Users.Where(o => db.Users.Any(u => EnumStoredAsString.Large == u.Enum1)), + db.Timesheets.Where(o => o.Users.Any(u => EnumStoredAsString.Large == u.Enum1)) + ); + } + + [Test] + public void EqualStringEnumTestWithMaxSubQuery() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType} + }, + db.Users.Fetch(o => o.Role).Where(o => db.Users.Max(u => u.Enum1 == EnumStoredAsString.Large ? u.Id : -u.Id) == o.Id), + db.Users.Fetch(o => o.Role).Where(o => db.Users.Max(u => EnumStoredAsString.Large == u.Enum1 ? u.Id : -u.Id) == o.Id), + db.Users.Where(o => db.Users.Max(u => u.Enum1 == EnumStoredAsString.Large ? u.Id : -u.Id) == o.Id), + db.Users.Where(o => db.Users.Max(u => EnumStoredAsString.Large == u.Enum1 ? u.Id : -u.Id) == o.Id) + ); + } + + [Test] + public void EqualStringTest() + { + AssertResults( + new Dictionary> + { + {"\"London\"", o => o is StringType stringType && stringType.SqlType.Length == 15} + }, + db.Orders.Where(o => o.ShippingAddress.City == "London"), + db.Orders.Where(o => "London" == o.ShippingAddress.City) + ); + } + + [Test] + public void CompareToStringTest() + { + AssertResults( + new Dictionary> + { + {"1", o => o is Int32Type}, + {"\"London\"", o => o is StringType stringType && stringType.SqlType.Length == 15} + }, + db.Orders.Where(o => o.ShippingAddress.City.CompareTo("London") > 1), + db.Orders.Where(o => "London".CompareTo(o.ShippingAddress.City) > 1), + db.Orders.Where(o => string.Compare("London", o.ShippingAddress.City) > 1), + db.Orders.Where(o => string.Compare(o.ShippingAddress.City, "London") > 1) + ); + } + + [Test] + public void EqualEntityTest() + { + var order = new Order(); + AssertResults( + new Dictionary> + { + { + $"value({typeof(Order).FullName})", + o => o is ManyToOneType manyToOne && manyToOne.Name == typeof(Order).FullName + } + }, + db.Orders.Where(o => o == order), + db.Orders.Where(o => order == o) + ); + } + + [Test] + public void DoubleEqualTest() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType}, + {"1", o => o is PersistentEnumType} + }, + db.Users.Where(o => o.Enum1 == EnumStoredAsString.Large && o.Enum2 == EnumStoredAsInt32.High), + db.Users.Where(o => EnumStoredAsInt32.High == o.Enum2 && EnumStoredAsString.Large == o.Enum1) + ); + } + + [Test] + public void NotEqualTest() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => o.Enum1 != EnumStoredAsString.Large), + db.Users.Where(o => EnumStoredAsString.Large != o.Enum1) + ); + } + + [Test] + public void DoubleNotEqualTest() + { + AssertResults( + new Dictionary> + { + {"3", o => o is EnumStoredAsStringType}, + {"1", o => o is PersistentEnumType} + }, + db.Users.Where(o => o.Enum1 != EnumStoredAsString.Large || o.NullableEnum2 != EnumStoredAsInt32.High), + db.Users.Where(o => EnumStoredAsInt32.High != o.NullableEnum2 || o.Enum1 != EnumStoredAsString.Large) + ); + } + + [Test] + public void CoalesceTest() + { + AssertResults( + new Dictionary> + { + {"2", o => o is EnumStoredAsStringType}, + {"Large", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => (o.NullableEnum1 ?? EnumStoredAsString.Large) == EnumStoredAsString.Medium), + db.Users.Where(o => EnumStoredAsString.Medium == (o.NullableEnum1 ?? EnumStoredAsString.Large)) + ); + } + + [Test] + public void DoubleCoalesceTest() + { + AssertResults( + new Dictionary> + { + {"2", o => o is EnumStoredAsStringType}, + {"Large", o => o is EnumStoredAsStringType}, + }, + db.Users.Where(o => ((o.NullableEnum1 ?? (EnumStoredAsString?) EnumStoredAsString.Large) ?? o.Enum1) == EnumStoredAsString.Medium), + db.Users.Where(o => EnumStoredAsString.Medium == ((o.NullableEnum1 ?? (EnumStoredAsString?) EnumStoredAsString.Large) ?? o.Enum1)) + ); + } + + [Test] + public void ConditionalTest() + { + AssertResults( + new Dictionary> + { + {"2", o => o is EnumStoredAsStringType}, + {"Unspecified", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => (o.NullableEnum2.HasValue ? o.Enum1 : EnumStoredAsString.Unspecified) == EnumStoredAsString.Medium), + db.Users.Where(o => EnumStoredAsString.Medium == (o.NullableEnum2.HasValue ? EnumStoredAsString.Unspecified : o.Enum1)) + ); + } + + [Test] + public void DoubleConditionalTest() + { + AssertResults( + new Dictionary> + { + {"0", o => o is PersistentEnumType}, + {"2", o => o is EnumStoredAsStringType}, + {"Small", o => o is EnumStoredAsStringType}, + {"Unspecified", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => (o.Enum2 != EnumStoredAsInt32.Unspecified + ? (o.NullableEnum2.HasValue ? o.Enum1 : EnumStoredAsString.Unspecified) + : EnumStoredAsString.Small) == EnumStoredAsString.Medium), + db.Users.Where(o => EnumStoredAsString.Medium == (o.Enum2 != EnumStoredAsInt32.Unspecified + ? EnumStoredAsString.Small + : (o.NullableEnum2.HasValue ? EnumStoredAsString.Unspecified : o.Enum1))) + ); + } + + [Test] + public void CoalesceMemberTest() + { + AssertResults( + new Dictionary> + { + {"2", o => o is EnumStoredAsStringType} + }, + db.Users.Where(o => (o.NotMappedUser ?? o).Enum1 == EnumStoredAsString.Medium), + db.Users.Where(o => EnumStoredAsString.Medium == (o ?? o.NotMappedUser).Enum1) + ); + } + + [Test] + public void ConditionalMemberTest() + { + AssertResults( + new Dictionary> + { + {"2", o => o is EnumStoredAsStringType}, + {"\"test\"", o => o is AnsiStringType}, + }, + db.Users.Where(o => (o.Name == "test" ? o.NotMappedUser : o).Enum1 == EnumStoredAsString.Medium), + db.Users.Where(o => EnumStoredAsString.Medium == (o.Name == "test" ? o : o.NotMappedUser).Enum1) + ); + } + + [Test] + public void DynamicMemberTest() + { + AssertResults( + new Dictionary> + { + {"\"test\"", o => o is AnsiStringType}, + }, + db.DynamicUsers.Where("Properties.Name == @0", "test"), + db.DynamicUsers.Where("@0 == Properties.Name", "test") + ); + } + + [Test] + public void DynamicDictionaryMemberTest() + { + AssertResults( + new Dictionary> + { + {"\"test\"", o => o is AnsiStringType}, + }, +#pragma warning disable CS0252 + db.DynamicUsers.Where(o => o.Settings["Property1"] == "test"), +#pragma warning restore CS0252 +#pragma warning disable CS0253 + db.DynamicUsers.Where(o => "test" == o.Settings["Property1"]) +#pragma warning restore CS0253 + ); + } + + [Test] + public void AssignMemberTest() + { + AssertResult( + new Dictionary> + { + {"0", o => o is Int32Type}, + {"\"val\"", o => o is AnsiStringType}, + {"Large", o => o is EnumStoredAsStringType}, + }, + QueryMode.Insert, + db.Users.Where(o => o.InvalidLoginAttempts > 0), + o => new User {Name = "val", Enum1 = EnumStoredAsString.Large} + ); + } + + [Test] + public void AssignComponentMemberTest() + { + AssertResult( + new Dictionary> + { + {"0", o => o is Int32Type}, + {"\"prop1\"", o => o is AnsiStringType} + }, + QueryMode.Insert, + db.Users.Where(o => o.InvalidLoginAttempts > 0), + o => new User {Component = new UserComponent {Property1 = "prop1"}} + ); + } + + [Test] + public void AssignNestedComponentMemberTest() + { + AssertResult( + new Dictionary> + { + {"0", o => o is Int32Type}, + {"\"other\"", o => o is AnsiStringType} + }, + QueryMode.Insert, + db.Users.Where(o => o.InvalidLoginAttempts > 0), + o => new User + { + Component = new UserComponent {OtherComponent = new UserComponent2 {OtherProperty1 = "other"}} + } + ); + } + + [Test] + public void AnonymousAssignMemberTest() + { + AssertResult( + new Dictionary> + { + {"0", o => o is Int32Type}, + {"\"val\"", o => o is AnsiStringType}, + {"Large", o => o is EnumStoredAsStringType}, + }, + QueryMode.Insert, + db.Users.Where(o => o.InvalidLoginAttempts > 0), + o => new {Name = "val", Enum1 = EnumStoredAsString.Large} + ); + } + + [Test] + public void AnonymousAssignComponentMemberTest() + { + AssertResult( + new Dictionary> + { + {"0", o => o is Int32Type}, + {"\"prop1\"", o => o is AnsiStringType} + }, + QueryMode.Insert, + db.Users.Where(o => o.InvalidLoginAttempts > 0), + o => new {Component = new {Property1 = "prop1"}} + ); + } + + [Test] + public void AnonymousAssignNestedComponentMemberTest() + { + AssertResult( + new Dictionary> + { + {"0", o => o is Int32Type}, + {"\"other\"", o => o is AnsiStringType} + }, + QueryMode.Insert, + db.Users.Where(o => o.InvalidLoginAttempts > 0), + o => new {Component = new {OtherComponent = new {OtherProperty1 = "other"}}} + ); + } + + private void AssertResults( + Dictionary> expectedResults, + params IQueryable[] queries) + { + foreach (var query in queries) + { + AssertResult(expectedResults, query); + } + } + + private void AssertResult( + Dictionary> expectedResults, + IQueryable query) + { + AssertResult(expectedResults, QueryMode.Select, query.Expression, query.Expression.Type); + } + + private void AssertResult( + Dictionary> expectedResults, + QueryMode queryMode, + IQueryable query, + Expression> expression) + { + var dmlExpression = expression != null + ? DmlExpressionRewriter.PrepareExpression(query.Expression, expression) + : query.Expression; + + AssertResult(expectedResults, queryMode, dmlExpression, typeof(T)); + } + + private void AssertResult( + Dictionary> expectedResults, + QueryMode queryMode, + IQueryable query, + Expression> expression) + { + var dmlExpression = expression != null + ? DmlExpressionRewriter.PrepareExpressionFromAnonymous(query.Expression, expression) + : query.Expression; + + AssertResult(expectedResults, queryMode, dmlExpression, typeof(T)); + } + + private void AssertResult( + Dictionary> expectedResults, + QueryMode queryMode, + Expression expression, + System.Type targetType) + { + var result = NhRelinqQueryParser.PreTransform(expression, new PreTransformationParameters(queryMode, Sfi)); + var parameters = ExpressionParameterVisitor.Visit(result); + expression = result.Expression; + var queryModel = NhRelinqQueryParser.Parse(expression); + ParameterTypeLocator.SetParameterTypes(parameters, queryModel, targetType, Sfi); + Assert.That(parameters.Count, Is.EqualTo(expectedResults.Count), "Incorrect number of parameters"); + foreach (var pair in parameters) + { + var origCulture = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + var expressionText = pair.Key.ToString(); + Assert.That(expectedResults.ContainsKey(expressionText), Is.True, $"{expressionText} constant is not expected"); + Assert.That(expectedResults[expressionText](pair.Value.Type), Is.True, $"Invalid type, actual type: {pair.Value?.Type?.Name ?? "null"}"); + } + finally + { + CultureInfo.CurrentCulture = origCulture; + } + } + } + } +} diff --git a/src/NHibernate.Test/Linq/PreEvaluationTests.cs b/src/NHibernate.Test/Linq/PreEvaluationTests.cs new file mode 100644 index 00000000000..4e12c4b9d82 --- /dev/null +++ b/src/NHibernate.Test/Linq/PreEvaluationTests.cs @@ -0,0 +1,524 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.SqlTypes; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Test.Linq +{ + [TestFixture(false, false)] + [TestFixture(true, false)] + [TestFixture(false, true)] + public class PreEvaluationTests : LinqTestCase + { + private readonly bool LegacyPreEvaluation; + private readonly bool FallbackOnPreEvaluation; + + public PreEvaluationTests(bool legacy, bool fallback) + { + LegacyPreEvaluation = legacy; + FallbackOnPreEvaluation = fallback; + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + configuration.SetProperty(Environment.FormatSql, "false"); + configuration.SetProperty(Environment.LinqToHqlLegacyPreEvaluation, LegacyPreEvaluation.ToString()); + configuration.SetProperty(Environment.LinqToHqlFallbackOnPreEvaluation, FallbackOnPreEvaluation.ToString()); + } + + [Test] + public void CanQueryByDateTimeNowUsingNotEqual() + { + var isSupported = IsFunctionSupported("current_timestamp"); + RunTest( + isSupported, + spy => + { + var x = db.Orders.Count(o => o.OrderDate.Value != DateTime.Now); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("current_timestamp", spy); + }); + } + + [Test] + public void CanQueryByDateTimeNow() + { + var isSupported = IsFunctionSupported("current_timestamp"); + RunTest( + isSupported, + spy => + { + var x = db.Orders.Count(o => o.OrderDate.Value < DateTime.Now); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("current_timestamp", spy); + }); + } + + [Test] + public void CanSelectDateTimeNow() + { + var isSupported = IsFunctionSupported("current_timestamp"); + RunTest( + isSupported, + spy => + { + var x = + db + .Orders.Select(o => new { id = o.OrderId, d = DateTime.Now }) + .OrderBy(o => o.id).Take(1).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + Assert.That(x[0].d.Kind, Is.EqualTo(DateTimeKind.Local)); + AssertFunctionInSql("current_timestamp", spy); + }); + } + + [Test] + public void CanQueryByDateTimeUtcNow() + { + var isSupported = IsFunctionSupported("current_utctimestamp"); + RunTest( + isSupported, + spy => + { + var x = db.Orders.Count(o => o.OrderDate.Value < DateTime.UtcNow); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("current_utctimestamp", spy); + }); + } + + [Test] + public void CanSelectDateTimeUtcNow() + { + var isSupported = IsFunctionSupported("current_utctimestamp"); + RunTest( + isSupported, + spy => + { + var x = + db + .Orders.Select(o => new { id = o.OrderId, d = DateTime.UtcNow }) + .OrderBy(o => o.id).Take(1).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + Assert.That(x[0].d.Kind, Is.EqualTo(DateTimeKind.Utc)); + AssertFunctionInSql("current_utctimestamp", spy); + }); + } + + [Test] + public void CanQueryByDateTimeToday() + { + var isSupported = IsFunctionSupported("current_date"); + RunTest( + isSupported, + spy => + { + var x = db.Orders.Count(o => o.OrderDate.Value < DateTime.Today); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("current_date", spy); + }); + } + + [Test] + public void CanSelectDateTimeToday() + { + var isSupported = IsFunctionSupported("current_date"); + RunTest( + isSupported, + spy => + { + var x = + db + .Orders.Select(o => new { id = o.OrderId, d = DateTime.Today }) + .OrderBy(o => o.id).Take(1).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + Assert.That(x[0].d.Kind, Is.EqualTo(DateTimeKind.Local)); + AssertFunctionInSql("current_date", spy); + }); + } + + [Test] + public void CanQueryByDateTimeOffsetTimeNow() + { + if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet)) + Assert.Ignore("Dialect does not support DateTimeOffSet"); + + var isSupported = IsFunctionSupported("current_timestamp_offset"); + RunTest( + isSupported, + spy => + { + var testDate = DateTimeOffset.Now.AddDays(-1); + var x = db.Orders.Count(o => testDate < DateTimeOffset.Now); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("current_timestamp_offset", spy); + }); + } + + [Test] + public void CanSelectDateTimeOffsetNow() + { + if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet)) + Assert.Ignore("Dialect does not support DateTimeOffSet"); + + var isSupported = IsFunctionSupported("current_timestamp_offset"); + RunTest( + isSupported, + spy => + { + var x = + db + .Orders.Select(o => new { id = o.OrderId, d = DateTimeOffset.Now }) + .OrderBy(o => o.id).Take(1).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + Assert.That(x[0].d.Offset, Is.EqualTo(DateTimeOffset.Now.Offset)); + AssertFunctionInSql("current_timestamp_offset", spy); + }); + } + + [Test] + public void CanQueryByDateTimeOffsetUtcNow() + { + if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet)) + Assert.Ignore("Dialect does not support DateTimeOffSet"); + + var isSupported = IsFunctionSupported("current_utctimestamp_offset"); + RunTest( + isSupported, + spy => + { + var testDate = DateTimeOffset.UtcNow.AddDays(-1); + var x = db.Orders.Count(o => testDate < DateTimeOffset.UtcNow); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("current_utctimestamp_offset", spy); + }); + } + + [Test] + public void CanSelectDateTimeOffsetUtcNow() + { + if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet)) + Assert.Ignore("Dialect does not support DateTimeOffSet"); + + var isSupported = IsFunctionSupported("current_utctimestamp_offset"); + RunTest( + isSupported, + spy => + { + var x = + db + .Orders.Select(o => new { id = o.OrderId, d = DateTimeOffset.UtcNow }) + .OrderBy(o => o.id).Take(1).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + Assert.That(x[0].d.Offset, Is.EqualTo(TimeSpan.Zero)); + AssertFunctionInSql("current_utctimestamp_offset", spy); + }); + } + + private void RunTest(bool isSupported, Action test) + { + using (var spy = new SqlLogSpy()) + { + try + { + test(spy); + } + catch (QueryException) + { + if (!isSupported && !FallbackOnPreEvaluation) + // Expected failure + return; + throw; + } + } + + if (!isSupported && !FallbackOnPreEvaluation) + Assert.Fail("The test should have thrown a QueryException, but has not thrown anything"); + } + + [Test] + public void CanQueryByNewGuid() + { + if (!TestDialect.SupportsSqlType(SqlTypeFactory.Guid)) + Assert.Ignore("Guid are not supported by the target database"); + + var isSupported = IsFunctionSupported("new_uuid"); + RunTest( + isSupported, + spy => + { + var guid = Guid.NewGuid(); + var x = db.Orders.Count(o => guid != Guid.NewGuid()); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("new_uuid", spy); + }); + } + + [Test] + public void CanSelectNewGuid() + { + if (!TestDialect.SupportsSqlType(SqlTypeFactory.Guid)) + Assert.Ignore("Guid are not supported by the target database"); + + var isSupported = IsFunctionSupported("new_uuid"); + RunTest( + isSupported, + spy => + { + var x = + db + .Orders.Select(o => new { id = o.OrderId, g = Guid.NewGuid() }) + .OrderBy(o => o.id).Take(1).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + AssertFunctionInSql("new_uuid", spy); + }); + } + + [Test] + public void CanQueryByRandomDouble() + { + var isSupported = IsFunctionSupported("random"); + RunTest( + isSupported, + spy => + { + var random = new Random(); + var x = db.Orders.Count(o => o.OrderId > random.NextDouble()); + + Assert.That(x, Is.GreaterThan(0)); + AssertFunctionInSql("random", spy); + }); + } + + [Test] + public void CanSelectRandomDouble() + { + var isSupported = IsFunctionSupported("random"); + RunTest( + isSupported, + spy => + { + var random = new Random(); + var x = + db + .Orders.Select(o => new { id = o.OrderId, r = random.NextDouble() }) + .OrderBy(o => o.id).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + var randomValues = x.Select(o => o.r).Distinct().ToArray(); + Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(1)); + + if (!LegacyPreEvaluation && IsFunctionSupported("random")) + { + // Naïve randomness check + Assert.That( + randomValues, + Has.Length.GreaterThan(x.Count / 2), + "Generated values do not seem very random"); + } + + AssertFunctionInSql("random", spy); + }); + } + + [Test] + public void CanQueryByRandomInt() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + var idMin = db.Orders.Min(o => o.OrderId); + RunTest( + isSupported, + spy => + { + var random = new Random(); + // Dodge a Firebird driver limitation by putting the constants before the order id. + // This driver cast parameters to their types in some cases for avoiding Firebird complaining of not + // knowing the type of the condition. For some reasons the driver considers the casting should not be + // done next to the conditional operator. Having the cast only on one side is enough for avoiding + // Firebird complain, so moving the constants on the left side have been put before the order id, in + // order for these constants to be casted by the driver. + var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next()); + + Assert.That(x, Is.GreaterThan(0)); + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public void CanSelectRandomInt() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + RunTest( + isSupported, + spy => + { + var random = new Random(); + var x = + db + .Orders.Select(o => new { id = o.OrderId, r = random.Next() }) + .OrderBy(o => o.id).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + var randomValues = x.Select(o => o.r).Distinct().ToArray(); + Assert.That( + randomValues, + Has.All.GreaterThanOrEqualTo(0).And.LessThan(int.MaxValue).And.TypeOf()); + + if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor")) + { + // Naïve randomness check + Assert.That( + randomValues, + Has.Length.GreaterThan(x.Count / 2), + "Generated values do not seem very random"); + } + + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public void CanQueryByRandomIntWithMax() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + var idMin = db.Orders.Min(o => o.OrderId); + RunTest( + isSupported, + spy => + { + var random = new Random(); + // Dodge a Firebird driver limitation by putting the constants before the order id. + // This driver cast parameters to their types in some cases for avoiding Firebird complaining of not + // knowing the type of the condition. For some reasons the driver considers the casting should not be + // done next to the conditional operator. Having the cast only on one side is enough for avoiding + // Firebird complain, so moving the constants on the left side have been put before the order id, in + // order for these constants to be casted by the driver. + var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10)); + + Assert.That(x, Is.GreaterThan(0).And.LessThan(11)); + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public void CanSelectRandomIntWithMax() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + RunTest( + isSupported, + spy => + { + var random = new Random(); + var x = + db + .Orders.Select(o => new { id = o.OrderId, r = random.Next(10) }) + .OrderBy(o => o.id).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + var randomValues = x.Select(o => o.r).Distinct().ToArray(); + Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(10).And.TypeOf()); + + if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor")) + { + // Naïve randomness check + Assert.That( + randomValues, + Has.Length.GreaterThan(Math.Min(10, x.Count) / 2), + "Generated values do not seem very random"); + } + + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public void CanQueryByRandomIntWithMinMax() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + var idMin = db.Orders.Min(o => o.OrderId); + RunTest( + isSupported, + spy => + { + var random = new Random(); + // Dodge a Firebird driver limitation by putting the constants before the order id. + // This driver cast parameters to their types in some cases for avoiding Firebird complaining of not + // knowing the type of the condition. For some reasons the driver considers the casting should not be + // done next to the conditional operator. Having the cast only on one side is enough for avoiding + // Firebird complain, so moving the constants on the left side have been put before the order id, in + // order for these constants to be casted by the driver. + var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10)); + + Assert.That(x, Is.GreaterThan(0).And.LessThan(10)); + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + [Test] + public void CanSelectRandomIntWithMinMax() + { + var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor"); + RunTest( + isSupported, + spy => + { + var random = new Random(); + var x = + db + .Orders.Select(o => new { id = o.OrderId, r = random.Next(1, 11) }) + .OrderBy(o => o.id).ToList(); + + Assert.That(x, Has.Count.GreaterThan(0)); + var randomValues = x.Select(o => o.r).Distinct().ToArray(); + Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(1).And.LessThan(11).And.TypeOf()); + + if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor")) + { + // Naïve randomness check + Assert.That( + randomValues, + Has.Length.GreaterThan(Math.Min(10, x.Count) / 2), + "Generated values do not seem very random"); + } + + // Next requires support of both floor and rand + AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy); + }); + } + + private void AssertFunctionInSql(string functionName, SqlLogSpy spy) + { + if (!IsFunctionSupported(functionName)) + Assert.Inconclusive($"{functionName} is not supported by the dialect"); + + var function = Dialect.Functions[functionName].Render(new List(), Sfi).ToString(); + + if (LegacyPreEvaluation) + Assert.That(spy.GetWholeLog(), Does.Not.Contain(function)); + else + Assert.That(spy.GetWholeLog(), Does.Contain(function)); + } + } +} diff --git a/src/NHibernate.Test/Linq/ProjectionsTests.cs b/src/NHibernate.Test/Linq/ProjectionsTests.cs index 9966e2ceb76..2072964783a 100644 --- a/src/NHibernate.Test/Linq/ProjectionsTests.cs +++ b/src/NHibernate.Test/Linq/ProjectionsTests.cs @@ -97,7 +97,6 @@ orderby user.Name Assert.AreEqual("nhibernate nhibernate", query[1].DoubleName); Assert.AreEqual("rahien rahien", query[2].DoubleName); - Assert.AreEqual(new DateTime(2010, 06, 17), query[0].RegisteredAt); Assert.AreEqual(new DateTime(2000, 1, 1), query[1].RegisteredAt); Assert.AreEqual(new DateTime(1998, 12, 31), query[2].RegisteredAt); @@ -115,7 +114,6 @@ orderby user.Id Assert.AreEqual("rahien", query[1].Key); Assert.AreEqual("nhibernate", query[2].Key); - Assert.AreEqual(new DateTime(2010, 06, 17), query[0].Value); Assert.AreEqual(new DateTime(1998, 12, 31), query[1].Value); Assert.AreEqual(new DateTime(2000, 1, 1), query[2].Value); @@ -132,7 +130,6 @@ orderby user.Id Assert.AreEqual("rahien", query[1].Name); Assert.AreEqual("nhibernate", query[2].Name); - Assert.AreEqual(new DateTime(2010, 06, 17), query[0].RegisteredAt); Assert.AreEqual(new DateTime(1998, 12, 31), query[1].RegisteredAt); Assert.AreEqual(new DateTime(2000, 1, 1), query[2].RegisteredAt); @@ -511,7 +508,6 @@ private string FormatName(string name, DateTime? lastLoginDate) return string.Format("User {0} logged in at {1}", name, lastLoginDate); } - /// /// This mimic classes in System.Data.Services.Internal. /// diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index 93f41608fee..4475971f09e 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -2,6 +2,7 @@ using NHibernate.Cfg; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Linq; +using NHibernate.Transform; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -325,7 +326,6 @@ public void FetchIsCachable() Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); - } [Test] @@ -396,13 +396,102 @@ public void FutureFetchIsCachable() Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Unexpected cache hit count"); } + + [Explicit("Not working. dto.Customer retrieved from cache as uninitialized proxy")] + [Test] + public void ProjectedEntitiesAreCachable() + { + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + var dto = session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new { x.Customer, Order = x }) + .FirstOrDefault(); + + Assert.That(dto, Is.Not.Null, "dto should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + session.Clear(); + + dto = session.Query() + .WithOptions(o => o.SetCacheable(true)) + .Where(x => x.OrderId == 10248) + .Select(x => new { x.Customer, Order = x }) + .FirstOrDefault(); + + Assert.That(dto, Is.Not.Null, "dto from cache should not be null"); + Assert.That(dto.Order, Is.Not.Null, "dto.Order from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Order), Is.True, "dto.Order from cache should be initialized"); + Assert.That(dto.Customer, Is.Not.Null, "dto.Customer from cache should not be null"); + Assert.That(NHibernateUtil.IsInitialized(dto.Customer), Is.True, "dto.Customer from cache should be initialized"); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + [Test] + public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() + { + if (!TestDialect.SupportsDuplicatedColumnAliases) + Assert.Ignore("Ignored due to GH-2092"); + + Sfi.Statistics.Clear(); + Sfi.EvictQueries(); + + // the combination of query and transformer doesn't make sense. + // It's simply used as example of returned data being transformed before caching leading to mismatch between + // Loader.ResultTypes collection and provided tuple + var order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResult(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + + session.Clear(); + Sfi.Statistics.Clear(); + + order = session.CreateQuery("select o.Employee.FirstName, o from Order o join fetch o.Customer where o.OrderId = :id") + .SetInt32("id", 10248) + .SetCacheable(true) + .SetResultTransformer(Transformers.RootEntity) + .UniqueResult(); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + Assert.That(order, Is.Not.Null); + Assert.That(order.Customer, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True); + } private static void AssertFetchedOrder(Order order) { + Assert.That(NHibernateUtil.IsInitialized(order), "Expected the order to be initialized"); + Assert.That(order.Customer, Is.Not.Null, "Expected the fetched Customer to be not null"); Assert.That(NHibernateUtil.IsInitialized(order.Customer), Is.True, "Expected the fetched Customer to be initialized"); Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); Assert.That(order.OrderLines, Has.Count.EqualTo(3), "Expected the fetched OrderLines to have 3 items"); var orderLine = order.OrderLines.First(); + Assert.That(orderLine.Product, Is.Not.Null, "Expected the fetched Product to be not null"); Assert.That(NHibernateUtil.IsInitialized(orderLine.Product), Is.True, "Expected the fetched Product to be initialized"); Assert.That(NHibernateUtil.IsInitialized(orderLine.Product.OrderLines), Is.True, "Expected the fetched OrderLines to be initialized"); } diff --git a/src/NHibernate.Test/Linq/QueryFlushModeTests.cs b/src/NHibernate.Test/Linq/QueryFlushModeTests.cs new file mode 100644 index 00000000000..796a76bf0e0 --- /dev/null +++ b/src/NHibernate.Test/Linq/QueryFlushModeTests.cs @@ -0,0 +1,166 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.Linq +{ + [TestFixture] + public class QueryFlushModeTests : LinqTestCase + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.GenerateStatistics, "true"); + base.Configure(configuration); + } + + [Test] + public void CanSetFlushModeOnQueries( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] + FlushMode flushMode) + { + Sfi.Statistics.Clear(); + + using (var t = session.BeginTransaction()) + { + var customer = db.Customers.First(); + customer.CompanyName = "Blah"; + + var unused = + db.Customers + .Where(c => c.CompanyName == "Bon app'") + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToList(); + + var expectedFlushCount = 0; + switch (flushMode) + { + case FlushMode.Always: + case FlushMode.Auto: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on same entity query"); + + customer.CompanyName = "Other blah"; + + var dummy = + db.Orders + .Where(o => o.OrderId > 10) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToList(); + + switch (flushMode) + { + case FlushMode.Always: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on other entity query"); + + // Tests here should not alter data, LinqTestCase derives from ReadonlyTestCase + t.Rollback(); + } + } + + [Test] + public void CanSetCommentOnPagingQuery( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] + FlushMode flushMode) + { + Sfi.Statistics.Clear(); + + using (var t = session.BeginTransaction()) + { + var customer = db.Customers.First(); + customer.CompanyName = "Blah"; + + var unused = + db.Customers + .Skip(1).Take(1) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToList(); + + var expectedFlushCount = 0; + switch (flushMode) + { + case FlushMode.Always: + case FlushMode.Auto: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on same entity query"); + + customer.CompanyName = "Other blah"; + + var dummy = + db.Orders + .Skip(1).Take(1) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToList(); + + switch (flushMode) + { + case FlushMode.Always: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on other entity query"); + + // Tests here should not alter data, LinqTestCase derives from ReadonlyTestCase + t.Rollback(); + } + } + + [Test] + public void CanSetCommentBeforeSkipOnOrderedPageQuery( + [Values(FlushMode.Always, FlushMode.Auto, FlushMode.Commit, FlushMode.Manual)] + FlushMode flushMode) + { + Sfi.Statistics.Clear(); + + using (var t = session.BeginTransaction()) + { + var customer = db.Customers.First(); + customer.CompanyName = "Blah"; + + var unused = + db.Customers + .OrderBy(c => c.CompanyName) + .Skip(5).Take(5) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToList(); + + var expectedFlushCount = 0; + switch (flushMode) + { + case FlushMode.Always: + case FlushMode.Auto: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on same entity query"); + + customer.CompanyName = "Other blah"; + + var dummy = + db.Orders + .OrderBy(o => o.OrderId) + .Skip(5).Take(5) + .WithOptions(o => o.SetFlushMode(flushMode)) + .ToList(); + + switch (flushMode) + { + case FlushMode.Always: + expectedFlushCount++; + break; + } + Assert.That(Sfi.Statistics.FlushCount, Is.EqualTo(expectedFlushCount), "Unexpected flush count on other entity query"); + + // Tests here should not alter data, LinqTestCase derives from ReadonlyTestCase + t.Rollback(); + } + } + } +} diff --git a/src/NHibernate.Test/Linq/QueryLock.cs b/src/NHibernate.Test/Linq/QueryLock.cs index ab6396ebc43..16964d9db30 100644 --- a/src/NHibernate.Test/Linq/QueryLock.cs +++ b/src/NHibernate.Test/Linq/QueryLock.cs @@ -9,7 +9,6 @@ using NHibernate.Linq; using NUnit.Framework; - namespace NHibernate.Test.Linq { [TestFixture] diff --git a/src/NHibernate.Test/Linq/QueryTimeoutTests.cs b/src/NHibernate.Test/Linq/QueryTimeoutTests.cs index ae8a2b7fbf2..bf6ba97bec5 100644 --- a/src/NHibernate.Test/Linq/QueryTimeoutTests.cs +++ b/src/NHibernate.Test/Linq/QueryTimeoutTests.cs @@ -27,7 +27,6 @@ protected override void Configure(Configuration configuration) typeof(TimeoutCatchingNonBatchingBatcherFactory).AssemblyQualifiedName); } - [Test] public void CanSetTimeoutOnLinqQueries() { @@ -40,7 +39,6 @@ public void CanSetTimeoutOnLinqQueries() Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - [Test] public void CanSetTimeoutOnLinqPagingQuery() { @@ -54,7 +52,6 @@ public void CanSetTimeoutOnLinqPagingQuery() Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - [Test] public void CanSetTimeoutBeforeSkipOnLinqOrderedPageQuery() { @@ -68,7 +65,6 @@ orderby e.CompanyName Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - [Test] public void CanSetTimeoutOnLinqGroupPageQuery() { @@ -88,13 +84,11 @@ into g Assert.That(TimeoutCatchingNonBatchingBatcher.LastCommandTimeout, Is.EqualTo(17)); } - public partial class TimeoutCatchingNonBatchingBatcher : NonBatchingBatcher { // Is there an easier way to inspect the DbCommand instead of // creating a custom batcher? - public static int LastCommandTimeout; public TimeoutCatchingNonBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor) @@ -109,7 +103,6 @@ public override DbDataReader ExecuteReader(DbCommand cmd) } } - public partial class TimeoutCatchingNonBatchingBatcherFactory : IBatcherFactory { public IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor) diff --git a/src/NHibernate.Test/Linq/SelectionTests.cs b/src/NHibernate.Test/Linq/SelectionTests.cs index 3873558badf..6114e38c44d 100644 --- a/src/NHibernate.Test/Linq/SelectionTests.cs +++ b/src/NHibernate.Test/Linq/SelectionTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using NHibernate.DomainModel.NHSpecific; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Type; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -326,6 +328,14 @@ public void CanSelectWrappedType() Assert.IsTrue(query.ToArray().Length > 0); } + [Test] + public void CanSelectNotMappedEntityProperty() + { + var list = db.Animals.Where(o => o.Mother != null).Select(o => o.FatherOrMother.SerialNumber).ToList(); + + Assert.That(list, Has.Count.GreaterThan(0)); + } + [Test] public void CanProjectWithCast() { @@ -346,6 +356,10 @@ public void CanProjectWithCast() var names5 = db.Users.Select(p => new { p1 = (p as IUser).Name }).ToList(); Assert.AreEqual(3, names5.Count); + + var names6 = db.Users.Select(p => new { p1 = (long) p.Id }).ToList(); + Assert.AreEqual(3, names6.Count); + // ReSharper restore RedundantCast } @@ -478,6 +492,39 @@ public void CanSelectConditionalEntityValueWithEntityComparison() Assert.That(fatherInsteadOfChild, Has.Exactly(2).EqualTo("5678")); } + [Test] + public void CanExecuteMethodWithNullObjectAndSubselect() + { + var list1 = db.Animals.Select( + a => new + { + NullableId = (int?) a.Father.Father.Id, + }) + .ToList(); + Assert.That(list1, Has.Count.GreaterThan(0)); + Assert.That(list1[0].NullableId, Is.Null); + + var list2 = db.Animals.Select( + a => new + { + Descriptions = a.Children.Select(z => z.Description) + }) + .ToList(); + Assert.That(list2, Has.Count.GreaterThan(0)); + Assert.That(list2[0].Descriptions, Is.Not.Null); + + var list3 = db.Animals.Select( + a => new + { + NullableId = (int?) a.Father.Father.Id, + Descriptions = a.Children.Select(z => z.Description) + }) + .ToList(); + Assert.That(list3, Has.Count.GreaterThan(0)); + Assert.That(list3[0].NullableId, Is.Null); + Assert.That(list3[0].Descriptions, Is.Not.Null); + } + [Test] public void CanSelectConditionalEntityValueWithEntityComparisonRepeat() { @@ -492,6 +539,23 @@ public void CanSelectConditionalObject() Assert.That(fatherIsKnown, Has.Exactly(1).With.Property("FatherIsKnown").True); } + [Test] + public void CanCastToDerivedType() + { + var dogs = db.Animals + .Where(a => ((Dog) a).Pregnant) + .Select(a => new {a.SerialNumber}) + .ToList(); + Assert.That(dogs, Has.Exactly(1).With.Property("SerialNumber").Not.Null); + } + + [Test] + public void CanCastToCustomRegisteredType() + { + TypeFactory.RegisterType(typeof(NullableInt32), new NullableInt32Type(), Enumerable.Empty()); + Assert.That(db.Users.Where(o => (NullableInt32) o.Id == 1).ToList(), Has.Count.EqualTo(1)); + } + public class Wrapper { public T item; diff --git a/src/NHibernate.Test/Linq/TryGetMappedTests.cs b/src/NHibernate.Test/Linq/TryGetMappedTests.cs new file mode 100644 index 00000000000..880ce2e3e69 --- /dev/null +++ b/src/NHibernate.Test/Linq/TryGetMappedTests.cs @@ -0,0 +1,817 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.DomainModel; +using NHibernate.DomainModel.NHSpecific; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Engine; +using NHibernate.Engine.Query; +using NHibernate.Linq; +using NHibernate.Linq.Visitors; +using NHibernate.Persister.Entity; +using NHibernate.Type; +using NHibernate.Util; +using NUnit.Framework; +using IQueryable = System.Linq.IQueryable; + +namespace NHibernate.Test.Linq +{ + /// + /// Tests form ExpressionsHelper.TryGetMappedType and ExpressionsHelper.TryGetMappedNullability + /// + public class TryGetMappedTests : LinqTestCase + { + private static readonly TryGetMappedType _tryGetMappedType; + private static readonly TryGetMappedNullability _tryGetMappedNullability; + + private delegate bool TryGetMappedType( + ISessionFactoryImplementor sessionFactory, + Expression expression, + out IType mappedType, + out IEntityPersister entityPersister, + out IAbstractComponentType component, + out string memberPath); + + private delegate bool TryGetMappedNullability( + ISessionFactoryImplementor sessionFactory, + Expression expression, + out bool nullability); + + static TryGetMappedTests() + { + var method = typeof(ExpressionsHelper).GetMethod( + nameof(TryGetMappedType), + BindingFlags.NonPublic | BindingFlags.Static); + var sessionFactoryParam = Expression.Parameter(typeof(ISessionFactoryImplementor), "sessionFactory"); + var expressionParam = Expression.Parameter(typeof(Expression), "expression"); + var mappedTypeParam = Expression.Parameter(typeof(IType).MakeByRefType(), "mappedType"); + var entityPersisterParam = Expression.Parameter(typeof(IEntityPersister).MakeByRefType(), "entityPersister"); + var componentParam = Expression.Parameter(typeof(IAbstractComponentType).MakeByRefType(), "component"); + var memberPathParam = Expression.Parameter(typeof(string).MakeByRefType(), "memberPath"); + var methodCall = Expression.Call( + method, + sessionFactoryParam, + expressionParam, + mappedTypeParam, + entityPersisterParam, + componentParam, + memberPathParam); + _tryGetMappedType = Expression.Lambda( + methodCall, + sessionFactoryParam, + expressionParam, + mappedTypeParam, + entityPersisterParam, + componentParam, + memberPathParam).Compile(); + + method = typeof(ExpressionsHelper).GetMethod( + nameof(TryGetMappedNullability), + BindingFlags.NonPublic | BindingFlags.Static); + var nullabilityParam = Expression.Parameter(typeof(bool).MakeByRefType(), "nullability"); + methodCall = Expression.Call( + method, + sessionFactoryParam, + expressionParam, + nullabilityParam); + _tryGetMappedNullability = Expression.Lambda( + methodCall, + sessionFactoryParam, + expressionParam, + nullabilityParam).Compile(); + } + + protected override string[] Mappings + { + get + { + return + new[] + { + "ABC.hbm.xml", + "Baz.hbm.xml", + "FooBar.hbm.xml", + "Glarch.hbm.xml", + "Fee.hbm.xml", + "Qux.hbm.xml", + "Fum.hbm.xml", + "Holder.hbm.xml", + "One.hbm.xml", + "Many.hbm.xml" + }.Concat(base.Mappings).ToArray(); + } + } + + [Test] + public void SelfTest() + { + var query = db.OrderLines.Select(o => o); + AssertSupportedAndResultNotNullable( + query, + typeof(OrderLine).FullName, + null, + o => o is EntityType entityType && entityType.ReturnedClass == typeof(OrderLine)); + } + + [Test] + public void SelfCastNotMappedTest() + { + var query = session.Query().Select(o => (object) o); + AssertSupportedAndResultNotNullable( + query, + false, + typeof(A).FullName, + null, + o => o is SerializableType serializableType && serializableType.ReturnedClass == typeof(object)); + } + + [Test] + public void PropertyTest() + { + var query = db.OrderLines.Select(o => o.Quantity); + AssertSupportedAndResultNotNullable(query, typeof(OrderLine).FullName, "Quantity", o => o is Int32Type); + } + + [Test] + public void NotMappedPropertyTest() + { + var query = db.Users.Select(o => o.NotMapped); + AssertUnsupported(query, typeof(User).FullName, "NotMapped", o => o is null); + } + + [Test] + public void NestedNotMappedPropertyTest() + { + var query = db.Users.Select(o => o.Name.Length); + AssertUnsupported(query, false, null, null, o => o is null); + } + + [Test] + public void PropertyCastTest() + { + var query = db.OrderLines.Select(o => (long) o.Quantity); + AssertSupportedAndResultNotNullable(query, typeof(OrderLine).FullName, "Quantity", o => o is Int64Type); + } + + [Test] + public void PropertyIndexer() + { + var query = db.Products.Select(o => o.Name[0]); + AssertUnsupported(query, null, null, o => o == null); + } + + [Test] + public void EnumInt32Test() + { + var query = db.Users.Select(o => o.Enum2); + AssertSupportedAndResultNotNullable( + query, + typeof(User).FullName, + "Enum2", + o => o.GetType().GetGenericArguments().FirstOrDefault() == typeof(EnumStoredAsInt32)); + } + + [Test] + public void EnumInt32CastTest() + { + var query = db.Users.Select(o => (int) o.Enum2); + AssertSupportedAndResultNotNullable(query, typeof(User).FullName, "Enum2", o => o is Int32Type); + } + + [Test] + public void EnumAsStringTest() + { + var query = db.Users.Select(o => o.Enum1); + AssertSupported(query, typeof(User).FullName, "Enum1", o => o is EnumStoredAsStringType); + } + + [Test] + public void IdentifierTest() + { + var query = db.OrderLines.Select(o => o.Id); + AssertSupportedAndResultNotNullable(query, typeof(OrderLine).FullName, "Id", o => o is Int64Type); + } + + [Test] + public void CompositeIdentifierTest() + { + var query = session.Query().Select(o => o.Id.Date); + AssertSupportedAndResultNotNullable( + query, + typeof(Fum).FullName, + "Id.Date", + o => o is DateTimeType, + o => o?.Name == "component[String,Short,Date]"); + } + + [Test] + public void ComponentTest() + { + var query = db.Customers.Select(o => o.Address); + AssertSupported( + query, + typeof(Customer).FullName, + "Address", + o => o is ComponentType && o.Name == "component[Street,City,Region,PostalCode,Country,PhoneNumber,Fax]"); + } + + [Test] + public void ComponentPropertyTest() + { + var query = db.Customers.Select(o => o.Address.City); + AssertSupported( + query, + typeof(Customer).FullName, + "Address.City", + o => o is StringType, + o => o?.Name == "component[Street,City,Region,PostalCode,Country,PhoneNumber,Fax]"); + } + + [Test] + public void ComponentNotMappedPropertyTest() + { + var query = db.Customers.Select(o => o.Address.NotMapped); + AssertUnsupported( + query, + typeof(Customer).FullName, + "Address.NotMapped", + o => o == null, + o => o?.Name == "component[Street,City,Region,PostalCode,Country,PhoneNumber,Fax]"); + } + + [Test] + public void ComponentNestedNotMappedPropertyTest() + { + var query = db.Customers.Select(o => o.Address.City.Length); + AssertUnsupported(query, false, null, null, o => o == null); + } + + [Test] + public void NestedComponentPropertyTest() + { + var query = db.Users.Select(o => o.Component.OtherComponent.OtherProperty1); + AssertSupported( + query, + typeof(User).FullName, + "Component.OtherComponent.OtherProperty1", + o => o is AnsiStringType, + o => o?.Name == "component[OtherProperty1]"); + } + + [Test] + public void NestedComponentPropertyCastTest() + { + var query = db.Users.Select(o => (object) o.Component.OtherComponent.OtherProperty1); + AssertSupported( + query, + typeof(User).FullName, + "Component.OtherComponent.OtherProperty1", + o => o is SerializableType serializableType && serializableType.ReturnedClass == typeof(object), + o => o?.Name == "component[OtherProperty1]"); + } + + [Test] + public void ManyToOneTest() + { + var query = db.OrderLines.Select(o => o.Order); + AssertSupportedAndResultNotNullable(query, typeof(OrderLine).FullName, "Order", + o => o is ManyToOneType manyToOne && manyToOne.PropertyName == "Order"); + } + + [Test] + public void ManyToOnePropertyTest() + { + var query = db.OrderLines.Select(o => o.Order.Freight); + AssertSupported(query, typeof(Order).FullName, "Freight", o => o is DecimalType); + } + + [Test] + public void ManyToOneNotMappedPropertyTest() + { + var query = db.OrderLines.Select(o => o.Product.NotMapped); + AssertUnsupported(query, typeof(Product).FullName, "NotMapped", o => o == null); + } + + [Test] + public void NotMappedManyToOnePropertyTest() + { + var query = db.Users.Select(o => o.NotMappedRole.Name); + AssertUnsupported(query, false, null, null, o => o is null); + } + + [Test] + public void NestedManyToOneTest() + { + var query = db.OrderLines.Select(o => o.Order.Employee); + AssertSupported(query, false, typeof(Order).FullName, "Employee", + o => o is ManyToOneType manyToOne && manyToOne.PropertyName == "Employee"); + } + + [Test] + public void NestedManyToOnePropertyTest() + { + var query = db.OrderLines.Select(o => o.Order.Employee.BirthDate); + AssertSupported(query, typeof(Employee).FullName, "BirthDate", o => o is DateTimeType); + } + + [Test] + public void OneToManyTest() + { + var query = db.Customers.SelectMany(o => o.Orders); + AssertSupported( + query, + typeof(Customer).FullName, + "Orders", + o => o is CollectionType collectionType && collectionType.Role == $"{typeof(Customer).FullName}.Orders"); + } + + [Test] + public void OneToManyElementIndexerTest() + { + var query = session.Query().Select(o => o.StringList[0]); + AssertSupported(query, false, typeof(Baz).FullName, "StringList", o => o is StringType); + } + + [Test] + public void OneToManyElementIndexerNotMappedPropertyTest() + { + var query = session.Query().Select(o => o.StringList[0].Length); + AssertUnsupported(query, false, null, null, o => o == null); + } + + [Test] + public void OneToManyCustomElementIndexerTest() + { + var query = session.Query().Select(o => o.Customs[0]); + AssertSupported( + query, + false, + typeof(Baz).FullName, + "Customs", + o => o is CompositeCustomType customType && customType.UserType is DoubleStringType); + } + + [Test] + public void OneToManyIndexerCastTest() + { + var query = session.Query().Select(o => (long) o.IntArray[0]); + AssertSupported(query, false, typeof(Baz).FullName, "IntArray", o => o is Int64Type); + } + + [Test] + public void OneToManyIndexerPropertyTest() + { + var query = session.Query().Select(o => o.Fees[0].Count); + AssertSupported(query, false, typeof(Fee).FullName, "Count", o => o is Int32Type); + } + + [Test] + public void OneToManyElementAtTest() + { + var query = session.Query().Select(o => o.StringList.ElementAt(0)); + AssertSupported(query, false, typeof(Baz).FullName, "StringList", o => o is StringType); + } + + [Test] + public void NestedOneToManyManyToOneComponentPropertyTest() + { + var query = session.Query().SelectMany(o => o.Fees).Select(o => o.TheFee.Compon.Name); + AssertSupported( + query, + typeof(Fee).FullName, + "Compon.Name", + o => o is StringType, + o => o?.Name == "component[Name,NullString]"); + } + + [Test] + public void OneToManyCompositeElementPropertyTest() + { + var query = session.Query().Select(o => o.Components[0].Count); + AssertSupported( + query, + false, + null, + "Count", + o => o is Int32Type, + o => o?.Name == "component[Name,Count,Subcomponent]"); + } + + [Test] + public void OneToManyCompositeElementPropertyIndexerTest() + { + var query = session.Query().Select(o => o.Components[0].Name[0]); + AssertUnsupported(query, false, null, null, o => o == null); + } + + [Test] + public void OneToManyCompositeElementNotMappedPropertyTest() + { + var query = session.Query().Select(o => o.Components[0].NotMapped); + AssertUnsupported( + query, + false, + null, + "NotMapped", + o => o == null, + o => o?.Name == "component[Name,Count,Subcomponent]"); + } + + [Test] + public void OneToManyCompositeElementCastPropertyTest() + { + var query = session.Query().Select(o => (long) o.Components[0].Count); + AssertSupported( + query, + false, + null, + "Count", + o => o is Int64Type, + o => o?.Name == "component[Name,Count,Subcomponent]"); + } + + [Test] + public void OneToManyCompositeElementCollectionNotMappedPropertyTest() + { + var query = session.Query().SelectMany(o => o.Components[0].ImportantDates); + AssertUnsupported( + query, + false, + null, + "ImportantDates", + o => o == null, + o => o?.Name == "component[Name,Count,Subcomponent]"); + } + + [Test] + public void NestedOneToManyCompositeElementTest() + { + var query = session.Query().Select(o => o.Components[0].Subcomponent); + AssertSupported( + query, + false, + null, + "Subcomponent", + o => o is IAbstractComponentType componentType && componentType.ReturnedClass == typeof(FooComponent), + o => o?.Name == "component[Name,Count,Subcomponent]"); + } + + [Test] + public void NestedOneToManyCompositeElementPropertyTest() + { + var query = session.Query().Select(o => o.Components[0].Subcomponent.Name); + AssertSupported(query, false, null, "Name", o => o is StringType, o => o?.Name == "component[Name,Count]"); + } + + [Test] + public void NestedOneToManyCompositeElementPropertyIndexerTest() + { + var query = session.Query().Select(o => o.Components[0].Subcomponent.Name[0]); + AssertUnsupported(query, false, null, null, o => o == null); + } + + [Test] + public void ManyToManyTest() + { + var query = session.Query().Select(o => o.FooArray); + AssertSupported( + query, + false, + typeof(Baz).FullName, + "FooArray", + o => o is ArrayType arrayType && arrayType.Role == $"{typeof(Baz).FullName}.FooArray"); + } + + [Test] + public void ManyToManyIndexerTest() + { + var query = session.Query().Select(o => o.FooArray[0].Null); + AssertSupported(query, false, typeof(Foo).FullName, "Null", o => o is NullableInt32Type); + } + + [Test] + public void SubclassCastTest() + { + var query = session.Query().Select(o => (B) o); + AssertSupportedAndResultNotNullable( + query, + typeof(A).FullName, + null, + o => o is EntityType entityType && entityType.ReturnedClass == typeof(B)); + } + + [Test] + public void NestedSubclassCastTest() + { + var query = session.Query().Select(o => (C1) ((B) o)); + AssertSupportedAndResultNotNullable( + query, + false, + typeof(A).FullName, + null, + o => o is EntityType entityType && entityType.ReturnedClass == typeof(C1)); + } + + [Test] + public void SubclassPropertyTest() + { + var query = session.Query().Select(o => ((C1) o).Count); + AssertSupported(query, typeof(C1).FullName, "Count", o => o is Int32Type); + } + + [Test] + public void NestedSubclassCastPropertyTest() + { + var query = session.Query().Select(o => ((C1) ((B) o)).Id); + AssertSupportedAndResultNotNullable(query, typeof(C1).FullName, "Id", o => o is Int64Type); + } + + [Test] + public void AnyTest() + { + var query = session.Query().Select(o => o.Object); + AssertSupported(query, typeof(Bar).FullName, "Object", o => o.IsAnyType); + } + + [Test] + public void CastAnyTest() + { + var query = session.Query().Select(o => (Foo) o.Object); + AssertSupported( + query, + typeof(Bar).FullName, + "Object", + o => o is EntityType entityType && entityType.ReturnedClass == typeof(Foo)); + } + + [Test] + public void NestedCastAnyTest() + { + var query = session.Query().Select(o => (Foo) ((Bar) o.Object).Object); + AssertSupported( + query, + false, + typeof(Bar).FullName, + "Object", + o => o is EntityType entityType && entityType.ReturnedClass == typeof(Foo)); + } + + [Test] + public void CastAnyManyToOneTest() + { + var query = session.Query().Select(o => ((Foo) o.Object).Dependent); + AssertSupportedAndResultNotNullable( + query, + typeof(Foo).FullName, + "Dependent", + o => o is EntityType entityType && entityType.ReturnedClass == typeof(Fee)); + } + + [Test] + public void CastAnyPropertyTest() + { + var query = session.Query().Select(o => ((Foo) o.Object).String); + AssertSupported(query, false, typeof(Foo).FullName, "String", o => o is StringType); + } + + [Test] + public void QueryUnmappedEntityTest() + { + var query = session.Query>().Select(o => o.Id); + AssertSupportedAndResultNotNullable(query, typeof(User).FullName, "Id", o => o is Int32Type); + } + + [Test] + public void ConditionalExpressionTest() + { + var query = db.Users.Select(o => (o.Name == "Test" ? o.RegisteredAt : o.LastLoginDate)); + AssertSupported(query, false, typeof(User).FullName, "RegisteredAt", o => o is DateTimeType); + } + + [Test] + public void ConditionalIfFalseExpressionTest() + { + var query = db.Users.Select(o => (o.Name == "Test" ? DateTime.Today : o.LastLoginDate)); + AssertSupported(query, false, typeof(User).FullName, "LastLoginDate", o => o is DateTimeType); + } + + [Test] + public void ConditionalMemberExpressionTest() + { + var query = db.Users.Select(o => (o.Name == "Test" ? o.NotMappedRole : o.Role).IsActive); + AssertSupported(query, false, typeof(Role).FullName, "IsActive", o => o is BooleanType); + } + + [Test] + public void ConditionalNestedExpressionTest() + { + var query = db.Users.Select(o => (o.Name == "Test" ? o.Component.OtherComponent.OtherProperty1 : o.Component.Property1)); + AssertSupported( + query, + false, + typeof(User).FullName, + "Component.OtherComponent.OtherProperty1", + o => o is AnsiStringType, + o => o?.Name == "component[OtherProperty1]"); + } + + [Test] + public void CoalesceExpressionTest() + { + var query = db.Users.Select(o => o.LastLoginDate ?? o.RegisteredAt); + AssertSupported(query, false, typeof(User).FullName, "LastLoginDate", o => o is DateTimeType); + } + + [Test] + public void CoalesceRightExpressionTest() + { + var query = db.Users.Select(o => ((DateTime?) DateTime.Now) ?? o.RegisteredAt); + AssertSupported(query, false, typeof(User).FullName, "RegisteredAt", o => o is DateTimeType); + } + + [Test] + public void CoalesceMemberExpressionTest() + { + var query = db.Users.Select(o => (o.NotMappedRole ?? o.Role).IsActive); + AssertSupported(query, false, typeof(Role).FullName, "IsActive", o => o is BooleanType); + } + + [Test] + public void CoalesceNestedExpressionTest() + { + var query = db.Users.Select(o => o.Component.OtherComponent.OtherProperty1 ?? o.Component.Property1); + AssertSupported( + query, + false, + typeof(User).FullName, + "Component.OtherComponent.OtherProperty1", + o => o is AnsiStringType, + o => o?.Name == "component[OtherProperty1]"); + } + + [Test] + public void CoalesceConditionalMemberExpressionTest() + { + var query = db.Users.Select(o => (o.Name == "Test" ? o.NotMappedRole : (o.NotMappedRole ?? new Role() ?? o.Role)).IsActive); + AssertSupported(query, false, typeof(Role).FullName, "IsActive", o => o is BooleanType); + } + + [Test] + public void JoinTest() + { + var query = from o in db.Orders + from p in db.Products + join d in db.OrderLines + on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId} + into details + from d in details + select d.UnitPrice; + AssertSupportedAndResultNotNullable(query, typeof(OrderLine).FullName, "UnitPrice", o => o is DecimalType); + } + + [Test] + public void NotNullComponentPropertyTest() + { + var query = session.Query().SelectMany(o => o.PatientRecords.Select(r => r.Name.FirstName)); + AssertSupportedAndResultNotNullable( + query, + typeof(PatientRecord).FullName, + "Name.FirstName", + o => o is StringType, + o => o?.Name == "component[FirstName,LastName]"); + } + + [Test] + public void NotRelatedTypeTest() + { + var query = session.Query().Select(o => o.CanReduce); + AssertUnsupported(query, null, null, o => o == null); + } + + [Test] + public void NotNhQueryableTest() + { + var query = new List().AsQueryable().Select(o => o.Name); + AssertUnsupported(query, false, null, null, o => o == null); + } + + private void AssertUnsupported( + IQueryable query, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null) + { + AssertResult(query, true, false, expectedEntityName, expectedMemberPath, expectedMemberType, expectedComponentType); + } + + private void AssertUnsupported( + IQueryable query, + bool rewriteQuery, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null) + { + AssertResult(query, rewriteQuery, false, expectedEntityName, expectedMemberPath, expectedMemberType, expectedComponentType); + } + + private void AssertSupported( + IQueryable query, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null) + { + AssertResult(query, true, true, expectedEntityName, expectedMemberPath, expectedMemberType, expectedComponentType); + } + + private void AssertSupported( + IQueryable query, + bool rewriteQuery, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null) + { + AssertResult(query, rewriteQuery, true, expectedEntityName, expectedMemberPath, expectedMemberType, expectedComponentType); + } + + private void AssertSupportedAndResultNotNullable( + IQueryable query, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null) + { + AssertResult(query, true, true, expectedEntityName, expectedMemberPath, expectedMemberType, expectedComponentType, false); + } + + private void AssertSupportedAndResultNotNullable( + IQueryable query, + bool rewriteQuery, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null) + { + AssertResult(query, rewriteQuery, true, expectedEntityName, expectedMemberPath, expectedMemberType, expectedComponentType, false); + } + + private void AssertResult( + IQueryable query, + bool rewriteQuery, + bool supported, + string expectedEntityName, + string expectedMemberPath, + Predicate expectedMemberType, + Predicate expectedComponentType = null, + bool nullability = true) + { + expectedComponentType = expectedComponentType ?? (o => o == null); + + var expression = query.Expression; + var preTransformResult = NhRelinqQueryParser.PreTransform(expression, new PreTransformationParameters(QueryMode.Select, Sfi)); + expression = preTransformResult.Expression; + var constantToParameterMap = ExpressionParameterVisitor.Visit(preTransformResult); + var queryModel = NhRelinqQueryParser.Parse(expression); + var requiredHqlParameters = new List(); + var visitorParameters = new VisitorParameters( + Sfi, + constantToParameterMap, + requiredHqlParameters, + new QuerySourceNamer(), + expression.Type, + QueryMode.Select); + if (rewriteQuery) + { + QueryModelVisitor.GenerateHqlQuery( + queryModel, + visitorParameters, + true, + NhLinqExpressionReturnType.Scalar); + } + + var found = _tryGetMappedType( + Sfi, + queryModel.SelectClause.Selector, + out var memberType, + out var entityPersister, + out var componentType, + out var memberPath); + Assert.That(found, Is.EqualTo(supported), $"Expression should be {(supported ? "supported" : "unsupported")}"); + Assert.That(entityPersister?.EntityName, Is.EqualTo(expectedEntityName), "Invalid entity name"); + Assert.That(memberPath, Is.EqualTo(expectedMemberPath), "Invalid member path"); + Assert.That(() => expectedMemberType(memberType), $"Invalid member type: {memberType?.Name ?? "null"}"); + Assert.That(() => expectedComponentType(componentType), $"Invalid component type: {componentType?.Name ?? "null"}"); + + if (found) + { + Assert.That(_tryGetMappedNullability(Sfi, queryModel.SelectClause.Selector, out var isNullable), Is.True, "Expression should be supported"); + Assert.That(nullability, Is.EqualTo(isNullable), "Nullability is not correct"); + } + } + } +} diff --git a/src/NHibernate.Test/Linq/WhereSubqueryTests.cs b/src/NHibernate.Test/Linq/WhereSubqueryTests.cs index 060d6bc1710..543a5380461 100644 --- a/src/NHibernate.Test/Linq/WhereSubqueryTests.cs +++ b/src/NHibernate.Test/Linq/WhereSubqueryTests.cs @@ -1,7 +1,9 @@ using System; using System.Linq; using System.Linq.Expressions; +using NHibernate.Dialect; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -372,6 +374,17 @@ where timesheet.Entries.AsQueryable().Any(e => !new[] { 1 }.Contains(e.Id)) Assert.That(query.Count, Is.EqualTo(2)); } + [Test(Description = "GH-2471")] + public void TimeSheetsWithStringContainsSubQueryWithAsQueryableAfterWhere() + { + var query = ( + from timesheet in db.Timesheets + where timesheet.Entries.Where(e => e.Comments != null).AsQueryable().Any(e => e.Comments.Contains("testing")) + select timesheet).ToList(); + + Assert.That(query.Count, Is.EqualTo(2)); + } + [Test(Description = "NH-3002")] public void HqlOrderLinesWithInnerJoinAndSubQuery() { @@ -509,6 +522,172 @@ where subquery.Any(x => x.OrderId == order.OrderId) Assert.That(query.Count, Is.EqualTo(61)); } + [Test(Description = "GH2479")] + public void OrdersWithSubquery9() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId) + .Take(2); + + var orderLinesFuture = db.OrderLines + .Where(x => ordersQuery.Any(o => o == x.Order)) + .OrderBy(x => x.Id) + .ToFuture(); + + var orders = ordersQuery.ToFuture().ToList(); + var orderLines = orderLinesFuture.ToList(); + + Assert.That(orders.Count, Is.EqualTo(2), nameof(orders)); + Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines)); + } + + [Test] + public void OrdersWithSubquery9A() + { + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId); + + var orderLinesFuture = db.OrderLines + .Where(x => ordersQuery.Any(o => o == x.Order)) + .OrderBy(x => x.Id) + .ToFuture(); + + var orders = ordersQuery.ToFuture().ToList(); + var orderLines = orderLinesFuture.ToList(); + + Assert.That(orders.Count, Is.EqualTo(286), nameof(orders)); + Assert.That(orderLines.Count, Is.EqualTo(711), nameof(orderLines)); + } + + [Test] + public void OrdersWithSubquery9Count() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); + + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId) + .Take(2); + + var orderLines = db.OrderLines + .Where(x => ordersQuery.Count(o => o == x.Order) > 0) + .OrderBy(x => x.Id) + .ToList(); + + Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines)); + } + + [Test] + public void OrdersWithSubquery9Sum() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); + + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId) + .Take(2); + + var orderLines = db.OrderLines + .Where(x => ordersQuery.Where(o => o == x.Order).Sum(o => o.Freight.Value) > 0) + .OrderBy(x => x.Id) + .ToList(); + + Assert.That(orderLines.Count, Is.EqualTo(4), nameof(orderLines)); + } + + [Test(Description = "GH2479")] + public void OrdersWithSubquery10() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId) + .Take(2); + + var productsQuery = ordersQuery.SelectMany(x => x.OrderLines).Select(x => x.Product); + var productsFuture = db.Products + .Where(x => productsQuery.Contains(x)) + .OrderBy(x => x.ProductId) + .ToFuture(); + + var orders = ordersQuery.ToFuture().ToList(); + var products = productsFuture.ToList(); + + Assert.That(orders.Count, Is.EqualTo(2), nameof(orders)); + Assert.That(products.Count, Is.EqualTo(4), nameof(products)); + } + + [Test] + public void OrdersWithSubquery10A() + { + var ordersQuery = db.Orders + .Where(x => x.Employee.EmployeeId > 5) + .OrderByDescending(x => x.OrderId); + + var productsQuery = ordersQuery.SelectMany(x => x.OrderLines).Select(x => x.Product); + var productsFuture = db.Products + .Where(x => productsQuery.Contains(x)) + .OrderBy(x => x.ProductId) + .ToFuture(); + + var orders = ordersQuery.ToFuture().ToList(); + var products = productsFuture.ToList(); + + Assert.That(orders.Count, Is.EqualTo(286), nameof(orders)); + Assert.That(products.Count, Is.EqualTo(77), nameof(products)); + } + + [Test(Description = "GH2479")] + public void OrdersWithSubquery11() + { + if (Dialect is MySQLDialect) + Assert.Ignore("MySQL does not support LIMIT in subqueries."); + if (Dialect is MsSqlCeDialect) + Assert.Ignore("MS SQL CE does not support sorting on a subquery."); + + var ordersQuery = db.Orders + .OrderByDescending(x => x.OrderLines.Count).ThenBy(x => x.OrderId) + .Take(2); + + var orderLineQuery = ordersQuery.SelectMany(x => x.OrderLines); + var productsNotInLargestOrders = db.Products + .Where(x => orderLineQuery.All(p => p.Product != x)) + .OrderBy(x => x.ProductId) + .ToList(); + + Assert.That(productsNotInLargestOrders.Count, Is.EqualTo(49), nameof(productsNotInLargestOrders)); + } + + [Test] + public void OrdersWithSubquery11A() + { + var ordersQuery = db.Orders + .OrderByDescending(x => x.OrderLines.Count).ThenBy(x => x.OrderId); + + var orderLineQuery = ordersQuery.SelectMany(x => x.OrderLines); + var productsNotInLargestOrders = db.Products + .Where(x => orderLineQuery.All(p => p.Product != x)) + .OrderBy(x => x.ProductId) + .ToList(); + + Assert.That(productsNotInLargestOrders.Count, Is.EqualTo(0), nameof(productsNotInLargestOrders)); + } + [Test(Description = "NH-2654")] public void CategoriesWithDiscountedProducts() { @@ -739,7 +918,6 @@ public void ProductsWithSubqueryReturningStringFirstOrDefaultEq() Assert.That(result.Count, Is.EqualTo(13)); } - [Test(Description = "NH-3423")] public void NullComparedToNewExpressionInWhereClause() { @@ -778,5 +956,30 @@ public void NullComparedToMemberInitExpressionInWhereClause() Assert.That(result.Count, Is.EqualTo(45)); } + + public class Specification + { + private Expression> _expression; + + public Specification(Expression> expression) + { + _expression = expression; + } + + public static implicit operator Expression>(Specification specification) + { + return specification._expression; + } + } + + [Test] + public void ImplicitConversionInsideWhereSubqueryExpression() + { + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries"); + + var spec = new Specification(x => x.Freight > 1000); + db.Orders.Where(o => db.Orders.Where(spec).Any(x => x.OrderId == o.OrderId)).ToList(); + } } } diff --git a/src/NHibernate.Test/Linq/WhereTests.cs b/src/NHibernate.Test/Linq/WhereTests.cs index e75e232939a..f991df12030 100644 --- a/src/NHibernate.Test/Linq/WhereTests.cs +++ b/src/NHibernate.Test/Linq/WhereTests.cs @@ -4,9 +4,11 @@ using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; +using log4net.Core; using NHibernate.Engine.Query; using NHibernate.Linq; using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Linq.Functions; using NUnit.Framework; namespace NHibernate.Test.Linq @@ -131,7 +133,6 @@ public void UsersRegisteredAtOrAfterY2K() Assert.That(query.Count, Is.EqualTo(2)); } - [Test] public void UsersRegisteredAtOrAfterY2K_And_Before2001() { @@ -350,7 +351,6 @@ public void UsersWithStringContainsAndNotNullNameComplicated() query.ToList(); } - [Test] [Description("NH-3337")] public void ProductWithDoubleStringContainsAndNotNull() @@ -400,7 +400,6 @@ public void ProductWithDoubleStringContainsAndNotNull() var results = db.Products.Where(expr).ToList(); Assert.That(results, Has.Count.EqualTo(1)); } - [Test(Description = "NH-3261")] public void UsersWithStringContainsAndNotNullName() @@ -422,6 +421,34 @@ public void UsersWithStringContainsAndNotNullNameHQL() Assert.That(users.Count, Is.EqualTo(1)); } + [Test] + public void StringComparisonParamEmitsWarning() + { + Assert.Multiple( + () => + { + AssertStringComparisonWarning(x => string.Compare(x.CustomerId, "ANATR", StringComparison.Ordinal) <= 0, 2); + AssertStringComparisonWarning(x => x.CustomerId.StartsWith("ANATR", StringComparison.Ordinal), 1); + AssertStringComparisonWarning(x => x.CustomerId.EndsWith("ANATR", StringComparison.Ordinal), 1); + AssertStringComparisonWarning(x => x.CustomerId.IndexOf("ANATR", StringComparison.Ordinal) == 0, 1); + AssertStringComparisonWarning(x => x.CustomerId.IndexOf("ANATR", 0, StringComparison.Ordinal) == 0, 1); +#if NETCOREAPP2_0 + AssertStringComparisonWarning(x => x.CustomerId.Replace("AN", "XX", StringComparison.Ordinal) == "XXATR", 1); +#endif + }); + } + + private void AssertStringComparisonWarning(Expression> whereParam, int expected) + { + using (var log = new LogSpy(typeof(BaseHqlGeneratorForMethod))) + { + var customers = session.Query().Where(whereParam).ToList(); + + Assert.That(customers, Has.Count.EqualTo(expected), whereParam.ToString); + Assert.That(log.GetWholeLog(), Does.Contain($"parameter of type '{nameof(StringComparison)}' is ignored"), whereParam.ToString); + } + } + [Test] public void UsersWithArrayContains() { @@ -763,7 +790,6 @@ public void AnimalsWithFathersSerialNumberListContainsWithLocalVariable() Assert.That(query.Count, Is.EqualTo(1)); } - [Test(Description = "NH-3366")] public void CanUseCompareInQueryWithNonConstantZero() { @@ -783,7 +809,6 @@ public void CanUseCompareInQueryWithNonConstantZero() } } - [Test(Description = "NH-3366")] [TestCaseSource(typeof(WhereTests), nameof(CanUseCompareInQueryDataSource))] public void CanUseCompareInQuery(Expression> expression, int expectedCount, bool expectCase) @@ -799,7 +824,6 @@ public void CanUseCompareInQuery(Expression> expression, int } } - [Test(Description = "NH-3665")] public void SelectOnCollectionReturnsResult() { diff --git a/src/NHibernate.Test/LinqBulkManipulation/Fixture.cs b/src/NHibernate.Test/LinqBulkManipulation/Fixture.cs index d41510c4947..ab846536b39 100644 --- a/src/NHibernate.Test/LinqBulkManipulation/Fixture.cs +++ b/src/NHibernate.Test/LinqBulkManipulation/Fixture.cs @@ -289,6 +289,76 @@ public void InsertWithClientSideRequirementsThrowsException() } } + [Test(Description = "GH2594")] + public void InsertIntoWithSubquery() + { + if(!Dialect.SupportsScalarSubSelects) + Assert.Ignore("Dialect does not support scalar sub-select"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s + .Query() + .InsertInto( + x => new Pickup + { + Id = -s.Query().Where(c => c.Id == x.Id).Select(c => c.Id).FirstOrDefault(), + Vin = x.Vin, + Owner = x.Owner + }); + + t.Commit(); + } + } + + [Test] + public void UpdateWithSubquery() + { + if(!TestDialect.SupportsModifyAndSelectSameTable || !Dialect.SupportsScalarSubSelects) + Assert.Ignore("Not supported by dialect"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s + .Query() + .Update( + x => new + { + Id = -s.Query().Where(c => c.Id == x.Id).Select(c => c.Id).FirstOrDefault(), + Vin = x.Vin, + Owner = x.Owner + }); + + t.Commit(); + } + } + + [Test] + public void MultiTableUpdateWithSubquery() + { + if (!Dialect.SupportsTemporaryTables) + Assert.Ignore("Cannot perform multi-table updates using dialect not supporting temp tables."); + + if(!TestDialect.SupportsModifyAndSelectSameTable || !Dialect.SupportsScalarSubSelects) + Assert.Ignore("Not supported by dialect"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s + .Query() + .Update( + x => new + { + Description = s.Query().Where(c => c.Id == x.Id).Select(c => c.Description).FirstOrDefault(), + }); + + t.Commit(); + } + } + [Test] public void InsertWithManyToOne() { @@ -456,9 +526,8 @@ public void InsertWithSelectListUsingJoins() // this is just checking parsing and syntax... using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - Assert.DoesNotThrow(() => { s @@ -466,7 +535,7 @@ public void InsertWithSelectListUsingJoins() .InsertInto(x => new Animal { Description = x.Description, BodyWeight = x.BodyWeight }); }); - s.Transaction.Commit(); + t.Commit(); } } @@ -749,7 +818,6 @@ public void UpdateOnDiscriminatorSubclass() } using (var t = s.BeginTransaction()) { - var count = s.Query().UpdateBuilder().Set(y => y.Name, y => y.Name).Update(); Assert.That(count, Is.EqualTo(2), "Incorrect discrim subclass update count"); @@ -793,7 +861,6 @@ public void UpdateOnAnimal() s.Query().UpdateBuilder().Set(y => y.FireTemperature, 300).Update(); Assert.That(count, Is.EqualTo(1), "Incorrect entity-updated count"); - count = s.Query().UpdateBuilder().Set(y => y.BodyWeight, y => y.BodyWeight + 1 + 1).Update(); Assert.That(count, Is.EqualTo(10), "incorrect count on 'complex' update assignment"); @@ -974,11 +1041,29 @@ public void DeleteWithSubquery() } using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); var count = s.Query().Where(x => x.AssociatedEntities.Count == 0 && x.Name.Contains("myEntity")).Delete(); Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); - s.Transaction.Commit(); + t.Commit(); + } + } + + [Test] + public void DeleteWithSubquery2() + { + if(!TestDialect.SupportsModifyAndSelectSameTable || !Dialect.SupportsScalarSubSelects) + Assert.Ignore("Not supported by dialect"); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s + .Query() + .Where(x => x.Id == -s.Query().Where(c => c.Id == x.Id).Select(c => c.Id).FirstOrDefault()) + .Delete(); + + t.Commit(); } } @@ -1006,7 +1091,6 @@ public void SimpleDeleteOnAnimal() using (var s = OpenSession()) using (var t = s.BeginTransaction()) { - var count = s.Query().Where(x => x.Id == _polliwog.Id).Delete(); Assert.That(count, Is.EqualTo(1), "Incorrect delete count"); diff --git a/src/NHibernate.Test/Log4netConfiguration.cs b/src/NHibernate.Test/Log4netConfiguration.cs deleted file mode 100644 index 841ecc09911..00000000000 --- a/src/NHibernate.Test/Log4netConfiguration.cs +++ /dev/null @@ -1,2 +0,0 @@ -using log4net.Config; -[assembly: XmlConfigurator()] \ No newline at end of file diff --git a/src/NHibernate.Test/Logging/LoggerProviderTest.cs b/src/NHibernate.Test/Logging/LoggerProviderTest.cs index b00bfa91fbc..8a03dc3e4f2 100644 --- a/src/NHibernate.Test/Logging/LoggerProviderTest.cs +++ b/src/NHibernate.Test/Logging/LoggerProviderTest.cs @@ -24,8 +24,6 @@ public void LoggerProviderCanCreateLoggers_Obsolete() [Test] public void WhenNotConfiguredAndLog4NetExistsThenUseLog4NetFactory() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - // NoLoggingNHibernateLogger is internal Assert.That(NHibernateLogger.For("pizza").GetType().Name, Is.Not.EqualTo("NoLoggingNHibernateLogger")); } @@ -33,8 +31,6 @@ public void WhenNotConfiguredAndLog4NetExistsThenUseLog4NetFactory() [Test, Obsolete] public void WhenNotConfiguredAndLog4NetExistsThenUseLog4NetFactory_Obsolete() { - Assume.That(TestsContext.ExecutingWithVsTest, Is.False); - Assert.That(LoggerProvider.LoggerFor("pizza"), Is.Not.InstanceOf()); // works because this is the legacy provider with a legacy logger diff --git a/src/NHibernate.Test/MappingByCode/ConventionModelMapperTests/SafePoidTests.cs b/src/NHibernate.Test/MappingByCode/ConventionModelMapperTests/SafePoidTests.cs index 9462807e97b..16204e812cc 100644 --- a/src/NHibernate.Test/MappingByCode/ConventionModelMapperTests/SafePoidTests.cs +++ b/src/NHibernate.Test/MappingByCode/ConventionModelMapperTests/SafePoidTests.cs @@ -9,7 +9,6 @@ public class SafePoidTests { private class MyClassWithoutPoid { - } private class MyClass diff --git a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/AllPropertiesRegistrationTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/AllPropertiesRegistrationTests.cs index d0b169cd2ff..11f66934fa3 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/AllPropertiesRegistrationTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/AllPropertiesRegistrationTests.cs @@ -110,7 +110,6 @@ private class Related private class Inherited:MyClass { - } [Test] @@ -266,7 +265,6 @@ public void WhenMapComponentWithWrongElementTypeThenThrows() }), Throws.TypeOf()); } - [Test] public void WhenMapOneToOneWithWrongTypeThenThrows() { diff --git a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs index 457107d5a4e..3b9bf5c07fd 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs @@ -6,7 +6,7 @@ using NHibernate.Mapping.ByCode; using NUnit.Framework; -namespace NHibernate.Test.MappingByCode.ExpliticMappingTests +namespace NHibernate.Test.MappingByCode.ExplicitMappingTests { [TestFixture] public class DynamicComponentMappingTests @@ -33,6 +33,141 @@ public IDictionary Info } } + private class PersonWithDynamicInfo + { + public int Id { get; set; } + public dynamic Info { get; set; } + } + + [Test] + public void WhenMapDynCompoByDictionaryThenMapItAndItsProperties() + { + //NH-3704 + var mapper = new ModelMapper(); + mapper.Class( + map => + { + map.Id(x => x.Id, idmap => { }); + map.Component( + x => x.Info, + new Dictionary + {{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}}, + z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(Person) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent, Is.Not.Null); + Assert.That( + hbmDynamicComponent.Properties.Select(x => x.Name), + Is.EquivalentTo(new[] { "MyInt", "MyDate" })); + } + + [Test] + public void WhenMapDynCompoByDictionaryThenMapItAndItsPropertiesGeneric() + { + //NH-3704 + var mapper = new ModelMapper(); + mapper.Class( + map => + { + map.Id(x => x.Id, idmap => { }); + map.Component( + x => x.Info, + new Dictionary + {{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}}, + z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent, Is.Not.Null); + Assert.That( + hbmDynamicComponent.Properties.Select(x => x.Name), + Is.EquivalentTo(new[] { "MyInt", "MyDate" })); + } + + [Test] + public void WhenMapDynCompoByDictionaryThenMapItAndItsPropertiesDynamic() + { + //NH-3704 + var mapper = new ModelMapper(); + mapper.Class( + map => + { + map.Id(x => x.Id, idmap => { }); + map.Component( + nameof(PersonWithDynamicInfo.Info), + new Dictionary + {{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}}, + z => + { + z.Property("MyInt", pm => pm.Column("MY_COLUMN")); + z.Component("MyDate"); + }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithDynamicInfo) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent, Is.Not.Null); + Assert.That( + hbmDynamicComponent.Properties.Select(x => x.Name), + Is.EquivalentTo(new[] { "MyInt", "MyDate" })); + } + + [Test] + public void WhenMapPrivateDynCompoByDictionaryThenMapItAndItsProperties() + { + //NH-3704 + var mapper = new ModelMapper(); + mapper.Class( + map => + { + map.Id(x => x.Id, idmap => { }); + map.Component( + "Info", + new Dictionary + {{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}}, + z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(Person) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent, Is.Not.Null); + Assert.That( + hbmDynamicComponent.Properties.Select(x => x.Name), + Is.EquivalentTo(new[] { "MyInt", "MyDate" })); + } + + [Test] + public void WhenMapPrivateDynCompoByDictionaryThenMapItAndItsPropertiesGeneric() + { + //NH-3704 + var mapper = new ModelMapper(); + mapper.Class( + map => + { + map.Id(x => x.Id, idmap => { }); + map.Component( + "Info", + new Dictionary + {{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}}, + z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent, Is.Not.Null); + Assert.That( + hbmDynamicComponent.Properties.Select(x => x.Name), + Is.EquivalentTo(new[] { "MyInt", "MyDate" })); + } + [Test] public void WhenMapDynCompoThenMapItAndItsProperties() { diff --git a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs index d21147c6acd..b19e304b860 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs @@ -192,4 +192,4 @@ public void WhenJoinedDynamicComponentIsMappedOnJoinThenItBelongsToJoinTable() Assert.That(hbmPropJoinedAttributes, Is.TypeOf()); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/ComponentMappingRegistrationTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/ComponentMappingRegistrationTests.cs index f7621dcce61..16bf43111db 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/ComponentMappingRegistrationTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/ComponentMappingRegistrationTests.cs @@ -8,7 +8,6 @@ public class ComponentMappingRegistrationTests { private class MyComponent { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassMappingStrategyTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassMappingStrategyTests.cs index 93cff8f772d..2d080531291 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassMappingStrategyTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassMappingStrategyTests.cs @@ -8,15 +8,12 @@ public class JoinedSubclassMappingStrategyTests { private class MyClass { - } private class Inherited1 : MyClass { - } private class Inherited2 : Inherited1 { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassSequenceRegistrationTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassSequenceRegistrationTests.cs index e90888414cf..1ce252f60e6 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassSequenceRegistrationTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/JoinedSubclassSequenceRegistrationTests.cs @@ -8,11 +8,9 @@ public class JoinedSubclassSequenceRegistrationTests { private class MyClass { - } private class Inherited1 : MyClass { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/RootClassMappingStrategyTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/RootClassMappingStrategyTests.cs index 11c77180841..22b3ca35fb5 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/RootClassMappingStrategyTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/RootClassMappingStrategyTests.cs @@ -8,15 +8,12 @@ public class RootClassMappingStrategyTests { private class MyClass { - } private class Inherited1: MyClass { - } private class Inherited2 : Inherited1 { - } [Test] @@ -74,7 +71,6 @@ public void WhenRegisteredDeepSubclassThenTheStrategyIsDefinedEvenForRoot() Assert.That(inspector.IsTablePerConcreteClass(typeof(MyClass)), Is.False); } - [Test] public void WhenRegisteredConcreteClassThenTheStrategyIsDefinedEvenForRoot() { diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassMappingStrategyTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassMappingStrategyTests.cs index 5438c6c9341..384f2015815 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassMappingStrategyTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassMappingStrategyTests.cs @@ -8,15 +8,12 @@ public class SubclassMappingStrategyTests { private class MyClass { - } private class Inherited1 : MyClass { - } private class Inherited2 : Inherited1 { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassSequenceRegistrationTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassSequenceRegistrationTests.cs index 580a50f1884..4bac9ebf561 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassSequenceRegistrationTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/SubclassSequenceRegistrationTests.cs @@ -8,11 +8,9 @@ public class SubclassSequenceRegistrationTests { private class MyClass { - } private class Inherited1 : MyClass { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassMappingStrategyTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassMappingStrategyTests.cs index f221bf93e0e..ba43e701107 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassMappingStrategyTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassMappingStrategyTests.cs @@ -8,15 +8,12 @@ public class UnionSubclassMappingStrategyTests { private class MyClass { - } private class Inherited1 : MyClass { - } private class Inherited2 : Inherited1 { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassSequenceRegistrationTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassSequenceRegistrationTests.cs index c1c11b785fe..e7c67a2a609 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassSequenceRegistrationTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitlyDeclaredModelTests/UnionSubclassSequenceRegistrationTests.cs @@ -8,11 +8,9 @@ public class UnionSubclassSequenceRegistrationTests { private class MyClass { - } private class Inherited1 : MyClass { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/Dog.cs b/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/Dog.cs index 2a6f1377a2c..5fa24d6e91e 100644 --- a/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/Dog.cs +++ b/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/Dog.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.MappingByCode.IntegrationTests.NH2728 +namespace NHibernate.Test.MappingByCode.IntegrationTests.NH2728 { public class Dog : IAnimal { diff --git a/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/IAnimal.cs b/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/IAnimal.cs index 7548cd1a216..1ab3f3ac78a 100644 --- a/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/IAnimal.cs +++ b/src/NHibernate.Test/MappingByCode/IntegrationTests/NH2728/IAnimal.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.MappingByCode.IntegrationTests.NH2728 +namespace NHibernate.Test.MappingByCode.IntegrationTests.NH2728 { public interface IAnimal { diff --git a/src/NHibernate.Test/MappingByCode/IntegrationTests/NH3105/Fixture.cs b/src/NHibernate.Test/MappingByCode/IntegrationTests/NH3105/Fixture.cs index f7c0e410cfb..f248093a368 100644 --- a/src/NHibernate.Test/MappingByCode/IntegrationTests/NH3105/Fixture.cs +++ b/src/NHibernate.Test/MappingByCode/IntegrationTests/NH3105/Fixture.cs @@ -27,7 +27,6 @@ public void CanMapComponentAsIdWhenComponentIsDeclaredInBaseClass() Assert.That(key.Columns.Single().name, Is.EqualTo("IdColumn")); } - [Test] public void CanMapIdWhenIdIsDeclaredInBaseClass() { @@ -66,4 +65,4 @@ public void CanMapComponentAsIdWhenComponentIsDeclaredInClass() Assert.That(key.Columns.Single().name, Is.EqualTo("IdColumn")); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/ClassMapperTests/ComponetAsIdTests.cs b/src/NHibernate.Test/MappingByCode/MappersTests/ClassMapperTests/ComponetAsIdTests.cs index 0b628645201..92367f86348 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/ClassMapperTests/ComponetAsIdTests.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/ClassMapperTests/ComponetAsIdTests.cs @@ -83,6 +83,5 @@ public void WhenMapExternalMemberAsComponentIdThenThrows() Assert.That(() => mapper.ComponentAsId(For.Property(x => x.Id), map => map.Access(Accessor.Field)), Throws.TypeOf()); } - } } diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/ComponentWithBagOfNestedComponentsTests.cs b/src/NHibernate.Test/MappingByCode/MappersTests/ComponentWithBagOfNestedComponentsTests.cs index 5ab7056215e..7bea39764f8 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/ComponentWithBagOfNestedComponentsTests.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/ComponentWithBagOfNestedComponentsTests.cs @@ -219,7 +219,6 @@ public void PropertyCustomizerDifferentiatesBetweenChildClasses() var mappings = mapper.CompileMappingForAllExplicitlyAddedEntities(); - HbmBag bag1 = mappings .Items.Cast() .Where(c => c.Name == typeof(OwnerChildOne).FullName) @@ -242,7 +241,5 @@ public void PropertyCustomizerDifferentiatesBetweenChildClasses() HbmProperty propertyMapping2 = childElement2.Properties.Cast().Single(); Assert.That(propertyMapping2.Columns.Single().name, Is.EqualTo("OwnerChildTwo_CustomColumnName")); } - - } } diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynCompAttributesSettingTest.cs b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynCompAttributesSettingTest.cs index 91c56775013..23cbbb1f6c3 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynCompAttributesSettingTest.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynCompAttributesSettingTest.cs @@ -75,6 +75,5 @@ public void CanAddSimpleProperty() Assert.That(component.Properties.Single(), Is.TypeOf().And.Property("Name").EqualTo("Pizza")); } - } } diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs index 8d8fc4fcd50..b08be0a53b5 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs @@ -36,14 +36,13 @@ public IDictionary Info } } - [Test] public void WhenAddThenHas() { var mapdoc = new HbmMapping(); var component = new HbmDynamicComponent(); var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); - var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + var propertyInfo = For.Property(p => p.Info); //just as another dyn-compo mapper.Component(propertyInfo, (IDynamicComponentMapper x) => { }); @@ -56,7 +55,7 @@ public void WhenAddThenHasGeneric() var mapdoc = new HbmMapping(); var component = new HbmDynamicComponent(); var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); - var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + var propertyInfo = For.Property(p => p.Info); //just as another dyn-compo mapper.Component(propertyInfo, (IDynamicComponentMapper x) => { }); @@ -69,7 +68,7 @@ public void WhenCustomizeThenCallCustomizer() var mapdoc = new HbmMapping(); var component = new HbmDynamicComponent(); var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); - var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + var propertyInfo = For.Property(p => p.Info); //just as another dyn-compo var called = false; mapper.Component(propertyInfo, (IDynamicComponentMapper x) => called = true); @@ -83,7 +82,7 @@ public void WhenCustomizeThenCallCustomizerGeneric() var mapdoc = new HbmMapping(); var component = new HbmDynamicComponent(); var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); - var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + var propertyInfo = For.Property(p => p.Info); //just as another dyn-compo var called = false; mapper.Component(propertyInfo, (IDynamicComponentMapper x) => called = true); @@ -97,7 +96,7 @@ public void WhenCustomizeAccessorThenIgnore() var mapdoc = new HbmMapping(); var component = new HbmDynamicComponent(); var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); - var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + var propertyInfo = For.Property(p => p.Info); //just as another dyn-compo mapper.Component(propertyInfo, (IDynamicComponentMapper x) => x.Access(Accessor.Field)); @@ -110,7 +109,7 @@ public void WhenCustomizeAccessorThenIgnoreGeneric() var mapdoc = new HbmMapping(); var component = new HbmDynamicComponent(); var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); - var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + var propertyInfo = For.Property(p => p.Info); //just as another dyn-compo mapper.Component(propertyInfo, (IDynamicComponentMapper x) => x.Access(Accessor.Field)); diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/IdMapperTest.cs b/src/NHibernate.Test/MappingByCode/MappersTests/IdMapperTest.cs index 9b9f4eb4b5f..a6213e0eba6 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/IdMapperTest.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/IdMapperTest.cs @@ -137,7 +137,6 @@ public int Id private class Entity : BaseEntity { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/OneToOneMapperTest.cs b/src/NHibernate.Test/MappingByCode/MappersTests/OneToOneMapperTest.cs index 718ff7d96ec..58a0811204f 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/OneToOneMapperTest.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/OneToOneMapperTest.cs @@ -164,5 +164,31 @@ public void WhenSetFormulaWithMultipleLinesThenSetFormulaNode() Assert.That(hbmFormula.Text[1], Is.EqualTo("Line2")); Assert.That(mapping.formula1, Is.Null); } + + [Test] + public void WhenSetFetchModeToJoinThenSetFetch() + { + var member = For.Property(c => c.Relation); + var mapping = new HbmOneToOne(); + var mapper = new OneToOneMapper(member, mapping); + + mapper.Fetch(FetchKind.Join); + + Assert.That(mapping.fetch, Is.EqualTo(HbmFetchMode.Join)); + Assert.That(mapping.fetchSpecified, Is.True); + } + + [Test] + public void WhenSetFetchModeToSelectThenSetFetch() + { + var member = For.Property(c => c.Relation); + var mapping = new HbmOneToOne(); + var mapper = new OneToOneMapper(member, mapping); + + mapper.Fetch(FetchKind.Select); + + Assert.That(mapping.fetch, Is.EqualTo(HbmFetchMode.Select)); + Assert.That(mapping.fetchSpecified, Is.True); + } } } diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/PropertyMapperTest.cs b/src/NHibernate.Test/MappingByCode/MappersTests/PropertyMapperTest.cs index 9216e072c42..070765d784a 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/PropertyMapperTest.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/PropertyMapperTest.cs @@ -37,7 +37,6 @@ public void Access(Accessor accessor) public void Access(System.Type accessorType) { - } } [Test] diff --git a/src/NHibernate.Test/MappingByCode/MixAutomapping/ComponentsTests.cs b/src/NHibernate.Test/MappingByCode/MixAutomapping/ComponentsTests.cs index cebceb91b0f..cd1af378a33 100644 --- a/src/NHibernate.Test/MappingByCode/MixAutomapping/ComponentsTests.cs +++ b/src/NHibernate.Test/MappingByCode/MixAutomapping/ComponentsTests.cs @@ -25,7 +25,6 @@ private class Entity private enum Something { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/MixAutomapping/EntityTests.cs b/src/NHibernate.Test/MappingByCode/MixAutomapping/EntityTests.cs index 65ac232b6a9..00b3565a7f4 100644 --- a/src/NHibernate.Test/MappingByCode/MixAutomapping/EntityTests.cs +++ b/src/NHibernate.Test/MappingByCode/MixAutomapping/EntityTests.cs @@ -24,7 +24,6 @@ private class Entity private enum Something { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/MixAutomapping/OneToManyTests.cs b/src/NHibernate.Test/MappingByCode/MixAutomapping/OneToManyTests.cs index 82297d18b5b..47b62ae8b56 100644 --- a/src/NHibernate.Test/MappingByCode/MixAutomapping/OneToManyTests.cs +++ b/src/NHibernate.Test/MappingByCode/MixAutomapping/OneToManyTests.cs @@ -20,7 +20,6 @@ private class MyClass private class Related { - } private class Bidirectional diff --git a/src/NHibernate.Test/MappingByCode/MixAutomapping/PolymorphicPropertiesMapping.cs b/src/NHibernate.Test/MappingByCode/MixAutomapping/PolymorphicPropertiesMapping.cs index 0e66a60053c..783813ea6d0 100644 --- a/src/NHibernate.Test/MappingByCode/MixAutomapping/PolymorphicPropertiesMapping.cs +++ b/src/NHibernate.Test/MappingByCode/MixAutomapping/PolymorphicPropertiesMapping.cs @@ -23,7 +23,7 @@ private class BaseEntity : IBaseEntity private interface IProduct : IBaseEntity { - string Description { get;} + string Description { get; } } private abstract class BaseProduct : BaseEntity, IProduct diff --git a/src/NHibernate.Test/MappingByCode/MixAutomapping/PropertiesExclusionTests.cs b/src/NHibernate.Test/MappingByCode/MixAutomapping/PropertiesExclusionTests.cs index b9fc52ad283..ba3166d8f78 100644 --- a/src/NHibernate.Test/MappingByCode/MixAutomapping/PropertiesExclusionTests.cs +++ b/src/NHibernate.Test/MappingByCode/MixAutomapping/PropertiesExclusionTests.cs @@ -112,6 +112,5 @@ public void IncludesAutoprop() var pi = typeof(MyEntity).GetProperty("AutoPropWithPrivateSet"); Assert.That(inspector.IsPersistentProperty(pi), Is.True); } - } } diff --git a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorConcreteClassesTest.cs b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorConcreteClassesTest.cs index 75cb8fdcdca..322a988b2bc 100644 --- a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorConcreteClassesTest.cs +++ b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorConcreteClassesTest.cs @@ -8,19 +8,15 @@ public class GetFirstImplementorConcreteClassesTest { private class MyClass1 { - } private class MyClass2 : MyClass1 { - } private class MyClass3 : MyClass2 { - } private class MyClass4 : MyClass3 { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorTest.cs b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorTest.cs index 1e6b77fd3bb..e02c1d7fc07 100644 --- a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorTest.cs +++ b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetFirstImplementorTest.cs @@ -9,35 +9,27 @@ public class GetFirstImplementorTest { private interface IInterfaceNoImpl { - } private interface IInterface1 { - } private interface IInterface2 { - } private interface IInterface3 { - } private class MyClassNoInterface { - } private class MyClass1: IInterface1 { - } private class MyClass2: MyClass1, IInterface2 { - } private class MyClass3 : MyClass2, IInterface3 { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetMemberFromInterfacesTest.cs b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetMemberFromInterfacesTest.cs index 2e9c7ec3472..fc891725f88 100644 --- a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetMemberFromInterfacesTest.cs +++ b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/GetMemberFromInterfacesTest.cs @@ -41,7 +41,6 @@ private interface IInheritedHasSomething : IHasSomething string Blah { get; set; } } - [Test] public void WhenNullArgumentThenThrows() { diff --git a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/TypeExtensionsTest.cs b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/TypeExtensionsTest.cs index 7c695b8480a..d7b4072f870 100644 --- a/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/TypeExtensionsTest.cs +++ b/src/NHibernate.Test/MappingByCode/TypeExtensionsTests/TypeExtensionsTest.cs @@ -48,7 +48,6 @@ private class MyBaseClass private class MyClass : MyBaseClass { - } [Test] @@ -181,7 +180,6 @@ public void GetFirstPropertyOfType_WhenPropertyNotExistThenNull() private interface IMyEntity : IEntity { - } [Test] diff --git a/src/NHibernate.Test/MappingByCode/TypeNameUtilTests.cs b/src/NHibernate.Test/MappingByCode/TypeNameUtilTests.cs index 0fcb18da9d6..5a6f4acffe0 100644 --- a/src/NHibernate.Test/MappingByCode/TypeNameUtilTests.cs +++ b/src/NHibernate.Test/MappingByCode/TypeNameUtilTests.cs @@ -7,11 +7,9 @@ namespace NHibernate.Test.MappingByCode { public class MyEntity { - } public class MyGenericEntity { - } [TestFixture] diff --git a/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs new file mode 100644 index 00000000000..2a37052266b --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs @@ -0,0 +1,334 @@ +using System; +using System.Data.Common; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NHibernate.MultiTenancy; +using NHibernate.Util; +using NUnit.Framework; + +namespace NHibernate.Test.MultiTenancy +{ + [TestFixture] + public class DatabaseStrategyNoDbSpecificFixture : TestCaseMappingByCode + { + private Guid _id; + + protected override void Configure(Configuration configuration) + { + configuration.DataBaseIntegration( + x => + { + x.MultiTenancy = MultiTenancyStrategy.Database; + x.MultiTenancyConnectionProvider(); + }); + configuration.Properties[Cfg.Environment.GenerateStatistics] = "true"; + base.Configure(configuration); + } + + [Test] + public void ShouldThrowWithNoTenantIdentifier() + { + Assert.Throws(() => Sfi.WithOptions().Tenant(new TenantConfiguration(null))); + } + + [Test] + public void DifferentConnectionStringForDifferentTenants() + { + if (!IsSqlServerDialect) + Assert.Ignore("MSSqlServer specific test"); + + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = OpenTenantSession("tenant2")) + { + Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); + ValidateSqlServerConnectionAppName(session1, "tenant1"); + ValidateSqlServerConnectionAppName(session2, "tenant2"); + Assert.That(GetTenantId(session1), Is.EqualTo("tenant1")); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant2")); + } + } + + [Test] + public void StatelessSessionShouldThrowWithNoTenantIdentifier() + { + Assert.Throws(() => Sfi.WithStatelessOptions().Tenant(new TenantConfiguration(null))); + } + + [Test] + public void StatelessSessionDifferentConnectionStringForDifferentTenants() + { + if (!IsSqlServerDialect) + Assert.Ignore("MSSqlServer specific test"); + + using (var session1 = OpenTenantStatelessSession("tenant1")) + using (var session2 = OpenTenantStatelessSession("tenant2")) + { + Assert.That(session1.Connection.ConnectionString, Is.Not.EqualTo(session2.Connection.ConnectionString)); + ValidateSqlServerConnectionAppName(session1, "tenant1"); + ValidateSqlServerConnectionAppName(session2, "tenant2"); + Assert.That(GetTenantId(session1), Is.EqualTo("tenant1")); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant2")); + } + } + + [Test] + public void SharedSessionSameConnectionString() + { + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = session1.SessionWithOptions().OpenSession()) + { + Assert.That(session1.Connection, Is.Not.EqualTo(session2.Connection)); + Assert.That(session1.Connection.ConnectionString, Is.EqualTo(session2.Connection.ConnectionString)); + Assert.That(session2.GetSessionImplementation().GetTenantIdentifier(), Is.EqualTo("tenant1")); + } + } + + [Test] + public void SharedSessionSameConnection() + { + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = session1.SessionWithOptions().Connection().OpenSession()) + { + Assert.That(session1.Connection, Is.EqualTo(session2.Connection)); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant1")); + } + } + + [Test] + public void SharedStatelessSessionSameConnectionString() + { + using (var session1 = OpenTenantSession("tenant1")) + using (var session2 = session1.StatelessSessionWithOptions().OpenStatelessSession()) + { + Assert.That(session1.Connection.ConnectionString, Is.EqualTo(session2.Connection.ConnectionString)); + Assert.That(GetTenantId(session2), Is.EqualTo("tenant1")); + } + } + + private static void ValidateSqlServerConnectionAppName(ISession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + + private static void ValidateSqlServerConnectionAppName(IStatelessSession s, string tenantId) + { + var builder = new SqlConnectionStringBuilder(s.Connection.ConnectionString); + Assert.That(builder.ApplicationName, Is.EqualTo(tenantId)); + } + + [Test] + public void SecondLevelCacheReusedForSameTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Get(_id); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = sesTen2.Get(_id); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public void SecondLevelCacheSeparationPerTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Get(_id); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = sesTen2.Get(_id); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(0)); + } + + [Test] + public void QueryCacheReusedForSameTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant1")) + { + var entity = sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1)); + } + + [Test] + public void QueryCacheSeparationPerTenant() + { + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Sfi.Statistics.Clear(); + using (var sesTen2 = OpenTenantSession("tenant2")) + { + var entity = sesTen2.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1)); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0)); + } + + [Test] + public void TenantSessionIsSerializableAndCanBeReconnected() + { + ISession deserializedSession = null; + using (var sesTen1 = OpenTenantSession("tenant1")) + { + var entity = sesTen1.Query().WithOptions(x => x.SetCacheable(true)).Where(e => e.Id == _id).SingleOrDefault(); + sesTen1.Disconnect(); + deserializedSession = SpoofSerialization(sesTen1); + } + + Sfi.Statistics.Clear(); + using (deserializedSession) + { + deserializedSession.Reconnect(); + + //Expect session cache hit + var entity = deserializedSession.Get(_id); + if (IsSqlServerDialect) + ValidateSqlServerConnectionAppName(deserializedSession, "tenant1"); + deserializedSession.Clear(); + + //Expect second level cache hit + deserializedSession.Get(_id); + Assert.That(GetTenantId(deserializedSession), Is.EqualTo("tenant1")); + } + + Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0)); + Assert.That(Sfi.Statistics.SecondLevelCacheHitCount, Is.EqualTo(1)); + } + + private static string GetTenantId(ISession session) + { + return session.GetSessionImplementation().GetTenantIdentifier(); + } + + private static string GetTenantId(IStatelessSession session) + { + return session.GetSessionImplementation().GetTenantIdentifier(); + } + + private T SpoofSerialization(T session) + { + var formatter = new BinaryFormatter + { +#if !NETFX + SurrogateSelector = new SerializationHelper.SurrogateSelector() +#endif + }; + var stream = new MemoryStream(); + formatter.Serialize(stream, session); + + stream.Position = 0; + + return (T) formatter.Deserialize(stream); + } + + private ISession OpenTenantSession(string tenantId) + { + return Sfi.WithOptions().Tenant(GetTenantConfig(tenantId)).OpenSession(); + } + + private IStatelessSession OpenTenantStatelessSession(string tenantId) + { + return Sfi.WithStatelessOptions().Tenant(GetTenantConfig(tenantId)).OpenStatelessSession(); + } + + private TenantConfiguration GetTenantConfig(string tenantId) + { + return new TestTenantConfiguration(tenantId, IsSqlServerDialect); + } + + private bool IsSqlServerDialect => Sfi.Dialect is MsSql2000Dialect && !(Sfi.ConnectionProvider.Driver is OdbcDriver); + + #region Test Setup + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + rc => + { + rc.Cache(m => m.Usage(CacheUsage.NonstrictReadWrite)); + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override DbConnection OpenConnectionForSchemaExport() + { + return Sfi.Settings.MultiTenancyConnectionProvider + .GetConnectionAccess(GetTenantConfig("defaultTenant"), Sfi).GetConnection(); + } + + protected override ISession OpenSession() + { + return OpenTenantSession("defaultTenant"); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + session.Flush(); + transaction.Commit(); + _id = e1.Id; + } + } + + #endregion Test Setup + } +} diff --git a/src/NHibernate.Test/MultiTenancy/Entity.cs b/src/NHibernate.Test/MultiTenancy/Entity.cs new file mode 100644 index 00000000000..04636a58484 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/Entity.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs b/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..b690b78f14c --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TestMultiTenancyConnectionProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Data.SqlClient; +using NHibernate.Connection; +using NHibernate.Engine; +using NHibernate.MultiTenancy; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + public class TestMultiTenancyConnectionProvider : AbstractMultiTenancyConnectionProvider + { + protected override string GetTenantConnectionString(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory) + { + return tenantConfiguration is TestTenantConfiguration tenant && tenant.IsSqlServerDialect + ? new SqlConnectionStringBuilder(sessionFactory.ConnectionProvider.GetConnectionString()) {ApplicationName = tenantConfiguration.TenantIdentifier}.ToString() + : sessionFactory.ConnectionProvider.GetConnectionString(); + } + } +} diff --git a/src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs b/src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs new file mode 100644 index 00000000000..b743cc95ae0 --- /dev/null +++ b/src/NHibernate.Test/MultiTenancy/TestTenantConfiguration.cs @@ -0,0 +1,16 @@ +using System; +using NHibernate.MultiTenancy; + +namespace NHibernate.Test.MultiTenancy +{ + [Serializable] + public class TestTenantConfiguration : TenantConfiguration + { + public TestTenantConfiguration(string tenantIdentifier, bool isSqlServerDialect) : base(tenantIdentifier) + { + IsSqlServerDialect = isSqlServerDialect; + } + + public bool IsSqlServerDialect { get; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs index a8c78545962..c514fcd4a89 100644 --- a/src/NHibernate.Test/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/BagWithLazyExtraAndFilter/Fixture.cs @@ -10,8 +10,8 @@ public class Fixture: BugTestCase public void CanUseFilterForLazyExtra() { using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); var machineRequest = new MachineRequest { EnvId = 1L, Id = 2L }; s.Save(new Env { @@ -22,7 +22,7 @@ public void CanUseFilterForLazyExtra() } }); s.Save(machineRequest); - s.Transaction.Commit(); + t.Commit(); } using (var s = OpenSession()) @@ -39,11 +39,11 @@ public void CanUseFilterForLazyExtra() } using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Delete(s.Load(2L)); s.Delete(s.Load(1L)); - s.Transaction.Commit(); + t.Commit(); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/BasicClassFixture.cs b/src/NHibernate.Test/NHSpecificTest/BasicClassFixture.cs index 5bfd888248c..454357fd05e 100644 --- a/src/NHibernate.Test/NHSpecificTest/BasicClassFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/BasicClassFixture.cs @@ -152,7 +152,6 @@ public void TestCRUD() index++; - // update a property to make sure it picks up that it is dirty s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -383,7 +382,6 @@ public void TestArrayCRUD() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -437,7 +435,6 @@ public void TestPrimitiveArrayCRUD() index++; - // modify the array to a new array so it is recreated s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -453,7 +450,6 @@ public void TestPrimitiveArrayCRUD() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -523,7 +519,6 @@ public void TestMapCRUD() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -590,7 +585,6 @@ public void TestSetCRUD() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -675,7 +669,6 @@ public void TestBagCRUD() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -793,7 +786,6 @@ public void TestListCRUD() index++; - // VERIFY PREVIOUS UPDATE & PERFORM DELETE s[index] = OpenSession(); t[index] = s[index].BeginTransaction(); @@ -1060,4 +1052,4 @@ private void InitializeBasicClass(int id, ref BasicClass basicClass) basicClass.AddToStringSet("zero"); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/BasicObjectFixture.cs b/src/NHibernate.Test/NHSpecificTest/BasicObjectFixture.cs index 0938cfd4ee8..a1c6e270f08 100644 --- a/src/NHibernate.Test/NHSpecificTest/BasicObjectFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/BasicObjectFixture.cs @@ -49,7 +49,6 @@ public void TestCRUD() s = OpenSession(); bo = (BasicObject) s.Load(typeof(BasicObject), bo.Id); - Assert.IsNotNull(bo.AnyWithProxy, "AnyWithProxy should not be null"); Assert.IsTrue(bo.AnyWithProxy is IBasicObjectProxy, "AnyWithProxy should have been a IBasicObjectProxy instance"); Assert.AreEqual(anyProxy.Id, ((IBasicObjectProxy) bo.AnyWithProxy).Id); @@ -68,4 +67,4 @@ public void TestCRUD() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/CriteriaFromHql/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/CriteriaFromHql/Fixture.cs index 1d84c60080b..594264c5f01 100644 --- a/src/NHibernate.Test/NHSpecificTest/CriteriaFromHql/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/CriteriaFromHql/Fixture.cs @@ -9,7 +9,6 @@ namespace NHibernate.Test.NHSpecificTest.CriteriaFromHql [TestFixture] public class Fixture : TestCase { - protected override string[] Mappings { get { return new string[] { "NHSpecificTest.CriteriaFromHql.Mappings.hbm.xml" }; } diff --git a/src/NHibernate.Test/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs index 0592869e927..412343033de 100644 --- a/src/NHibernate.Test/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/CriteriaQueryOnComponentCollection/Fixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Collections.Generic; using NHibernate.Cfg; using NHibernate.Criterion; @@ -19,7 +18,7 @@ protected override void Configure(Configuration configuration) protected override void OnSetUp() { using (var s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var parent = new Employee { @@ -45,18 +44,18 @@ protected override void OnSetUp() s.Save(parent); s.Save(emp); - s.Transaction.Commit(); + t.Commit(); } } protected override void OnTearDown() { using (var s = Sfi.OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from System.Object"); - s.Transaction.Commit(); + t.Commit(); } } @@ -94,7 +93,6 @@ public void CanQueryByCriteriaOnSetOfElement() } } - [TestCase(JoinType.LeftOuterJoin)] [TestCase(JoinType.InnerJoin)] public void CanQueryByCriteriaOnSetOfElementByCreateAlias(JoinType joinType) @@ -113,7 +111,6 @@ public void CanQueryByCriteriaOnSetOfElementByCreateAlias(JoinType joinType) } } - [Test] public void CanQueryByCriteriaOnSetOfCompositeElement_UsingDetachedCriteria() { @@ -133,7 +130,6 @@ public void CanQueryByCriteriaOnSetOfCompositeElement_UsingDetachedCriteria() } } - protected override string[] Mappings { get { return new[] {"NHSpecificTest.CriteriaQueryOnComponentCollection.Mappings.hbm.xml"}; } @@ -143,6 +139,5 @@ protected override string MappingsAssembly { get { return "NHibernate.Test"; } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/Dates/DateTime2Fixture.cs b/src/NHibernate.Test/NHSpecificTest/Dates/DateTime2Fixture.cs index a87a119772a..23359dd5b3f 100644 --- a/src/NHibernate.Test/NHSpecificTest/Dates/DateTime2Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/Dates/DateTime2Fixture.cs @@ -26,7 +26,6 @@ public void SavingAndRetrievingTest() SavingAndRetrievingAction(new AllDates {Sql_datetime2 = Now}, entity => DateTimeAssert.AreEqual(entity.Sql_datetime2, Now)); - SavingAndRetrievingAction(new AllDates { Sql_datetime2 = DateTime.MinValue }, entity => DateTimeAssert.AreEqual(entity.Sql_datetime2, DateTime.MinValue)); @@ -43,4 +42,4 @@ public void SaveMillisecond() entity => Assert.That(entity.Sql_datetime2, Is.EqualTo(datetime2))); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeAssert.cs b/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeAssert.cs index baa56cbcc1f..1024422de80 100644 --- a/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeAssert.cs +++ b/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeAssert.cs @@ -28,7 +28,6 @@ public static void AreEqual(DateTime dt1, DateTime dt2, bool OnlyDatePart) Assert.Fail(string.Format("Expected {0} but was {1}", dt1, dt2)); } - public static void AreEqual(DateTimeOffset dt1, DateTimeOffset dt2) { bool areEqual = new DateTimeOffsetType().IsEqual(dt1, dt2); @@ -59,4 +58,4 @@ private static bool DatePartAreEqual(DateTime x, DateTime y) date1.Day == date2.Day); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs b/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs index 2fd610656a1..096419a336e 100644 --- a/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/Dates/DateTimeOffsetQueryFixture.cs @@ -38,7 +38,6 @@ protected override void Configure(Cfg.Configuration configuration) configuration.SetProperty(Environment.ShowSql, "true"); } - protected override void OnSetUp() { base.OnSetUp(); @@ -55,7 +54,6 @@ protected override void OnSetUp() } } - protected override void OnTearDown() { using (ISession s = OpenSession()) @@ -66,7 +64,6 @@ protected override void OnTearDown() } } - [Test] public void CanQueryWithCastInHql() { @@ -78,7 +75,6 @@ public void CanQueryWithCastInHql() } } - [Test(Description = "NH-3357")] public void CanQueryWithAggregateInLinq() { @@ -93,7 +89,6 @@ public void CanQueryWithAggregateInLinq() Assert.That(datesRecovered, Is.EqualTo(new DateTimeOffset(2012, 11, 1, 2, 0, 0, TimeSpan.FromHours(3)))); } - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs b/src/NHibernate.Test/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs index bcb343761d7..f19f1aef7ee 100644 --- a/src/NHibernate.Test/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/ElementsEnums/AbstractIntEnumsBagFixture.cs @@ -17,25 +17,25 @@ public void LoadEnums() { object savedId; using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { savedId = s.Save(new SimpleWithEnums { Things = new List { Something.B, Something.C, Something.D, Something.E } }); - s.Transaction.Commit(); + t.Commit(); } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var swe = s.Get(savedId); Assert.That(swe.Things, Is.EqualTo(new[] { Something.B, Something.C, Something.D, Something.E })); - s.Transaction.Commit(); + t.Commit(); } using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from SimpleWithEnums"); - s.Transaction.Commit(); + t.Commit(); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/EntityNameAndInheritance/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/EntityNameAndInheritance/Fixture.cs index c7459eede5a..24e6e9bde15 100644 --- a/src/NHibernate.Test/NHSpecificTest/EntityNameAndInheritance/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/EntityNameAndInheritance/Fixture.cs @@ -1,4 +1,3 @@ - using System.Collections; using NUnit.Framework; diff --git a/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/EntityWithUserTypeProperty.cs b/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/EntityWithUserTypeProperty.cs index 63138e0a3af..a4f8d84acfb 100644 --- a/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/EntityWithUserTypeProperty.cs +++ b/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/EntityWithUserTypeProperty.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.NHSpecificTest.EntityWithUserTypeCanHaveLinqGenerators +namespace NHibernate.Test.NHSpecificTest.EntityWithUserTypeCanHaveLinqGenerators { public class EntityWithUserTypeProperty { @@ -8,6 +7,4 @@ public class EntityWithUserTypeProperty public virtual IExample Example { get; set; } public virtual double DoubleStoredAsString { get; set; } } - - } diff --git a/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs index 2dde82ff090..66e1a522eb5 100644 --- a/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/Fixture.cs @@ -6,7 +6,6 @@ namespace NHibernate.Test.NHSpecificTest.EntityWithUserTypeCanHaveLinqGenerators { - [TestFixture] public class Fixture : TestCase { diff --git a/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/IExample.cs b/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/IExample.cs index 0c7f8db7b6f..7e521794137 100644 --- a/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/IExample.cs +++ b/src/NHibernate.Test/NHSpecificTest/EntityWithUserTypeCanHaveLinqGenerators/IExample.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.NHSpecificTest.EntityWithUserTypeCanHaveLinqGenerators { public interface IExample diff --git a/src/NHibernate.Test/NHSpecificTest/Evicting/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/Evicting/Fixture.cs index cdb76a8a09e..a2551bf2eaf 100644 --- a/src/NHibernate.Test/NHSpecificTest/Evicting/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/Evicting/Fixture.cs @@ -35,7 +35,6 @@ protected override void OnTearDown() base.OnTearDown(); } - [Test] public void Can_evict_entity_from_session() { @@ -56,7 +55,6 @@ public void Can_evict_entity_from_session() [Test] public void Can_evict_non_persistent_object() { - using (var session = Sfi.OpenSession()) using (var tx = session.BeginTransaction()) { @@ -74,11 +72,9 @@ public void Can_evict_non_persistent_object() [Test] public void Can_evict_when_trying_to_evict_entity_from_another_session() { - using (var session1 = Sfi.OpenSession()) using (var tx1 = session1.BeginTransaction()) { - using (var session2 = Sfi.OpenSession()) using (var tx2 = session2.BeginTransaction()) { @@ -98,6 +94,5 @@ public void Can_evict_when_trying_to_evict_entity_from_another_session() tx1.Commit(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs index 4a8ce621f17..58c728c7028 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0000/Fixture.cs @@ -29,7 +29,7 @@ protected override void OnTearDown() // The HQL delete does all the job inside the database without loading the entities, but it does // not handle delete order for avoiding violating constraints if any. Use // session.Delete("from System.Object"); - // instead if in need of having NHbernate ordering the deletes, but this will cause + // instead if in need of having NHibernate ordering the deletes, but this will cause // loading the entities in the session. session.CreateQuery("delete from System.Object").ExecuteUpdate(); diff --git a/src/NHibernate.Test/NHSpecificTest/GH1149/Classes.cs b/src/NHibernate.Test/NHSpecificTest/GH1149/Classes.cs index 2b131b23c61..c13763099a5 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1149/Classes.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1149/Classes.cs @@ -23,7 +23,6 @@ public Address(Company company) public virtual Company Company { get; set; } public virtual string AddressLine1 { get; set; } - } public class CompanyO2O @@ -46,6 +45,5 @@ public virtual void SetCompany(CompanyO2O company) Company = company; company.Address = this; } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1149/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1149/Fixture.cs index 152f182438c..a555a80a50c 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1149/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1149/Fixture.cs @@ -72,7 +72,6 @@ public void StatelessSessionLoadsOneToOneRelatedObject_WithoutPropertyRef() } } - using (var stateless = Sfi.OpenStatelessSession()) { var loadedCompany = stateless.Get(companyId); diff --git a/src/NHibernate.Test/NHSpecificTest/GH1180/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1180/Entity.cs new file mode 100644 index 00000000000..bf16619beb8 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1180/Entity.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH1180 +{ + internal class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual decimal Amount { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1180/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH1180/FixtureByCode.cs new file mode 100644 index 00000000000..59f87a543ca --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1180/FixtureByCode.cs @@ -0,0 +1,146 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1180 +{ + //NH-3847 + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => {m.Type(NHibernateUtil.AnsiString); m.Length(5); }); + rc.Property(x => x.Amount, m => { m.Precision(8); m.Scale(2); }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public void StringTypes() + { + var whenFalse = + Dialect is Oracle8iDialect + //Most dialects allow to return DbType.String and DbType.AnsiString in case statement + //But Oracle throws 'ORA-12704: character set mismatch' + ? Projections.Constant("otherstring", NHibernateUtil.AnsiString) + : Projections.Constant("otherstring"); + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // data + session.Save(new Entity {Name = "Alpha"}); + session.Save(new Entity {Name = "Beta"}); + session.Save(new Entity {Name = "Gamma"}); + + transaction.Commit(); + } + + // whenTrue is constant, whenFalse is property + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Not( + Restrictions.Like(nameof(Entity.Name), "B%")), + //Property - ansi string length 5; contstant - string, length 10 + whenFalse, + Projections.Property(nameof(Entity.Name))); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = tagCriteria.List(); + + Assert.That(results, Is.EquivalentTo(new[] {"otherstring", "Beta", "otherstring"})); + } + + // whenTrue is property, whenFalse is constant + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Like(nameof(Entity.Name), "B%"), + Projections.Property(nameof(Entity.Name)), + whenFalse); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = tagCriteria.List(); + + Assert.That(results, Is.EquivalentTo(new[] {"otherstring", "Beta", "otherstring"})); + } + } + + [Test] + public void DecimalTypes() + { + //On some dialects (SQLite) Scale mapping is ignored + var propertyResult = TestDialect.HasBrokenDecimalType ? 42.131m : 42.13m; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Save(new Entity {Amount = 3.141m}); + session.Save(new Entity {Amount = 42.131m}); + session.Save(new Entity {Amount = 17.991m}); + + transaction.Commit(); + } + + // whenTrue is constant, whenFalse is property + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Not( + Restrictions.Ge(nameof(Entity.Amount), 20m)), + //Property scale is 2, make sure constant scale 3 is not lost + Projections.Constant(20.123m), + Projections.Property(nameof(Entity.Amount))); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = tagCriteria.List(); + + Assert.That(results, Is.EquivalentTo(new[] {20.123m, propertyResult, 20.123m})); + } + + // whenTrue is property, whenFalse is constant + using (var session = OpenSession()) + { + ICriteria tagCriteria = session.CreateCriteria(typeof(Entity)); + + var conditionalProjection = Projections.Conditional( + Restrictions.Ge(nameof(Entity.Amount), 20m), + Projections.Property(nameof(Entity.Amount)), + Projections.Constant(20.123m)); + tagCriteria.SetProjection(conditionalProjection); + + // run query + var results = tagCriteria.List(); + + Assert.That(results, Is.EquivalentTo(new[] {20.123m, propertyResult, 20.123m})); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs new file mode 100644 index 00000000000..940b721a90b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs @@ -0,0 +1,133 @@ +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1228 +{ + public class Fixture : BugTestCase + { + [Test] + public void TestOk() + { + using (ISession s = OpenSession()) + { + using (ITransaction t = s.BeginTransaction()) + { + try + { + { + var queryThatWorks = s.CreateQuery(@" + SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT + WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv + , ROOT.Folder AS ROOT_Folder + WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1 + ) ) + AND ROOT.Name = 'SomeName'"); + queryThatWorks.List(); + } + { + var queryThatWorks = s.CreateQuery(@" + SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT + WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet + , ROOT.Folders AS ROOT_Folder + WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName' + ) ) + AND ROOT.Id = 1"); + queryThatWorks.List(); + } + } + finally + { + s.Delete("from Sheet"); + s.Delete("from Folder"); + s.Delete("from Shelf"); + t.Commit(); + } + } + } + } + + [Test] + public void TestWrongSql() + { + using (ISession s = OpenSession()) + { + using (ITransaction t = s.BeginTransaction()) + { + try + { + { + var queryThatCreatesWrongSQL = s.CreateQuery(@" + SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT + WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv + JOIN ROOT.Folder AS ROOT_Folder + WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1 + ) ) + AND ROOT.Name = 'SomeName'"); + queryThatCreatesWrongSQL.List(); + } + { + // The only assertion here is that the generated SQL is valid and can be executed. + // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder. + var queryThatCreatesWrongSQL = s.CreateQuery(@" + SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT + WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet + JOIN ROOT.Folders AS ROOT_Folder + WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName' + ) ) + AND ROOT.Id = 1"); + queryThatCreatesWrongSQL.List(); + // The only assertion here is that the generated SQL is valid and can be executed. + // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder. + } + } + finally + { + s.Delete("from Sheet"); + s.Delete("from Folder"); + s.Delete("from Shelf"); + t.Commit(); + } + } + } + } + + [Test] + public void Test3() { + using (ISession s = OpenSession()) { + using (ITransaction t = s.BeginTransaction()) { + try { + { + // The only assertion here is that the generated SQL is valid and can be executed. + // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder. + var queryThatCreatesWrongSQL = s.CreateQuery(@" + SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT + WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet + JOIN sheet.Folder AS folder + WHERE folder.Shelf = ROOT AND sheet.Name = 'SomeName' + ) ) + AND ROOT.Id = 1"); + queryThatCreatesWrongSQL.List(); + // The only assertion here is that the generated SQL is valid and can be executed. + // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder. + } + { + var queryThatCreatesWrongSQL = s.CreateQuery(@" + SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT + WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv + JOIN inv.Folders AS folder + WHERE folder = ROOT.Folder AND inv.Id = 1 + ) ) + AND ROOT.Name = 'SomeName'"); + queryThatCreatesWrongSQL.List(); + } + } + finally { + s.Delete("from Sheet"); + s.Delete("from Folder"); + s.Delete("from Shelf"); + t.Commit(); + } + } + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1228/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1228/Mappings.hbm.xml new file mode 100644 index 00000000000..e34a34bb9c0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1228/Mappings.hbm.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH1228/Model.cs b/src/NHibernate.Test/NHSpecificTest/GH1228/Model.cs new file mode 100644 index 00000000000..86d8f5cceb7 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1228/Model.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH1228 +{ + public class Shelf + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual ISet Folders { get; protected set; } = new HashSet(); + } + + public class Folder + { + public virtual int Id { get; set; } + public virtual Shelf Shelf { get; set; } + public virtual string Name { get; set; } + public virtual ISet Sheets { get; protected set; } = new HashSet(); + } + + public class Sheet + { + public virtual int Id { get; set; } + public virtual Folder Folder { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1486/Entities.cs b/src/NHibernate.Test/NHSpecificTest/GH1486/Entities.cs index efe49cfd6cd..4017d6abf61 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1486/Entities.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1486/Entities.cs @@ -1,6 +1,5 @@ using System; - namespace NHibernate.Test.NHSpecificTest.GH1486 { public class Person diff --git a/src/NHibernate.Test/NHSpecificTest/GH1496/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1496/Fixture.cs index 61ce3e75ef0..73c3be88fe8 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1496/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1496/Fixture.cs @@ -40,7 +40,6 @@ private void SetupPerson() session.Save(person); testPerson = person; - address = new Address("Postal T", "State T", "Street T"); person = new Person(2, "Tom", address); session.Save(address); @@ -66,7 +65,6 @@ private void SetupEmployee() session.Save(employee); testEmployee = employee; - employee = new Employee(2, "Tom"); contact = new Contact { Phone = "666-666-6666", ContactIdentifier = new ContactIdentifier(WORK_TYPENAME, "2") }; session.Save(contact); @@ -93,7 +91,6 @@ protected override void OnTearDown() [Test] public void EventListener_Entity_NoChange() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -166,8 +163,6 @@ public void SelectBeforeUpdate_Entity_ChangeProperty() } } - - [Test] public void EventListener_EntityWithCompositeId_ChangeProperty() { @@ -207,11 +202,9 @@ public void SelectBeforeUpdate_EntityWithCompositeId_ChangeProperty() } } - [Test] public void EventListener_ManyToOne_ChangeProperty() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -251,7 +244,6 @@ public void SelectBeforeUpdate_ManyToOne_ChangeProperty() [Test] public void EventListener_Entity_SetNewManyToOne() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -295,7 +287,6 @@ public void SelectBeforeUpdate_Entity_SetNewManyToOne() [Test] public void EventListener_ManyToOneWithCompositeId_ChangeProperty() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -335,7 +326,6 @@ public void SelectBeforeUpdate_ManyToOneWithCompositeId_ChangeProperty() [Test] public void EventListener_Entity_SetNewManyToOneWithCompositeId() { - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -355,7 +345,6 @@ public void EventListener_Entity_SetNewManyToOneWithCompositeId() } } - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) @@ -396,7 +385,6 @@ public void SelectBeforeUpdate_Entity_SetNewManyToOneWithCompositeId() } } - using (var session = OpenSession()) { using (var transaction = session.BeginTransaction()) diff --git a/src/NHibernate.Test/NHSpecificTest/GH1515/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1515/Fixture.cs index bf8753b71a0..1813850b7e9 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1515/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1515/Fixture.cs @@ -46,7 +46,5 @@ public void IntializeForwaredToPersistentCollection() collection.Received().ForceInitialization(); } - - } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1526/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1526/Fixture.cs index 5318d771ec2..ee2b82c02f9 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1526/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1526/Fixture.cs @@ -71,7 +71,7 @@ public void ShouldCreateDifferentKeys_TypeBinaryExpression() private static string GetCacheKey(Expression exp) { - return ExpressionKeyVisitor.Visit(exp, new Dictionary()); + return ExpressionKeyVisitor.Visit(exp, new Dictionary(), null); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1530/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1530/Fixture.cs index 622440fdbbe..5dee006ca8b 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1530/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1530/Fixture.cs @@ -101,5 +101,4 @@ public class NonInverseFixture : FixtureBase { protected override string[] Mappings => new[] { "NonInverseMappings.hbm.xml" }; } - } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs b/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs index efc3f52831e..70d82885768 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs @@ -5,7 +5,7 @@ namespace NHibernate.Test.NHSpecificTest.GH1594 { public static class ExecutionContextExtensions { - public static int LocalValuesCount(this ExecutionContext c) + public static int? LocalValuesCount(this ExecutionContext c) { #if NETFX const string localValuesFieldName = "_localValues"; @@ -13,8 +13,10 @@ public static int LocalValuesCount(this ExecutionContext c) const string localValuesFieldName = "m_localValues"; #endif var f = typeof(ExecutionContext).GetField(localValuesFieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - var d = (IDictionary) f.GetValue(c); - return d?.Count ?? 0; + // The property value may not implement IDictionary, especially when there is less than 4 values, but not only. + // So we may not be able to know anything about this count. + var d = f.GetValue(c) as IDictionary; + return d?.Count; } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs index 0f0118cb56a..7c342aa2dbf 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs @@ -50,8 +50,13 @@ public void ExecutionContextLocalValuesLeak() { RunInTransaction(session); var localValuesCountAfterFirstCall = ExecutionContext.Capture().LocalValuesCount(); + if (!localValuesCountAfterFirstCall.HasValue) + Assert.Ignore("Unable to get async local values count"); RunInTransaction(session); var localValuesCountAfterSecondCall = ExecutionContext.Capture().LocalValuesCount(); + if (!localValuesCountAfterSecondCall.HasValue) + Assert.Ignore("Unable to get async local values count"); + Assert.AreEqual(localValuesCountAfterFirstCall, localValuesCountAfterSecondCall); } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1738/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1738/Entity.cs new file mode 100644 index 00000000000..7671ec8d273 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1738/Entity.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH1738 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual IList Children { get; set; } = new List(); + } + + class Child + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Entity Parent { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1738/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH1738/FixtureByCode.cs new file mode 100644 index 00000000000..4cff30628ca --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1738/FixtureByCode.cs @@ -0,0 +1,87 @@ +using System; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1738 +{ + [TestFixture] + public class RefreshLocallyRemovedCollectionItemFixture : TestCaseMappingByCode + { + private Guid _id; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.Bag( + x => x.Children, + m => + { + m.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + m.Inverse(true); + m.Key(km => km.Column("Parent")); + }, + relation => relation.OneToMany()); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Bob"}; + e1.Children.Add(new Child() {Name = "Child", Parent = e1}); + session.Save(e1); + transaction.Commit(); + _id = e1.Id; + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [Test] + public void RefreshLocallyRemovedCollectionItem() + { + Entity entity; + using (var session = OpenSession()) + { + entity = session.Get(_id); + entity.Children.RemoveAt(0); + } + + using (var session = OpenSession()) + { + session.Update(entity); + session.Refresh(entity); + foreach (var child in entity.Children) + { + session.Refresh(child); + } + + Assert.That(session.GetSessionImplementation().PersistenceContext.IsReadOnly(entity), Is.False); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs index 3fe9211a449..6b491f59d00 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Exceptions; using NHibernate.Mapping.ByCode; using NHibernate.Type; using NUnit.Framework; @@ -111,7 +112,7 @@ protected void AreEqual( { expectedResult = expectedQuery(session.Query()).ToList(); } - catch (Exception e) + catch (GenericADOException e) { Assert.Ignore($"Not currently supported query: {e}"); } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1963/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1963/Fixture.cs index 5f54e4f7cb3..9701371bc93 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1963/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1963/Fixture.cs @@ -28,11 +28,6 @@ protected override void OnTearDown() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - // The HQL delete does all the job inside the database without loading the entities, but it does - // not handle delete order for avoiding violating constraints if any. Use - // session.Delete("from System.Object"); - // instead if in need of having NHibernate ordering the deletes, but this will cause - // loading the entities in the session. session.CreateQuery("delete from System.Object").ExecuteUpdate(); transaction.Commit(); @@ -50,7 +45,7 @@ where e.Flag select e; Assert.That( - result.ToList, + () => result.ToList(), Throws .InnerException.TypeOf() .And.InnerException.Message.Contains(nameof(IEnhancedUserType))); diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs new file mode 100644 index 00000000000..3d195379b5d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Entity.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH1994 +{ + public class Base + { + public virtual Guid Key { get; set; } + + public virtual bool IsDeleted { get; set; } + } + + public class Asset : Base + { + public virtual ISet Documents { get; set; } = new HashSet(); + public virtual ISet DocumentsFiltered { get; set; } = new HashSet(); + public virtual IList DocumentsBag { get; set; } = new List(); + } + + public class Document : Base + { + public virtual ISet Assets { get; set; } = new HashSet(); + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs new file mode 100644 index 00000000000..5dbe043ebfe --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs @@ -0,0 +1,143 @@ +using System.Linq; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Linq; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1994 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var a = new Asset(); + a.Documents.Add(new Document { IsDeleted = true }); + a.Documents.Add(new Document { IsDeleted = false }); + + session.Save(a); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + + session.Delete("from System.Object"); + + transaction.Commit(); + } + } + + [Test] + public void TestUnfilteredLinqQuery() + { + using (var s = OpenSession()) + { + var query = s.Query() + .FetchMany(x => x.Documents) + .ToList(); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(2), "unfiltered asset documents"); + } + } + + [Test] + public void TestFilteredByWhereCollectionLinqQuery() + { + if(Dialect is PostgreSQLDialect) + Assert.Ignore("Dialect doesn't support 0/1 to bool implicit cast"); + + using (var s = OpenSession()) + { + var query = s.Query() + .FetchMany(x => x.DocumentsFiltered) + .ToList(); + + Assert.That(query.Count, Is.EqualTo(1), "unfiltered assets"); + Assert.That(query[0].DocumentsFiltered.Count, Is.EqualTo(1), "unfiltered asset documents"); + } + } + + //GH-1994 + [Test] + public void TestFilteredLinqQuery() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + var query = s.Query() + .FetchMany(x => x.Documents) + .ToList(); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + [Test] + public void TestFilteredQueryOver() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var query = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Documents) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + [Test] + public void TestFilteredBagQueryOver() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var query = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DocumentsBag) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].DocumentsBag.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + + //NH-2991 + [Test] + public void TestQueryOverRestrictionWithClause() + { + using (var s = OpenSession()) + { + Document docs = null; + var query = s.QueryOver() + .JoinQueryOver(a => a.Documents, () => docs, JoinType.LeftOuterJoin, Restrictions.Where(() => docs.IsDeleted != true)) + .TransformUsing(Transformers.DistinctRootEntity) + .List(); + + Assert.That(query.Count, Is.EqualTo(1), "filtered assets"); + Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml new file mode 100644 index 00000000000..449a4b8bc97 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2029/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2029/Fixture.cs new file mode 100644 index 00000000000..544034db0ea --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2029/Fixture.cs @@ -0,0 +1,178 @@ +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2029 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.NullableInt32Prop); + rc.Property(x => x.Int32Prop); + rc.Property(x => x.NullableInt64Prop); + rc.Property(x => x.Int64Prop); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return !(dialect is SQLiteDialect); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.Save(new TestClass + { + Int32Prop = int.MaxValue, + NullableInt32Prop = int.MaxValue, + Int64Prop = int.MaxValue, + NullableInt64Prop = int.MaxValue + }); + session.Save(new TestClass + { + Int32Prop = int.MaxValue, + NullableInt32Prop = int.MaxValue, + Int64Prop = int.MaxValue, + NullableInt64Prop = int.MaxValue + }); + session.Save(new TestClass + { + Int32Prop = int.MaxValue, + NullableInt32Prop = null, + Int64Prop = int.MaxValue, + NullableInt64Prop = null + }); + + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.CreateQuery("delete from TestClass").ExecuteUpdate(); + + tx.Commit(); + } + } + + [Test] + public void NullableIntOverflow() + { + var hasCast = Dialect.GetCastTypeName(NHibernateUtil.Int32.SqlType) != + Dialect.GetCastTypeName(NHibernateUtil.Int64.SqlType); + + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = session.Query() + .GroupBy(i => 1) + .Select(g => new + { + s = g.Sum(i => (long) i.NullableInt32Prop) + }) + .ToList(); + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "cast"), Is.EqualTo(hasCast ? 1 : 0)); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 2)); + } + } + + [Test] + public void IntOverflow() + { + var hasCast = Dialect.GetCastTypeName(NHibernateUtil.Int32.SqlType) != + Dialect.GetCastTypeName(NHibernateUtil.Int64.SqlType); + + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = session.Query() + .GroupBy(i => 1) + .Select(g => new + { + s = g.Sum(i => (long) i.Int32Prop) + }) + .ToList(); + + Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "cast"), Is.EqualTo(hasCast ? 1 : 0)); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 3)); + } + } + + [Test] + public void NullableInt64NoCast() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = session.Query() + .GroupBy(i => 1) + .Select(g => new { + s = g.Sum(i => i.NullableInt64Prop) + }) + .ToList(); + + Assert.That(sqlLog.GetWholeLog(), Does.Not.Contains("cast")); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 2)); + } + } + + [Test] + public void Int64NoCast() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + using (var sqlLog = new SqlLogSpy()) + { + var groups = session.Query() + .GroupBy(i => 1) + .Select(g => new { + s = g.Sum(i => i.Int64Prop) + }) + .ToList(); + + Assert.That(sqlLog.GetWholeLog(), Does.Not.Contains("cast")); + Assert.That(groups, Has.Count.EqualTo(1)); + Assert.That(groups[0].s, Is.EqualTo((long) int.MaxValue * 3)); + } + } + + private int FindAllOccurrences(string source, string substring) + { + if (source == null) + { + return 0; + } + int n = 0, count = 0; + while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1) + { + n += substring.Length; + count++; + } + return count; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2029/TestClass.cs b/src/NHibernate.Test/NHSpecificTest/GH2029/TestClass.cs new file mode 100644 index 00000000000..c15c60dfee3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2029/TestClass.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Test.NHSpecificTest.GH2029 +{ + public class TestClass + { + public virtual int Id { get; set; } + public virtual int? NullableInt32Prop { get; set; } + public virtual int Int32Prop { get; set; } + public virtual long? NullableInt64Prop { get; set; } + public virtual long Int64Prop { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2064/OneToOneEntity.cs b/src/NHibernate.Test/NHSpecificTest/GH2064/OneToOneEntity.cs new file mode 100644 index 00000000000..40673d98926 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2064/OneToOneEntity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2064 +{ + public class OneToOneEntity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2064/OneToOneSelectProjectionFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2064/OneToOneSelectProjectionFixture.cs new file mode 100644 index 00000000000..7d6acdc74e5 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2064/OneToOneSelectProjectionFixture.cs @@ -0,0 +1,106 @@ +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2064 +{ + [TestFixture] + public class OneToOneSelectProjectionFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.Assigned)); + rc.Property(e => e.Name); + }); + + mapper.Class( + rc => + { + rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(e => e.Name); + rc.OneToOne(e => e.OneToOne, m => { }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var nullableOwner = new ParentEntity() {Name = "Owner",}; + var oneToOne = new OneToOneEntity() {Name = "OneToOne"}; + nullableOwner.OneToOne = oneToOne; + session.Save(nullableOwner); + oneToOne.Id = nullableOwner.Id; + session.Save(oneToOne); + session.Flush(); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void QueryOneToOne() + { + using (var session = OpenSession()) + { + var entity = + session + .Query() + .FirstOrDefault(); + Assert.That(entity.OneToOne, Is.Not.Null); + } + } + + [Test] + public void QueryOneToOneProjection() + { + using (var session = OpenSession()) + { + var entity = + session + .Query() + .Select( + x => new + { + x.Id, + SubType = new {x.OneToOne, x.Name}, + SubType2 = new + { + x.Id, + x.OneToOne, + SubType3 = new {x.Id, x.OneToOne} + }, + x.OneToOne + }).FirstOrDefault(); + Assert.That(entity.OneToOne, Is.Not.Null); + Assert.That(entity.SubType.OneToOne, Is.Not.Null); + Assert.That(entity.SubType2.OneToOne, Is.Not.Null); + Assert.That(entity.SubType2.SubType3.OneToOne, Is.Not.Null); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2064/ParentEntity.cs b/src/NHibernate.Test/NHSpecificTest/GH2064/ParentEntity.cs new file mode 100644 index 00000000000..03325f531b0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2064/ParentEntity.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2064 +{ + public class ParentEntity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual OneToOneEntity OneToOne { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2089/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2089/Fixture.cs new file mode 100644 index 00000000000..547591f7778 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2089/Fixture.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2089 +{ + [KnownBug("gh-2089")] + public class Fixture : BugTestCase + { + private Parent _parent; + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + _parent = new Parent(); + _parent.AddChild(new Child()); + s.Save(_parent); + tx.Commit(); + } + } + + [Test] + public virtual void CanAddChild() + { + var newChild = new Child(); + _parent.AddChild(newChild); + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.Merge(newChild); + tx.Commit(); + } + + using (var s = OpenSession()) + { + Assert.That(s.Get(_parent.Id).Children.Count, Is.EqualTo(2)); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.Delete(s.Load(_parent.Id)); + tx.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2089/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2089/Mappings.hbm.xml new file mode 100644 index 00000000000..c5f278906e0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2089/Mappings.hbm.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2089/Model.cs b/src/NHibernate.Test/NHSpecificTest/GH2089/Model.cs new file mode 100644 index 00000000000..5506a484b1e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2089/Model.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2089 +{ + public class Parent + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual IList Children { get; protected set; } = new List(); + + public virtual void AddChild(Child child) + { + Children.Add(child); + child.Parent = this; + } + + public override bool Equals(object obj) + { + return obj is Parent other && Id == other.Id; + } + + public override int GetHashCode() + { + return 0; + } + } + + public class Child + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual Parent Parent { get; set; } + + public virtual int OrderIndex + { + get + { + if (Parent == null) + return -2; + return Parent.Children.IndexOf(this); + } + } + + public override bool Equals(object obj) + { + return obj is Child other && Id == other.Id; + } + + public override int GetHashCode() + { + return 0; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2099/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2099/Entity.cs new file mode 100644 index 00000000000..cb0f1e2cd1a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2099/Entity.cs @@ -0,0 +1,26 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2099 +{ + abstract class PersistentObject + { + public virtual Guid Id { get; set; } + public virtual bool PDO_Deleted { get; set; } + } + + class WorkflowInstance : PersistentObject + { + public virtual bool IsWaiting { get; set; } + } + + class Level3 : WorkflowInstance + { + public virtual bool IsActive { get; set; } + } + + class WorkflowInstance2 : PersistentObject + { + public virtual bool IsActive { get; set; } + public virtual bool IsWaiting { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2099/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2099/Fixture.cs new file mode 100644 index 00000000000..d2ac2c46bb8 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2099/Fixture.cs @@ -0,0 +1,27 @@ +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2099 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + } + + protected override void OnTearDown() + { + } + + [Test] + public void BaseClassCanShareIndexNameWithSubclass() + { + var table = cfg.CreateMappings().IterateTables.FirstOrDefault(t => t.Name == "Level3"); + + var index = table.GetIndex("CF_BASE_IDX"); + Assert.That(index?.ColumnSpan, Is.EqualTo(3)); + Assert.That(index.ColumnIterator.FirstOrDefault(c => c.Name == "PDO_Deleted"), Is.Not.Null); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2099/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2099/Mappings.hbm.xml new file mode 100644 index 00000000000..128242e2aee --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2099/Mappings.hbm.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2137/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2137/Entity.cs new file mode 100644 index 00000000000..c1ce36e6db1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2137/Entity.cs @@ -0,0 +1,18 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2137 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual ExtendedProperties ExtendedProperties { get; set; } + } + + class ExtendedProperties + { + public virtual Guid Id { get; set; } + + public virtual string Value { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2137/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2137/Fixture.cs new file mode 100644 index 00000000000..9c16bd8a5d6 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2137/Fixture.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2137 +{ + [TestFixture] + public class Fixture : BugTestCase + { + private Entity e1; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHibernate ordering the deletes, but this will cause + // loading the entities in the session. + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void TestUpdateDetachedEntity() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + e1.Name = "Sally"; + session.Update(e1); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2137/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2137/Mappings.hbm.xml new file mode 100644 index 00000000000..b054d08c03b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2137/Mappings.hbm.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2175/Domain.cs b/src/NHibernate.Test/NHSpecificTest/GH2175/Domain.cs new file mode 100644 index 00000000000..920e6f591cd --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2175/Domain.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2175 +{ + public class Concept + { + public int Id { get; set; } + public string DisplayName { get; set; } + public ISet Mappings { get; private set; } = new HashSet(); + } + + public class ConceptCodeMapping + { + /// + /// Constructs empty instance. Intended for use by persistence + /// frameworks only. + /// + protected ConceptCodeMapping() + { } + + public ConceptCodeMapping(ConceptCodeRelationship relationship, ConceptCode code) + { + this.Relationship = relationship; + this.Code = code; + } + + public ConceptCodeRelationship Relationship { get; protected set; } + public ConceptCode Code { get; protected set; } + + public static bool operator ==(ConceptCodeMapping left, ConceptCodeMapping right) + { + if (ReferenceEquals(left, right)) return true; + if (ReferenceEquals(left, null) || ReferenceEquals(right, null)) return false; + + return left.Relationship == right.Relationship + && Equals(left.Code, right.Code); + } + + public static bool operator !=(ConceptCodeMapping left, ConceptCodeMapping right) + { + return !(left == right); + } + + public bool Equals(ConceptCodeMapping other) + { + return this == other; + } + + public override bool Equals(object obj) + { + return this == obj as ConceptCodeMapping; + } + + public override int GetHashCode() + { + return this.Code?.GetHashCode() ?? 0; + } + } + + public enum ConceptCodeRelationship + { + Undefined = 0, + SameAs = '=', + NarrowerThan = '<', + BroaderThan = '>' + } + + public class ConceptCode : IEquatable + { + protected ConceptCode() + { } + + public ConceptCode(string codeSource, string value) + { + this.CodeSource = codeSource; + this.Value = value; + } + + public int Id { get; set; } + public string CodeSource { get; protected set; } + public string Value { get; protected set; } + + public static bool operator ==(ConceptCode left, ConceptCode right) + { + if (ReferenceEquals(left, right)) return true; + if (ReferenceEquals(left, null) || ReferenceEquals(right, null)) return false; + + return left.Value == right.Value + && left.CodeSource == right.CodeSource; + } + + public static bool operator !=(ConceptCode left, ConceptCode right) + { + return !(left == right); + } + + public bool Equals(ConceptCode other) + { + return this == other; + } + + public override bool Equals(object obj) + { + return this == obj as ConceptCode; + } + + public override int GetHashCode() + { + unchecked + { + return ((this.CodeSource?.GetHashCode() ?? 0) * 397) + ^ (this.Value?.GetHashCode() ?? 0); + } + } + + public override string ToString() + { + return this.CodeSource + "::" + this.Value; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2175/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2175/Fixture.cs new file mode 100644 index 00000000000..fe2d10a95ef --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2175/Fixture.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2175 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(m => + { + m.Lazy(false); + m.Id(c => c.Id, id => + { + id.Column("concept_id"); + id.Generator(Generators.Native); + }); + m.Set(c => c.Mappings, + collection => + { + collection.Fetch(CollectionFetchMode.Subselect); + collection.Lazy(CollectionLazy.NoLazy); + collection.Table("concept_mapping"); + collection.Key(key => + { + key.Column("concept_id"); + }); + }, + element => + { + element.Component(component => + { + component.Property(c => c.Relationship, p => p.Column("relationship")); + component.ManyToOne(c => c.Code, manyToOne => + { + manyToOne.Column("code_id"); + manyToOne.Fetch(FetchKind.Join); + manyToOne.Cascade(NHibernate.Mapping.ByCode.Cascade.None); + }); + }); + }); + }); + + mapper.Class(m => + { + m.Table("concept_code"); + m.Lazy(false); + + m.Id(c => c.Id, id => + { + id.Column("code_id"); + id.Generator(Generators.Native); + }); + + m.Property(c => c.CodeSource, p => + { + p.Column("source"); + }); + m.Property(c => c.Value, p => + { + p.Column("`value`"); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public void SubcriteriaOnNestedComponent() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var concepts = new List(); + for (var i = 1; i < 10; i++) + { + var concept = new Concept { DisplayName = $"Concept{i}" }; + concept.Mappings.Add(new ConceptCodeMapping(ConceptCodeRelationship.SameAs, + CreatePersistentCode(session, "src-x", "C" + i))); + concept.Mappings.Add(new ConceptCodeMapping(ConceptCodeRelationship.SameAs, + CreatePersistentCode(session, "src-y", "CX" + (0x1000 + i).ToString("X")))); + concepts.Add(concept); + session.Save(concept); + } + + var criteria = session.CreateCriteria() + .CreateAlias(nameof(Concept.Mappings), "cm") + .CreateCriteria("cm." + nameof(ConceptCodeMapping.Code), "code") + .Add(Restrictions.Eq(nameof(ConceptCode.CodeSource), "src-y")) + .Add(Restrictions.In(nameof(ConceptCode.Value), new object[] { "CX1001", "CX1003", "CX1008" })); + + var persistentConcepts = criteria.List(); + Assert.That(persistentConcepts, Is.EquivalentTo(new[] { concepts[0], concepts[2], concepts[7] })); + } + } + + private ConceptCode CreatePersistentCode(ISession session, string codeSource, string value) + { + var code = new ConceptCode(codeSource, value); + session.Save(code); + return code; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2286/Entities.cs b/src/NHibernate.Test/NHSpecificTest/GH2286/Entities.cs new file mode 100644 index 00000000000..29712c0f79b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2286/Entities.cs @@ -0,0 +1,18 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2286 +{ + class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + class Customer : Entity + { + } + + class IndividualCustomer : Customer + { + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2286/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2286/Fixture.cs new file mode 100644 index 00000000000..12c99d8b4ab --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2286/Fixture.cs @@ -0,0 +1,47 @@ +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2286 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public void FilterOnJoinedSubclassKey() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var c1 = new IndividualCustomer {Id = 2, Name = "2"}; + var c2 = new IndividualCustomer {Id = 4, Name = "4"}; + session.Save(c1); + session.Save(c2); + + session.Flush(); + transaction.Commit(); + } + + using (var s = OpenSession()) + { + var count = s.Query().Select(c => c.Id).ToList().Count; + s.EnableFilter("filterName"); + var countFiltered = s.Query().Select(c => c.Id).ToList().Count; + + Assert.AreEqual(2, count); + Assert.AreEqual(1, countFiltered); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2286/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2286/Mappings.hbm.xml new file mode 100644 index 00000000000..b3e4dcb4db9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2286/Mappings.hbm.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs new file mode 100644 index 00000000000..bb8354e2b44 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/Fixture.cs @@ -0,0 +1,144 @@ +using System; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var user = new User { UserCode = "gokhanabatay", IsOpen = true, UserName = "Gökhan Abatay" }; + session.Save(user); + + for (var i = 0; i < 10; i++) + { + var userSession = new UserSession + { + Claims = "My Claims", + ExpireDateTime = DateTime.Now.AddDays(1), + MbrId = 1, + OpenDate = DateTime.Now, + LocalIpAddress = "192.168.1.1", + RemoteIpAddress = "127.0.0.1", + LocalPort = 80 + i.ToString(), + RemotePort = 80 + i.ToString(), + DeviceId = "None", + UserCode = "gokhanabatay", + IsOpen = true + }; + + session.Save(userSession); + } + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from UserSession").ExecuteUpdate(); + session.CreateQuery("delete from User").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void Get_DateCustomType_NullableDateValueEquals() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = session.Query().Where(x => x.OpenDate.Value == DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public void Get_DateCustomType_NullableDateValueEqualsMethod() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = session.Query().Where(x => x.OpenDate.Value.Equals(DateTime.Now)).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public void Get_DateTimeCustomType_NullableDateValueEquals() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = session.Query().Where(x => x.ExpireDateTime.Value > DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public void Get_DateCustomType_DateEquals() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = session.Query().Where(x => x.OpenDate == DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public void Get_DateTimeCustomType_DateEquals() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var sessions = session.Query().Where(x => x.ExpireDateTime > DateTime.Now).ToList(); + + Assert.That(sessions, Has.Count.EqualTo(10)); + } + } + + [Test] + public void Get_BooleanCustomType() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var results = session.Query() + .Where(x => x.OpenDate == DateTime.Now) + .Select(x => new NullableBooleanResult() {IsOpen = x.User.IsOpen}) + .ToList(); + + Assert.That(results, Has.Count.EqualTo(10)); + } + } + + public class NullableBooleanResult + { + public bool? IsOpen { get; set; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs new file mode 100644 index 00000000000..ca2eb52f3ff --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlBoolean.cs @@ -0,0 +1,181 @@ +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.UserTypes; +using System; +using System.Data; +using System.Data.Common; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + [Serializable] + public class SqlBoolean : IEnhancedUserType + { + /// + /// Compare two instances of the class mapped by this type for persistent "equality" + /// ie. equality of persistent state + /// + /// + /// + public new bool Equals(object x, object y) + { + if (x == y) + { + return true; + } + if (x == null || y == null) + { + return false; + } + + return x.Equals(y); + } + + /// + /// Get a hashcode for the instance, consistent with persistence "equality" + /// + public int GetHashCode(object x) + { + if (x == null) + { + return 0; + } + + return x.GetHashCode(); + } + + /// + /// Retrieve an instance of the mapped class from a JDBC resultset. + /// Implementors should handle possibility of null values. + /// + /// a IDataReadercolumn namesthe containing entity + /// session + /// + /// HibernateException + public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) + { + object result = NHibernateUtil.Int16.NullSafeGet(rs, names[0], session, owner); + + if (result == null) + { + return default(bool?); + } + else + { + return ((short) result) == 1; + } + } + + /// + /// Write an instance of the mapped class to a prepared statement. + /// Implementors should handle possibility of null values. + /// A multi-column type should be written to parameters starting from index. + /// + /// a IDbCommandthe object to write + /// command parameter index + /// session + /// HibernateException + public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + short? result = value != null ? Convert.ToInt16(value) : default(short?); + + NHibernateUtil.Int16.NullSafeSet(cmd, result, index, session); + } + + /// + /// Return a deep copy of the persistent state, stopping at entities and at collections. + /// + /// generally a collection element or entity field + /// + /// a copy + /// + public object DeepCopy(object value) + { + return value; + } + + /// + /// During merge, replace the existing () value in the entity + /// we are merging to with a new () value from the detached + /// entity we are merging. For immutable objects, or null values, it is safe to simply + /// return the first parameter. For mutable objects, it is safe to return a copy of the + /// first parameter. For objects with component values, it might make sense to + /// recursively replace component values. + /// + /// the value from the detached entity being mergedthe value in the managed entitythe managed entity + /// + /// the value to be merged + /// + public object Replace(object original, object target, object owner) + { + return DeepCopy(original); + } + + /// + /// Reconstruct an object from the cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. (optional operation) + /// + /// the object to be cachedthe owner of the cached object + /// + /// a reconstructed object from the cacheable representation + /// + public object Assemble(object cached, object owner) + { + return DeepCopy(cached); + } + + /// + /// Transform the object into its cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. That may not be enough + /// for some implementations, however; for example, associations must be cached as + /// identifier values. (optional operation) + /// + /// the object to be cached + /// + /// a cacheable representation of the object + /// + public object Disassemble(object value) + { + return DeepCopy(value); + } + + public object FromXMLString(string xml) + { + if (bool.TryParse(xml, out var value)) + { + return value; + } + + return null; + } + + public string ObjectToSQLString(object value) + { + if (value == null) + { + return "is null"; + } + + return ((bool) value) ? "1" : "0"; + } + + public string ToXMLString(object value) + { + return value?.ToString(); + } + + /// + /// The SQL types for the columns mapped by this type. + /// + public SqlType[] SqlTypes => new[] { new SqlType(DbType.Int16) }; + + /// + /// The type returned by NullSafeGet() + /// + public System.Type ReturnedType => typeof(bool?); + + /// + /// Are objects of this type mutable? + /// + public bool IsMutable => false; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs new file mode 100644 index 00000000000..feb28b4755b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDate.cs @@ -0,0 +1,58 @@ +using System; +using System.Data; +using System.Data.Common; +using NHibernate.Engine; +using NHibernate.SqlTypes; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + /// + /// Short Number Date support with format yyyyMMdd + /// + [Serializable] + public class SqlNumberDate : SqlNumberDateTime + { + protected override string Format { get; } = "yyyyMMdd"; + public static readonly DateTime DefaultDate = new DateTime(1900, 1, 1); + + /// + /// Write an instance of the mapped class to a prepared statement. + /// Implementors should handle possibility of null values. + /// A multi-column type should be written to parameters starting from index. + /// + /// a IDbCommandthe object to writecommand parameter indexHibernateException + public override void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + Int32? result; + if (value != null) + { + var dateTime = (DateTime?) value; + if (dateTime < DefaultDateTime) + { + result = default(Int32?); + } + else + { + result = Int32.Parse(dateTime?.ToString(Format)); + } + } + else + { + result = default(Int32?); + } + + NHibernateUtil.Int32.NullSafeSet(cmd, result, index, session); + } + + /// + /// The SQL types for the columns mapped by this type. + /// + public override SqlType[] SqlTypes + { + get + { + return new[] { new SqlType(DbType.Int32) }; + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs new file mode 100644 index 00000000000..62dec517e92 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/SqlNumberDateTime.cs @@ -0,0 +1,182 @@ +using System; +using System.Data; +using System.Data.Common; +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.UserTypes; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + /// + /// Long Number Date support with format yyyyMMddHHmmssfff + /// + [Serializable] + public class SqlNumberDateTime : IUserType + { + protected virtual string Format { get; } = "yyyyMMddHHmmssfff"; + protected static readonly DateTime DefaultDateTime = new DateTime(1900, 1, 1, 0, 0, 0, 0); + + /// + /// Compare two instances of the class mapped by this type for persistent "equality" + /// ie. equality of persistent state + /// + /// + /// + public new bool Equals(object x, object y) + { + if (x == y) return true; + if (x == null && y != null && ((DateTime?) y) < DefaultDateTime) + { + return true; + } + if (x == null || y == null) return false; + DateTime? l = (DateTime?) x; + DateTime? r = (DateTime?) y; + + return l.Equals(r); + } + + /// + /// Get a hashcode for the instance, consistent with persistence "equality" + /// + public int GetHashCode(object x) + { + DateTime? value = (DateTime?) x; + + return value.GetHashCode(); + } + + /// + /// Retrieve an instance of the mapped class from a JDBC resultset. + /// Implementors should handle possibility of null values. + /// + /// a IDataReadercolumn namesthe containing entity + /// + /// HibernateException + public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) + { + object result = NHibernateUtil.String.NullSafeGet(rs, names[0], session, owner); + + if (result == null) + return default(DateTime?); + else + { + DateTime.TryParseExact(result.ToString(), Format, null, System.Globalization.DateTimeStyles.None, out var date); + return date; + } + } + + /// + /// Write an instance of the mapped class to a prepared statement. + /// Implementors should handle possibility of null values. + /// A multi-column type should be written to parameters starting from index. + /// + /// a IDbCommandthe object to writecommand parameter indexHibernateException + public virtual void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + Int64? result; + if (value != null) + { + var dateTime = (DateTime?) value; + if (dateTime < DefaultDateTime) + { + result = default(Int64?); + } + else + { + result = Int64.Parse(dateTime?.ToString(Format)); + } + } + else + { + result = default(Int64?); + } + + NHibernateUtil.Int64.NullSafeSet(cmd, result, index, session); + } + + /// + /// Return a deep copy of the persistent state, stopping at entities and at collections. + /// + /// generally a collection element or entity field + /// + /// a copy + /// + public object DeepCopy(object value) + { + return value; + } + + /// + /// During merge, replace the existing () value in the entity + /// we are merging to with a new () value from the detached + /// entity we are merging. For immutable objects, or null values, it is safe to simply + /// return the first parameter. For mutable objects, it is safe to return a copy of the + /// first parameter. For objects with component values, it might make sense to + /// recursively replace component values. + /// + /// the value from the detached entity being mergedthe value in the managed entitythe managed entity + /// + /// the value to be merged + /// + public object Replace(object original, object target, object owner) + { + return DeepCopy(original); + } + + /// + /// Reconstruct an object from the cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. (optional operation) + /// + /// the object to be cachedthe owner of the cached object + /// + /// a reconstructed object from the cachable representation + /// + public object Assemble(object cached, object owner) + { + return DeepCopy(cached); + } + + /// + /// Transform the object into its cacheable representation. At the very least this + /// method should perform a deep copy if the type is mutable. That may not be enough + /// for some implementations, however; for example, associations must be cached as + /// identifier values. (optional operation) + /// + /// the object to be cached + /// + /// a cacheable representation of the object + /// + public object Disassemble(object value) + { + return DeepCopy(value); + } + + /// + /// The SQL types for the columns mapped by this type. + /// + public virtual SqlType[] SqlTypes + { + get + { + return new[] { new SqlType(DbType.Int64) }; + } + } + + /// + /// The type returned by NullSafeGet() + /// + public System.Type ReturnedType + { + get { return typeof(DateTime?); } + } + + /// + /// Are objects of this type mutable? + /// + public bool IsMutable + { + get { return false; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/User.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/User.cs new file mode 100644 index 00000000000..00c01c07e2a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/User.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class User + { + public virtual string UserCode { get; set; } + public virtual bool IsOpen { get; set; } + public virtual string UserName { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs new file mode 100644 index 00000000000..eefd2d6d602 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/UserMapping.cs @@ -0,0 +1,21 @@ +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class UserMapping : ClassMapping + { + public UserMapping() + { + Table("Users"); + Id(o => o.UserCode, m => m.Generator(Generators.Assigned)); + Property( + o => o.IsOpen, + m => + { + m.Type(); + m.Precision(8); + }); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs new file mode 100644 index 00000000000..ac31f5b5e6f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSession.cs @@ -0,0 +1,21 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class UserSession + { + public virtual long Guid { get; set; } + public virtual short MbrId { get; set; } + public virtual string UserCode { get; set; } + public virtual DateTime? OpenDate { get; set; } + public virtual DateTime? ExpireDateTime { get; set; } + public virtual bool? IsOpen { get; set; } + public virtual string RemoteIpAddress { get; set; } + public virtual string RemotePort { get; set; } + public virtual string LocalIpAddress { get; set; } + public virtual string LocalPort { get; set; } + public virtual string DeviceId { get; set; } + public virtual string Claims { get; set; } + public virtual User User { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs new file mode 100644 index 00000000000..1c164fe0c3c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2437/UserSessionMapping.cs @@ -0,0 +1,38 @@ +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace NHibernate.Test.NHSpecificTest.GH2437 +{ + public class UserSessionMapping : ClassMapping + { + public UserSessionMapping() + { + Table("UserSessions"); + Id(o => o.Guid, m => m.Generator(Generators.Native)); + Property(o => o.UserCode, m => m.NotNullable(true)); + Property(o => o.OpenDate, m => + { + m.Type(); + m.Precision(8); + }); + Property(o => o.ExpireDateTime, m => + { + m.Type(); + m.Precision(17); + }); + Property(o => o.IsOpen, m => + { + m.Type(); + m.Precision(8); + }); + ManyToOne( + o => o.User, + m => + { + m.Column("UserCode"); + m.Insert(false); + m.Update(false); + }); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2445/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2445/Fixture.cs new file mode 100644 index 00000000000..1963dacee42 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2445/Fixture.cs @@ -0,0 +1,156 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NHibernate.Test.NHSpecificTest.GH2445; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2446 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.Hbm2ddlKeyWords, "auto-quote"); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MySQLDialect || dialect is SQLiteDialect; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(m => + { + m.Id(c => c.Id, id => + { + id.Generator(Generators.Native); + }); + m.Property(o => o.Short, o => o.NotNullable(true)); + m.Property(o => o.Integer, o => o.NotNullable(true)); + m.Property(o => o.Long, o => o.NotNullable(true)); + m.Property(o => o.NullableNumber); + m.Property(o => o.ShortNumber, o => o.NotNullable(true)); + m.Property(o => o.NullableShortNumber); + m.Property(o => o.LargeNumber, o => o.NotNullable(true)); + m.Property(o => o.NullableLargeNumber); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var entity = new UnsignedEntity() + { + Short = 123, + Integer = 12345, + Long = 123456789, + LargeNumber = 123456789, + NullableLargeNumber = null, + NullableNumber = null, + ShortNumber = 123, + NullableShortNumber = null + }; + session.Save(entity); + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + session.CreateQuery("delete from UnsignedEntity").ExecuteUpdate(); + tx.Commit(); + } + } + + [Test] + public void TestUInt() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var number = 10; + session.Query().Where(o => o.Id == (uint) number).ToList(); + session.Query().Where(o => o.Id == (uint) o.Integer).ToList(); + session.Query().Where(o => o.Id > number).ToList(); + } + } + + [Test] + public void TestNullableUInt() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var number = 10; + session.Query().Where(o => o.NullableNumber == (uint?) number).ToList(); + session.Query().Where(o => o.NullableNumber == (uint?) o.Integer).ToList(); + session.Query().Where(o => o.NullableNumber > number).ToList(); + } + } + + + [Test] + public void TestUShort() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + short number = 10; + session.Query().Where(o => o.ShortNumber == (ushort) number).ToList(); + session.Query().Where(o => o.ShortNumber == (ushort) o.Short).ToList(); + session.Query().Where(o => o.ShortNumber > number).ToList(); + } + } + + [Test] + public void TestNullableUShort() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + short number = 10; + session.Query().Where(o => o.NullableShortNumber == (ushort?) number).ToList(); + session.Query().Where(o => o.NullableShortNumber == (ushort?) o.Short).ToList(); + session.Query().Where(o => o.NullableShortNumber > number).ToList(); + } + } + + [Test] + public void TestULong() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + long number = 10; + session.Query().Where(o => o.LargeNumber == (ulong) number).ToList(); + session.Query().Where(o => o.LargeNumber == (ulong) o.Long).ToList(); + session.Query().Where(o => o.LargeNumber > (ulong) number).ToList(); + } + } + + [Test] + public void TestNullableULong() + { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + long number = 10; + session.Query().Where(o => o.NullableLargeNumber == (ulong?) number).ToList(); + session.Query().Where(o => o.NullableLargeNumber == (ulong?) o.Long).ToList(); + session.Query().Where(o => o.NullableLargeNumber > (ulong?) number).ToList(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2445/UnsignedEntity.cs b/src/NHibernate.Test/NHSpecificTest/GH2445/UnsignedEntity.cs new file mode 100644 index 00000000000..f31bda62efa --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2445/UnsignedEntity.cs @@ -0,0 +1,23 @@ +namespace NHibernate.Test.NHSpecificTest.GH2445 +{ + public class UnsignedEntity + { + public virtual uint Id { get; set; } + + public virtual short Short { get; set; } + + public virtual int Integer { get; set; } + + public virtual long Long { get; set; } + + public virtual uint? NullableNumber { get; set; } + + public virtual ushort ShortNumber { get; set; } + + public virtual ushort? NullableShortNumber { get; set; } + + public virtual ulong LargeNumber { get; set; } + + public virtual ulong? NullableLargeNumber { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2454/Domain.cs b/src/NHibernate.Test/NHSpecificTest/GH2454/Domain.cs new file mode 100644 index 00000000000..48fe46e2b5c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2454/Domain.cs @@ -0,0 +1,25 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2454 +{ + public class Project + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public class Component + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Project Project { get; set; } + } + + public class Tag + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Component Component1 { get; set; } + public virtual Component Component2 { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2454/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH2454/FixtureByCode.cs new file mode 100644 index 00000000000..30a93637682 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2454/FixtureByCode.cs @@ -0,0 +1,119 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Criterion; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlCommand; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2454 +{ + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Project, m => { m.Column("ProjectId"); m.NotNullable(true); }); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Component1, m => { m.Column("Component1Id"); m.NotNullable(true); }); + rc.ManyToOne(x => x.Component2, m => { m.Column("Component2Id"); m.NotNullable(false); }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // alpha entities + var projectAlpha = new Project {Name = "Alpha"}; + session.Save(projectAlpha); + + var componentAlpha = new Component {Project = projectAlpha, Name = "Thingie"}; + session.Save(componentAlpha); + + var tagAlpha = new Tag {Component1 = componentAlpha, Name = "A20"}; + session.Save(tagAlpha); + + // beta entities + var projectBeta = new Project {Name = "Beta"}; + session.Save(projectBeta); + + var componentBeta = new Component {Project = projectBeta, Name = "Thingie"}; + session.Save(componentBeta); + + var tagBeta = new Tag {Component1 = componentBeta, Name = "B17"}; + session.Save(tagBeta); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Tag").ExecuteUpdate(); + session.CreateQuery("delete from Component").ExecuteUpdate(); + session.CreateQuery("delete from Project").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public void SubqueryCorrelatedThroughConditional() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + var tagCriteria = session.CreateCriteria(typeof(Tag), "t"); + tagCriteria.CreateCriteria("Component1", "c1"); + tagCriteria.CreateCriteria("Component2", "c2", JoinType.LeftOuterJoin); + + // create correlated subquery + var projectCriteria = DetachedCriteria.For(typeof(Project), "p"); + + var conditionalCorrelationProjection = Projections.Conditional( + Restrictions.IsNotNull(Projections.Property("t.Component2")), + Projections.Property("c2.Project"), + Projections.Property("c1.Project")); + projectCriteria.Add(Restrictions.EqProperty("p.Id", conditionalCorrelationProjection)); + + projectCriteria.SetProjection(Projections.Property("p.Name")); + + var projectNameProjection = Projections.SubQuery(projectCriteria); + + tagCriteria.Add(Restrictions.Eq(projectNameProjection, "Beta")); + tagCriteria.SetProjection(Projections.Property("t.Name")); + + // run query + var results = tagCriteria.List(); + + Assert.That(results, Is.EquivalentTo(new[] {"B17"})); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2463/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2463/Fixture.cs new file mode 100644 index 00000000000..c8253b90014 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2463/Fixture.cs @@ -0,0 +1,75 @@ +using System.Linq; +using NHibernate.Criterion; +using NHibernate.DomainModel; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2463 +{ + [TestFixture] + public class Fixture : TestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return Dialect.SupportsScalarSubSelects; + } + + protected override string[] Mappings + { + get { return new[] {"ABC.hbm.xml"}; } + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var a = new A {Name = "A", AnotherName = "X"}; + session.Save(a); + + var b = new B {Name = "B", AnotherName = "X"}; + session.Save(b); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + //Also see GH-2599 + [Test] + public void CanJoinOnEntityWithDiscriminatorLinq() + { + using (var s = OpenSession()) + { + var list = s.Query().Join( + s.Query(), + a => a.AnotherName, + b => b.AnotherName, + (a, b) => + new {a, b}).ToList(); + } + } + + [Test] + public void CanJoinOnEntityWithDiscriminatorQueryOver() + { + using (var s = OpenSession()) + { + A a = null; + B b = null; + var list = s.QueryOver(() => a) + .JoinEntityAlias(() => b, () => a.AnotherName == b.AnotherName) + .Select((x) => a.AsEntity(), (x) => b.AsEntity()).List(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2465/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2465/Entity.cs new file mode 100644 index 00000000000..72dac9bddcf --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2465/Entity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2465 +{ + public class Entity + { + private readonly IList _identityNames = new List(); + + public virtual Guid Id { get; set; } + + public virtual IList IdentityNames => _identityNames; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2465/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2465/Fixture.cs new file mode 100644 index 00000000000..121b3756270 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2465/Fixture.cs @@ -0,0 +1,88 @@ +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2465 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var applicant = new Entity {IdentityNames = {"name1", "name2"}}; + session.Save(applicant); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [Test] + public void ContainsInsideValueCollection() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var identityNames = new[] {"name1", "x"}; + session + .Query() + .Where(a => a.IdentityNames.Any(n => identityNames.Contains(n))) + .ToList(); + session + .Query() + .Where(a => a.IdentityNames.All(n => identityNames.Contains(n))) + .ToList(); + session + .Query() + .Where(a => a.IdentityNames.FirstOrDefault(n => identityNames.Contains(n)) == "test") + .ToList(); + + transaction.Commit(); + } + } + + [Test] + public void EqualsInsideValueCollection() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var value = "test"; + session + .Query() + .Where(a => a.IdentityNames.Any(n => n == value)) + .ToList(); + session + .Query() + .Where(a => a.IdentityNames.Any(n => (string) n == value)) + .ToList(); + session + .Query() + .Where(a => a.IdentityNames.All(n => n == value)) + .ToList(); + session + .Query() + .Where(a => a.IdentityNames.FirstOrDefault(n => n == "test") == "test") + .ToList(); + + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2465/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2465/Mappings.hbm.xml new file mode 100644 index 00000000000..996ab1025da --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2465/Mappings.hbm.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2508/AuditEventListener.cs b/src/NHibernate.Test/NHSpecificTest/GH2508/AuditEventListener.cs new file mode 100644 index 00000000000..8d6d2c6b291 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2508/AuditEventListener.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cfg; +using NHibernate.Event; +using NHibernate.Persister.Entity; + +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + public partial class AuditEventListener : IPreCollectionUpdateEventListener + { + public void OnPreUpdateCollection(PreCollectionUpdateEvent @event) + { + var ownerEntity = @event.AffectedOwnerOrNull; + var collectionEntry = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection); + if (!collectionEntry.LoadedPersister.IsInverse) + return; + + var abstractCollectionPersister = collectionEntry.LoadedPersister as Persister.Collection.AbstractCollectionPersister; + if (abstractCollectionPersister == null) + return; + + var ownerEntityPersister = abstractCollectionPersister.OwnerEntityPersister; + ownerEntityPersister.GetPropertyValues(ownerEntity); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2508/Child.cs b/src/NHibernate.Test/NHSpecificTest/GH2508/Child.cs new file mode 100644 index 00000000000..fd35585f90c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2508/Child.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + public class Child + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual LoggerBase Logger { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2508/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2508/Fixture.cs new file mode 100644 index 00000000000..b7ef5cef56f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2508/Fixture.cs @@ -0,0 +1,60 @@ +using System.Linq; +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void Configure(Configuration configuration) + { + var listeners = configuration.EventListeners; + listeners.PreCollectionUpdateEventListeners = + new[] {new AuditEventListener()} + .Concat(listeners.PreCollectionUpdateEventListeners) + .ToArray(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new LoggerCase {Name = "Bob"}; + session.Save(e1); + + var e2 = new LoggerCase {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + transaction.Commit(); + } + } + + [Test] + public void TestPreCollectionUpdateEvent() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var result = (from e in session.Query() + where e.Name == "Bob" + select e).First(); + + result.Children.Add(new Child { Logger = result, Name = "child" }); + session.SaveOrUpdate(result); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2508/LoggerBase.cs b/src/NHibernate.Test/NHSpecificTest/GH2508/LoggerBase.cs new file mode 100644 index 00000000000..c348790c73d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2508/LoggerBase.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + public abstract class LoggerBase + { + public LoggerBase() + { + Children = new List(); + } + + public virtual string Solution { get; set; } + + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + public virtual IList Children { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2508/LoggerCase.cs b/src/NHibernate.Test/NHSpecificTest/GH2508/LoggerCase.cs new file mode 100644 index 00000000000..fcc495c2505 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2508/LoggerCase.cs @@ -0,0 +1,7 @@ +namespace NHibernate.Test.NHSpecificTest.GH2508 +{ + public class LoggerCase : LoggerBase + { + public virtual string Description { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2508/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2508/Mappings.hbm.xml new file mode 100644 index 00000000000..8b7a549cb20 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2508/Mappings.hbm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2549/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2549/Fixture.cs new file mode 100644 index 00000000000..a01c1ec2572 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2549/Fixture.cs @@ -0,0 +1,70 @@ +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2549 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new Person {Id = 1, Name = "Name"}); + s.Save(new Customer {Deleted = false, Name = "Name", Id = 1}); + s.Save(new Customer {Deleted = true, Name = "Name", Id = 2}); + + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + t.Commit(); + } + } + + [Test] + public void EntityJoinFilterLinq() + { + using (var s = OpenSession()) + { + var list = (from p in s.Query() + join c in s.Query() on p.Name equals c.Name + select p).ToList(); + + s.EnableFilter("DeletedCustomer").SetParameter("deleted", false); + + var filteredList = (from p in s.Query() + join c in s.Query() on p.Name equals c.Name + select p).ToList(); + + Assert.That(list, Has.Count.EqualTo(2)); + Assert.That(filteredList, Has.Count.EqualTo(1)); + } + } + + [Test] + public void EntityJoinFilterQueryOver() + { + using (var s = OpenSession()) + { + Customer c = null; + Person p = null; + var list = s.QueryOver(() => p).JoinEntityAlias(() => c, () => c.Name == p.Name).List(); + + s.EnableFilter("DeletedCustomer").SetParameter("deleted", false); + + var filteredList = s.QueryOver(() => p).JoinEntityAlias(() => c, () => c.Name == p.Name).List(); + + Assert.That(list, Has.Count.EqualTo(2)); + Assert.That(filteredList, Has.Count.EqualTo(1)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2549/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH2549/Mappings.hbm.xml new file mode 100644 index 00000000000..65b540b9202 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2549/Mappings.hbm.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH2549/Model.cs b/src/NHibernate.Test/NHSpecificTest/GH2549/Model.cs new file mode 100644 index 00000000000..b6d678c7b20 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2549/Model.cs @@ -0,0 +1,15 @@ +namespace NHibernate.Test.NHSpecificTest.GH2549 +{ + public class Customer + { + public virtual int Id { get; set; } + public virtual bool Deleted { get; set; } + public virtual string Name { get; set; } + } + + public class Person + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Car.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Car.cs new file mode 100644 index 00000000000..b93e028c8b6 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Car.cs @@ -0,0 +1,12 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Car + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual Person Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Child.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Child.cs new file mode 100644 index 00000000000..43f56d8b701 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Child.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Child + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual Person Parent { get; set; } + + public virtual ISet Pets + { + get => _pets ?? (_pets = new HashSet()); + set => _pets = value; + } + private ISet _pets; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/FixtureByCode.cs new file mode 100644 index 00000000000..33348db8bd9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/FixtureByCode.cs @@ -0,0 +1,145 @@ +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Guid)); + rc.Property(x => x.Name); + rc.Property(x => x.Age); + rc.Set( + x => x.Children, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Set( + x => x.Cars, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + rc.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Parent); + + ch.Set( + x => x.Pets, + colMap => + { + colMap.Inverse(true); + colMap.Cascade(Mapping.ByCode.Cascade.DeleteOrphans); + colMap.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }, + rel => rel.OneToMany()); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + mapper.Class(ch => + { + ch.Id(x => x.Id, m => m.Generator(Generators.Guid)); + ch.Property(x => x.Name); + ch.ManyToOne(c => c.Owner); + + ch.Cache(c => c.Usage(CacheUsage.ReadWrite)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var person = new Person { Name = "Person 1", Age = 18 }; + + var car1 = new Car { Name = "Car1", Owner = person }; + var car2 = new Car { Name = "Car2", Owner = person }; + session.Save(car1); + session.Save(car2); + + session.Save(person); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Pet").ExecuteUpdate(); + session.CreateQuery("delete from Child").ExecuteUpdate(); + session.CreateQuery("delete from Car").ExecuteUpdate(); + session.CreateQuery("delete from Person").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public void TestQueryCachingWithThenFetchMany() + { + Person dbPerson; + Person cachePerson; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var query = + session + .Query() + .FetchMany(p => p.Children) + .ThenFetchMany(ch => ch.Pets) + .FetchMany(p => p.Cars) as IQueryable; + + query = query.WithOptions(opt => + opt.SetCacheable(true) + .SetCacheMode(CacheMode.Normal) + .SetCacheRegion("Long_Cache")); + + dbPerson = query.ToList().First(); // First time the result will be cached + cachePerson = query.ToList().First(); + + transaction.Commit(); + } + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Cars), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Cars), Is.True); + Assert.That(dbPerson.Cars, Has.Count.EqualTo(2)); + Assert.That(cachePerson.Cars, Has.Count.EqualTo(2)); + + Assert.That(NHibernateUtil.IsInitialized(dbPerson.Children), Is.True); + Assert.That(NHibernateUtil.IsInitialized(cachePerson.Children), Is.True); + Assert.That(dbPerson.Children, Has.Count.EqualTo(0)); + Assert.That(cachePerson.Children, Has.Count.EqualTo(0)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Person.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Person.cs new file mode 100644 index 00000000000..8d125021ec9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Person.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Person + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual int Age { get; set; } + + public virtual ISet Cars + { + get => _cars ?? (_cars = new HashSet()); + set => _cars = value; + } + private ISet _cars; + + public virtual ISet Children + { + get => _children ?? (_children = new HashSet()); + set => _children = value; + } + private ISet _children; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2559/Pet.cs b/src/NHibernate.Test/NHSpecificTest/GH2559/Pet.cs new file mode 100644 index 00000000000..7b580d24bd1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2559/Pet.cs @@ -0,0 +1,12 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2559 +{ + public class Pet + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + + public virtual Child Owner { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/HqlOnMapWithForumula/Domain.cs b/src/NHibernate.Test/NHSpecificTest/HqlOnMapWithForumula/Domain.cs index 19ac5286d86..5f792ac1b86 100644 --- a/src/NHibernate.Test/NHSpecificTest/HqlOnMapWithForumula/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/HqlOnMapWithForumula/Domain.cs @@ -22,7 +22,6 @@ public virtual int ID set { id = value; } } - public IDictionary MyMaps { get { return myMap; } @@ -30,7 +29,6 @@ public IDictionary MyMaps } } - public class Info { private int rowCount; diff --git a/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Employee.cs b/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Employee.cs index 5ecf823a1ce..2babfd1cdd7 100644 --- a/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Employee.cs +++ b/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Employee.cs @@ -20,8 +20,5 @@ public int Id get { return id; } set { id = value; } } - - - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Fixture.cs index 1f58ff9c20f..06c6c1c3c93 100644 --- a/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/ListsWithHoles/Fixture.cs @@ -45,7 +45,6 @@ public void CanHandleHolesInList() tx.Commit(); } - using (ISession sess = OpenSession()) using (ITransaction tx = sess.BeginTransaction()) { @@ -54,8 +53,6 @@ public void CanHandleHolesInList() tx.Commit(); } - - using (ISession sess = OpenSession()) using (ITransaction tx = sess.BeginTransaction()) { diff --git a/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Employee.cs b/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Employee.cs index fc3671e00b8..151faa2c94a 100644 --- a/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Employee.cs +++ b/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Employee.cs @@ -4,7 +4,6 @@ namespace NHibernate.Test.NHSpecificTest.LoadingNullEntityInSet { - public class Employee { private int id; diff --git a/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs index ac23623834b..e4d6b70bb92 100644 --- a/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/LoadingNullEntityInSet/Fixture.cs @@ -10,7 +10,6 @@ namespace NHibernate.Test.NHSpecificTest.LoadingNullEntityInSet [TestFixture] public class Fixture : TestCase { - protected override string[] Mappings { get { return new string[] { "NHSpecificTest.LoadingNullEntityInSet.Mappings.hbm.xml" }; } @@ -73,7 +72,6 @@ public void CanHandleNullEntityInList() criteria.List(); } - using (ISession sess = OpenSession()) { sess.Delete("from WantedProfession"); diff --git a/src/NHibernate.Test/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs index 4cb4e976acc..ffdc1f10ae1 100644 --- a/src/NHibernate.Test/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/ManyToOneFilters20Behaviour/Fixture.cs @@ -241,6 +241,5 @@ public void Verify20BehaviourForPropertyRefAndFilter() Assert.That(resHql[0].Address, Is.Not.Null); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/MapFixture.cs b/src/NHibernate.Test/NHSpecificTest/MapFixture.cs index 4e86bc799cc..eda191cffeb 100644 --- a/src/NHibernate.Test/NHSpecificTest/MapFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/MapFixture.cs @@ -47,7 +47,6 @@ protected override void OnTearDown() } } - [Test] public void TestSelect() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1001/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1001/Fixture.cs index 948b97a4ce6..1dabda7a0ef 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1001/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1001/Fixture.cs @@ -244,7 +244,7 @@ public void Department5IsNull() } } - [Test, Ignore("Not fixed yet")] + [Test] public void Department5IsNotFound() { var statistics = Sfi.Statistics; @@ -254,7 +254,7 @@ public void Department5IsNotFound() using(var transaction = session.BeginTransaction()) { ExecuteStatement(session, transaction, $"UPDATE EMPLOYEES SET DEPARTMENT_ID_1 = 11, DEPARTMENT_ID_2 = 12, DEPARTMENT_ID_3 = 13, DEPARTMENT_ID_4 = 24, DEPARTMENT_ID_5 = 99999, ADDRESS_ID = 31 WHERE EMPLOYEE_ID = {employeeId}"); - Assert.That(() => session.Get(employeeId), Throws.InstanceOf()); + Assert.That(() => session.Get(employeeId), Throws.InstanceOf()); } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1027/ManyToManyAssoc.cs b/src/NHibernate.Test/NHSpecificTest/NH1027/ManyToManyAssoc.cs index 72eb0dec7dc..b5599ff6cf7 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1027/ManyToManyAssoc.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1027/ManyToManyAssoc.cs @@ -63,7 +63,6 @@ public virtual string Name7 set { name = value; } } - public virtual ISet Containers { get { return containers; } @@ -111,6 +110,5 @@ public virtual int Id get { return id; } set { id = value; } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1028/ManyToManyAssoc.cs b/src/NHibernate.Test/NHSpecificTest/NH1028/ManyToManyAssoc.cs index 46a6112ee51..3eb788952e0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1028/ManyToManyAssoc.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1028/ManyToManyAssoc.cs @@ -64,7 +64,6 @@ public virtual string Name7 set { name = value; } } - public virtual ISet Containers { get { return containers; } @@ -112,6 +111,5 @@ public virtual int Id get { return id; } set { id = value; } } - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1033/Animal.cs b/src/NHibernate.Test/NHSpecificTest/NH1033/Animal.cs index 9071de7261b..8879f329150 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1033/Animal.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1033/Animal.cs @@ -14,6 +14,5 @@ public virtual string SerialNumber get; set; } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1033/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1033/Fixture.cs index a06a108fc58..b2c98a6a25f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1033/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1033/Fixture.cs @@ -15,7 +15,6 @@ protected override void OnSetUp() { using (var tran = session.BeginTransaction()) { - var animal0 = new Animal(); var animal1 = new Reptile(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1033/Reptile.cs b/src/NHibernate.Test/NHSpecificTest/NH1033/Reptile.cs index 5407fec0783..fd33f6c2b60 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1033/Reptile.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1033/Reptile.cs @@ -4,11 +4,10 @@ namespace NHibernate.Test.NHSpecificTest.NH1033 { public class Reptile : Animal { - public virtual float BodyTemperature { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1039/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1039/Fixture.cs index b6b69f5296c..1f3bb1bc1c5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1039/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1039/Fixture.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.NHSpecificTest.NH1039 { [TestFixture] diff --git a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs index dc756ed75e4..ca445766ede 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.NHSpecificTest.NH1039Generic { [TestFixture] diff --git a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs index 44f1d986396..847d5f4266e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs @@ -31,5 +31,4 @@ public virtual IDictionary Properties set { _Properties = value; } } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1080/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH1080/Domain.cs index 26f407c4fe5..68c6d74e330 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1080/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1080/Domain.cs @@ -17,7 +17,6 @@ public virtual C C set { c = value; } } - public virtual B B1 { get { return b1; } @@ -30,15 +29,12 @@ public virtual B B2 set { b2 = value; } } - public virtual string Value { get { return this.value; } set { this.value = value; } } - - public virtual int ID { get { return id; } @@ -46,7 +42,6 @@ public virtual int ID } } - public class B { private int id; diff --git a/src/NHibernate.Test/NHSpecificTest/NH1080/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1080/Fixture.cs index 1c9b996fc8d..d8a377cad13 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1080/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1080/Fixture.cs @@ -34,9 +34,6 @@ public void TestBug() c.ID = 1; c.Value = "OneToOne"; - - - A a = new A(); a.ID = 1; a.Value = "Parent"; @@ -51,7 +48,6 @@ public void TestBug() try { - using (ISession s = Sfi.OpenSession()) { s.Save(c); @@ -74,7 +70,6 @@ public void TestBug() { using (ISession s = Sfi.OpenSession()) { - s.Delete(a); s.Delete(b1); s.Delete(c); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1082/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH1082/Domain.cs index 61be19161a7..918dae02539 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1082/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1082/Domain.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.NHSpecificTest.NH1082 { public class C diff --git a/src/NHibernate.Test/NHSpecificTest/NH1093/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1093/Fixture.cs index 555f3c68d54..531ed40b4f1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1093/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1093/Fixture.cs @@ -22,13 +22,11 @@ protected override string CacheConcurrencyStrategy private void Cleanup() { - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { - s.CreateQuery("delete from SimpleCached").ExecuteUpdate(); - s.Transaction.Commit(); - } + s.CreateQuery("delete from SimpleCached").ExecuteUpdate(); + t.Commit(); } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1097/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1097/Fixture.cs index 1f136d907f6..2765656d5a0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1097/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1097/Fixture.cs @@ -36,7 +36,6 @@ public void ThrowsExceptionWhenColumnNameIsUsedInQuery() using (var session = this.OpenSession()) using (var tran = session.BeginTransaction()) { - Assert.Throws(delegate { var query = session.CreateQuery("from Person p where p.namecolumn=:nameOfPerson"); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1097/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1097/Person.cs index a942080b51a..65d07225cfc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1097/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1097/Person.cs @@ -8,6 +8,5 @@ public class Person { public virtual int Id { get; set; } public virtual string Name { get; set; } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs index c6b123ac1ef..ae2d5b7f395 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1098/FilterParameterOrderFixture.cs @@ -13,7 +13,6 @@ protected override void OnSetUp() { using (ISession session = OpenSession()) { - var a1 = new A { Id = 1, ValueA = 5, Enabled = false }; session.Save(a1); @@ -231,4 +230,4 @@ public void QueryMapElements() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1159/Contact.cs b/src/NHibernate.Test/NHSpecificTest/NH1159/Contact.cs index 1db50c9744d..10d8457662f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1159/Contact.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1159/Contact.cs @@ -5,7 +5,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1159 [Serializable] public class Contact { - private bool isChanged; private Int64 id; private string forename; @@ -30,7 +29,6 @@ public Contact( this.preferredname = String.Empty; } - #region Public Properties public virtual Int64 Id @@ -41,7 +39,6 @@ public virtual Int64 Id isChanged |= (id != value); id = value; } - } public virtual string Forename @@ -117,7 +114,6 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - int hash = 57; hash = 27 * hash * id.GetHashCode(); return hash; diff --git a/src/NHibernate.Test/NHSpecificTest/NH1159/ContactTitle.cs b/src/NHibernate.Test/NHSpecificTest/NH1159/ContactTitle.cs deleted file mode 100644 index 167b828091e..00000000000 --- a/src/NHibernate.Test/NHSpecificTest/NH1159/ContactTitle.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace NHibernate.Test.NHSpecificTest.NH1159 -{ - /// - /// Generated by MyGeneration using the NHibernate Object Mapping template - /// - [Serializable] - public class ContactTitle - { - - #region Private Members - private bool isChanged; - - private Int64 id; - private IList contactList; - private string descr; - #endregion - - #region Default ( Empty ) Class Constuctor - /// - /// default constructor - /// - public ContactTitle() - { - id = 0; - contactList = new List(); - descr = String.Empty; - } - #endregion // End of Default ( Empty ) Class Constuctor - - #region Required Fields Only Constructor - /// - /// required (not null) fields only constructor - /// - public ContactTitle( - string descr) - : this() - { - this.descr = descr; - } - #endregion // End Required Fields Only Constructor - - #region Public Properties - - /// - /// - /// - public virtual Int64 Id - { - get { return id; } - set - { - isChanged |= (id != value); - id = value; - } - - } - - public virtual IList ContactList - { - get - { - return contactList; - } - set - { - contactList = value; - } - } - - /// - /// - /// - public virtual string Descr - { - get { return descr; } - - set - { - if (value == null) - throw new ArgumentOutOfRangeException("Null value not allowed for Descr", value, "null"); - - if (value.Length > 50) - throw new ArgumentOutOfRangeException("Invalid value for Descr", value, value.ToString()); - - isChanged |= (descr != value); descr = value; - } - } - - /// - /// Returns whether or not the object has changed it's values. - /// - public virtual bool IsChanged - { - get { return isChanged; } - } - - #endregion - - #region Equals And HashCode Overrides - /// - /// local implementation of Equals based on unique value members - /// - public override bool Equals(object obj) - { - if (this == obj) return true; - if ((obj == null) || (obj.GetType() != this.GetType())) return false; - ContactTitle castObj = (ContactTitle)obj; - return (castObj != null) && - (this.id == castObj.Id); - } - - /// - /// local implementation of GetHashCode based on unique value members - /// - public override int GetHashCode() - { - - int hash = 57; - hash = 27 * hash * id.GetHashCode(); - return hash; - } - #endregion - - public override string ToString() - { - return id.ToString() + " " + descr; - } - } - -} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs index 0e54f626526..b9138c6e340 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1159/Fixture.cs @@ -9,7 +9,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1159 [TestFixture] public class Fixture:BugTestCase { - protected override void OnSetUp() { using (ISession session = OpenSession()) @@ -20,8 +19,7 @@ protected override void OnSetUp() tran.Commit(); } HibernateInterceptor.CallCount = 0; - - } + } protected override void OnTearDown() { @@ -50,9 +48,9 @@ public void DoesNotFlushWithCriteriaWithCommit() Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); - ICriteria query = session.CreateCriteria(typeof(ContactTitle)); + ICriteria query = session.CreateCriteria(typeof(Contact)); query.Add(Expression.Eq("Id", (Int64)1)); - query.UniqueResult(); + query.UniqueResult(); Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); @@ -80,9 +78,9 @@ public void DoesNotFlushWithCriteriaWithNever() Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); - ICriteria query = session.CreateCriteria(typeof(ContactTitle)); + ICriteria query = session.CreateCriteria(typeof(Contact)); query.Add(Expression.Eq("Id", (Int64)1)); - query.UniqueResult(); + query.UniqueResult(); Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); @@ -111,9 +109,9 @@ public void DoesNotFlushWithCriteriaWithAuto() Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(1)); - ICriteria query = session.CreateCriteria(typeof(ContactTitle)); + ICriteria query = session.CreateCriteria(typeof(Contact)); query.Add(Expression.Eq("Id", (Int64)1)); - query.UniqueResult(); + query.UniqueResult(); Assert.That(HibernateInterceptor.CallCount, Is.EqualTo(2)); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1159/HibernateInterceptor.cs b/src/NHibernate.Test/NHSpecificTest/NH1159/HibernateInterceptor.cs index 54832a10f76..f7243cd29ad 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1159/HibernateInterceptor.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1159/HibernateInterceptor.cs @@ -12,5 +12,4 @@ public override bool OnFlushDirty(object entity, object id, object[] currentStat return false; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1179/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1179/Fixture.cs index 98794761131..2f042c58be0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1179/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1179/Fixture.cs @@ -56,7 +56,6 @@ public void ApplyFilterExplicitJoin() s.Delete("from RelatedClass"); tx.Commit(); } - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1192/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH1192/Domain.cs index e7c71b8f433..6dbbff0517b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1192/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1192/Domain.cs @@ -10,8 +10,8 @@ public enum Status Bold=1, Italic=2, Underlined=4 - } + public class ObjectA { public virtual int Id { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1217/DomainObjects.cs b/src/NHibernate.Test/NHSpecificTest/NH1217/DomainObjects.cs index 5c12d308262..dc5bc9c7da3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1217/DomainObjects.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1217/DomainObjects.cs @@ -7,7 +7,6 @@ public class DomainBase : IDomainBase { private Int32 _id; - private Int32 _versionNumber; #region IDomainBase Members @@ -18,7 +17,6 @@ public virtual Int32 Id set { _id = value; } } - public virtual Int32 VersionNumber { get { return _versionNumber; } @@ -28,12 +26,10 @@ public virtual Int32 VersionNumber #endregion } - public class Root : DomainBase, IRoot { private String _name; - private IList _nodes; #region IRoot Members @@ -44,7 +40,6 @@ public virtual String Name set { _name = value; } } - public virtual IList Nodes { get @@ -55,7 +50,6 @@ public virtual IList Nodes set { _nodes = value; } } - public virtual INode AddNode(string label) { INode result = new Node(); @@ -97,14 +91,12 @@ public virtual IRoot Root set { _root = value; } } - public virtual String Label { get { return _label; } set { _label = value; } } - public virtual ISet FromEdges { get @@ -115,7 +107,6 @@ public virtual ISet FromEdges set { _fromEdges = value; } } - public virtual ISet ToEdges { get @@ -129,7 +120,6 @@ public virtual ISet ToEdges #endregion } - public class Edge : DomainBase, IEdge { private INode _fromNode; @@ -150,7 +140,6 @@ public virtual INode FromNode set { _fromNode = value; } } - public virtual INode ToNode { get { return _toNode; } @@ -159,4 +148,4 @@ public virtual INode ToNode #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1217/IEdge.cs b/src/NHibernate.Test/NHSpecificTest/NH1217/IEdge.cs index 38a73f8c054..070a9dabd86 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1217/IEdge.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1217/IEdge.cs @@ -1,6 +1,5 @@ using System; - namespace NHibernate.Test.NHSpecificTest.NH1217 { public interface IEdge @@ -11,4 +10,4 @@ public interface IEdge INode ToNode { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1217/IRoot.cs b/src/NHibernate.Test/NHSpecificTest/NH1217/IRoot.cs index 923777604b1..f900261eb57 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1217/IRoot.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1217/IRoot.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; - namespace NHibernate.Test.NHSpecificTest.NH1217 { public interface IRoot @@ -14,4 +13,4 @@ public interface IRoot IEdge AddLink(INode from, INode to, String label); } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1230/Foo.cs b/src/NHibernate.Test/NHSpecificTest/NH1230/Foo.cs index bc6fedd1a42..05fc487fac1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1230/Foo.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1230/Foo.cs @@ -7,7 +7,6 @@ public class Foo public Foo() { - } public Foo(string description) @@ -27,4 +26,4 @@ public int Id get { return id; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1235/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1235/Fixture.cs index 6bf3b5bff71..d41949823bc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1235/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1235/Fixture.cs @@ -59,7 +59,6 @@ public void Test() tx.Commit(); } - using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) { @@ -73,6 +72,5 @@ public void Test() tx.Commit(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1252/NH1252Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1252/NH1252Fixture.cs index 85a40cc6c93..17900bc61dc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1252/NH1252Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1252/NH1252Fixture.cs @@ -47,7 +47,6 @@ public void Test() Assert.IsNull(s.Get(savedId)); tx.Commit(); } - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Home.cs b/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Home.cs index 0ee16224bdc..55a6662fb9d 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Home.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Home.cs @@ -8,7 +8,6 @@ public class Home public Home() { - } public Home(string city, int zip) @@ -47,5 +46,4 @@ public class Home_Drop : Home { public Home_Drop() { } public Home_Drop(string c public class Home_None : Home {public Home_None() { } public Home_None(string city, int zip) : base(city, zip) { } } public class Home_All : Home {public Home_All() { } public Home_All(string city, int zip) : base(city, zip) { } } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs index 4433f923f47..3cc8b6df1dc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/NH1274ExportExcludeFixture.cs @@ -103,7 +103,6 @@ private Configuration GetConfiguration() return cfg; } - protected static string MappingsAssembly { get { return "NHibernate.Test"; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Person.cs index d5b346f3712..d93c5c61c5d 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1274ExportExclude/Person.cs @@ -13,7 +13,6 @@ public class Person public Person() { - } public Person(string name, int iq, int shoeSize) @@ -70,6 +69,5 @@ virtual public Home_Update Home_Update get { return home_update; } set { home_update = value; } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1275/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1275/Fixture.cs index 350b40f4dc9..4318dc5839f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1275/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1275/Fixture.cs @@ -42,6 +42,13 @@ public void Retrieving() string sql = sqlLogSpy.Appender.GetEvents()[0].RenderedMessage; Assert.That(sql.IndexOf(Dialect.ForUpdateString, StringComparison.Ordinal), Is.GreaterThan(0)); } + s.Clear(); + using (SqlLogSpy sqlLogSpy = new SqlLogSpy()) + { + s.Get(typeof(A).FullName, savedId, LockMode.Upgrade); + string sql = sqlLogSpy.Appender.GetEvents()[0].RenderedMessage; + Assert.That(sql.IndexOf(Dialect.ForUpdateString, StringComparison.Ordinal), Is.GreaterThan(0)); + } using (SqlLogSpy sqlLogSpy = new SqlLogSpy()) { s.CreateQuery("from A a where a.Id= :pid").SetLockMode("a", LockMode.Upgrade).SetParameter("pid", savedId). diff --git a/src/NHibernate.Test/NHSpecificTest/NH1280/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1280/Person.cs index 70065a6fb88..9f1b58a978b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1280/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1280/Person.cs @@ -1,7 +1,6 @@ using System; using System.Collections; - namespace NHibernate.Test.NHSpecificTest.NH1280 { public class Person @@ -13,7 +12,6 @@ public class Person public Person() { - } public Person(string name, int iq, int shoeSize) @@ -47,4 +45,4 @@ virtual public int ShoeSize set { shoeSize = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_Product.cs b/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_Product.cs index 0c00e21ad90..3f05375242d 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_Product.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_Product.cs @@ -7,7 +7,6 @@ public class Cons_Product : Product { #region Fields - public virtual string ImageName { get; @@ -16,4 +15,4 @@ public virtual string ImageName #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseItem.cs b/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseItem.cs index aced321453a..943a6f04d93 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseItem.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseItem.cs @@ -4,6 +4,5 @@ namespace NHibernate.Test.NHSpecificTest.NH1289 { public class Cons_PurchaseItem : PurchaseItem { - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseOrder.cs b/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseOrder.cs index 35a0214547c..74beb863db5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseOrder.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1289/Cons_PurchaseOrder.cs @@ -2,10 +2,8 @@ namespace NHibernate.Test.NHSpecificTest.NH1289 { - [Serializable] public class Cons_PurchaseOrder : PurchaseOrder { - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1289/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1289/Fixture.cs index 4af29e113a5..939729bb25c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1289/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1289/Fixture.cs @@ -26,7 +26,6 @@ protected override void OnSetUp() ImageName = "abc" }; - var purchaseItem = new Cons_PurchaseItem { Product = product, @@ -39,9 +38,8 @@ protected override void OnSetUp() tran.Commit(); } - - } + protected override void OnTearDown() { using (var ses = OpenSession()) diff --git a/src/NHibernate.Test/NHSpecificTest/NH1289/Product.cs b/src/NHibernate.Test/NHSpecificTest/NH1289/Product.cs index 90d0ffc3ef6..601a6536a9e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1289/Product.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1289/Product.cs @@ -30,7 +30,6 @@ public virtual String ProductName set; } - public virtual Int32? Units { get; @@ -39,4 +38,4 @@ public virtual Int32? Units #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1289/PurchaseItem.cs b/src/NHibernate.Test/NHSpecificTest/NH1289/PurchaseItem.cs index 595cbc1e9f0..fa2fd548e3c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1289/PurchaseItem.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1289/PurchaseItem.cs @@ -14,18 +14,16 @@ public virtual Int32 Id #endregion - public virtual PurchaseOrder PurchaseOrder { get; set; } - public virtual Product Product { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1290/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1290/Fixture.cs index 6a3b3bfca50..5c4b27cd496 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1290/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1290/Fixture.cs @@ -9,7 +9,6 @@ public class Fixture [Test] public void ExposeBug() { - string hbm = @" (savedId); NHibernateUtil.Initialize(entity.Children); NHibernateUtil.Initialize(entity.Components); NHibernateUtil.Initialize(entity.Elements); - session.Transaction.Commit(); + tran.Commit(); } } @@ -65,13 +65,13 @@ public void WhenReassociateCollectionUsingMergeThenReassingOwner() // When I reassociate the collections the Owner has value using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var merged = (MyClass)session.Merge(scenario.Entity); Assert.That(((IPersistentCollection)merged.Children).Owner, Is.Not.Null); Assert.That(((IPersistentCollection)merged.Components).Owner, Is.Not.Null); Assert.That(((IPersistentCollection)merged.Elements).Owner, Is.Not.Null); - session.Transaction.Commit(); + tran.Commit(); } } } @@ -86,7 +86,7 @@ public void WhenReassociateCollectionUsingLockThenTheCommitNotThrows() ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { // When I reassociate the collections the Owner is null session.Lock(scenario.Entity, LockMode.None); @@ -94,17 +94,17 @@ public void WhenReassociateCollectionUsingLockThenTheCommitNotThrows() scenario.Entity.Children.Add(new MyChild { Parent = scenario.Entity }); scenario.Entity.Components.Add(new MyComponent { Something = "something" }); scenario.Entity.Elements.Add("somethingelse"); - session.Transaction.Commit(); + tran.Commit(); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = session.Get(scenario.Entity.Id); Assert.That(fresh.Children, Has.Count.EqualTo(2)); Assert.That(fresh.Components, Has.Count.EqualTo(2)); Assert.That(fresh.Elements, Has.Count.EqualTo(2)); - session.Transaction.Commit(); + tran.Commit(); } } } @@ -119,24 +119,24 @@ public void WhenReassociateCollectionUsingUpdateThenTheCommitNotThrows() ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { scenario.Entity.Children.Add(new MyChild { Parent = scenario.Entity }); scenario.Entity.Components.Add(new MyComponent { Something = "something" }); scenario.Entity.Elements.Add("somethingelse"); // When I reassociate the collections the Owner is null session.Update(scenario.Entity); - session.Transaction.Commit(); + tran.Commit(); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = session.Get(scenario.Entity.Id); Assert.That(fresh.Children, Has.Count.EqualTo(2)); Assert.That(fresh.Components, Has.Count.EqualTo(2)); Assert.That(fresh.Elements, Has.Count.EqualTo(2)); - session.Transaction.Commit(); + tran.Commit(); } } } @@ -151,24 +151,24 @@ public void WhenReassociateCollectionUsingSaveOrUpdateThenTheCommitNotThrows() ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { scenario.Entity.Children.Add(new MyChild { Parent = scenario.Entity }); scenario.Entity.Components.Add(new MyComponent { Something = "something" }); scenario.Entity.Elements.Add("somethingelse"); // When I reassociate the collections the Owner is null session.SaveOrUpdate(scenario.Entity); - session.Transaction.Commit(); + tran.Commit(); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = session.Get(scenario.Entity.Id); Assert.That(fresh.Children, Has.Count.EqualTo(2)); Assert.That(fresh.Components, Has.Count.EqualTo(2)); Assert.That(fresh.Elements, Has.Count.EqualTo(2)); - session.Transaction.Commit(); + tran.Commit(); } } } @@ -183,18 +183,18 @@ public void WhenReassociateCollectionUsingDeleteThenTheCommitNotThrows() ((IPersistentCollection)scenario.Entity.Elements).Owner = null; using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.Delete(scenario.Entity); - session.Transaction.Commit(); + tran.Commit(); } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var fresh = session.Get(scenario.Entity.Id); Assert.That(fresh, Is.Null); - session.Transaction.Commit(); + tran.Commit(); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1324/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1324/Person.cs index cf07e869841..0f43616eeab 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1324/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1324/Person.cs @@ -1,7 +1,6 @@ using System; using System.Collections; - namespace NHibernate.Test.NHSpecificTest.NH1324 { public class Person @@ -13,7 +12,6 @@ public class Person public Person() { - } public Person(string name, int? iq, int shoeSize) @@ -47,4 +45,4 @@ virtual public int ShoeSize set { shoeSize = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1326/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1326/Person.cs index f4935788b29..98aba8b32fb 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1326/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1326/Person.cs @@ -1,7 +1,6 @@ using System; using System.Collections; - namespace NHibernate.Test.NHSpecificTest.NH1326 { public class Person @@ -13,7 +12,6 @@ public class Person public Person() { - } public Person(string name, int iq, int shoeSize) @@ -47,4 +45,4 @@ virtual public int ShoeSize set { shoeSize = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1332/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1332/Fixture.cs index c05ccbabfce..c7c34253a6e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1332/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1332/Fixture.cs @@ -25,7 +25,6 @@ public void Bug() using (ISession s = OpenSession()) using (ITransaction tx = s.BeginTransaction()) { - s.Save(a); tx.Commit(); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1349/Services.cs b/src/NHibernate.Test/NHSpecificTest/NH1349/Services.cs index f910d2fdb90..c7b564381a1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1349/Services.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1349/Services.cs @@ -44,8 +44,6 @@ public virtual int CompanyCount public override string ToString() { return (this.id + "] [" + this.accountNumber + "] [" + this.name + "] [" + this.type + "] [" + this.CompanyCount + "]"); - } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1359/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1359/Fixture.cs index bcb01ecc385..13d5a2ee8c5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1359/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1359/Fixture.cs @@ -127,7 +127,6 @@ public void CanGetSelectSubqueryWithSpecifiedParameter() } } - [Test] public void CanPageAndSortResultsWithParametersAndFilters() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1388/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1388/Fixture.cs index 7a7afd596e4..16b3c0bf72c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1388/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1388/Fixture.cs @@ -114,9 +114,9 @@ public void BagTest() protected override void OnTearDown() { // clean up the database - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); foreach (var student in session.CreateCriteria(typeof (Student)).List()) { session.Delete(student); @@ -125,7 +125,7 @@ protected override void OnTearDown() { session.Delete(subject); } - session.Transaction.Commit(); + tran.Commit(); } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture.cs index 56644dd090b..bdccd0be7ca 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture.cs @@ -46,7 +46,6 @@ protected override void OnSetUp() var sivasKangalForSivasKangals = new SivasKangal { Name = "Karabas4", Country = "Turkey", HouseAddress = "Atakoy", Owner = personWithSivasKangals }; personWithSivasKangals.AnimalsGeneric.Add(sivasKangalForSivasKangals); - session.Save(animalForCats); session.Save(dogForCats); @@ -60,8 +59,6 @@ protected override void OnSetUp() _idOfPersonWithCats = session.Save(personWithCats); _idOfPersonWithDogs = session.Save(personWithDogs); _idOfPersonWithSivasKangals = session.Save(personWithSivasKangals); - - tran.Commit(); } @@ -77,7 +74,6 @@ protected override void OnTearDown() } } - [Test] public void Can_discriminate_subclass_on_list_with_lazy_loading_when_used_get() { @@ -99,4 +95,4 @@ public void Can_discriminate_subclass_on_list_with_lazy_loading_when_used_get() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture2.cs b/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture2.cs index c300b487495..773e1417112 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture2.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1391/Fixture2.cs @@ -42,7 +42,6 @@ protected override void OnTearDown() } } - [Test] public void Can_discriminate_subclass_on_list_with_lazy_loading_when_used_and_person_had_multiple_list() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAllTypes.cs b/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAllTypes.cs index d94fd0dd0d3..37831b61fdb 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAllTypes.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAllTypes.cs @@ -10,5 +10,4 @@ public class PersonWithAllTypes:Person public virtual IList SivasKangalsGeneric { get; set; } public virtual IList CatsGeneric { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAnimals.cs b/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAnimals.cs index 375d7654bb9..830d54e15d5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAnimals.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1391/PersonWithAnimals.cs @@ -9,8 +9,6 @@ public class PersonWithAnimals:Person { public PersonWithAnimals() { - } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1399/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1399/Fixture.cs index 7336e54ab9c..d84f267527c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1399/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1399/Fixture.cs @@ -31,7 +31,6 @@ public void UsingTwoInstancesWithSameValuesTheFkNameIsTheSame() string t1Fk = table1.UniqueColumnString(new object[] { table1ITestManyA }, "BluewireTechnologies.Core.Framework.DynamicTypes2.Albatross.ITestManyA"); string t2Fk = table1.UniqueColumnString(new object[] { table1ITestManyB }, "BluewireTechnologies.Core.Framework.DynamicTypes2.Albatross.ITestManyB"); - Table table1_ = new Table("ATABLE"); Column table1ITestManyA_ = new Column("itestmanyaid"); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1443/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1443/Fixture.cs index 7613aa9cfdd..3422c1c39cd 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1443/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1443/Fixture.cs @@ -15,14 +15,12 @@ private static void Bug(Configuration cfg) su.Execute(x => sb.AppendLine(x), false, false); string script = sb.ToString(); - if (Dialect.Dialect.GetDialect(cfg.Properties).SupportsIfExistsBeforeTableName) Assert.That(script, Does.Match("drop table if exists nhibernate.dbo.Aclass")); else Assert.That(script, Does.Match("drop table nhibernate.dbo.Aclass")); Assert.That(script, Does.Match("create ((column|row) )?table nhibernate.dbo.Aclass")); - } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH1447/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1447/Person.cs index 3e48d5ff54b..d055ff194a2 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1447/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1447/Person.cs @@ -9,7 +9,6 @@ public class Person { public Person() { - } public Person(string name, bool wantsNewsLetter) @@ -33,7 +32,5 @@ public virtual bool WantsNewsletter get; set; } - } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1464/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1464/Fixture.cs index 3979944f878..e975bd76fed 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1464/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1464/Fixture.cs @@ -14,14 +14,12 @@ public class CPPMimicBase { public virtual void Dispose() { - } } public class CPPMimic : CPPMimicBase { public sealed override void Dispose() { - } } @@ -29,7 +27,6 @@ public class Another: IDisposable { protected void Dispose(bool disposing) { - } public void Dispose() @@ -38,7 +35,6 @@ public void Dispose() ~Another() { - } } @@ -46,7 +42,6 @@ public class OneMore : IDisposable { public void Dispose(bool disposing) { - } public void Dispose() @@ -55,7 +50,6 @@ public void Dispose() ~OneMore() { - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1478/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1478/Fixture.cs index 6a49c034baa..2534e2b3416 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1478/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1478/Fixture.cs @@ -42,13 +42,10 @@ public void TestIfColonInStringIsNotInterpretedAsParameterInSQL() { using (ISession session=OpenSession()) { - - IList lst = session.CreateSQLQuery("select Biography from Person where Biography='Born in Istanbul :Turkey'") .AddScalar("Biography", NHibernateUtil.String).List(); Assert.AreEqual(1,lst.Count); } - } [Test] @@ -56,14 +53,10 @@ public void TestIfColonInStringIsNotInterpretedAsParameterInHQL() { using (ISession session = OpenSession()) { - - IList lst = session.CreateSQLQuery("select p.Biography from Person p where p.Biography='Born in Istanbul :Turkey'") .List(); Assert.AreEqual(1, lst.Count); } - } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1478/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1478/Person.cs index d3929838a73..c41a1fef390 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1478/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1478/Person.cs @@ -8,7 +8,6 @@ public class Person { public Person() { - } public Person(string name, string bio) @@ -33,6 +32,5 @@ public virtual string Biography get; set; } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1487/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1487/Fixture.cs index a42b02aad54..4458ee19c90 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1487/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1487/Fixture.cs @@ -22,7 +22,6 @@ public class Entity [TestFixture] public class Fixture { - public Configuration GetConf() { var cfg = new Configuration(); @@ -209,6 +208,5 @@ public void GenerateSchemaUniqueOnId() new SchemaExport(cfg).Drop(false, true); } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1495/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1495/Fixture.cs index ec68596a5f1..849f541fb39 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1495/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1495/Fixture.cs @@ -37,6 +37,5 @@ public void CreateTest() } } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1495/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1495/Person.cs index 752d083d13d..553d1f0e97a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1495/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1495/Person.cs @@ -2,7 +2,7 @@ { public interface IPerson { - object Id { get;} + object Id { get; } string Name { get; set; } } @@ -12,4 +12,4 @@ public class Person : IPerson public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1499/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1499/Fixture.cs index e00c7314c26..326cc1dd6c2 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1499/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1499/Fixture.cs @@ -58,7 +58,5 @@ public void CheckIfDetachedCriteriaCanBeUsedOnPropertyRestriction() Assert.Throws(() => criteria.List()); } } - - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1502/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1502/Person.cs index d7e3fd87bb6..894e93157be 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1502/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1502/Person.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.NHSpecificTest.NH1502 { public class Person @@ -42,6 +41,5 @@ public virtual int ShoeSize get { return shoeSize; } set { shoeSize = value; } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1508/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1508/Fixture.cs index 302f6e57063..bfc3e0ede3f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1508/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1508/Fixture.cs @@ -104,7 +104,6 @@ public void DoesntThrowsExceptionWhenNamedSqlQueryIsGiven() using (var session = OpenSession()) using (session.BeginTransaction()) { - var multiquery = session.CreateMultiQuery(); Assert.That(() => multiquery.AddNamedQuery("SampleSqlQuery"), Throws.Nothing); } @@ -116,7 +115,6 @@ public void DoesntThrowsExceptionWhenNamedSqlQueryIsGivenToQueryBatch() using (var session = OpenSession()) using (session.BeginTransaction()) { - var multiquery = session.CreateQueryBatch(); var q = session.GetNamedQuery("SampleSqlQuery"); Assert.That(() => multiquery.Add(q), Throws.Nothing); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1533/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH1533/Person.cs index 5af03aca8ac..c2f39162d85 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1533/Person.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1533/Person.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.NHSpecificTest.NH1533 +namespace NHibernate.Test.NHSpecificTest.NH1533 { public class Person { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1549/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1549/Fixture.cs index 0393603e0bd..07a4f306ad0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1549/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1549/Fixture.cs @@ -78,7 +78,6 @@ public void CanLoadForEntitiesWithTheirOwnIds() protected override void OnTearDown() { using (ISession session = OpenSession()) { - using (ITransaction trans = session.BeginTransaction()) { session.Delete("from ProductWithId"); @@ -90,4 +89,4 @@ protected override void OnTearDown() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1552/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1552/Fixture.cs index b00b233ea2d..a5eb55a5240 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1552/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1552/Fixture.cs @@ -83,7 +83,6 @@ public void Paging_with_sql_works_as_expected_with_MaxResult() } } - [Test] public void Paging_with_sql_works_as_expected_with_FirstResultMaxResult() { @@ -103,4 +102,4 @@ public void Paging_with_sql_works_as_expected_with_FirstResultMaxResult() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1574/Principal.cs b/src/NHibernate.Test/NHSpecificTest/NH1574/Principal.cs index 476e5a825b8..74f08e7f03c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1574/Principal.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1574/Principal.cs @@ -280,4 +280,4 @@ public virtual SpecializedTeamStorage Team set { team = value; } } } ; -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedPrincipal.cs b/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedPrincipal.cs index 5c27cd3f804..40ae8b8e306 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedPrincipal.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedPrincipal.cs @@ -1,4 +1,4 @@ namespace NHibernate.Test.NHSpecificTest.NH1574 { public class SpecializedPrincipal : Principal {} ; -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedTeaml.cs b/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedTeaml.cs index ffdf15b9f76..08381537f56 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedTeaml.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1574/SpecializedTeaml.cs @@ -1,4 +1,4 @@ namespace NHibernate.Test.NHSpecificTest.NH1574 { public class SpecializedTeamStorage : TeamStorage {} ; -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1574/Team.cs b/src/NHibernate.Test/NHSpecificTest/NH1574/Team.cs index 8fd51feb6d1..8f94362937f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1574/Team.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1574/Team.cs @@ -131,4 +131,4 @@ public virtual int Id set { id = value; } } } ; -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1579/Fruit.cs b/src/NHibernate.Test/NHSpecificTest/NH1579/Fruit.cs index ef376f1b1ed..d0df080b3a0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1579/Fruit.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1579/Fruit.cs @@ -18,6 +18,5 @@ protected Fruit() } public Entity Container { get; private set; } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1587/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1587/Fixture.cs index 6a716ce5a66..68eb38d6bab 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1587/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1587/Fixture.cs @@ -1,5 +1,3 @@ -using log4net; -using log4net.Config; using log4net.Core; using NHibernate.Cfg; using NUnit.Framework; @@ -12,7 +10,6 @@ public class Fixture [Test] public void Bug() { - XmlConfigurator.Configure(LogManager.GetRepository(typeof(Fixture).Assembly)); var cfg = new Configuration(); if (TestConfigurationHelper.hibernateConfigFile != null) cfg.Configure(TestConfigurationHelper.hibernateConfigFile); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture1.cs b/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture1.cs index c7077c576d7..31413b3c873 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture1.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture1.cs @@ -94,7 +94,6 @@ public ProjectWithOneList SaveProject( ) return project; } - public ProjectWithOneList LoadProject() { ProjectWithOneList project; @@ -113,7 +112,6 @@ public ProjectWithOneList LoadProject() public void RefreshProject(ProjectWithOneList project) { - using (ISession session = OpenSession()) using (ITransaction tx = session.BeginTransaction()) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture2.cs b/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture2.cs index dd860d3d778..329bc4eb52f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture2.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1601/Fixture2.cs @@ -51,7 +51,6 @@ public void TestRefreshWithThreeCounts() SaveLoadAndRefreshProject(); } - /// /// Create and save a Project /// @@ -71,7 +70,6 @@ public void SaveLoadAndRefreshProject() RefreshProject(project); } - public Project SaveProject( ) { Project project; @@ -85,13 +83,11 @@ public Project SaveProject( ) Scenario scenario2 = new Scenario(); Scenario scenario3 = new Scenario(); - //Add the scenario to all lists project.ScenarioList1.Add(scenario1); project.ScenarioList2.Add(scenario2); project.ScenarioList3.Add(scenario3); - //Set the primary key on the project project.Name = "Test"; @@ -103,7 +99,6 @@ public Project SaveProject( ) return project; } - public Project LoadProject() { Project project; @@ -122,7 +117,6 @@ public Project LoadProject() public void RefreshProject(Project project) { - using (ISession session = OpenSession()) using (ITransaction tx = session.BeginTransaction()) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1601/Project.cs b/src/NHibernate.Test/NHSpecificTest/NH1601/Project.cs index 3db39a0ca04..573cb994607 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1601/Project.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1601/Project.cs @@ -21,19 +21,21 @@ public IList ScenarioList1 set { scenarioList1 = value; if (TestAccessToList) - { int i = scenarioList1.Count;} - } - } - - public IList ScenarioList2 - { - get { return scenarioList2; } - set { scenarioList2 = value; - int i = scenarioList2.Count; + { int i = scenarioList1.Count; } } } - public IList ScenarioList3 + public IList ScenarioList2 + { + get { return scenarioList2; } + set + { + scenarioList2 = value; + int i = scenarioList2.Count; + } + } + + public IList ScenarioList3 { get { return scenarioList3; } set @@ -49,19 +51,18 @@ public Project( ) private string name; - public string Name - { - get { return name; } - set { name = value;} - } + public string Name + { + get { return name; } + set { name = value; } + } - private IList scenarioList1 = new List(); + private IList scenarioList1 = new List(); private IList scenarioList2 = new List(); private IList scenarioList3 = new List(); + } - - } - public class ProjectWithOneList + public class ProjectWithOneList { public static bool TestAccessToList = false; @@ -94,6 +95,5 @@ public string Name } private IList scenarioList1 = new List(); - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1601/Scenario.cs b/src/NHibernate.Test/NHSpecificTest/NH1601/Scenario.cs index 3888ebdc903..3d949edf824 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1601/Scenario.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1601/Scenario.cs @@ -4,24 +4,18 @@ namespace NHibernate.Test.NHSpecificTest.NH1601 { - public class Scenario { - protected int _id; - public virtual int id { get { return _id; } set { _id = value; } } - public Scenario( ) { } - - } - + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Adjunct.cs b/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Adjunct.cs index 38aefcaec63..3b170bf382c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Adjunct.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Adjunct.cs @@ -7,7 +7,6 @@ public class Adjunct public Adjunct() { - } virtual public int ID @@ -45,4 +44,4 @@ public override int GetHashCode() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs index c04a1341da0..fef57ed31b3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/NH1611OneToOneIdentityFixture.cs @@ -40,7 +40,6 @@ protected override void OnSetUp() } } - [Test] public void CanQueryOneToOneWithCompositeId() { @@ -52,10 +51,8 @@ public void CanQueryOneToOneWithCompositeId() IList list = criteria.List(); Assert.AreEqual("blarg", list[0].Description); Assert.AreEqual("nuts", list[0].Adjunct.AdjunctDescription); - } } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Primary.cs b/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Primary.cs index fb0c5f50621..ccc46bfeede 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Primary.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1611OneToOneIdentity/Primary.cs @@ -8,7 +8,6 @@ public class Primary public Primary() { - } virtual public int ID @@ -17,7 +16,6 @@ virtual public int ID set { id = value; } } - virtual public string Description { get { return description; } @@ -53,4 +51,4 @@ public override int GetHashCode() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs index 61d5b603a63..e4dbf790c3f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs @@ -71,7 +71,6 @@ public void Dispose_session_inside_transaction_scope() { using (s = Sfi.OpenSession()) { - } tx.Complete(); } @@ -79,7 +78,6 @@ public void Dispose_session_inside_transaction_scope() Assert.IsFalse(s.IsOpen); } - [Test] public void When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_cache() { @@ -93,7 +91,6 @@ public void When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_ } try { - using (var tx = new TransactionScope()) { using (var s = OpenSession()) @@ -194,7 +191,6 @@ public void When_using_two_sessions_with_explicit_flush() using (ISession s1 = Sfi.OpenSession()) using (ISession s2 = Sfi.OpenSession()) { - id1 = s1.Save(new Nums { NumA = 1, NumB = 2, ID = 5 }); s1.Flush(); @@ -234,7 +230,6 @@ public void When_using_two_sessions() using (ISession s1 = Sfi.OpenSession()) using (ISession s2 = Sfi.OpenSession()) { - id1 = s1.Save(new Nums { NumA = 1, NumB = 2, ID = 5 }); id2 = s2.Save(new Nums { NumA = 1, NumB = 2, ID = 6 }); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1643/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1643/Fixture.cs index 19f6a0e3a27..d0b0a431458 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1643/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1643/Fixture.cs @@ -42,8 +42,6 @@ public void Test() tx.Commit(); } - - using (ISession sess = OpenSession()) using (ITransaction tx = sess.BeginTransaction()) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1665/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1665/Fixture.cs index 789485ae30f..c8e90b47993 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1665/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1665/Fixture.cs @@ -13,16 +13,17 @@ protected override bool AppliesTo(Dialect.Dialect dialect) [Test] public void SupportsHibernateQuotingSequenceName() { - ISession session = OpenSession(); - session.BeginTransaction(); + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var e = new MyEntity { Name = "entity-1" }; + session.Save(e); + Assert.AreEqual(1, (int) session.GetIdentifier(e)); - var e = new MyEntity { Name = "entity-1" }; - session.Save(e); - Assert.AreEqual(1, (int)session.GetIdentifier(e)); - - session.Delete(e); - session.Transaction.Commit(); - session.Close(); + session.Delete(e); + tran.Commit(); + session.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1688/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1688/Fixture.cs index 0cdcc160d3d..1673fde4fb4 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1688/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1688/Fixture.cs @@ -56,7 +56,7 @@ public void TestAction(System.Action action) { using (ISession session = OpenSession()) { - DetachedCriteria criteria = DetachedCriteria.For("alias"); + DetachedCriteria criteria = DetachedCriteria.For("alias"); action.Invoke(criteria); @@ -65,4 +65,4 @@ public void TestAction(System.Action action) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1689/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH1689/DomainClass.cs index ab00447a705..d6c1ab1d409 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1689/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1689/DomainClass.cs @@ -1,6 +1,4 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH1689 +namespace NHibernate.Test.NHSpecificTest.NH1689 { using System.Collections.Generic; diff --git a/src/NHibernate.Test/NHSpecificTest/NH1689/SampleTest.cs b/src/NHibernate.Test/NHSpecificTest/NH1689/SampleTest.cs index 721bd80aeff..04c35216853 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1689/SampleTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1689/SampleTest.cs @@ -43,5 +43,4 @@ public void ShouldBeAbleToCallGenericMethod() } } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1693/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1693/Fixture.cs index 52b49f3048c..aec13abcc00 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1693/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1693/Fixture.cs @@ -70,6 +70,5 @@ public void with_filter() tx.Commit(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1714/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH1714/DomainClass.cs index fbf713c63b1..4378ee170b6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1714/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1714/DomainClass.cs @@ -1,22 +1,20 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH1714 +namespace NHibernate.Test.NHSpecificTest.NH1714 { - public class DomainClass - { - private byte[] byteData; - private int id; + public class DomainClass + { + private byte[] byteData; + private int id; - public int Id - { - get { return id; } - set { id = value; } - } + public int Id + { + get { return id; } + set { id = value; } + } - public byte[] ByteData - { - get { return byteData; } - set { byteData = value; } - } - } -} \ No newline at end of file + public byte[] ByteData + { + get { return byteData; } + set { byteData = value; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1727/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1727/Fixture.cs index 4cd4be029a4..9f0a02475c7 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1727/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1727/Fixture.cs @@ -13,7 +13,6 @@ public class Fixture : BugTestCase * The second test passes where I've just switched the order in the where clause */ - [Test] public void VerifyFilterAndInAndProperty_DoesNotWorkToday() { @@ -37,7 +36,6 @@ where a.Value in (:aValues) } } - [Test] public void VerifyFilterAndInAndProperty_WorksToday() { @@ -71,6 +69,5 @@ protected override void OnTearDown() t.Commit(); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1727/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1727/Model.cs index b657c8a77d4..9d1d96efef1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1727/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1727/Model.cs @@ -9,11 +9,11 @@ public class ClassA public virtual IList BCollection { get; set; } public virtual string Name { get; set; } public virtual int Value { get; set; } - public virtual ClassB B {get;set;} + public virtual ClassB B { get; set; } } public class ClassB { public virtual Guid Id { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1747/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1747/Fixture.cs index 9e0ef628412..7f6d0206a31 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1747/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1747/Fixture.cs @@ -53,5 +53,4 @@ public void TraversingBagToJoinChildElementShouldWork() } } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1756/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH1756/Domain.cs index 0c3ac8abbaa..eb23300ee3e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1756/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1756/Domain.cs @@ -1,10 +1,8 @@ - using System; using System.Collections.Generic; namespace NHibernate.Test.NHSpecificTest.NH1756 { - public class Book { private DateTime _version; @@ -39,5 +37,4 @@ public virtual DateTime Version public virtual string Name { get; set; } public virtual IList Pages { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1761/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH1761/FixtureByCode.cs index f153236e941..b521244cb61 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1761/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1761/FixtureByCode.cs @@ -19,7 +19,6 @@ protected override HbmMapping GetMappings() rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name); rc.Bag(x => x.FundingPrograms, m => {}, r => r.OneToMany()); - }); mapper.Class(rc => { @@ -74,7 +73,6 @@ public class FundingCategory public virtual Guid Id { get; set; } public virtual string Name { get; set; } public virtual IList FundingPrograms { get; set; } = new List(); - } public class FundingProgram diff --git a/src/NHibernate.Test/NHSpecificTest/NH1775/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH1775/Domain.cs index 3b69f4234e7..42372ef0d5b 100755 --- a/src/NHibernate.Test/NHSpecificTest/NH1775/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1775/Domain.cs @@ -6,7 +6,7 @@ namespace NHibernate.Test.NHSpecificTest.NH1775 { public class Member { - public virtual int Id { get; set;} + public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual int Roles { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1788/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1788/Fixture.cs index 28443f0d991..deb2aa1637c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1788/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1788/Fixture.cs @@ -26,7 +26,6 @@ public void CanUseSqlTimestampWithDynamicInsert() tx.Commit(); } - using (var session = OpenSession()) using (var tx = session.BeginTransaction()) { @@ -35,14 +34,12 @@ public void CanUseSqlTimestampWithDynamicInsert() tx.Commit(); } - using (ISession session = OpenSession()) using (var tx = session.BeginTransaction()) { session.Delete(session.Get(1)); tx.Commit(); } - } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1810/Children.cs b/src/NHibernate.Test/NHSpecificTest/NH1810/Children.cs index 89fed8e2faf..797eda30dd5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1810/Children.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1810/Children.cs @@ -52,7 +52,6 @@ public Children(ICollection initialValues) : base(initialValues) behaviour = new ChildrenBehaviour(this); } - public Children(ICollection initialValues, IComparer comparer) : base(initialValues, comparer) { behaviour = new ChildrenBehaviour(this); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1812/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1812/Model.cs index d97cf88dd17..ed798fc6e7b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1812/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1812/Model.cs @@ -5,10 +5,10 @@ namespace NHibernate.Test.NHSpecificTest.NH1812 { public class Person { - public virtual int Id {get; set;} + public virtual int Id {get; set; } public virtual IList PeriodCollection { get; set; } - public Person(){PeriodCollection=new List();} + public Person(){PeriodCollection=new List(); } } public class Period @@ -16,4 +16,4 @@ public class Period public virtual int Id { get; set; } public virtual DateTime? Start { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1818/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH1818/DomainClass.cs index 34ca1c11a9f..1770a4ff7a1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1818/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1818/DomainClass.cs @@ -1,6 +1,4 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH1818 +namespace NHibernate.Test.NHSpecificTest.NH1818 { public class DomainClass { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1818/Fixture1818.cs b/src/NHibernate.Test/NHSpecificTest/NH1818/Fixture1818.cs index c39f2cc90d4..7e907958fe6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1818/Fixture1818.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1818/Fixture1818.cs @@ -32,7 +32,6 @@ protected override bool AppliesTo(Dialect.Dialect dialect) return dialect as PostgreSQL82Dialect != null; } - [Test] [Description("Test HQL query on a property mapped with a formula.")] public void ComputedPropertyShouldRetrieveDataCorrectly() @@ -44,4 +43,4 @@ public void ComputedPropertyShouldRetrieveDataCorrectly() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1837/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1837/Fixture.cs index cbc62aa67ff..c2cd487ba5f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1837/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1837/Fixture.cs @@ -37,7 +37,6 @@ protected override void OnTearDown() [Test] public void ExecutesOneQueryWithUniqueResultWithChildCriteriaNonGeneric() { - Sfi.Statistics.Clear(); using (ISession session = this.OpenSession()) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1845/Category.cs b/src/NHibernate.Test/NHSpecificTest/NH1845/Category.cs index 59753e7541f..25e8f852203 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1845/Category.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1845/Category.cs @@ -6,7 +6,9 @@ public class Category { private readonly IList subcategories = new List(); - public Category() : this("") {} + public Category() : this("") + { + } public Category(string name) { @@ -17,6 +19,8 @@ public Category(string name) public virtual string Name { get; set; } + public virtual int SortIndex { get; set; } + public virtual Category Parent { get; set; } public virtual IList Subcategories @@ -50,4 +54,4 @@ public override int GetHashCode() return Name.GetHashCode(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1845/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1845/Fixture.cs index a838b17644b..dd1fdc03a2a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1845/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1845/Fixture.cs @@ -1,31 +1,9 @@ -using NHibernate.Cfg.MappingSchema; -using NHibernate.Mapping.ByCode; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH1845 { [TestFixture] - public class Fixture : TestCaseMappingByCode + public class Fixture : BugTestCase { - - protected override HbmMapping GetMappings() - { - var mapper = new ModelMapper(); - mapper.Class(rc => - { - rc.Id(x => x.Id, map => map.Generator(Generators.Native)); - rc.Property(x => x.Name); - rc.ManyToOne(x => x.Parent, map => map.Column("ParentId")); - rc.Bag(x => x.Subcategories, map => - { - map.Access(Accessor.NoSetter); - map.Key(km => km.Column("ParentId")); - map.Cascade(Mapping.ByCode.Cascade.All.Include(Mapping.ByCode.Cascade.DeleteOrphans)); - }, rel => rel.OneToMany()); - }); - var mappings = mapper.CompileMappingForAllExplicitlyAddedEntities(); - return mappings; - } - [Test] public void LazyLoad_Initialize_AndEvict() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1845/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH1845/Mappings.hbm.xml new file mode 100644 index 00000000000..9950bc4c5df --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1845/Mappings.hbm.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH1863/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH1863/Domain.cs index 527d28068d8..75e8afc1870 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1863/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1863/Domain.cs @@ -23,5 +23,4 @@ public class Category public virtual bool IsActive { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1863/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1863/Fixture.cs index 024258ad46b..2c5d4651d9b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1863/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1863/Fixture.cs @@ -55,7 +55,6 @@ public void CanGetCustomerWithCategoryWhenFilterIsEnabled() Assert.That(hasCategoryResult.Count, Is.EqualTo(1)); } - } [Test] @@ -87,7 +86,6 @@ public void CanGetCustomerWithNoCategoryWhenFilterIsEnabled() Assert.That(hasNoCategoryResult.Count, Is.EqualTo(1)); } - } [Test] @@ -103,7 +101,6 @@ public void CanGetCustomerWithNoCategoryWhenFilterIsDisabled() Assert.That(hasNoCategoryResult.Count, Is.EqualTo(1)); } - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs b/src/NHibernate.Test/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs index cbe3206dfb5..6f6804197b0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1882/TestCollectionInitializingDuringFlush.cs @@ -89,29 +89,29 @@ public void TestInitializationDuringFlush() Assert.False(listener.FoundAny); using (var s1 = OpenSession()) + using (var t1 = s1.BeginTransaction()) { - s1.BeginTransaction(); var publisher = new Publisher("acme"); var author = new Author("john"); author.Publisher = publisher; publisher.Authors.Add(author); author.Books.Add(new Book("Reflections on a Wimpy Kid", author)); s1.Save(author); - s1.Transaction.Commit(); + t1.Commit(); s1.Clear(); using (var s2 = OpenSession()) + using (var t2 = s2.BeginTransaction()) { - s2.BeginTransaction(); publisher = s2.Get(publisher.Id); publisher.Name = "random nally"; s2.Flush(); - s2.Transaction.Commit(); + t2.Commit(); s2.Clear(); using (var s3 = OpenSession()) + using (var t3 = s3.BeginTransaction()) { - s3.BeginTransaction(); s3.Delete(author); - s3.Transaction.Commit(); + t3.Commit(); s3.Clear(); s3.Close(); } @@ -121,4 +121,4 @@ public void TestInitializationDuringFlush() Assert.That(listener.FoundAny, Is.True); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1899/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH1899/DomainClass.cs index d945015788e..315e10da519 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1899/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1899/DomainClass.cs @@ -1,33 +1,35 @@ - - -using System.Collections; +using System.Collections; using System.Collections.Generic; + namespace NHibernate.Test.NHSpecificTest.NH1899 { - public class Parent - { - private int id; - private IDictionary _relations; + public class Parent + { + private int id; + private IDictionary _relations; - public int Id - { - get { return id; } - set { id = value; } - } + public int Id + { + get { return id; } + set { id = value; } + } - public IDictionary Relations { - get { return _relations; } - set { _relations = value; } - } - } + public IDictionary Relations + { + get { return _relations; } + set { _relations = value; } + } + } - public enum Key { - One, - Two - } + public enum Key + { + One, + Two + } - public enum Value { - ValOne, - ValTwo - } -} \ No newline at end of file + public enum Value + { + ValOne, + ValTwo + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1905/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1905/Model.cs index f8451af6d82..266df0d0f40 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1905/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1905/Model.cs @@ -21,7 +21,6 @@ public virtual ISet Els } } - public class Det { private int _Id; @@ -57,4 +56,4 @@ public virtual string Descr set { _Descr = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1907/MyType.cs b/src/NHibernate.Test/NHSpecificTest/NH1907/MyType.cs index c99c8f52a57..6da80a0ad27 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1907/MyType.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1907/MyType.cs @@ -30,7 +30,6 @@ public class SimpleCustomType : IUserType { private static readonly SqlType[] ReturnSqlTypes = { SqlTypeFactory.Int32 }; - #region IUserType Members public new bool Equals(object x, object y) @@ -112,5 +111,4 @@ public object Disassemble(object value) #endregion } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1908/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1908/Fixture.cs index 3cd9194d95a..1d9e7bd3792 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1908/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1908/Fixture.cs @@ -6,7 +6,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1908 [TestFixture] public class Fixture : BugTestCase { - [Test] public void QueryPropertyInBothFilterAndQuery() { @@ -58,4 +57,4 @@ Invoice inv } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1908ThreadSafety/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1908ThreadSafety/Model.cs index 5961247fedf..3fd8e2693da 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1908ThreadSafety/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1908ThreadSafety/Model.cs @@ -6,7 +6,7 @@ namespace NHibernate.Test.NHSpecificTest.NH1908ThreadSafety public class Order { public virtual long Id { get; set; } - public virtual IList ActiveOrderLines { get; set;} + public virtual IList ActiveOrderLines { get; set; } public virtual string Email { get; set; } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1911/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1911/Fixture.cs index 559fad9bca9..136330c4b31 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1911/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1911/Fixture.cs @@ -5,11 +5,9 @@ namespace NHibernate.Test.NHSpecificTest.NH1911 { - [TestFixture] public class Fixture : BugTestCase { - protected override void OnSetUp() { base.OnSetUp(); @@ -59,7 +57,5 @@ public void ConditionalAggregateProjection() Assert.That(actual[0][1], Is.EqualTo(2)); } } - } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1911/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1911/Model.cs index 4f9ba89e963..8014b1af2d8 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1911/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1911/Model.cs @@ -9,12 +9,10 @@ namespace NHibernate.Test.NHSpecificTest.NH1911 { - public class LogEvent { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual string Level { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1920/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1920/Fixture.cs index ccdd3cbf9cd..5e82dbb2d5a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1920/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1920/Fixture.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1920 [TestFixture] public class Fixture : BugTestCase { - [Test] public void Can_Query_Without_Collection_Size_Condition() { @@ -66,6 +65,5 @@ public void Can_Query_With_Collection_Size_Condition() tx.Commit(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1920/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1920/Model.cs index 3ba4d1b8ce5..1c35870df39 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1920/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1920/Model.cs @@ -9,7 +9,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1920 { - public class Customer { public virtual int Id { get; set; } @@ -24,5 +23,4 @@ public class Order public virtual string Memo { get; set; } public virtual Customer Customer { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1922/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1922/Fixture.cs index 7c8cf920aa6..6ede2701c51 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1922/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1922/Fixture.cs @@ -35,7 +35,6 @@ protected override void OnTearDown() base.OnTearDown(); } - [Test] public void CanExecuteQueryOnStatelessSessionUsingDetachedCriteria() { @@ -50,7 +49,5 @@ public void CanExecuteQueryOnStatelessSessionUsingDetachedCriteria() Assert.IsNotNull(cust); } } - - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1927/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1927/Fixture.cs index 0dcfdfac490..9c333fcb129 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1927/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1927/Fixture.cs @@ -60,8 +60,7 @@ private void TestQuery(QueryFactoryFunc queryFactoryFunc) Assert.That(queryFactoryFunc(session), Is.Not.Null, "failed with filter on"); tx.Commit(); } - - } + } [Test] public void CriteriaWithEagerFetch() diff --git a/src/NHibernate.Test/NHSpecificTest/NH1938/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1938/Fixture.cs index 1214cc71bf5..998e3570da7 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1938/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1938/Fixture.cs @@ -8,7 +8,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1938 [TestFixture] public class Fixture : BugTestCase { - protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) { // Database needs to be case-sensitive @@ -48,6 +47,5 @@ public void Can_Query_By_Example_Case_Insensitive() t.Rollback(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1938/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1938/Model.cs index a4d3cfcdcb0..7e1169d129d 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1938/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1938/Model.cs @@ -9,11 +9,9 @@ namespace NHibernate.Test.NHSpecificTest.NH1938 { - public class Person { public virtual int Id { get; set; } public virtual string Name { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1939/AuxType.cs b/src/NHibernate.Test/NHSpecificTest/NH1939/AuxType.cs index 382568bdb2b..9da9e914c8f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1939/AuxType.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1939/AuxType.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1939 { public class AuxType : AbstractAuxiliaryDatabaseObject { - override public string SqlCreateString(Dialect.Dialect dialect, IMapping p, string defaultCatalog, string defaultSchema) { return "select '" + Parameters["scriptParameter"] + "'"; @@ -17,6 +16,5 @@ override public string SqlDropString(Dialect.Dialect dialect, string defaultCata { return "select 'drop script'"; } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1939/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1939/Fixture.cs index ab34367fc3a..ca334649cea 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1939/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1939/Fixture.cs @@ -10,7 +10,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1939 [TestFixture] public class Fixture : BugTestCase { - private StringBuilder schemaBuilder; private void AddString(string sqlString) @@ -23,7 +22,6 @@ protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) return (dialect is Dialect.MsSql2000Dialect); } - [Test] public void Can_Parameterise_Auxiliary_Database_Objects() { @@ -40,6 +38,5 @@ public void Can_Parameterise_Auxiliary_Database_Objects() Assert.That(schema.Contains("select 'create script'"), Is.True, "parameterised schema create script not exported"); } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1948/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1948/Fixture.cs index d6abd924f4d..ceb9110be14 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1948/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1948/Fixture.cs @@ -6,7 +6,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1948 [TestFixture] public class Fixture : BugTestCase { - [Test] public void CanUseDecimalScaleZero() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH1959/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1959/Fixture.cs index df45ad7a4a4..dc367d80780 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1959/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1959/Fixture.cs @@ -5,7 +5,6 @@ namespace NHibernate.Test.NHSpecificTest.NH1959 [TestFixture] public class Fixture : BugTestCase { - protected override void OnTearDown() { using (ISession s = OpenSession()) diff --git a/src/NHibernate.Test/NHSpecificTest/NH1963/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH1963/DomainClass.cs index 37a7f403108..ff6f279aa96 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1963/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1963/DomainClass.cs @@ -1,22 +1,20 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH1963 +namespace NHibernate.Test.NHSpecificTest.NH1963 { - public class DomainClass - { - private byte[] byteData; - private int id; + public class DomainClass + { + private byte[] byteData; + private int id; - public int Id - { - get { return id; } - set { id = value; } - } + public int Id + { + get { return id; } + set { id = value; } + } - public byte[] ByteData - { - get { return byteData; } - set { byteData = value; } - } - } -} \ No newline at end of file + public byte[] ByteData + { + get { return byteData; } + set { byteData = value; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1965/ReattachWithCollectionTest.cs b/src/NHibernate.Test/NHSpecificTest/NH1965/ReattachWithCollectionTest.cs index c81c12b2e6e..c90bf1b0fa7 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1965/ReattachWithCollectionTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1965/ReattachWithCollectionTest.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; @@ -41,10 +40,10 @@ public void WhenReattachThenNotThrows() { var cat = new Cat(); using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.Save(cat); - session.Transaction.Commit(); + tran.Commit(); } using (var session = OpenSession()) @@ -53,11 +52,11 @@ public void WhenReattachThenNotThrows() } using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.Delete(cat); - session.Transaction.Commit(); + tran.Commit(); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2030/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2030/Fixture.cs index 33cffb0db30..dba4873091e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2030/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2030/Fixture.cs @@ -12,7 +12,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2030 [TestFixture] public class Fixture { - [Test] public void GetTypeWithLenShouldBeThreadSafe() { @@ -60,7 +59,6 @@ public void GetTypeWithLenShouldBeThreadSafe() throw exceptions[0]; } - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2031/HqlModFuctionForMsSqlTest.cs b/src/NHibernate.Test/NHSpecificTest/NH2031/HqlModFuctionForMsSqlTest.cs index 6fb2ab5f0cb..93f45581b30 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2031/HqlModFuctionForMsSqlTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2031/HqlModFuctionForMsSqlTest.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2031 { public class MyClass { - } [TestFixture] diff --git a/src/NHibernate.Test/NHSpecificTest/NH2037/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2037/Fixture.cs index 80065a2d8c7..254f42ae04e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2037/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2037/Fixture.cs @@ -1,13 +1,14 @@ - using NUnit.Framework; +using NUnit.Framework; + namespace NHibernate.Test.NHSpecificTest.NH2037 { - [TestFixture] - public class Fixture : BugTestCase + [TestFixture] + public class Fixture : BugTestCase { - [Test] - public void Test() - { - var country = new Country {Name = "Argentina"}; + [Test] + public void Test() + { + var country = new Country { Name = "Argentina" }; var city = new City { @@ -16,41 +17,40 @@ public void Test() Name = "Cordoba" }; - + using (ISession session = OpenSession()) - using(var tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { session.Save(city.Country); session.Save(city); tx.Commit(); - } - - using(ISession session = OpenSession()) + } + + using (ISession session = OpenSession()) using (var tx = session.BeginTransaction()) - { + { //THROW session.SaveOrUpdate(city); tx.Commit(); - } - + } + using (var session = OpenSession()) using (var tx = session.BeginTransaction()) - { + { Assert.IsNotNull(session.Get(city.Id)); tx.Commit(); - } + } } - + protected override void OnTearDown() { - using(var session = OpenSession()) - using(var tx = session.BeginTransaction()) - { + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { session.Delete("from City"); session.Delete("from Country"); tx.Commit(); } } - - } - } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2044/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH2044/DomainClass.cs index c49b336adfe..8b39bece628 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2044/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2044/DomainClass.cs @@ -1,22 +1,20 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH2044 +namespace NHibernate.Test.NHSpecificTest.NH2044 { - public class DomainClass - { - private char symbol; - private int id; + public class DomainClass + { + private char symbol; + private int id; - public int Id - { - get { return id; } - set { id = value; } - } + public int Id + { + get { return id; } + set { id = value; } + } - public char Symbol - { - get { return symbol; } - set { symbol = value; } - } - } -} \ No newline at end of file + public char Symbol + { + get { return symbol; } + set { symbol = value; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2044/SampleTest.cs b/src/NHibernate.Test/NHSpecificTest/NH2044/SampleTest.cs index 56c226c6dbc..5f572304727 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2044/SampleTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2044/SampleTest.cs @@ -34,7 +34,6 @@ protected override void OnTearDown() } } - [Test] public void IgnoreCaseShouldWorkWithCharCorrectly() { @@ -45,8 +44,7 @@ public void IgnoreCaseShouldWorkWithCharCorrectly() IList list = criteria.List(); Assert.AreEqual(1, list.Count); - - } + } } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2049/Fixture2049.cs b/src/NHibernate.Test/NHSpecificTest/NH2049/Fixture2049.cs index b82cd03450f..7aa2d4e6508 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2049/Fixture2049.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2049/Fixture2049.cs @@ -26,7 +26,6 @@ protected override void OnSetUp() } } - protected override void OnTearDown() { base.OnTearDown(); @@ -37,15 +36,13 @@ protected override void OnTearDown() } } - [Test] - [KnownBug("Known bug NH-2049.")] public void CanCriteriaQueryWithFilterOnJoinClassBaseClassProperty() { using (ISession session = OpenSession()) { session.EnableFilter("DeletedCustomer").SetParameter("deleted", false); - IList persons = session.CreateCriteria(typeof (Person)).List(); + IList persons = session.QueryOver().JoinQueryOver(x => x.IndividualCustomer).List(); Assert.That(persons, Has.Count.EqualTo(1)); Assert.That(persons[0].Id, Is.EqualTo(1)); @@ -55,16 +52,14 @@ public void CanCriteriaQueryWithFilterOnJoinClassBaseClassProperty() } } - [Test] - [KnownBug("Known bug NH-2049.", "NHibernate.Exceptions.GenericADOException")] public void CanHqlQueryWithFilterOnJoinClassBaseClassProperty() { using (ISession session = OpenSession()) { session.EnableFilter("DeletedCustomer").SetParameter("deleted", false); - var persons = session.CreateQuery("from Person as person left join person.IndividualCustomer as indCustomer") - .List(); + var persons = session.CreateQuery("from Person as person inner join fetch person.IndividualCustomer as indCustomer") + .List(); Assert.That(persons, Has.Count.EqualTo(1)); Assert.That(persons[0].Id, Is.EqualTo(1)); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2049/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2049/Model.cs index a85eeb97883..234b581d2da 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2049/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2049/Model.cs @@ -1,6 +1,4 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH2049 +namespace NHibernate.Test.NHSpecificTest.NH2049 { public abstract class Customer { @@ -20,4 +18,4 @@ public class Person public IndividualCustomer IndividualCustomer { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2053/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2053/Fixture.cs index 76beb7b6f87..8e6b199f602 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2053/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2053/Fixture.cs @@ -76,6 +76,5 @@ public void JoinedSubClass_Filter() } } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2055/AuxType.cs b/src/NHibernate.Test/NHSpecificTest/NH2055/AuxType.cs index 876526d4284..ee1884a7321 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2055/AuxType.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2055/AuxType.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2055 { public class AuxType : AbstractAuxiliaryDatabaseObject { - override public string SqlCreateString(Dialect.Dialect dialect, IMapping p, string defaultCatalog, string defaultSchema) { return "select '" + Parameters["scriptParameter"] + "'"; @@ -17,6 +16,5 @@ override public string SqlDropString(Dialect.Dialect dialect, string defaultCata { return "select 'drop script'"; } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2055/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2055/Fixture.cs index 5b9e70153ba..54ed1c463ea 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2055/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2055/Fixture.cs @@ -41,6 +41,5 @@ public void CanCreateAndDropSchema() } } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2057/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2057/Model.cs index db053f5cda5..1ad448190f1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2057/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2057/Model.cs @@ -9,11 +9,9 @@ namespace NHibernate.Test.NHSpecificTest.NH2057 { - public class Person { public virtual int Id { get; set; } public virtual string Name { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2065/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2065/Fixture.cs index 0cbadaddc52..a3f01a4b500 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2065/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2065/Fixture.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using NUnit.Framework; - namespace NHibernate.Test.NHSpecificTest.NH2065 { [TestFixture] @@ -10,7 +9,7 @@ public class Fixture : BugTestCase protected override void OnSetUp() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var person = new Person { @@ -21,17 +20,17 @@ protected override void OnSetUp() s.Save(child); person.Children.Add(child); - s.Transaction.Commit(); + t.Commit(); } } protected override void OnTearDown() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from Person"); - s.Transaction.Commit(); + t.Commit(); } } @@ -40,17 +39,17 @@ public void GetGoodErrorForDirtyReassociatedCollection() { Person person; using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { person = s.Get(1); NHibernateUtil.Initialize(person.Children); - s.Transaction.Commit(); + t.Commit(); } person.Children.Clear(); using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { Assert.That( () => @@ -62,6 +61,5 @@ public void GetGoodErrorForDirtyReassociatedCollection() "reassociated object has dirty collection: NHibernate.Test.NHSpecificTest.NH2065.Person.Children")); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2065/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2065/Model.cs index f2eda8b05cf..30f0d1b6533 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2065/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2065/Model.cs @@ -8,5 +8,4 @@ public class Person public virtual string Name { get; set; } public virtual ICollection Children { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2069/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2069/Fixture.cs index 95073c1bc15..38a57a414e0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2069/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2069/Fixture.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Transactions; -using NHibernate; -using NHibernate.Impl; using NHibernate.Proxy; -using NHibernate.Criterion; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH2069 @@ -15,7 +9,7 @@ public class Fixture : BugTestCase protected override void OnSetUp() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var test2 = new Test2(); test2.Cid = 5; @@ -29,19 +23,19 @@ protected override void OnSetUp() s.Save(test2); s.Save(test); - s.Transaction.Commit(); - } + t.Commit(); + } } protected override void OnTearDown() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Delete("from Test"); s.Delete("from Test2"); - s.Transaction.Commit(); - } + t.Commit(); + } } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH2069/ITest.cs b/src/NHibernate.Test/NHSpecificTest/NH2069/ITest.cs index ab208703125..9c6852ac204 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2069/ITest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2069/ITest.cs @@ -4,7 +4,7 @@ namespace NHibernate.Test.NHSpecificTest.NH2069 { public interface ITest : ITestBase { - string Description { get; set;} + string Description { get; set; } ITest2 Category { get; set; } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2074/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2074/Fixture.cs index 680cf9e69c6..6caf31cd829 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2074/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2074/Fixture.cs @@ -6,7 +6,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2074 [TestFixture] public class Fixture : BugTestCase { - protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) { return dialect is MsSql2000Dialect; @@ -20,6 +19,5 @@ public void CanQueryOnPropertyUsingUnicodeToken() s.CreateQuery("from Person").List(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2074/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2074/Model.cs index dd770dd2a9b..70e3e109521 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2074/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2074/Model.cs @@ -8,5 +8,4 @@ public class Person public virtual string Name { get; set; } public virtual int CalculatedProperty { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2077/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2077/Fixture.cs index 386d16c650e..8dc46f49761 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2077/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2077/Fixture.cs @@ -11,7 +11,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2077 [TestFixture] public class Fixture : BugTestCase { - protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect) { return dialect is MsSql2000Dialect; @@ -33,6 +32,5 @@ public void CanExecuteMultipleQueriesUsingNativeSQL() .ExecuteUpdate(); } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2077/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2077/Model.cs index fd6f14adf0b..3e54a2e2741 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2077/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2077/Model.cs @@ -8,5 +8,4 @@ public class Person public virtual string Name { get; set; } public virtual ICollection Children { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2112/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2112/Fixture.cs index a33f4759c86..3c6dc344e15 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2112/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2112/Fixture.cs @@ -73,6 +73,5 @@ protected void AssertDeleteCount(int expected) { Assert.That(Sfi.Statistics.EntityDeleteCount, Is.EqualTo(expected), "unexpected delete count"); } - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2112/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2112/Model.cs index 36b9e44f0e7..11e8e9ce4f1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2112/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2112/Model.cs @@ -23,7 +23,6 @@ public virtual bool Equals(TEntity other) } public override bool Equals(object obj) { - return this.Equals(obj as TEntity); } private int? mHashCode; @@ -62,8 +61,7 @@ public virtual int Version public virtual string Name { get; set; } public virtual IDictionary Map { get; set; } - - } + } public class B : BaseEntity { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2113/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2113/Fixture.cs index 63038658bfc..73295817f9b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2113/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2113/Fixture.cs @@ -11,7 +11,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2113 [TestFixture] public class Fixture : BugTestCase { - [Test] public void ShouldNotEagerLoadKeyManyToOneWhenOverridingGetHashCode() { @@ -42,7 +41,6 @@ public void ShouldNotEagerLoadKeyManyToOneWhenOverridingGetHashCode() tx.Commit(); } - using (var s = OpenSession()) using (var tx = s.BeginTransaction()) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2113/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2113/Model.cs index c44727aecaa..9e99dcde8c3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2113/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2113/Model.cs @@ -2,7 +2,7 @@ namespace NHibernate.Test.NHSpecificTest.NH2113 { public class Loan { - public virtual Broker Broker{ get; set;} + public virtual Broker Broker{ get; set; } public virtual Group Group { get; set; } public virtual string Name { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2148/BugFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2148/BugFixture.cs index 7e1f234c40d..7a6352ad995 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2148/BugFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2148/BugFixture.cs @@ -17,7 +17,6 @@ protected override void OnSetUp() }); tx.Commit(); } - } protected override void OnTearDown() diff --git a/src/NHibernate.Test/NHSpecificTest/NH2208/E1.generated.cs b/src/NHibernate.Test/NHSpecificTest/NH2208/E1.generated.cs index 63413654708..bfc63b14c0b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2208/E1.generated.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2208/E1.generated.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2208 /// public partial class E1 { - #region private fields Int32 _id; String _p1; @@ -50,4 +49,4 @@ public virtual SubOfReferred BO } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2208/Filter.cs b/src/NHibernate.Test/NHSpecificTest/NH2208/Filter.cs index 86ef23441e8..5fa579fa297 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2208/Filter.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2208/Filter.cs @@ -5,8 +5,8 @@ namespace NHibernate.Test.NHSpecificTest.NH2208 [TestFixture] public class Filter : BugTestCase { - [Test, Ignore("Not fixed yet")] - public void Test() + [Test] + public void TestHql() { using (ISession session = OpenSession()) { @@ -14,5 +14,15 @@ public void Test() session.CreateQuery("from E1 e join fetch e.BO").List(); } } + + [Test] + public void TestQueryOver() + { + using (ISession session = OpenSession()) + { + session.EnableFilter("myfilter"); + session.QueryOver().JoinQueryOver(x => x.BO).List(); + } + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2208/Referred.generated.cs b/src/NHibernate.Test/NHSpecificTest/NH2208/Referred.generated.cs index c22d369556f..eed86a8c857 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2208/Referred.generated.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2208/Referred.generated.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2208 /// public partial class Referred { - #region private fields Int32 _id; String _p1; @@ -41,4 +40,4 @@ public virtual String P1 } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2208/SubOfReferred.generated.cs b/src/NHibernate.Test/NHSpecificTest/NH2208/SubOfReferred.generated.cs index 218511a0f15..bea69493550 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2208/SubOfReferred.generated.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2208/SubOfReferred.generated.cs @@ -16,7 +16,6 @@ namespace NHibernate.Test.NHSpecificTest.NH2208 /// public partial class SubOfReferred : Referred { - #region private fields String _xX; #endregion @@ -32,4 +31,4 @@ public virtual String XX } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2224/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH2224/Domain.cs index 51d1186d829..13248a6d23a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2224/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2224/Domain.cs @@ -18,7 +18,6 @@ public virtual long Number } } - public virtual DateTime DateOfChange { get @@ -32,4 +31,4 @@ public virtual DateTime DateOfChange } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2234/MyType.cs b/src/NHibernate.Test/NHSpecificTest/NH2234/MyType.cs index 76eb017646e..7bcdeb67450 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2234/MyType.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2234/MyType.cs @@ -44,7 +44,6 @@ public static class MyUserTypes public static readonly List _values = new List { new MyUsertype(1, "Value 1"), new MyUsertype(2, "Value 2") }; - public static MyUsertype Value1 { get { return _values[0]; } @@ -65,7 +64,6 @@ public class SimpleCustomType : IUserType { private static readonly SqlType[] ReturnSqlTypes = { SqlTypeFactory.Int32 }; - #region IUserType Members public new bool Equals(object x, object y) @@ -129,5 +127,4 @@ public object Disassemble(object value) #endregion } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2244/PhoneNumber.cs b/src/NHibernate.Test/NHSpecificTest/NH2244/PhoneNumber.cs index 9c55473183c..d3e29ac4a52 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2244/PhoneNumber.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2244/PhoneNumber.cs @@ -9,7 +9,6 @@ public class PhoneNumber { public PhoneNumber() { - } public PhoneNumber(int countryCode, string number) diff --git a/src/NHibernate.Test/NHSpecificTest/NH2245/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2245/Model.cs index e11455db650..c3769954c9e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2245/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2245/Model.cs @@ -3,13 +3,12 @@ namespace NHibernate.Test.NHSpecificTest.NH2245 { -public class Foo -{ - public Foo() {} - public virtual Guid Id { get; protected set; } - public virtual string Name {get; set;} - public virtual string Description {get; set;} - public virtual int Version{get; set;} -} - -} \ No newline at end of file + public class Foo + { + public Foo() {} + public virtual Guid Id { get; protected set; } + public virtual string Name { get; set; } + public virtual string Description { get; set; } + public virtual int Version { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2280/OrganisationCode.cs b/src/NHibernate.Test/NHSpecificTest/NH2280/OrganisationCode.cs index 5e3ac6520a2..73317311509 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2280/OrganisationCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2280/OrganisationCode.cs @@ -36,7 +36,6 @@ public virtual Organisation Organisation set { key.Organisation = value; } } - public virtual DateTime? EndDate { get; set; } public override bool Equals(object obj) diff --git a/src/NHibernate.Test/NHSpecificTest/NH2297/InvalidCustomCompositeUserTypeBase.cs b/src/NHibernate.Test/NHSpecificTest/NH2297/InvalidCustomCompositeUserTypeBase.cs index b0906c684d8..7dadf3f3d24 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2297/InvalidCustomCompositeUserTypeBase.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2297/InvalidCustomCompositeUserTypeBase.cs @@ -15,7 +15,6 @@ public override string[] PropertyNames } } - public class InvalidTypesCustomCompositeUserType : InvalidCustomCompositeUserTypeBase { public override Type.IType[] PropertyTypes @@ -25,7 +24,6 @@ public override Type.IType[] PropertyTypes } } - /// /// An invalid custom user type mapper. /// @@ -91,7 +89,6 @@ public Object GetPropertyValue(Object component, int property) public void SetPropertyValue(Object object1, int i, Object object2) { - } public int GetHashCode(object x) @@ -104,4 +101,4 @@ public object Replace(object original, object target, ISessionImplementor sessio return DeepCopy(original); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2302/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2302/Fixture.cs index 4d3b8f56879..d214b5655f6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2302/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2302/Fixture.cs @@ -203,6 +203,5 @@ private static string GetFixedLengthString12000() { return new string('a', 12000); } - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2313/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH2313/Domain.cs index 429e89b95b0..13ebc470d4e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2313/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2313/Domain.cs @@ -7,6 +7,5 @@ public class MyClass public class TheOtherPart { - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2331/Nh2331Test.cs b/src/NHibernate.Test/NHSpecificTest/NH2331/Nh2331Test.cs index fad277386d5..7ad910399f2 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2331/Nh2331Test.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2331/Nh2331Test.cs @@ -148,4 +148,4 @@ DetachedCriteria personCriteria } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2362/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2362/Fixture.cs index 05ac017b3a1..6517d5a8d99 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2362/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2362/Fixture.cs @@ -18,7 +18,6 @@ in session.Query() into g let totalPrice = g.Sum(p => p.Price) select new { g.Key.CategoryId, g.Key.SupplierId, TotalPrice = totalPrice }).ToList(); - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2378/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2378/Fixture.cs index 62898934c04..c12abd924be 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2378/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2378/Fixture.cs @@ -59,9 +59,8 @@ public void ShortEntityCanBeQueryCorrectlyUsingLinqProvider() .Where(o => o.PersonId == 2) .ToList(); - Assert.That(m.Count, Is.EqualTo(1)); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2386/Organisation.cs b/src/NHibernate.Test/NHSpecificTest/NH2386/Organisation.cs index 4ff5d2fdf88..cfb78a2c688 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2386/Organisation.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2386/Organisation.cs @@ -4,47 +4,45 @@ namespace NHibernate.Test.NHSpecificTest.NH2386 { - public class Organisation - { - //internal to TGA - //private int organisationId; - public virtual Guid OrganisationId { get; set; } - private ISet tradingNames; - private ISet responsibleLegalPersons; + public class Organisation + { + //internal to TGA + //private int organisationId; + public virtual Guid OrganisationId { get; set; } + private ISet tradingNames; + private ISet responsibleLegalPersons; - /// - /// - /// - + /// + /// + /// + public virtual ISet ResponsibleLegalPersons + { + get + { + if (responsibleLegalPersons == null) + { + responsibleLegalPersons = new HashSet(); + } - public virtual ISet ResponsibleLegalPersons { - get { - if (responsibleLegalPersons == null) { - responsibleLegalPersons = new HashSet(); - } - return responsibleLegalPersons; - } - protected set { - responsibleLegalPersons = value; - - } - } + return responsibleLegalPersons; + } + protected set { responsibleLegalPersons = value; } + } - public virtual ISet TradingNames { - get { - if (tradingNames == null) { - tradingNames = new HashSet(); - } - return tradingNames; - } - protected set { - tradingNames = value; - - } - } + public virtual ISet TradingNames + { + get + { + if (tradingNames == null) + { + tradingNames = new HashSet(); + } - protected internal virtual byte[] RowVersion { get; protected set; } - - } + return tradingNames; + } + protected set { tradingNames = value; } + } + protected internal virtual byte[] RowVersion { get; protected set; } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2386/ResponsibleLegalPerson.cs b/src/NHibernate.Test/NHSpecificTest/NH2386/ResponsibleLegalPerson.cs index 5a4e3f243f0..e3a6bd275d1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2386/ResponsibleLegalPerson.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2386/ResponsibleLegalPerson.cs @@ -26,7 +26,6 @@ public virtual Guid ResponsibleLegalPersonId { protected set { responsibleLegalPersonId = value; } } - public virtual Organisation Organisation { get { return organisation; } protected set { organisation = value; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2386/TradingName.cs b/src/NHibernate.Test/NHSpecificTest/NH2386/TradingName.cs index 6bff1c52340..c6e2b183e54 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2386/TradingName.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2386/TradingName.cs @@ -11,8 +11,7 @@ namespace NHibernate.Test.NHSpecificTest.NH2386 public class TradingName { private Organisation organisation; - public virtual Guid TradingNameId { get; protected set;} - + public virtual Guid TradingNameId { get; protected set; } public TradingName(Organisation organisation) { if (organisation == null) { @@ -41,8 +40,5 @@ private bool ShouldSerializeEndDate() { public override string ToString() { return Name; } - - } - - + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs index 1c80edf6de7..be5eb0787ce 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs @@ -1,8 +1,8 @@ using System.Data.Common; using System.Data.Odbc; using System.Data.SqlClient; -using System.Configuration; using System.Transactions; +using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -32,9 +32,7 @@ private string FetchConnectionStringFromConfiguration() string connectionStringName; if (cfg.Properties.TryGetValue(Environment.ConnectionStringName, out connectionStringName)) { - var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; - Assert.That(connectionStringSettings, Is.Not.Null); - connectionString = connectionStringSettings.ConnectionString; + connectionString = ConfigurationProvider.Current.GetNamedConnectionString(connectionStringName); Assert.That(connectionString, Is.Not.Null.Or.Empty); return connectionString; } @@ -62,10 +60,8 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction() new DummyEnlistment(), EnlistmentOptions.None); - if (Sfi.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver)) - connection = new OdbcConnection(connectionString); - else - connection = new SqlConnection(connectionString); + connection = Sfi.ConnectionProvider.Driver.CreateConnection(); + connection.ConnectionString = connectionString; connection.Open(); using (s = Sfi.WithOptions().Connection(connection).OpenSession()) diff --git a/src/NHibernate.Test/NHSpecificTest/NH2459/TrainingComponent.cs b/src/NHibernate.Test/NHSpecificTest/NH2459/TrainingComponent.cs index c121793b33d..99b2704423f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2459/TrainingComponent.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2459/TrainingComponent.cs @@ -14,10 +14,8 @@ protected TrainingComponent() {} } public class SkillSet : TrainingComponent { - } public class Qualification : TrainingComponent { - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2467/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH2467/DomainClass.cs index b706fb32703..48d81fa9f86 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2467/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2467/DomainClass.cs @@ -1,10 +1,7 @@ - - namespace NHibernate.Test.NHSpecificTest.NH2467 { public class DomainClass { - public int Id { get; diff --git a/src/NHibernate.Test/NHSpecificTest/NH2467/NH2467Test.cs b/src/NHibernate.Test/NHSpecificTest/NH2467/NH2467Test.cs index a82f3d8c883..af0ecb5c05e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2467/NH2467Test.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2467/NH2467Test.cs @@ -38,7 +38,6 @@ public void ShouldNotThrowOnFuturePaging() { using (var session = OpenSession()) { - var contentQuery = session .CreateCriteria() .Add(Restrictions.Eq("Data", "Test")); @@ -62,7 +61,6 @@ public void ShouldNotThrowOnReversedFuturePaging() { using (var session = OpenSession()) { - var countQuery = session .CreateCriteria() .Add(Restrictions.Eq("Data", "Test")); @@ -86,7 +84,6 @@ public void ShouldNotThrowOnFuturePagingUsingHql() { using (var session = OpenSession()) { - var contentQuery = session.CreateQuery("from DomainClass as d where d.Data = ?"); contentQuery.SetString(0, "Test"); contentQuery.SetMaxResults(2); @@ -107,7 +104,6 @@ public void ShouldNotThrowOnReversedFuturePagingUsingHql() { using (var session = OpenSession()) { - var contentQuery = session.CreateQuery("from DomainClass as d where d.Data = ?"); contentQuery.SetString(0, "Test"); contentQuery.SetMaxResults(2); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2477/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2477/Fixture.cs index 5b9862c9dee..d4a81504a5f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2477/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2477/Fixture.cs @@ -31,23 +31,23 @@ public Scenario(ISessionFactory factory) { this.factory = factory; using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { for (int i = 0; i < 5; i++) { session.Persist(new Something { Name = i.ToString() }); } - session.Transaction.Commit(); + tran.Commit(); } } public void Dispose() { using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.CreateQuery("delete from Something").ExecuteUpdate(); - session.Transaction.Commit(); + tran.Commit(); } } } @@ -58,7 +58,7 @@ public void WhenTakeBeforeCountShouldApplyTake() using (new Scenario(Sfi)) { using (var session = Sfi.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { // This is another case where we have to work with subqueries and we have to write a specific query rewriter for Skip/Take instead flat the query in QueryReferenceExpressionFlattener //var actual = session.CreateQuery("select count(s) from Something s where s in (from Something take 3)").UniqueResult(); @@ -68,4 +68,4 @@ public void WhenTakeBeforeCountShouldApplyTake() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2490/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2490/Fixture.cs index 3db326425f7..8a338e0a66e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2490/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2490/Fixture.cs @@ -44,6 +44,5 @@ public void BadSqlFromJoinLogicError() } } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2490/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2490/Model.cs index 70cc82417e6..8602f85197a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2490/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2490/Model.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.NHSpecificTest.NH2490 { public class Base diff --git a/src/NHibernate.Test/NHSpecificTest/NH2500/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2500/Fixture.cs index 59fed1471ad..8e4c8b50e28 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2500/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2500/Fixture.cs @@ -18,7 +18,6 @@ public class Foo public virtual string Name { get; set; } } - [TestFixture] public class Fixture : TestCaseMappingByCode { diff --git a/src/NHibernate.Test/NHSpecificTest/NH251/CustomAccessDO.cs b/src/NHibernate.Test/NHSpecificTest/NH251/CustomAccessDO.cs index a9b921b9039..4273001600c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH251/CustomAccessDO.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH251/CustomAccessDO.cs @@ -37,7 +37,6 @@ public IDictionary Fields } } - /// /// Custom access strategy that uses IDynamicFieldContainer to get/set property values /// @@ -124,4 +123,4 @@ public MethodInfo Method } // Optional operation } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2510/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2510/Fixture.cs index c6df5b48d18..3cb9307a2f3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2510/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2510/Fixture.cs @@ -1,6 +1,5 @@ using System; using NHibernate.Cache; -using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NUnit.Framework; @@ -42,21 +41,21 @@ public Scenario(ISessionFactory factory) { this.factory = factory; using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.Persist(new Image { Id = 1 }); - session.Transaction.Commit(); + tran.Commit(); } } public void Dispose() { using (var session = factory.OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { session.CreateQuery("delete from Image").ExecuteUpdate(); - session.Transaction.Commit(); - } + tran.Commit(); + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2580/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2580/Fixture.cs index 41a042040f2..51077e6d0c1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2580/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2580/Fixture.cs @@ -7,7 +7,6 @@ public class Fixture: BugTestCase { private class MyClass { - } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs index d8778daec8a..967cbf8e953 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs @@ -1,5 +1,4 @@ using NHibernate.Cfg; -using NHibernate.Linq; using NUnit.Framework; using System; using System.Collections.Generic; @@ -22,9 +21,8 @@ protected override void Configure(Configuration configuration) configuration.Properties.Remove(cacheSetting); } configuration.SetProperty(Environment.UseSecondLevelCache, "false"); - } - private class ValueTuple + private class ValueTuple { public T1 Item1; public T2 Item2; @@ -131,7 +129,6 @@ protected int RunTest(Expression> c return TestAndAssert(condition, session, expectedIds); } } - } finally { @@ -237,9 +234,9 @@ private static IEnumerable CreateObjects(ISessi } if ((i%BatchSize) == 0) { - if (session.Transaction.IsActive) + if (session.GetCurrentTransaction()?.IsActive == true) { - session.Transaction.Commit(); + session.GetCurrentTransaction().Commit(); session.Clear(); } session.BeginTransaction(); @@ -253,9 +250,9 @@ private static IEnumerable CreateObjects(ISessi // emulating the outer-join logic for exceptional cases in Lin2Objects is IMO very hard. } } - if (session.Transaction.IsActive) + if (session.GetCurrentTransaction()?.IsActive == true) { - session.Transaction.Commit(); + session.GetCurrentTransaction().Commit(); session.Clear(); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs index 8f1061261d8..81c5193c693 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs @@ -153,8 +153,7 @@ public void OrShouldBeCompatibleWithAdditionForNullReferences() //Assert.AreEqual(0, leftResult.Count); //Assert.AreEqual(0, rightResult.Count); //Assert.AreEqual(0, orResult.Count); - - } + } } private static void TestCoreOrShouldBeCompatibleWithSum(ISession session, @@ -294,7 +293,6 @@ public void NHibernateLinqExploratoryTestWhichProvesNothing() // ); //result.ToList(); - var result = from r in session.Query() orderby (r.Id == 1101 || r.Id == 1102 ? r.Id - 1000 : r.Id) select (r.Id == 1101 || r.Id == 1102 ? r.Id + 1000 : r.Id); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingMoreOperatorsFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingMoreOperatorsFixture.cs index 30e0a5c8d7b..ffebb4e609b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingMoreOperatorsFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingMoreOperatorsFixture.cs @@ -80,7 +80,6 @@ public void TestNestedPlusBehindNotOrNav() Setters(MyBO.SetBO1_I1, MyBO.SetBO1_I2)); } - [Test] public void TestNestedPlusBehindOrNav2() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingOneOrTreeFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingOneOrTreeFixture.cs index 4d949b0631d..34615c8ff65 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingOneOrTreeFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingOneOrTreeFixture.cs @@ -87,7 +87,6 @@ public void Test_xyP_in_C__rsQ_in_D____xy_OJ_rs_OJ() Setters(MyBO.SetK1, MyBO.SetK2, MyBO.SetBO1_I1, MyBO.SetBO2_J1)); } - #endregion 1b. Two paths #region 1c. Path and subpath diff --git a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingThreeOrTreesSideBySideFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingThreeOrTreesSideBySideFixture.cs index 1a526dcb212..32cc09d5994 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingThreeOrTreesSideBySideFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingThreeOrTreesSideBySideFixture.cs @@ -103,7 +103,6 @@ public void Test_xyP_in_C__rsQ_in_D____xy_OJ_rs_OJ() Setters(MyBO.SetK1, MyBO.SetK2, MyBO.SetBO1_I1, MyBO.SetBO2_J1)); } - [Test] public void Test_xyP_in_A_C_D__rsQ_in_A_B_D____xy_IJ_rs_IJ() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingTwoOrTreesSideBySideFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingTwoOrTreesSideBySideFixture.cs index cb30eda8aaa..e76f413e495 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingTwoOrTreesSideBySideFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingTwoOrTreesSideBySideFixture.cs @@ -86,7 +86,6 @@ public void Test_xyP_in_C__rsQ_in_D____xy_OJ_rs_OJ() Setters(MyBO.SetK1, MyBO.SetK2, MyBO.SetBO1_I1, MyBO.SetBO2_J1)); } - [Test] public void Test_xyP_in_A_C_D__rsQ_in_A_B_D____xy_IJ_rs_IJ() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH266/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH266/Fixture.cs index f25e63ccb65..5294298b008 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH266/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH266/Fixture.cs @@ -54,7 +54,6 @@ protected override void OnTearDown() s.Close(); } - [Test] public void BaseClassLoad() { @@ -64,7 +63,6 @@ public void BaseClassLoad() Assert.AreEqual("the a", a.Name); s.Close(); - // load instance through hql s = OpenSession(); IQuery q = s.CreateQuery("from A as a where a.id = :id "); @@ -114,4 +112,4 @@ public void SpecificSubclass() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH266/UserFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH266/UserFixture.cs index 4542ef635ea..a3adbb67c85 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH266/UserFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH266/UserFixture.cs @@ -48,7 +48,6 @@ protected override void OnTearDown() s.Close(); } - /// /// This is testing problems that existed in 0.8.0-2 with extra "AND" /// being added to the sql when there was an attribute where="some sql". @@ -82,7 +81,6 @@ public void WhereAttribute() Assert.AreEqual(0, list.Count, "no 'inactive user' according to where clause"); s.Close(); - // // load a instance of B through hql // s = OpenSession(); // IQuery q = s.CreateQuery( "from B as b where b.id = :id" ); @@ -101,4 +99,4 @@ public void WhereAttribute() // s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2662/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2662/Fixture.cs index c94cc821745..aa48a553aea 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2662/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2662/Fixture.cs @@ -36,7 +36,7 @@ public void WhenCastAliasInQueryOverThenDoNotThrow() c => new {c.Id, c.Order.OrderDate, ((PizzaOrder)c.Order).PizzaName }) .ToArray(); - foreach (var item in temp) { Trace.WriteLine(item.PizzaName);} + foreach (var item in temp) { Trace.WriteLine(item.PizzaName); } }, Throws.Nothing); Assert.That(() => @@ -56,7 +56,6 @@ public void WhenCastAliasInQueryOverThenDoNotThrow() Assert.That(results.Count, Is.EqualTo(2)); Assert.That(results[0][2], Is.EqualTo("Margarita")); - }, Throws.Nothing); } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs index 1a19c8956a1..6dcf8071366 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs @@ -76,7 +76,6 @@ protected override void OnTearDown() tran.Commit(); } } - } [Test] @@ -136,4 +135,4 @@ public void Different_Key_In_DynamicComponentDictionary_Returns_Different_Keys() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2673/Blog.cs b/src/NHibernate.Test/NHSpecificTest/NH2673/Blog.cs index b1bd26ad065..d666eab6d9a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2673/Blog.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2673/Blog.cs @@ -24,11 +24,10 @@ public class Post public virtual string Body { get; set; } } - public class Comment { public virtual int Id { get; protected set; } public virtual string Title { get; set; } public virtual string Body { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2673/CachingWithTransformerTests.cs b/src/NHibernate.Test/NHSpecificTest/NH2673/CachingWithTransformerTests.cs index 08f8a3bc2be..afa5fb36df0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2673/CachingWithTransformerTests.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2673/CachingWithTransformerTests.cs @@ -52,7 +52,6 @@ public Scenario(ISessionFactory factory) blog.Posts.Add(new Post { Title = "Second post", Body = "Some other text" }); blog.Posts.Add(new Post { Title = "Third post", Body = "Third post text" }); - blog.Comments.Add(new Comment { Title = "First comment", Body = "Some text" }); blog.Comments.Add(new Comment { Title = "Second comment", Body = "Some other text" }); session.Save(blog); @@ -210,7 +209,6 @@ public void WhenEagerLoadingWithHqlThenNotThrows() } } - [Test(Description = "NH2961/3311")] public void CanCacheCriteriaWithLeftJoinAndResultTransformer() { @@ -228,7 +226,6 @@ public void CanCacheCriteriaWithLeftJoinAndResultTransformer() } } - [Test(Description = "NH2961/3311")] public void CanCacheCriteriaWithEagerLoadAndResultTransformer() { @@ -244,7 +241,6 @@ public void CanCacheCriteriaWithEagerLoadAndResultTransformer() } } - [Test(Description = "NH2961/3311")] public void CanCacheCriteriaWithLeftJoin() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2691/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2691/Fixture.cs index f8087519f50..2afb4588ae0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2691/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2691/Fixture.cs @@ -1,6 +1,5 @@ using System.Linq; using NHibernate.Cfg.MappingSchema; -using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NUnit.Framework; @@ -22,11 +21,11 @@ protected override HbmMapping GetMappings() public void WhenUseCountWithOrderThenCutTheOrder() { using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var baseQuery = from cat in session.Query() orderby cat.BirthDate select cat; Assert.That(() => baseQuery.Count(), Throws.Nothing); - session.Transaction.Commit(); + tran.Commit(); } } @@ -34,11 +33,11 @@ public void WhenUseCountWithOrderThenCutTheOrder() public void WhenUseLongCountWithOrderThenCutTheOrder() { using (var session = OpenSession()) - using (session.BeginTransaction()) + using (var tran = session.BeginTransaction()) { var baseQuery = from cat in session.Query() orderby cat.BirthDate select cat; Assert.That(() => baseQuery.LongCount(), Throws.Nothing); - session.Transaction.Commit(); + tran.Commit(); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2693/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2693/Fixture.cs index 29288782af7..7c2cd77ea05 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2693/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2693/Fixture.cs @@ -32,7 +32,6 @@ protected override void OnSetUp() var fourthLevel2 = new FourthLevel { SomeString = "second", SpecificThirdLevel = thirdLevel1 }; thirdLevel1.FourthLevels.Add(fourthLevel2); - var firstLevel2 = new FirstLevel(); var secondLevel2 = new SecondLevelComponent { FirstLevel = firstLevel2 }; @@ -47,7 +46,6 @@ protected override void OnSetUp() var fourthLevel4 = new FourthLevel { SomeString = "fourth", SpecificThirdLevel = thirdLevel2 }; thirdLevel2.FourthLevels.Add(fourthLevel4); - var firstLevel3 = new FirstLevel(); var secondLevel3 = new SecondLevelComponent { FirstLevel = firstLevel3 }; @@ -57,7 +55,6 @@ protected override void OnSetUp() secondLevel3.ThirdLevel = thirdLevel3; secondLevel3.SpecificThirdLevel = thirdLevel3; - session.Save(thirdLevel1); session.Save(thirdLevel2); session.Save(thirdLevel3); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2693/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2693/Model.cs index 25056895d0a..c474369c1b1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2693/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2693/Model.cs @@ -1,39 +1,46 @@ - -namespace NHibernate.Test.NHSpecificTest.NH2693 { - using System; - using System.Collections.Generic; +using System; +using System.Collections.Generic; - public class FirstLevel { - public FirstLevel() { - SecondLevels = new HashSet(); - } +namespace NHibernate.Test.NHSpecificTest.NH2693 +{ + public class FirstLevel + { + public FirstLevel() + { + SecondLevels = new HashSet(); + } - public virtual Guid Id { get; set; } - public virtual ICollection SecondLevels { get; set; } - } + public virtual Guid Id { get; set; } + public virtual ICollection SecondLevels { get; set; } + } - public class SecondLevelComponent { - public virtual FirstLevel FirstLevel { get; set; } - public virtual ThirdLevel ThirdLevel { get; set; } - public virtual SpecificThirdLevel SpecificThirdLevel { get; set; } - public virtual bool SomeBool { get; set; } - } + public class SecondLevelComponent + { + public virtual FirstLevel FirstLevel { get; set; } + public virtual ThirdLevel ThirdLevel { get; set; } + public virtual SpecificThirdLevel SpecificThirdLevel { get; set; } + public virtual bool SomeBool { get; set; } + } - public abstract class ThirdLevel { - public virtual Guid Id { get; set; } - } + public abstract class ThirdLevel + { + public virtual Guid Id { get; set; } + } - public class SpecificThirdLevel : ThirdLevel { - public SpecificThirdLevel() { - FourthLevels = new HashSet(); - } + public class SpecificThirdLevel : ThirdLevel + { + public SpecificThirdLevel() + { + FourthLevels = new HashSet(); + } - public virtual ICollection FourthLevels { get; set; } - } + public virtual ICollection FourthLevels { get; set; } + } - public class FourthLevel { - public virtual Guid Id { get; set; } - public virtual SpecificThirdLevel SpecificThirdLevel { get; set; } - public virtual string SomeString { get; set; } - } + public class FourthLevel + { + public virtual Guid Id { get; set; } + public virtual SpecificThirdLevel SpecificThirdLevel { get; set; } + public virtual string SomeString { get; set; } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2697/ArticleItem.cs b/src/NHibernate.Test/NHSpecificTest/NH2697/ArticleItem.cs index cadff6f74b8..09f9a877ba6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2697/ArticleItem.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2697/ArticleItem.cs @@ -1,15 +1,12 @@ - +using System; -using System; namespace NHibernate.Test.NHSpecificTest.NH2697 - { - public class ArticleItem - { + public class ArticleItem + { public virtual int Articleid { get; set; } public virtual Int16 IsFavorite { get; set; } public virtual string Name { get; set; } public virtual ArticleGroupItem Articlegroup { get; set; } - - } -} \ No newline at end of file + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2697/SampleTest.cs b/src/NHibernate.Test/NHSpecificTest/NH2697/SampleTest.cs index 9ca2f16e424..4670af02719 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2697/SampleTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2697/SampleTest.cs @@ -48,7 +48,6 @@ protected override void OnTearDown() { base.OnTearDown(); - using (ISession session = this.OpenSession()) { IList list = session.CreateCriteria("Article").List(); foreach (ArticleItem item in list) @@ -68,7 +67,6 @@ protected override void OnTearDown() session.Delete(hql); session.Flush(); } - } [Test] @@ -115,8 +113,6 @@ public void Can_GetListOfArticles() Assert.That(result.Count, Is.GreaterThan(0)); } - - [Test] public void Can_SetArticleFavoriteWithHQL_NamedParam() { @@ -142,7 +138,6 @@ public void Can_SetArticleFavoriteWithHQL_NamedParam() result = session.CreateQuery(HQL).List(); } Assert.That(result.Count, Is.GreaterThan(0)); - - } +} } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2700/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2700/Fixture.cs index ca6bc21cab2..56d685c8047 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2700/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2700/Fixture.cs @@ -54,7 +54,6 @@ public static string GetSql(ICriteria criteria) [Test] public void TestProjection() { - using (var s = OpenSession()) { var proj = new SqlFunctionProjection("AddDays", NHibernateUtil.DateTime, diff --git a/src/NHibernate.Test/NHSpecificTest/NH2700/ModelClass.cs b/src/NHibernate.Test/NHSpecificTest/NH2700/ModelClass.cs index c027ebebabf..6ea4b852674 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2700/ModelClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2700/ModelClass.cs @@ -8,5 +8,4 @@ public class ModelClass public virtual DateTime Date1 { get; set; } public virtual DateTime Value1 { get; set; } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2705/Test.cs b/src/NHibernate.Test/NHSpecificTest/NH2705/Test.cs index bec7503160f..18e0af79875 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2705/Test.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2705/Test.cs @@ -62,7 +62,6 @@ public void LinqQueryWithFetch_WhenDerivedClassesUseComponentAndManyToOne_DoesNo Assert.That(() => s.Query() .Fetch(p => p.SubItem).ToList(), Throws.Nothing); - // fetching second level properties should work too Assert.That(() => s.Query() .Fetch(p => p.SubItem).ThenFetch(p => p.Details).ToList(), Throws.Nothing); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2714/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2714/Fixture.cs new file mode 100644 index 00000000000..3f8123d48c3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2714/Fixture.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH2714 +{ + [TestFixture] + public class Fixture : BugTestCase + { + private const int ExtraId = 500; + + protected override void OnTearDown() + { + using (ISession s = OpenSession()) + { + s.Delete($"from {nameof(Item)}"); + s.Flush(); + s.Delete($"from {nameof(Information)}"); + s.Flush(); + } + } + + [Test] + public void PropertyRefUsesOtherColumns() + { + var information = new Information {Name = "First", ExtraId = ExtraId}; + + var item = new Item {Id = 1, Name = information.Name, ExtraId = information.ExtraId}; + + using (ISession session = OpenSession()) + { + session.Save(information); + session.Save(item); + session.Flush(); + } + + using (ISession session = OpenSession()) + { + var otherInformation = session.Get(information.Id); + Assert.That(otherInformation.Items.Count, Is.EqualTo(1)); + } + } + + [Test] + public void ChildKeyPropertiesOfParentAreRetrieved() + { + var information = new Information {Name = "First", ExtraId = ExtraId}; + + var item = new Item {Id = 1, Name = information.Name, ExtraId = information.ExtraId}; + + using (ISession session = OpenSession()) + { + session.Save(information); + session.Save(item); + session.Flush(); + } + + using (ISession session = OpenSession()) + { + var otherInformation = session.Get(information.Id); + + Assert.That(otherInformation.Name, Is.EqualTo(information.Name)); + Assert.That(otherInformation.ExtraId, Is.EqualTo(information.ExtraId)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2714/Information.cs b/src/NHibernate.Test/NHSpecificTest/NH2714/Information.cs new file mode 100644 index 00000000000..fbff5a49650 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2714/Information.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2714 +{ + public class Information + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual int ExtraId { get; set; } + public virtual ISet Items { get; set; } = new HashSet(); + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2714/Item.cs b/src/NHibernate.Test/NHSpecificTest/NH2714/Item.cs new file mode 100644 index 00000000000..db4128a4276 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2714/Item.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.NH2714 +{ + public class Item + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual int ExtraId { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2714/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH2714/Mappings.hbm.xml new file mode 100644 index 00000000000..eb4f07ee80d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2714/Mappings.hbm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH2773/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2773/Fixture.cs index a483269438f..85d07f5307a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2773/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2773/Fixture.cs @@ -50,7 +50,6 @@ public void DeserializedSession_ProxyType_ShouldBeEqualToOriginalProxyType() { tx.Commit(); } - using (MemoryStream sessionMemoryStream = new MemoryStream()) { var formatter = new BinaryFormatter diff --git a/src/NHibernate.Test/NHSpecificTest/NH2779/LineItem.cs b/src/NHibernate.Test/NHSpecificTest/NH2779/LineItem.cs index bb4affb0582..b5964e94e16 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2779/LineItem.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2779/LineItem.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.NHSpecificTest.NH2779 +namespace NHibernate.Test.NHSpecificTest.NH2779 { public class LineItem { diff --git a/src/NHibernate.Test/NHSpecificTest/NH280/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH280/Fixture.cs index 5d269ba2f2b..9f60dd9a2cc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH280/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH280/Fixture.cs @@ -49,7 +49,6 @@ public void ConstInSelect() Assert.AreEqual(123, result[0]); Assert.AreEqual("Fiammy", (result[1] as Foo).Description); - l = s.CreateQuery("select 123, f.Description from Foo f").List(); result = l[0] as IList; Assert.AreEqual(123, result[0]); diff --git a/src/NHibernate.Test/NHSpecificTest/NH280/Foo.cs b/src/NHibernate.Test/NHSpecificTest/NH280/Foo.cs index c2c098809dc..bc5fdf5b480 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH280/Foo.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH280/Foo.cs @@ -28,6 +28,5 @@ public virtual string Description get { return _description; } set { _description = value; } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2828/Entities.cs b/src/NHibernate.Test/NHSpecificTest/NH2828/Entities.cs index 260828eebf7..387f50ba402 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2828/Entities.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2828/Entities.cs @@ -91,7 +91,6 @@ public virtual bool RemoveAddress(Address address) } return false; } - } public class Address @@ -118,7 +117,6 @@ public virtual void RemoveCompany() { this.Company = null; } - } public class BankAccount @@ -128,7 +126,5 @@ public class BankAccount public virtual string Name { get; set; } public virtual Company Company { get; set; } - } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH283/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH283/Fixture.cs index 89154acca71..1b4c8ccb0f5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH283/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH283/Fixture.cs @@ -18,7 +18,6 @@ public void ForeignKeyNames() Assembly.GetAssembly(typeof(Master)) ); - string script = string.Join("\n", cfg.GenerateSchemaCreationScript(new MsSql2008Dialect())); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2846/Entities.cs b/src/NHibernate.Test/NHSpecificTest/NH2846/Entities.cs index ec7b0ef201e..7db47f61a9a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2846/Entities.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2846/Entities.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; - namespace NHibernate.Test.NHSpecificTest.NH2846 { public class Post @@ -34,4 +33,4 @@ public class Comment public virtual Post Post { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2846/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2846/Fixture.cs index f20da6a7aa3..0bdb7e7b0dd 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2846/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2846/Fixture.cs @@ -60,22 +60,18 @@ protected override void OnTearDown() } } - [Test] public void FetchOnCountWorks() { using (var session = OpenSession()) { - var count = session.Query() .Fetch(p => p.Category) .FetchMany(p => p.Comments) .Count(); Assert.AreEqual(1, count); - } } - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2869/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH2869/DomainClass.cs index f3eb9ff3d61..3f630cb5cd0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2869/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2869/DomainClass.cs @@ -2,9 +2,8 @@ { public class DomainClass { - public virtual int Id { get; set; } public virtual string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2892/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2892/Fixture.cs new file mode 100644 index 00000000000..8b64ff19e4b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2892/Fixture.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH2892 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty("hbm2ddl.keywords", "auto-quote"); + } + + protected override void OnSetUp() + { + using (ISession session = OpenSession()) + { + var order = new Order(); + + session.Save(order); + session.Flush(); + + var orderLine = new OrderLine + { + Orders = order + }; + + session.Save(orderLine); + session.Flush(); + } + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + { + session.Delete($"from {nameof(OrderLine)}"); + session.Delete($"from {nameof(Order)}"); + session.Flush(); + } + } + + [Test] + public void SelectOrderLineFromOrder() + { + using (ISession session = OpenSession()) + { + var order = session.Query().FirstOrDefault(); + + Assert.That(order, Is.Not.Null); + Assert.DoesNotThrow(() => order.Elements.FirstOrDefault()); + Assert.That(order.OrderLines, Is.Not.Null); + Assert.NotZero(order.OrderLines.Count); + Assert.NotZero(order.OrderLines.First().Id); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2892/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH2892/Mappings.hbm.xml new file mode 100644 index 00000000000..b208c5314c3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2892/Mappings.hbm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH2892/Order.cs b/src/NHibernate.Test/NHSpecificTest/NH2892/Order.cs new file mode 100644 index 00000000000..7b37fe6b9de --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2892/Order.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2892 +{ + public class Order + { + public virtual int Id { get; set; } + public virtual ISet OrderLines { get; set; } = new HashSet(); + public virtual IList Elements { get; set; } = new List(); + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2892/OrderLine.cs b/src/NHibernate.Test/NHSpecificTest/NH2892/OrderLine.cs new file mode 100644 index 00000000000..41e8bc9f0bd --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2892/OrderLine.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2892 +{ + public class OrderLine + { + public virtual int Id { get; set; } + public virtual Order Orders { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH295/SubclassFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH295/SubclassFixture.cs index 53408ed2825..28b80ed854a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH295/SubclassFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH295/SubclassFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NHibernate.Criterion; using NUnit.Framework; @@ -21,29 +20,35 @@ protected override string MappingsAssembly public void LoadByIDFailureSameSession() { User ui1 = new User("User1"); + object uid1; - ISession s = OpenSession(); - s.BeginTransaction(); - object uid1 = s.Save(ui1); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + uid1 = s.Save(ui1); + t.Commit(); + s.Close(); + } - Assert.IsNotNull(s.Get(typeof(User), uid1)); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.IsNotNull(s.Get(typeof(User), uid1)); - UserGroup ug = (UserGroup) s.Get(typeof(UserGroup), uid1); - Assert.IsNull(ug); + UserGroup ug = (UserGroup) s.Get(typeof(UserGroup), uid1); + Assert.IsNull(ug); - s.Transaction.Commit(); - s.Close(); + t.Commit(); + s.Close(); + } - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from Party"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Party"); + t.Commit(); + s.Close(); + } } [Test] @@ -52,79 +57,98 @@ public void LoadByIDFailure() UserGroup ug1 = new UserGroup(); ug1.Name = "Group1"; User ui1 = new User("User1"); + object gid1, uid1; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + gid1 = s.Save(ug1); + uid1 = s.Save(ui1); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load user with USER NAME: + ICriteria criteria1 = s.CreateCriteria(typeof(User)); + criteria1.Add(Expression.Eq("Name", "User1")); + Assert.AreEqual(1, criteria1.List().Count); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load group with USER NAME: + ICriteria criteria2 = s.CreateCriteria(typeof(UserGroup)); + criteria2.Add(Expression.Eq("Name", "User1")); + Assert.AreEqual(0, criteria2.List().Count); + t.Commit(); + s.Close(); + } - ISession s = OpenSession(); - s.BeginTransaction(); - object gid1 = s.Save(ug1); - object uid1 = s.Save(ui1); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load user with USER NAME: - ICriteria criteria1 = s.CreateCriteria(typeof(User)); - criteria1.Add(Expression.Eq("Name", "User1")); - Assert.AreEqual(1, criteria1.List().Count); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load group with USER NAME: - ICriteria criteria2 = s.CreateCriteria(typeof(UserGroup)); - criteria2.Add(Expression.Eq("Name", "User1")); - Assert.AreEqual(0, criteria2.List().Count); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load group with GROUP NAME - ICriteria criteria3 = s.CreateCriteria(typeof(UserGroup)); - criteria3.Add(Expression.Eq("Name", "Group1")); - Assert.AreEqual(1, criteria3.List().Count); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load user with GROUP NAME - ICriteria criteria4 = s.CreateCriteria(typeof(User)); - criteria4.Add(Expression.Eq("Name", "Group1")); - Assert.AreEqual(0, criteria4.List().Count); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - //Load group with USER IDENTITY - ug1 = (UserGroup) s.Get(typeof(UserGroup), uid1); - Assert.IsNull(ug1); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - ui1 = (User) s.Get(typeof(User), gid1); - Assert.IsNull(ui1); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - Party p = (Party) s.Get(typeof(Party), uid1); - Assert.IsTrue(p is User); - p = (Party) s.Get(typeof(Party), gid1); - Assert.IsTrue(p is UserGroup); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from Party"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load group with GROUP NAME + ICriteria criteria3 = s.CreateCriteria(typeof(UserGroup)); + criteria3.Add(Expression.Eq("Name", "Group1")); + Assert.AreEqual(1, criteria3.List().Count); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load user with GROUP NAME + ICriteria criteria4 = s.CreateCriteria(typeof(User)); + criteria4.Add(Expression.Eq("Name", "Group1")); + Assert.AreEqual(0, criteria4.List().Count); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + //Load group with USER IDENTITY + ug1 = (UserGroup) s.Get(typeof(UserGroup), uid1); + Assert.IsNull(ug1); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + ui1 = (User) s.Get(typeof(User), gid1); + Assert.IsNull(ui1); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Party p = (Party) s.Get(typeof(Party), uid1); + Assert.IsTrue(p is User); + p = (Party) s.Get(typeof(Party), gid1); + Assert.IsTrue(p is UserGroup); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Party"); + t.Commit(); + s.Close(); + } } [Test] @@ -150,4 +174,4 @@ public void List() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2951/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2951/Fixture.cs index c800c611099..5836b763332 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2951/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2951/Fixture.cs @@ -7,6 +7,11 @@ namespace NHibernate.Test.NHSpecificTest.NH2951 [TestFixture] public class Fixture : BugTestCase { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + protected override void OnTearDown() { using (ISession session = OpenSession()) @@ -20,13 +25,11 @@ protected override void OnTearDown() } [Test] - [Ignore("Not working.")] public void UpdateWithSubqueryToJoinedSubclass() { using (ISession session = OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { - var c = new Customer { Name = "Bob" }; session.Save(c); @@ -49,4 +52,4 @@ public void UpdateWithSubqueryToJoinedSubclass() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH2969/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2969/Fixture.cs index b83b6c48fe1..dd2044f1cb9 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2969/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2969/Fixture.cs @@ -103,7 +103,6 @@ public void CanGetGoldfishAsFish() } } - [Test] public void CanGetParrot() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH298/Category.cs b/src/NHibernate.Test/NHSpecificTest/NH298/Category.cs index 067db6e3d67..2eddf0b9ab3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH298/Category.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH298/Category.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; namespace NHibernate.Test.NHSpecificTest.NH298 { - public class Category { private int id; private string name; diff --git a/src/NHibernate.Test/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs b/src/NHibernate.Test/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs index f444ff0ee82..310ecebb71f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH298/IndexedBidirectionalOneToManyTest.cs @@ -6,10 +6,8 @@ using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH298 { - [TestFixture] public class IndexedBidirectionalOneToManyTest : BugTestCase { - protected override void OnSetUp() { base.OnSetUp(); using ( ISession session = this.OpenSession() ) { diff --git a/src/NHibernate.Test/NHSpecificTest/NH2985/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH2985/Model.cs index 7c322e208f7..e852e0efaf7 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2985/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2985/Model.cs @@ -12,8 +12,6 @@ public class ClassA public virtual String Name { get; set; } public virtual IList Childs { get; set; } - - } /// @@ -23,7 +21,6 @@ public class WebImage { public WebImage() { - OldPosition = -1; //di base un immagine è nuova. } /// @@ -67,13 +64,11 @@ public virtual Guid Id /// public virtual Int32 Height { get; set; } - /// /// /// public virtual Int32? ImgEval { get; set; } - /// /// /// @@ -143,9 +138,7 @@ private Bitmap CreateBitmap() { return null; } - } #endif } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3004/TestSqlClientDriver.cs b/src/NHibernate.Test/NHSpecificTest/NH3004/TestSqlClientDriver.cs index 24297856ff0..70f0f134ab6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3004/TestSqlClientDriver.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3004/TestSqlClientDriver.cs @@ -18,7 +18,6 @@ public class TestSqlClientDriver : SqlClientDriver public TestSqlClientDriver() { - } public TestSqlClientDriver(bool UseNamedPrefixInSql, bool UseNamedPrefixInParameter) @@ -30,7 +29,6 @@ public TestSqlClientDriver(bool UseNamedPrefixInSql, bool UseNamedPrefixInParame public override bool UseNamedPrefixInSql { get { return _UseNamedPrefixInSql; } - } public override bool UseNamedPrefixInParameter @@ -38,4 +36,4 @@ public override bool UseNamedPrefixInParameter get { return _UseNamedPrefixInParameter; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs b/src/NHibernate.Test/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs index f460546d6ad..0c6e98895a4 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3023/DeadlockConnectionPoolIssueTest.cs @@ -248,7 +248,6 @@ public void ConnectionPoolCorruptionAfterDeadlock(bool distributed, bool dispose ? "Deadlock not reported on initial request, and initial request failed" : "Initial request failed", subsequentFailedRequests); - } while (tryCount < 3); // // I'll change this to while(true) sometimes so I don't have to keep running the test @@ -284,7 +283,6 @@ private void RunScript(string script) foreach (var batch in Regex.Split(sql, @"^go\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline) .Where(b => !string.IsNullOrEmpty(b))) { - using (var cmd = new System.Data.SqlClient.SqlCommand(batch, cxn)) { cmd.ExecuteNonQuery(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH3046/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3046/Fixture.cs index 86a56f36ab4..f2bb9a1c4f1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3046/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3046/Fixture.cs @@ -1,4 +1,3 @@ - using System; using NUnit.Framework; using NHibernate.Mapping.ByCode; @@ -12,7 +11,6 @@ public class Fixture : BugTestCase [Test, Explicit] public void MemoryLeak() { - long initialMemory = GC.GetTotalMemory(true); long nextId = 1; long nextIdChild = 1; diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/Employer.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/Employer.cs new file mode 100644 index 00000000000..c5fe0ffd9ec --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/Employer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class Employer + { + public virtual EmployerCpId CpId { get; set; } + + public virtual string Name { get; set; } + + public virtual ICollection EmploymentList { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/EmployerCpId.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/EmployerCpId.cs new file mode 100644 index 00000000000..dd8bd8cb69a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/EmployerCpId.cs @@ -0,0 +1,22 @@ +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class EmployerCpId + { + public virtual int IdA { get; set; } + + public virtual int IdB { get; set; } + + public override bool Equals(object obj) + { + if (!(obj is EmployerCpId objCpId)) + return false; + + return IdA == objCpId.IdA && IdB == objCpId.IdB; + } + + public override int GetHashCode() + { + return IdA.GetHashCode() ^ IdB.GetHashCode(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/Employment.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/Employment.cs new file mode 100644 index 00000000000..a84fd07cc6c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/Employment.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class Employment + { + public virtual EmploymentCpId CpId { get; set; } + + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/EmploymentCpId.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/EmploymentCpId.cs new file mode 100644 index 00000000000..2207044966a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/EmploymentCpId.cs @@ -0,0 +1,25 @@ +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class EmploymentCpId + { + public virtual int Id { get; set; } + + public virtual Person PersonObj { get; set; } + + public virtual Employer EmployerObj { get; set; } + + public override bool Equals(object obj) + { + if (!(obj is EmploymentCpId objCpId)) + return false; + + return Id == objCpId.Id && PersonObj.CpId == objCpId.PersonObj.CpId && + EmployerObj.CpId == objCpId.EmployerObj.CpId; + } + + public override int GetHashCode() + { + return Id.GetHashCode() ^ PersonObj.CpId.GetHashCode() ^ EmployerObj.CpId.GetHashCode(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/Fixture.cs new file mode 100644 index 00000000000..f5a298c6afd --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/Fixture.cs @@ -0,0 +1,235 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + [TestFixture] + public class Fixture : BugTestCase + { + // Disable second level cache + protected override string CacheConcurrencyStrategy => null; + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from Employment").ExecuteUpdate(); + + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + + t.Commit(); + } + } + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var personList = new List(); + var employerList = new List(); + + // Global id to avoid false positive assertion with positional parameter + var gId = 1; + + for (var i = 0; i < 3; i++) + { + var personCpId = new PersonCpId { IdA = gId++, IdB = gId++ }; + var personObj = new Person + { CpId = personCpId, Name = "PERSON_" + personCpId.IdA + "_" + personCpId.IdB }; + s.Save(personObj); + personList.Add(personObj); + } + + for (var i = 0; i < 3; i++) + { + var employerCpId = new EmployerCpId { IdA = gId++, IdB = gId++ }; + var employerObj = new Employer + { CpId = employerCpId, Name = "EMPLOYER_" + employerCpId.IdA + "_" + employerCpId.IdB }; + s.Save(employerObj); + employerList.Add(employerObj); + } + + var employmentIds = new[] + { + gId++, + gId++, + gId++, + gId++, + }; + var employmentNames = new[] + { + //P1 + E1 + "EMPLOYMENT_" + employmentIds[0] + "_" + + personList[0].CpId.IdA + "_" + personList[0].CpId.IdA + "_" + + employerList[0].CpId.IdA + "_" + employerList[0].CpId.IdB, + //P1 + E2 + "EMPLOYMENT_" + employmentIds[1] + "_" + + personList[0].CpId.IdA + "_" + personList[0].CpId.IdA + "_" + + employerList[1].CpId.IdA + "_" + employerList[1].CpId.IdB, + //P2 + E2 + "EMPLOYMENT_" + employmentIds[2] + "_" + + personList[1].CpId.IdA + "_" + personList[1].CpId.IdA + "_" + + employerList[1].CpId.IdA + "_" + employerList[1].CpId.IdB, + //P2 + E3 + "EMPLOYMENT_" + employmentIds[2] + "_" + + personList[1].CpId.IdA + "_" + personList[1].CpId.IdA + "_" + + employerList[2].CpId.IdA + "_" + employerList[2].CpId.IdB + }; + var employmentPersons = new[] + { + personList[0], + personList[0], + personList[1], + personList[1] + }; + var employmentEmployers = new[] + { + employerList[0], + employerList[1], + employerList[1], + employerList[2] + }; + + for (var k = 0; k < employmentIds.Length; k++) + { + var employmentCpId = new EmploymentCpId + { + Id = employmentIds[k], + PersonObj = employmentPersons[k], + EmployerObj = employmentEmployers[k] + }; + var employmentObj = new Employment { CpId = employmentCpId, Name = employmentNames[k] }; + s.Save(employmentObj); + } + + for (var i = 0; i < 3; i++) + { + var personNoComponentObj = new PersonNoComponent { IdA = gId++, IdB = gId++ }; + personNoComponentObj.Name = "PERSON_NO_COMPONENT_" + personNoComponentObj.IdA + "_" + + personNoComponentObj.IdB; + s.Save(personNoComponentObj); + } + + t.Commit(); + } + } + + // Test reproducing the problem. + [Test] + public void GetPersonTest() + { + using (var session = OpenSession()) + { + var person1_2 = session.Get(new PersonCpId { IdA = 1, IdB = 2 }); + Assert.That(person1_2.Name, Is.EqualTo("PERSON_1_2")); + Assert.That( + person1_2.EmploymentList.Select(e => e.Name), + Is.EquivalentTo(new[] { "EMPLOYMENT_13_1_1_7_8", "EMPLOYMENT_14_1_1_9_10" })); + } + } + + // Test reproducing the problem. + [Test] + public void GetEmployerTest() + { + using (var session = OpenSession()) + { + var employer7_8 = session.Get(new EmployerCpId { IdA = 7, IdB = 8 }); + Assert.That(employer7_8.Name, Is.EqualTo("EMPLOYER_7_8")); + Assert.That( + employer7_8.EmploymentList.Select(e => e.Name), + Is.EquivalentTo(new[] { "EMPLOYMENT_13_1_1_7_8" })); + } + } + + [Test] + public void GetEmploymentTest() + { + using (var session = OpenSession()) + { + var employment_13_1_2_7_8 = + session.Get( + new EmploymentCpId + { + Id = 13, + PersonObj = + new Person + { + CpId = new PersonCpId { IdA = 1, IdB = 2 } + }, + EmployerObj = + new Employer + { + CpId = new EmployerCpId { IdA = 7, IdB = 8 } + } + }); + Assert.That(employment_13_1_2_7_8.Name, Is.EqualTo("EMPLOYMENT_13_1_1_7_8")); + } + } + + [Test] + public void HqlPersonPositional() + { + using (var session = OpenSession()) + { + var personList = + session + .GetNamedQuery("personPositional") + .SetParameter(0, new PersonCpId { IdA = 1, IdB = 2 }) + .SetParameter(1, new PersonCpId { IdA = 3, IdB = 4 }) + .List(); + Assert.That( + personList.Select(e => e.Name), + Is.EquivalentTo(new[] { "PERSON_1_2", "PERSON_3_4" })); + } + } + + [Test] + public void HqlPersonNamed() + { + using (var session = OpenSession()) + { + var personList = + session + .GetNamedQuery("personNamed") + .SetParameter("id1", new PersonCpId { IdA = 1, IdB = 2 }) + .SetParameter("id2", new PersonCpId { IdA = 3, IdB = 4 }) + .List(); + Assert.That( + personList.Select(e => e.Name), + Is.EquivalentTo(new[] { "PERSON_1_2", "PERSON_3_4" })); + } + } + + [Test] + public void GetPersonNoComponent() + { + using (var session = OpenSession()) + { + var person17_18 = + session.Get(new PersonNoComponent { IdA = 17, IdB = 18 }); + Assert.That(person17_18.Name, Is.EqualTo("PERSON_NO_COMPONENT_17_18")); + } + } + + [Test] + public void SqlPersonNoComponent() + { + using (var session = OpenSession()) + { + var personList = + session + .GetNamedQuery("personNoComponentSql") + .SetParameter(0, new PersonNoComponent { IdA = 17, IdB = 18 }) + .SetParameter(1, new PersonNoComponent { IdA = 19, IdB = 20 }) + .List(); + Assert.That( + personList.Select(e => e.Name), + Is.EquivalentTo(new[] { "PERSON_NO_COMPONENT_17_18", "PERSON_NO_COMPONENT_19_20" })); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3079/Mappings.hbm.xml new file mode 100644 index 00000000000..a9b8a606aa3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/Mappings.hbm.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/Person.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/Person.cs new file mode 100644 index 00000000000..b058470b237 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/Person.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class Person + { + public virtual PersonCpId CpId { get; set; } + + public virtual string Name { get; set; } + + public virtual ICollection EmploymentList { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/PersonCpId.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/PersonCpId.cs new file mode 100644 index 00000000000..51788014334 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/PersonCpId.cs @@ -0,0 +1,22 @@ +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class PersonCpId + { + public int IdA { get; set; } + + public int IdB { get; set; } + + public override bool Equals(object obj) + { + if (!(obj is PersonCpId objCpId)) + return false; + + return IdA == objCpId.IdA && IdB == objCpId.IdB; + } + + public override int GetHashCode() + { + return IdA.GetHashCode() ^ IdB.GetHashCode(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3079/PersonNoComponent.cs b/src/NHibernate.Test/NHSpecificTest/NH3079/PersonNoComponent.cs new file mode 100644 index 00000000000..7948701375d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3079/PersonNoComponent.cs @@ -0,0 +1,24 @@ +namespace NHibernate.Test.NHSpecificTest.NH3079 +{ + public class PersonNoComponent + { + public virtual int IdA { get; set; } + + public virtual int IdB { get; set; } + + public virtual string Name { get; set; } + + public override bool Equals(object obj) + { + if (!(obj is PersonNoComponent objNoComponent)) + return false; + + return IdA == objNoComponent.IdA && IdB == objNoComponent.IdB; + } + + public override int GetHashCode() + { + return IdA.GetHashCode() ^ IdB.GetHashCode(); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH309/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH309/Fixture.cs index a4d8e6540c3..d88dff5c1ec 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH309/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH309/Fixture.cs @@ -70,11 +70,10 @@ public void RemoveNodeFromNodesCollection() Assert.AreEqual(2, menu2.Nodes.Count, "Test count after removal"); Assert.AreEqual(rootNode, menu2.Nodes[0], "Test identity first node in menu"); - s.Delete("from Node"); s.Delete("from Menu"); s.Flush(); s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3121/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3121/Fixture.cs index 3ad3a6b365f..71a25114bc3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3121/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3121/Fixture.cs @@ -46,7 +46,6 @@ public void ShouldThrowWhenByteArrayTooLong() Is.EqualTo("The length of the byte[] value exceeds the length configured in the mapping/parameter.")); } - [Test] public void ShouldThrowWhenImageTooLarge() { @@ -63,7 +62,6 @@ public void ShouldThrowWhenImageTooLarge() Is.EqualTo("The length of the byte[] value exceeds the length configured in the mapping/parameter.")); } - [Test] public void ShouldThrowWhenImageAsISerializableTooLarge() { @@ -80,7 +78,6 @@ public void ShouldThrowWhenImageAsISerializableTooLarge() Is.EqualTo("The length of the byte[] value exceeds the length configured in the mapping/parameter.")); } - private void PersistReport(Report report) { using (var session = OpenSession()) diff --git a/src/NHibernate.Test/NHSpecificTest/NH3139/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3139/Fixture.cs index e5c3a3bb015..4f73dc902fd 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3139/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3139/Fixture.cs @@ -64,7 +64,6 @@ protected override void OnTearDown() tran.Commit(); } } - } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH3142/ChildrenTest.cs b/src/NHibernate.Test/NHSpecificTest/NH3142/ChildrenTest.cs index cc1b1d1d6d7..b6b77b7f8a5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3142/ChildrenTest.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3142/ChildrenTest.cs @@ -57,7 +57,6 @@ protected override void OnTearDown() } } - [Test] public void ChildrenCollectionOfAllParentsShouldContainsThreeElements() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH3142/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH3142/DomainClass.cs index c71afa559e7..f2ccdfbad1f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3142/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3142/DomainClass.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System; - namespace NHibernate.Test.NHSpecificTest.NH3142 { public class DomainParent @@ -72,4 +71,4 @@ public class DomainChildWCId public virtual int ParentId1 { get; set; } public virtual int ParentId2 { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3145/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH3145/Model.cs index 3b8d580bac0..08c03c1b69c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3145/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3145/Model.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.NHSpecificTest.NH3145 { public class Root diff --git a/src/NHibernate.Test/NHSpecificTest/NH3153/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3153/Fixture.cs index fc1d0e1268d..0c1b66564cf 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3153/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3153/Fixture.cs @@ -23,7 +23,6 @@ public void ShouldGetIdentifierSchemaFromClassElement() Assert.That(((SimpleValue)pc.Identifier).IdentifierGeneratorProperties["schema"], Is.EqualTo("Test")); } - [Test] public void ShouldGetIdentifierSchemaFromMappingElement() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH3202/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3202/Fixture.cs index 8734c483c18..219ef8ad1bd 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3202/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3202/Fixture.cs @@ -62,7 +62,6 @@ protected override void OnTearDown() base.OnTearDown(); } - [Test] public void OffsetNotStartingAtOneSetsParameterToSkipValue() { @@ -104,7 +103,6 @@ public class OffsetStartsAtOneTestDialect : MsSql2008Dialect public override bool OffsetStartsAtOne { get { return ForceOffsetStartsAtOne; } } } - public class OffsetTestDriver : SqlClientDriver { public OffsetStartsAtOneTestDialect OffsetStartsAtOneTestDialect; diff --git a/src/NHibernate.Test/NHSpecificTest/NH3252/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3252/Fixture.cs index 2638158af72..7fa2893833f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3252/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3252/Fixture.cs @@ -21,7 +21,6 @@ public void VerifyThatWeCanSaveAndLoad() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - session.Save(new Note { Text = new String('0', 9000) }); transaction.Commit(); } @@ -29,7 +28,6 @@ public void VerifyThatWeCanSaveAndLoad() using (var session = OpenSession()) using (session.BeginTransaction()) { - var note = session.Query().First(); Assert.AreEqual(9000, note.Text.Length); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3332/State.cs b/src/NHibernate.Test/NHSpecificTest/NH3332/State.cs index 63e89f2e1e1..e8d905a7df5 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3332/State.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3332/State.cs @@ -32,7 +32,6 @@ public override bool Equals(object obj) return toCompareWith != null && Id == toCompareWith.Id; } - public virtual Int32 Id { get { return _id; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs b/src/NHibernate.Test/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs index dd7697024bf..8058e7f5b04 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3332/TestJoinsWithSameTable.cs @@ -71,13 +71,11 @@ from std in me.State.StateDescriptions } } - private void CreateObjects(ISession session) { // Create the English culture Culture englishCulture = new Culture(); - englishCulture.CountryCode = "CA"; englishCulture.LanguageCode = "en"; @@ -104,8 +102,6 @@ private void CreateObjects(ISession session) dataType1.DataTypeDescriptions.Add(dataTypeDescription1); - - // Create a State and attach it an English description and a Spanish description State state1 = new State(); @@ -125,7 +121,6 @@ private void CreateObjects(ISession session) state1.StateDescriptions.Add(spanishStateDescription); - MasterEntity masterEntity = new MasterEntity(); masterEntity.Name = "MasterEntity 1"; @@ -136,4 +131,4 @@ private void CreateObjects(ISession session) session.Flush(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3372/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3372/Fixture.cs index 668c0085aaa..3d0f16cbb23 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3372/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3372/Fixture.cs @@ -42,6 +42,5 @@ public void CanGeneratePropertyOnUpdateOfEntityWithCustomLoader() Assert.That(entity.ShardId, Is.Not.Null & Has.Length.GreaterThan(0)); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3383/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3383/FixtureByCode.cs index 6d8bb28094e..8709a1a8119 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3383/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3383/FixtureByCode.cs @@ -21,7 +21,6 @@ protected override HbmMapping GetMappings() return mapper.CompileMappingForAllExplicitlyAddedEntities(); } - [Test] public void DeserializedCascadeStyleRefersToSameObject() { @@ -43,7 +42,6 @@ public void DeserializedCascadeStyleRefersToSameObject() Assert.That(deserializedCascadeStyle, Is.SameAs(CascadeStyle.Evict)); } - [Test] public void CanRoundTripSerializedMultipleCascadeStyle() { @@ -70,7 +68,6 @@ public void CanRoundTripSerializedMultipleCascadeStyle() "[NHibernate.Engine.CascadeStyle+DeleteCascadeStyle,NHibernate.Engine.CascadeStyle+LockCascadeStyle]")); } - [Test] public void DeserializedPropertyMapping_RefersToSameCascadeStyle() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH3392/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3392/Fixture.cs index 80482b1b77e..d3c693cb3e1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3392/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3392/Fixture.cs @@ -27,7 +27,6 @@ public void ExpandSubCollectionWithEmbeddedCompositeID() using (ISession s = OpenSession()) using (var tx = s.BeginTransaction()) { - var jenny = new Mum {Name = "Jenny"}; s.Save(jenny); var benny = new Dad {Name = "Benny"}; @@ -56,7 +55,6 @@ public void ExpandSubCollectionWithCompositeID() using (ISession s = OpenSession()) using (var tx = s.BeginTransaction()) { - var jenny = new Mum { Name = "Jenny" }; s.Save(jenny); var benny = new Dad { Name = "Benny" }; @@ -78,7 +76,5 @@ public void ExpandSubCollectionWithCompositeID() Assert.That(result[0].x.Friends, Is.EquivalentTo(result[0].Friends)); } } - - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3401/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3401/Fixture.cs index f4b22141086..e598d0b3d5c 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3401/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3401/Fixture.cs @@ -38,7 +38,6 @@ public void YesNoParameterLengthShouldBe1() transaction.Commit(); } - using (ISession session = OpenSession()) using (session.BeginTransaction()) { @@ -50,4 +49,4 @@ where e.YesNo } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs index 6bf273f93da..8da7a871fc1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs @@ -1,14 +1,57 @@ using System; using System.Linq; +using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; using NHibernate.Mapping.ByCode; using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.NHSpecificTest.NH3426 { - [TestFixture] + /// + /// Verify that we can convert a GUID column to a string in the standard GUID format inside + /// the database engine. + /// + [TestFixture(true)] + [TestFixture(false)] public class Fixture : TestCaseMappingByCode { + private readonly bool _useBinaryGuid; + + public Fixture(bool useBinaryGuid) + { + _useBinaryGuid = useBinaryGuid; + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + // For SQLite, we run the tests for both storage modes (SQLite specific setting). + if (dialect is SQLiteDialect) + return true; + + // For all other dialects, run the tests only once since the storage mode + // is not relevant. (We use the case of _useBinaryGuid==true since this is probably + // what most engines do internally.) + return _useBinaryGuid; + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + if (Dialect is SQLiteDialect) + { + var connStr = configuration.Properties[Environment.ConnectionString]; + + if (_useBinaryGuid) + connStr += "BinaryGuid=True;"; + else + connStr += "BinaryGuid=False;"; + + configuration.Properties[Environment.ConnectionString] = connStr; + } + } protected override HbmMapping GetMappings() { @@ -56,7 +99,7 @@ public void SelectGuidToString() .Select(x => new { Id = x.Id.ToString() }) .ToList(); - Assert.AreEqual(id.ToUpper(), list[0].Id.ToUpper()); + Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper())); } } @@ -98,5 +141,53 @@ public void CompareStringColumnWithNullableGuidToString() Assert.That(list, Has.Count.EqualTo(1)); } } + + [Test] + public void SelectGuidToStringImplicit() + { + if (Dialect is SQLiteDialect && _useBinaryGuid) + Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09)."); + + if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect) + Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " + + "because a simple cast cannot be used for GUID to string conversion on " + + "these dialects. See GH-2109."); + + using (var session = OpenSession()) + { + // Verify in-db GUID to string conversion when ToString() is applied to the entity that has + // a GUID id column (that is, we deliberately avoid mentioning the Id property). This + // exposes bug GH-2109. + var list = session.Query() + .Select(x => new { Id = x.ToString() }) + .ToList(); + + Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper())); + } + } + + [Test] + public void WhereGuidToStringImplicit() + { + if (Dialect is SQLiteDialect && _useBinaryGuid) + Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09)."); + + if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect) + Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " + + "because a simple cast cannot be used for GUID to string conversion on " + + "these dialects. See GH-2109."); + + using (var session = OpenSession()) + { + // Verify in-db GUID to string conversion when ToString() is applied to the entity that has + // a GUID id column (that is, we deliberately avoid mentioning the Id property). This + // exposes bug GH-2109. + var list = session.Query() + .Where(x => x.ToString().ToUpper() == id) + .ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + } + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3428/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3428/Fixture.cs index fff9f71b9b4..d9a7a0f1258 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3428/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3428/Fixture.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.NHSpecificTest.NH3428 [TestFixture] public class Fixture : BugTestCase { - protected override bool AppliesTo(Dialect.Dialect dialect) { return dialect is Dialect.MsSql2005Dialect; @@ -62,4 +61,4 @@ public void QueryFailsWhenDistinctOrderedResultIsPagedPastPageOne() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3453/Classes.cs b/src/NHibernate.Test/NHSpecificTest/NH3453/Classes.cs index 92569701588..878989b57f7 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3453/Classes.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3453/Classes.cs @@ -7,7 +7,6 @@ namespace NHibernate.Test.NHSpecificTest.NH3453 { class Direction { - #region Compisite ID public virtual Int32 Id1 { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3453/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3453/Fixture.cs index 8c4a503cccf..ebea5d3aa55 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3453/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3453/Fixture.cs @@ -13,7 +13,6 @@ public void PropertyRefWithCompositeIdUpdateTest() using (var session = OpenSession()) using (session.BeginTransaction()) { - var direction1 = new Direction { Id1 = 1, Id2 = 1, GUID = Guid.NewGuid() }; session.Save(direction1); @@ -40,6 +39,5 @@ public void PropertyRefWithCompositeIdUpdateTest() Assert.That(true); } } - - } -} \ No newline at end of file + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3487/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3487/Fixture.cs index 1ea873abf17..492c37a0d29 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3487/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3487/Fixture.cs @@ -78,7 +78,6 @@ public void CanDeserializeSessionWithEntityHashCollision() { formatter.Deserialize(serializationStream); } - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3567/DomainClass.cs b/src/NHibernate.Test/NHSpecificTest/NH3567/DomainClass.cs index 2b81ffe7bd9..eb12306b13f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3567/DomainClass.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3567/DomainClass.cs @@ -1,21 +1,16 @@ - - -namespace NHibernate.Test.NHSpecificTest.NH3567 +namespace NHibernate.Test.NHSpecificTest.NH3567 { public class Site { public virtual int Id { get; set; } public virtual string Name { get; set; } - - } + public class Post { public virtual int Id { get; set; } public virtual string Content { get; set; } public virtual Site Site { get; set; } - - } public class Comment @@ -23,7 +18,5 @@ public class Comment public virtual int Id { get; set; } public virtual string Content { get; set; } public virtual Post Post { get; set; } - } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3567/NH3567Tests.cs b/src/NHibernate.Test/NHSpecificTest/NH3567/NH3567Tests.cs index ca549cdfd31..d51bfdb9863 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3567/NH3567Tests.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3567/NH3567Tests.cs @@ -10,9 +10,9 @@ public class NH3567Tests : BugTestCase protected override void OnSetUp() { base.OnSetUp(); - using (ISession session = this.OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); var id = 0; var site1 = new Site { Id = ++id, Name = "Site 1" }; @@ -20,7 +20,6 @@ protected override void OnSetUp() session.Save(site1); session.Save(site2); - var p1 = new Post { Id = ++id, Content = "Post 1", Site = site1 }; var p2 = new Post { Id = ++id, Content = "Post 2", Site = site2 }; @@ -32,7 +31,7 @@ protected override void OnSetUp() session.Save(new Comment { Id = ++id, Content = "Comment 2.1", Post = p2 }); session.Save(new Comment { Id = ++id, Content = "Comment 2.2", Post = p2 }); session.Flush(); - session.Transaction.Commit(); + tran.Commit(); } } @@ -92,16 +91,9 @@ public void TestFlushModeAuto() Assert.That(numberOfComments, Is.EqualTo(2), "Query with sub-query returned an invalid number of rows."); - transaction.Rollback(); - } - } } - - } - - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3571/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3571/Fixture.cs index 00c76e6ddcd..1ef8fd60113 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3571/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3571/Fixture.cs @@ -73,7 +73,6 @@ protected override void OnTearDown() tran.Commit(); } } - } [Test] @@ -92,7 +91,6 @@ public void CanQueryDynamicComponentInComponent() } } - [Test] public void MultipleQueriesShouldNotCache() { @@ -113,7 +111,6 @@ public void MultipleQueriesShouldNotCache() } } - [Test] public void DifferentKeyInDynamicComponentDictionaryReturnsDifferentExpressionKeys() { @@ -131,4 +128,4 @@ public void DifferentKeyInDynamicComponentDictionaryReturnsDifferentExpressionKe } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3604/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3604/FixtureByCode.cs index f12d53072ce..383dcf7bc22 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3604/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3604/FixtureByCode.cs @@ -41,7 +41,6 @@ protected override void OnSetUp() var e1 = new Entity { Name = "Bob" }; session.Save(e1); - var e2 = new Entity { Name = "Sally" }; var ed2 = new EntityDetail(e2) { ExtraInfo = "Jo" }; e2.Detail = ed2; diff --git a/src/NHibernate.Test/NHSpecificTest/NH3620/TwoBlobs.cs b/src/NHibernate.Test/NHSpecificTest/NH3620/TwoBlobs.cs index 5f875f2338b..1e4cf6b4bb3 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3620/TwoBlobs.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3620/TwoBlobs.cs @@ -6,6 +6,5 @@ public class TwoBlobs { public virtual byte[] Blob1 { get; set; } public virtual byte[] Blob2 { get; set; } public virtual DateTime TheDate { get; set; } - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3622/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3622/Entity.cs new file mode 100644 index 00000000000..a8722639232 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3622/Entity.cs @@ -0,0 +1,90 @@ +using System; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace NHibernate.Test.NHSpecificTest.NH3622 +{ + public class Tag + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Equipment Equipment { get; set; } + } + + public class Equipment + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual Discipline Discipline { get; set; } + } + + public class Discipline + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual DisciplineType DisciplineType { get; set; } + } + + public class DisciplineType + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public class TagMap : ClassMapping + { + public TagMap() + { + this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned)); + this.Property(x => x.Name, mapper => mapper.NotNullable(true)); + this.ManyToOne( + x => x.Equipment, + mapper => + { + mapper.NotNullable(true); + mapper.Column("EquipmentId"); + }); + } + } + + public class EquipmentMap : ClassMapping + { + public EquipmentMap() + { + this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned)); + this.Property(x => x.Name, mapper => mapper.NotNullable(true)); + this.ManyToOne( + x => x.Discipline, + mapper => + { + mapper.Column("DisciplineId"); + mapper.NotNullable(false); + }); + } + } + + public class DisciplineMap : ClassMapping + { + public DisciplineMap() + { + this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned)); + this.Property(x => x.Name, mapper => mapper.NotNullable(true)); + this.ManyToOne( + x => x.DisciplineType, + mapper => + { + mapper.Column("DisciplineTypeId"); + mapper.NotNullable(false); + }); + } + } + + public class DisciplineTypeMap : ClassMapping + { + public DisciplineTypeMap() + { + this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned)); + this.Property(x => x.Name, mapper => mapper.NotNullable(true)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3622/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3622/FixtureByCode.cs new file mode 100644 index 00000000000..ef6ce320e65 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3622/FixtureByCode.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.NH3622 +{ + /// + /// Fixture using 'by code' mappings + /// + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public void MissingJoinsInSubquery() + { + var id = Guid.NewGuid(); + using( var logSpy = new SqlLogSpy()) + using (var s = OpenSession()) + { + var x = s.Query() + .Where(tag => tag.Equipment.Discipline.DisciplineType.Id == id) + .Select(tag => tag.Id); + + var y = s.Query() + .Where(tag => x.Contains(tag.Id)) + .Fetch(tag => tag.Equipment) + .ThenFetch(equipment => equipment.Discipline) + .ThenFetch(discipline => discipline.DisciplineType) + .ToList(); + + var sql = logSpy.GetWholeLog(); + var findSubqyeryIndex = sql.IndexOf(" in ("); + var capturesCount = Regex.Matches(sql.Substring(findSubqyeryIndex), "inner join").Count; + //Expected joins for tag.Equipment.Discipline in subquery + Assert.That(capturesCount, Is.EqualTo(2), "Missing inner joins in subquery: " + sql); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3641/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH3641/Domain.cs index b58449c04ac..9f96ca1d221 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3641/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3641/Domain.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Test.NHSpecificTest.NH3641 +namespace NHibernate.Test.NHSpecificTest.NH3641 { public interface IEntity { diff --git a/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs b/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs index e8ec6ebe300..5e143e7e1a8 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.NHSpecificTest.NH3749 { public class TestDialect : Dialect.Dialect diff --git a/src/NHibernate.Test/NHSpecificTest/NH3754/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH3754/Model.cs index 1f335d8f4f5..fe2756f2676 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3754/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3754/Model.cs @@ -24,5 +24,4 @@ public virtual short IsActive set { _isActive = value; } } } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3800/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH3800/Domain.cs index b94e1481bf1..c90fe375949 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3800/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3800/Domain.cs @@ -37,7 +37,6 @@ public TimeRecord() public virtual Project Project { get; set; } public virtual IList Components { get; set; } public virtual IList Tags { get; set; } - } public class Tag diff --git a/src/NHibernate.Test/NHSpecificTest/NH3813/Entities.cs b/src/NHibernate.Test/NHSpecificTest/NH3813/Entities.cs new file mode 100644 index 00000000000..61bf7fb6039 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3813/Entities.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH3813 +{ + public class AssociationTable + { + public virtual FirstTable FirstTable { get; set; } + public virtual OtherTable OtherTable { get; set; } + public virtual string Name { get; set; } + + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + public class FirstTable + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual IList AssociationTableCollection { get; set; } = new List(); + } + + public class OtherTable + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3813/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3813/FixtureByCode.cs new file mode 100644 index 00000000000..cb8c60cb6ed --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3813/FixtureByCode.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3813 +{ + [TestFixture] + public class KeyManyToOneInnerJoinFetchFixture : TestCaseMappingByCode + { + protected override Cfg.MappingSchema.HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + m => + { + m.Lazy(false); + + m.Id( + i => i.Id, + id => + { + id.Column("ID"); + id.Generator(Generators.Identity); + }); + m.Property(x => x.Name); + + m.Bag( + b => b.AssociationTableCollection, + bm => + { + bm.Inverse(true); + bm.Key(k => k.Column("FirstTableID")); + }, + mp => mp.OneToMany()); + }); + + mapper.Class( + m => + { + m.Lazy(false); + + m.Id( + i => i.Id, + id => + { + id.Column("ID"); + id.Generator(Generators.Identity); + }); + m.Property(x => x.Name); + }); + + mapper.Class( + m => + { + m.ComposedId( + i => + { + i.ManyToOne(c => c.FirstTable, p => { p.Column("FirstTableID"); }); + i.ManyToOne(c => c.OtherTable, p => { p.Column("OtherTableID"); }); + }); + m.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public void FetchQueryDoesNotContainInnerJoin() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var q = session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection).List(); + + var sql = logSpy.GetWholeLog(); + Assert.That(sql, Is.Not.Contains("inner join")); + } + } + + [Test] + public void FetchQueryDoesNotContainInnerJoinMulti() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var q = session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection) + .Left.JoinQueryOver(a => a.OtherTable).List(); + + var sql = logSpy.GetWholeLog(); + + Assert.That(sql, Does.Not.Contain("inner join")); + Assert.That(sql, Does.Match(@"join\s*AssociationTable").IgnoreCase); + Assert.That(sql, Does.Match(@"join\s*OtherTable")); + } + } + + [Test] + public void FetchLoadsAllRecords() + { + IList result = null; + + using (var session = OpenSession()) + { + // the query should return all records from the table with their collections fetched + result = session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection) + .List(); + } + + Assert.AreEqual(2, result.Count, "Query returned wrong number of records."); + Assert.IsTrue(result.All(x => NHibernateUtil.IsInitialized(x.AssociationTableCollection)), "Not all collections have been initialized"); + } + + [Test] + public void FetchInitializesAllCollections() + { + IList result = null; + + using (var session = OpenSession()) + { + // load all records + result = session.QueryOver() + .List(); + + // lazy-load the association collection + session.QueryOver() + .Fetch(SelectMode.Fetch, f => f.AssociationTableCollection) + .Left.JoinQueryOver(f => f.AssociationTableCollection) + .List(); + } + + Assert.IsTrue(result.All(x => NHibernateUtil.IsInitialized(x.AssociationTableCollection)), "Not all collections have been initialized"); + } + + protected override void OnSetUp() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + // a record that has association records will be loaded regularly + var withAssociations = new FirstTable(); + + var other1 = new OtherTable(); + var other2 = new OtherTable(); + + var assoc1 = new AssociationTable() {OtherTable = other1, FirstTable = withAssociations}; + var assoc2 = new AssociationTable() {OtherTable = other2, FirstTable = withAssociations}; + + withAssociations.AssociationTableCollection.Add(assoc1); + withAssociations.AssociationTableCollection.Add(assoc2); + s.Save(withAssociations); + + // a record with no associations will have problems if inner joined to association table + var withoutAssociations = new FirstTable(); + s.Save(withoutAssociations); + + t.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from System.Object").ExecuteUpdate(); + t.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3818/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3818/Fixture.cs index e8ef6797db5..b08574f6856 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3818/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3818/Fixture.cs @@ -58,9 +58,7 @@ public void SelectConditionalValuesTest() //Console.WriteLine(spy.ToString()); Assert.That(catInfo2.AliveDays, Is.EqualTo(0)); - - } + } } - - } + } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3844/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH3844/Domain.cs index 2d437aaec93..c00f19984b6 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3844/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3844/Domain.cs @@ -36,7 +36,6 @@ public TimeRecord() public virtual double TimeInHours { get; set; } public virtual Project Project { get; set; } public virtual IList Components { get; set; } - } public class Job diff --git a/src/NHibernate.Test/NHSpecificTest/NH3844/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3844/Fixture.cs index d8cca9fe754..ae7e0204e23 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3844/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3844/Fixture.cs @@ -54,7 +54,6 @@ protected override void OnSetUp() session.Save(compP3_x); session.Save(compP3_y); - session.Save(new TimeRecord { TimeInHours = 1, Project = project1, Components = { } }); session.Save(new TimeRecord { TimeInHours = 2, Project = project1, Components = { compP1_x } }); session.Save(new TimeRecord { TimeInHours = 3, Project = project1, Components = { compP1_y } }); diff --git a/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs index c4514867255..f617a69752f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3848/Fixture.cs @@ -274,7 +274,6 @@ public virtual void ChildCollectionsWithSelectModeFetchAndWhereClauseShouldNotBe } } - [Test] public void ChildCollectionsFromLeftOuterJoinWithWhereClauseRestrictionOnCollectionShouldNotBeInSecondLevelCache() { diff --git a/src/NHibernate.Test/NHSpecificTest/NH386/_Parent.cs b/src/NHibernate.Test/NHSpecificTest/NH386/_Parent.cs index 98e4b2a99af..ef3c00ed7ee 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH386/_Parent.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH386/_Parent.cs @@ -6,7 +6,6 @@ // ReSharper disable InconsistentNaming namespace NHibernate.Test.NHSpecificTest.NH386 { - public class _Parent { public int _Id { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3909/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3909/Entity.cs index e125c563256..d47d9f56b67 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3909/Entity.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3909/Entity.cs @@ -8,7 +8,6 @@ public class ParentEntity public virtual string Name { get; set; } } - public class ChildEntity { public virtual Guid Id { get; set; } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3909/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3909/FixtureByCode.cs index 85112494612..33b21285a9b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3909/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3909/FixtureByCode.cs @@ -79,7 +79,6 @@ public void YourTestName() //select b; Assert.AreEqual(2, q.ToList().Count); - } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3992/Entities.cs b/src/NHibernate.Test/NHSpecificTest/NH3992/Entities.cs index 635e1e0d6b7..9b2aee0cd0b 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3992/Entities.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3992/Entities.cs @@ -97,7 +97,6 @@ public class MappedExtension : UnmappedExtension public virtual string MappedExtensionField { get; set; } } - public class TopLevel : MappedExtension { public virtual string TopLevelExtensionField { get; set; } @@ -133,5 +132,4 @@ public class TopLevel : SecondUnmappedExtension public virtual string TopLevelExtensionField { get; set; } } } - } diff --git a/src/NHibernate.Test/NHSpecificTest/NH3992/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3992/FixtureByCode.cs index 9702f8318d0..22a5cb3829a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3992/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3992/FixtureByCode.cs @@ -30,7 +30,6 @@ public void Test_MappedSubclass_AllMapsAtTopLevel() Assert.IsNotNull(extendedProperty, "Sub class mapping did not map extended (class not mapped) class property"); var topLevelProperty = targetMapping.Properties.SingleOrDefault(p => p.Name == "TopLevelField"); Assert.IsNotNull(topLevelProperty, "Sub class mapping did not map base class property"); - } [Test] @@ -54,7 +53,6 @@ public void Test_MappedJoinedSubclass_AllMapsAtTopLevel() Assert.IsNotNull(extendedProperty, "Sub class mapping did not map extended (class not mapped) class property"); var topLevelProperty = targetMapping.Properties.SingleOrDefault(p => p.Name == "TopLevelField"); Assert.IsNotNull(topLevelProperty, "Sub class mapping did not map base class property"); - } [Test] @@ -78,7 +76,6 @@ public void Test_MappedUnionSubclass_AllMapsAtTopLevel() Assert.IsNotNull(extendedProperty, "Sub class mapping did not map extended (class not mapped) class property"); var topLevelProperty = targetMapping.Properties.SingleOrDefault(p => p.Name == "TopLevelField"); Assert.IsNotNull(topLevelProperty, "Sub class mapping did not map base class property"); - } [Test] @@ -102,7 +99,6 @@ public void Test_MappedSubclassInterface_AllMapsAtTopLevel() Assert.IsNotNull(extendedProperty, "Sub class mapping did not map extended (class not mapped) class property"); var topLevelProperty = targetMapping.Properties.SingleOrDefault(p => p.Name == "TopLevelField"); Assert.IsNotNull(topLevelProperty, "Sub class mapping did not map base class property"); - } [Test] @@ -126,7 +122,6 @@ public void Test_MappedJoinedSubclassInterface_AllMapsAtTopLevel() Assert.IsNotNull(extendedProperty, "Sub class mapping did not map extended (class not mapped) class property"); var topLevelProperty = targetMapping.Properties.SingleOrDefault(p => p.Name == "TopLevelField"); Assert.IsNotNull(topLevelProperty, "Sub class mapping did not map base class property"); - } [Test] @@ -150,7 +145,6 @@ public void Test_MappedUnionSubclassInterface_AllMapsAtTopLevel() Assert.IsNotNull(extendedProperty, "Sub class mapping did not map extended (class not mapped) class property"); var topLevelProperty = targetMapping.Properties.SingleOrDefault(p => p.Name == "TopLevelField"); Assert.IsNotNull(topLevelProperty, "Sub class mapping did not map base class property"); - } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH3993/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3993/Fixture.cs index e90b611328f..4f5ff5684ea 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3993/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3993/Fixture.cs @@ -39,7 +39,6 @@ public void Test_MapPrivateComponentProperty_MapsCorrectly() // Component element component var componentMapping = elementMapping.Properties.SingleOrDefault(p => p.Name == "_component"); Assert.IsNotNull(componentMapping, "Component Element did not private component"); - } [Test] @@ -64,4 +63,4 @@ protected override HbmMapping GetMappings() return mapper.CompileMappingForAllExplicitlyAddedEntities(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH401/Club.cs b/src/NHibernate.Test/NHSpecificTest/NH401/Club.cs index abeb5e3cb6c..a200ac09269 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH401/Club.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH401/Club.cs @@ -10,7 +10,6 @@ public class Club private Boolean m_Freelpcmember; private DateTime m_Lastupdated; - public Club() { this.Active = true; @@ -49,4 +48,4 @@ public DateTime LastUpdated set { m_Lastupdated = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH401/Clubmember.cs b/src/NHibernate.Test/NHSpecificTest/NH401/Clubmember.cs index f1598c24855..6e75bdf4c49 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH401/Clubmember.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH401/Clubmember.cs @@ -29,7 +29,6 @@ public Decimal ProfileId set { m_ProfileId = value; } } - /*public Accountdistribution Accountdistribution { get @@ -138,4 +137,4 @@ public Decimal Withdrawlamount } }*/ } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH440/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH440/Fixture.cs index 144bd2e1f64..0e2637fcc88 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH440/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH440/Fixture.cs @@ -21,7 +21,6 @@ protected override bool AppliesTo(Dialect.Dialect dialect) return TestDialect.SupportsEmptyInsertsOrHasNonIdentityNativeGenerator; } - protected override void OnSetUp() { base.OnSetUp(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH521/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH521/Fixture.cs index 2e99e3fd759..ad60cc6e1a8 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH521/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH521/Fixture.cs @@ -51,7 +51,6 @@ public void AttachUninitProxyCausesInit() NHibernateUtil.IsInitialized(uninitEntity), "session.GetCurrentLockMode() causes initialization of an unitialized entity."); - session.Delete("from System.Object"); transaction.Commit(); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH646/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH646/Fixture.cs index 0ba58b356cc..989926d55e0 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH646/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH646/Fixture.cs @@ -62,7 +62,6 @@ public void PolicemenOrderedByRank() { var station = session.Query().Single(); - Assert.AreEqual(2, station.Policemen.Count()); Assert.That(station.Policemen, Is.Ordered.By("Name")); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH681/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH681/Fixture.cs index b1b4d4a6c70..34cd7b0dc6f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH681/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH681/Fixture.cs @@ -8,7 +8,6 @@ public class Fixture : BugTestCase { protected override void Configure(NHibernate.Cfg.Configuration cfg) { - } [Test] diff --git a/src/NHibernate.Test/NHSpecificTest/NH712/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH712/Fixture.cs index bc5d67e2f52..6ecc89c0ef1 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH712/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH712/Fixture.cs @@ -10,7 +10,7 @@ namespace NHibernate.Test.NHSpecificTest.NH712 //Fri, 15 Sep 2006 07:31:13 -0400 (EDT) //1.2.0.Alpha1 //Core - //http://nhibernate.jira.com/browse/NH-712 + //http://nhibernate.jira.com/browse/NH-712 [TestFixture] public class Fixture { diff --git a/src/NHibernate.Test/NHSpecificTest/NH719/Domain.cs b/src/NHibernate.Test/NHSpecificTest/NH719/Domain.cs index 2da737c0048..d11c1f26382 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH719/Domain.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH719/Domain.cs @@ -49,7 +49,6 @@ public virtual int Id set { id = value; } } - public virtual string Foo { get { return foo; } @@ -112,4 +111,4 @@ public object Owner set { owner = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH732/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH732/Fixture.cs index fc62e5fb4fb..b45a4e2a2fc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH732/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH732/Fixture.cs @@ -12,7 +12,6 @@ namespace NHibernate.Test.NHSpecificTest.NH732 [TestFixture] public class Fixture : BugTestCase { - protected override bool AppliesTo(Dialect.Dialect dialect) { return dialect is MsSql2000Dialect; @@ -68,7 +67,6 @@ public System.Type ReturnedType get { return typeof (string); } } - public new bool Equals(object x, object y) { return StringComparer.InvariantCultureIgnoreCase.Equals((string)x, (string)y); diff --git a/src/NHibernate.Test/NHSpecificTest/NH734/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH734/Fixture.cs index 00a201b9198..a1fdd629ee9 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH734/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH734/Fixture.cs @@ -9,26 +9,15 @@ public class Fixture : BugTestCase [TestAttribute] public void LimitProblem() { - using (ISession session = Sfi.OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { ICriteria criteria = session.CreateCriteria(typeof(MyClass)); criteria.SetMaxResults(100); criteria.SetFirstResult(0); - try - { - session.BeginTransaction(); - IList result = criteria.List(); - session.Transaction.Commit(); - } - catch - { - if (session.Transaction != null) - { - session.Transaction.Rollback(); - } - throw; - } + IList result = criteria.List(); + tran.Commit(); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs index b3b38396591..9ff71f07057 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs @@ -1,4 +1,5 @@ using System; +using NHibernate.Cfg; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.NH750 @@ -16,6 +17,17 @@ protected override void OnTearDown() } } + protected override string CacheConcurrencyStrategy + { + get { return null; } + } + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false"); + base.Configure(configuration); + } + [Test] public void DeviceOfDrive() { @@ -55,7 +67,8 @@ public void DeviceOfDrive() dv2 = (Device) s.Load(typeof(Device), dvSavedId[1]); } Assert.AreEqual(2, dv1.Drives.Count); - Assert.AreEqual(2, dv2.Drives.Count); + // Verify one is missing + Assert.AreEqual(1, dv2.Drives.Count); // Verify dv1 unchanged Assert.IsTrue(dv1.Drives.Contains(dr1)); Assert.IsTrue(dv1.Drives.Contains(dr2)); @@ -63,13 +76,43 @@ public void DeviceOfDrive() // Verify dv2 Assert.IsTrue(dv2.Drives.Contains(dr1)); Assert.IsFalse(dv2.Drives.Contains(dr3)); - // Verify one null - int nullCount = 0; - for (int i = 0; i < dv2.Drives.Count; i++) + + //Make sure that flush didn't touch not-found="ignore" records for not modified collection + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) { - if (dv2.Drives[i] == null) nullCount++; + dv2 = s.Get(dv2.Id); + s.Flush(); + t.Commit(); + } + + VerifyResult(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection"); + + //Many-to-many clears collection and recreates it so not-found ignore records are lost + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = s.Get(dv2.Id); + dv2.Drives.Add(dr2); + t.Commit(); + } + + VerifyResult(2, 2, msg: "modified collection"); + + void VerifyResult(int expectedInCollection, int expectedInDb, string msg) + { + using (var s = Sfi.OpenSession()) + { + var realCound = Convert.ToInt32( + s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") + .SetParameter("id", dv2.Id) + .UniqueResult()); + dv2 = s.Get(dv2.Id); + + Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); + Assert.That(realCound, Is.EqualTo(expectedInDb), msg); + } } - Assert.AreEqual(1, nullCount); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH898/ClassC.cs b/src/NHibernate.Test/NHSpecificTest/NH898/ClassC.cs index 2c34052881d..670e9fc5f8e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH898/ClassC.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH898/ClassC.cs @@ -4,6 +4,5 @@ namespace NHibernate.Test.NHSpecificTest.NH898 { public class ClassC : ClassBParent { - } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH930/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH930/Model.cs index bc1dd419649..d2439c834ea 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH930/Model.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH930/Model.cs @@ -28,5 +28,4 @@ public class NConditionalUDV : NVariable public class NFilterUDV : NVariable { } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH995/LogPlugin.cs b/src/NHibernate.Test/NHSpecificTest/NH995/LogPlugin.cs index 4a29c696707..2b9f50d881a 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH995/LogPlugin.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH995/LogPlugin.cs @@ -7,7 +7,6 @@ public class LogPlugin : IPlugin { public void Attach(ILoggerRepository repository) { - } public void Shutdown() diff --git a/src/NHibernate.Test/NHSpecificTest/ProxyValidator/ShouldBeProxiableTests.cs b/src/NHibernate.Test/NHSpecificTest/ProxyValidator/ShouldBeProxiableTests.cs index 8e15f8419d3..1a6fe621ccd 100644 --- a/src/NHibernate.Test/NHSpecificTest/ProxyValidator/ShouldBeProxiableTests.cs +++ b/src/NHibernate.Test/NHSpecificTest/ProxyValidator/ShouldBeProxiableTests.cs @@ -12,7 +12,6 @@ private class MyClass: IDisposable { public void Dispose() { - } } private class ProtectedNoVirtualProperty diff --git a/src/NHibernate.Test/NHSpecificTest/SimpleComponentFixture.cs b/src/NHibernate.Test/NHSpecificTest/SimpleComponentFixture.cs index 8e7c0bef0b6..eb0becc55ca 100644 --- a/src/NHibernate.Test/NHSpecificTest/SimpleComponentFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/SimpleComponentFixture.cs @@ -37,7 +37,6 @@ protected override void OnSetUp() simpleComp.Audit.UpdatedDate = DateTime.Now; simpleComp.Audit.UpdatedUserId = "TestUpdated"; - s.Save(simpleComp, 10L); t.Commit(); @@ -53,7 +52,6 @@ protected override void OnTearDown() } } - [Test] public void TestLoad() { diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 9a2cf6767a2..5436c66ffac 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -25,6 +25,7 @@ + Always @@ -44,16 +45,27 @@ + + + UtilityTest\AsyncReaderWriterLock.cs + + + UtilityTest\SetSnapShot.cs + + + + - - - + + + + @@ -63,7 +75,6 @@ - @@ -71,8 +82,7 @@ - - + diff --git a/src/NHibernate.Test/Naturalid/Immutable/ImmutableNaturalIdFixture.cs b/src/NHibernate.Test/Naturalid/Immutable/ImmutableNaturalIdFixture.cs index 5f9a610bbb0..854867a5cc7 100644 --- a/src/NHibernate.Test/Naturalid/Immutable/ImmutableNaturalIdFixture.cs +++ b/src/NHibernate.Test/Naturalid/Immutable/ImmutableNaturalIdFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NHibernate.Cfg; using NHibernate.Criterion; using NUnit.Framework; @@ -30,31 +29,31 @@ public void Update() { // prepare some test data... User user; - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); user = new User(); user.UserName = "steve"; user.Email = "steve@hibernate.org"; user.Password = "brewhaha"; session.Save(user); - session.Transaction.Commit(); + tran.Commit(); } // 'user' is now a detached entity, so lets change a property and reattch... user.Password = "homebrew"; - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); session.Update(user); - session.Transaction.Commit(); + tran.Commit(); } // clean up - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); session.Delete(user); - session.Transaction.Commit(); + tran.Commit(); } } @@ -82,62 +81,72 @@ public void NaturalIdCheck() [Test] public void NaturalIdCache() { - ISession s = OpenSession(); - s.BeginTransaction(); - User u = new User("steve", "superSecret"); - s.Persist(u); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + User u = new User("steve", "superSecret"); + s.Persist(u); + t.Commit(); + s.Close(); + } Sfi.Statistics.Clear(); - s = OpenSession(); - s.BeginTransaction(); - u = - (User) - s.CreateCriteria(typeof (User)).Add(Restrictions.NaturalId().Set("UserName", "steve")).SetCacheable(true). - UniqueResult(); - Assert.That(u, Is.Not.Null); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var u = + (User) + s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")) + .SetCacheable(true).UniqueResult(); + Assert.That(u, Is.Not.Null); + t.Commit(); + s.Close(); + } Assert.AreEqual(1, Sfi.Statistics.QueryExecutionCount); Assert.AreEqual(0, Sfi.Statistics.QueryCacheHitCount); Assert.AreEqual(1, Sfi.Statistics.QueryCachePutCount); - s = OpenSession(); - s.BeginTransaction(); - User v = new User("gavin", "supsup"); - s.Persist(v); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + User v = new User("gavin", "supsup"); + s.Persist(v); + t.Commit(); + s.Close(); + } Sfi.Statistics.Clear(); - s = OpenSession(); - s.BeginTransaction(); - u = - (User) - s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")).SetCacheable(true). - UniqueResult(); - Assert.That(u, Is.Not.Null); - Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); - Assert.AreEqual(1, Sfi.Statistics.QueryCacheHitCount); - u = - (User) - s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")).SetCacheable(true). - UniqueResult(); - Assert.That(u, Is.Not.Null); - Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); - Assert.AreEqual(2, Sfi.Statistics.QueryCacheHitCount); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var u = + (User) + s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")) + .SetCacheable(true).UniqueResult(); + Assert.That(u, Is.Not.Null); + Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); + Assert.AreEqual(1, Sfi.Statistics.QueryCacheHitCount); + u = + (User) + s.CreateCriteria(typeof(User)).Add(Restrictions.NaturalId().Set("UserName", "steve")) + .SetCacheable(true).UniqueResult(); + Assert.That(u, Is.Not.Null); + Assert.AreEqual(0, Sfi.Statistics.QueryExecutionCount); + Assert.AreEqual(2, Sfi.Statistics.QueryCacheHitCount); + t.Commit(); + s.Close(); + } - s = OpenSession(); - s.BeginTransaction(); - s.Delete("from User"); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from User"); + t.Commit(); + s.Close(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Naturalid/Mutable/MutableNaturalIdFixture.cs b/src/NHibernate.Test/Naturalid/Mutable/MutableNaturalIdFixture.cs index c773e309ebf..7149e89a67c 100644 --- a/src/NHibernate.Test/Naturalid/Mutable/MutableNaturalIdFixture.cs +++ b/src/NHibernate.Test/Naturalid/Mutable/MutableNaturalIdFixture.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections; using System.Reflection; using NHibernate.Cfg; using NHibernate.Criterion; @@ -31,41 +29,33 @@ protected override void Configure(Configuration configuration) [Test] public void ReattachmentNaturalIdCheck() { - ISession s = OpenSession(); - s.BeginTransaction(); - User u = new User("gavin", "hb", "secret"); - s.Persist(u); - s.Transaction.Commit(); - s.Close(); + User u; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + u = new User("gavin", "hb", "secret"); + s.Persist(u); + t.Commit(); + s.Close(); + } FieldInfo name = u.GetType().GetField("name", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); name.SetValue(u, "Gavin"); - s = OpenSession(); - s.BeginTransaction(); - try + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.Update(u); - s.Transaction.Commit(); - } - catch (HibernateException) - { - s.Transaction.Rollback(); - } - catch (Exception) - { - s.Transaction.Rollback(); + t.Commit(); } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { + s.Delete(u); + t.Commit(); s.Close(); } - - s = OpenSession(); - s.BeginTransaction(); - s.Delete(u); - s.Transaction.Commit(); - s.Close(); } [Test] @@ -216,7 +206,6 @@ public void NaturalIdCache() t.Commit(); s.Close(); - s = OpenSession(); t = s.BeginTransaction(); s.Delete("from User"); @@ -247,4 +236,4 @@ public void Querying() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NetFxOnlyAttribute.cs b/src/NHibernate.Test/NetFxOnlyAttribute.cs new file mode 100644 index 00000000000..2c4bbed82f6 --- /dev/null +++ b/src/NHibernate.Test/NetFxOnlyAttribute.cs @@ -0,0 +1,20 @@ +using System; +using NUnit.Framework; +using NUnit.Framework.Interfaces; + +namespace NHibernate.Test +{ + public class NetFxOnlyAttribute : Attribute, ITestAction + { + public ActionTargets Targets => ActionTargets.Default; + + public void AfterTest(ITest test) { } + + public void BeforeTest(ITest test) + { +#if !NETFX + Assert.Ignore("This test is only for NETFX."); +#endif + } + } +} diff --git a/src/NHibernate.Test/ObjectAssertion.cs b/src/NHibernate.Test/ObjectAssertion.cs index c74d55db065..5c7e03eab71 100644 --- a/src/NHibernate.Test/ObjectAssertion.cs +++ b/src/NHibernate.Test/ObjectAssertion.cs @@ -24,7 +24,6 @@ internal static void AreEqual(ISet expected, ISet actual) } } - /// /// Compares one dimensional arrays for equality. /// @@ -59,7 +58,6 @@ internal static void AreEqual(IList expected, IList actual, bool indexM } } - /// /// /// @@ -78,7 +76,6 @@ internal static void AreEqual(IDictionary expected, IDi } } - [Test] public void TestIDictionaryEqual() { @@ -94,7 +91,6 @@ public void TestIDictionaryEqual() AreEqual(expected, actualWithEqualValues, true); } - public static void AreEqual(DateTime expected, DateTime actual, bool useMilliseconds) { Assert.AreEqual(expected.Year, actual.Year, "Year"); @@ -107,4 +103,4 @@ public static void AreEqual(DateTime expected, DateTime actual, bool useMillisec Assert.AreEqual(expected.Millisecond, actual.Millisecond, "Millisecond"); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Operations/MergeFixture.cs b/src/NHibernate.Test/Operations/MergeFixture.cs index 66c4367f82d..44526c27ef9 100644 --- a/src/NHibernate.Test/Operations/MergeFixture.cs +++ b/src/NHibernate.Test/Operations/MergeFixture.cs @@ -47,23 +47,31 @@ private void Cleanup() [Test] public void DeleteAndMerge() { - using (ISession s = OpenSession()) + using (var s = OpenSession()) { - s.BeginTransaction(); - var jboss = new Employer(); - s.Persist(jboss); - s.Transaction.Commit(); - s.Clear(); - - s.BeginTransaction(); - var otherJboss = s.Get(jboss.Id); - s.Delete(otherJboss); - s.Transaction.Commit(); - s.Clear(); + Employer jboss; + using (var t = s.BeginTransaction()) + { + jboss = new Employer(); + s.Persist(jboss); + t.Commit(); + s.Clear(); + } + + using (var t = s.BeginTransaction()) + { + var otherJboss = s.Get(jboss.Id); + s.Delete(otherJboss); + t.Commit(); + s.Clear(); + } + jboss.Vers = 1; - s.BeginTransaction(); - s.Merge(jboss); - s.Transaction.Commit(); + using (var t = s.BeginTransaction()) + { + s.Merge(jboss); + t.Commit(); + } } } @@ -88,13 +96,11 @@ public void MergeBidiForeignKeyOneToOne() p.Address.StreetAddress = "321 Main"; - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { - p = (Person) s.Merge(p); - s.Transaction.Commit(); - } + p = (Person) s.Merge(p); + t.Commit(); } AssertInsertCount(0); @@ -414,44 +420,41 @@ public void MergeStaleVersionFails() { var entity = new VersionedEntity {Id = "entity", Name = "entity"}; using(ISession s = OpenSession()) - using(s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Persist(entity); - s.Transaction.Commit(); + t.Commit(); } // make the detached 'entity' reference stale... using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var entity2 = s.Get(entity.Id); entity2.Name = "entity-name"; - s.Transaction.Commit(); + t.Commit(); } - // now try to reattch it - ISession s2 = null; - try - { - s2 = OpenSession(); - s2.BeginTransaction(); - - s2.Merge(entity); - s2.Transaction.Commit(); - Assert.Fail("was expecting staleness error"); - } - catch (StaleObjectStateException) + // now try to reattach it + using (var s2 = OpenSession()) + using (var t = s2.BeginTransaction()) { - // expected outcome... - } - finally - { - if (s2 != null) + try + { + s2.Merge(entity); + t.Commit(); + Assert.Fail("was expecting staleness error"); + } + catch (StaleObjectStateException) + { + // expected outcome... + } + finally { - s2.Transaction.Rollback(); + t.Rollback(); s2.Close(); + Cleanup(); } - Cleanup(); } } @@ -548,10 +551,10 @@ public void NoExtraUpdatesOnMerge() Name = "test" }; using(ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Persist(node); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -559,10 +562,10 @@ public void NoExtraUpdatesOnMerge() // node is now detached, but we have made no changes. so attempt to merge it // into this new session; this should cause no updates... using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { node = (Node) s.Merge(node); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -573,10 +576,10 @@ public void NoExtraUpdatesOnMerge() // make sure we get an update as a result... node.Description = "new description"; using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { node = (Node) s.Merge(node); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(0); @@ -588,10 +591,10 @@ public void NoExtraUpdatesOnMergeVersioned() { var entity = new VersionedEntity {Id = "entity", Name = "entity"}; using (ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.Persist(entity); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -600,10 +603,10 @@ public void NoExtraUpdatesOnMergeVersioned() // into this new session; this should cause no updates... VersionedEntity mergedEntity; using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { mergedEntity = (VersionedEntity) s.Merge(entity); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -615,10 +618,10 @@ public void NoExtraUpdatesOnMergeVersioned() // make sure we get an update as a result... entity.Name = "new name"; using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { entity = (VersionedEntity) s.Merge(entity); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(0); @@ -632,12 +635,12 @@ public void NoExtraUpdatesOnMergeVersionedWithCollection() var child = new VersionedEntity {Id = "child", Name = "child"}; using(ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent.Children.Add(child); child.Parent = parent; s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -646,10 +649,10 @@ public void NoExtraUpdatesOnMergeVersionedWithCollection() // into this new session; this should cause no updates... VersionedEntity mergedParent; using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { mergedParent = (VersionedEntity) s.Merge(parent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -666,10 +669,10 @@ public void NoExtraUpdatesOnMergeVersionedWithCollection() mergedParent.Name = "new name"; mergedParent.Children.Add(new VersionedEntity {Id = "child2", Name = "new child"}); using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent = (VersionedEntity) s.Merge(mergedParent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(1); @@ -685,7 +688,7 @@ public void NoExtraUpdatesOnMergeWithCollection() Name = "parent" }; using(ISession s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { var child = new Node { @@ -695,7 +698,7 @@ public void NoExtraUpdatesOnMergeWithCollection() parent.Children.Add(child); child.Parent = parent; s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -703,10 +706,10 @@ public void NoExtraUpdatesOnMergeWithCollection() // parent is now detached, but we have made no changes. so attempt to merge it // into this new session; this should cause no updates... using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent = (Node) s.Merge(parent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -725,10 +728,10 @@ public void NoExtraUpdatesOnMergeWithCollection() Name = "second child" }); using(var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { parent = (Node) s.Merge(parent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(1); diff --git a/src/NHibernate.Test/Pagination/CustomDialectFixture.cs b/src/NHibernate.Test/Pagination/CustomDialectFixture.cs index aedcceba788..3d0e571ae9a 100644 --- a/src/NHibernate.Test/Pagination/CustomDialectFixture.cs +++ b/src/NHibernate.Test/Pagination/CustomDialectFixture.cs @@ -68,7 +68,6 @@ protected override void OnSetUp() protected override void OnTearDown() { - using (ISession s = OpenSession()) using (ITransaction t = s.BeginTransaction()) { diff --git a/src/NHibernate.Test/PolymorphicGetAndLoad/Domain.cs b/src/NHibernate.Test/PolymorphicGetAndLoad/Domain.cs index 0dcad23004b..c94613335ab 100644 --- a/src/NHibernate.Test/PolymorphicGetAndLoad/Domain.cs +++ b/src/NHibernate.Test/PolymorphicGetAndLoad/Domain.cs @@ -37,5 +37,4 @@ public interface IMultiGraphNamed { string Name { get; set; } } - -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs b/src/NHibernate.Test/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs index 82e1cc4286a..0c586ad49b6 100644 --- a/src/NHibernate.Test/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs +++ b/src/NHibernate.Test/PolymorphicGetAndLoad/PolymorphicGetAndLoadTest.cs @@ -102,7 +102,6 @@ public void WhenSaveDeleteBaseClassCastedToInterfaceThenNotThrows() s.Flush(); } }, Throws.Nothing); - } [Test] diff --git a/src/NHibernate.Test/ProjectionFixtures/Fixture.cs b/src/NHibernate.Test/ProjectionFixtures/Fixture.cs index dd791b2909a..1f3aad19d54 100644 --- a/src/NHibernate.Test/ProjectionFixtures/Fixture.cs +++ b/src/NHibernate.Test/ProjectionFixtures/Fixture.cs @@ -64,7 +64,6 @@ protected override void OnTearDown() } } - [Test] public void ErrorFromDBWillGiveTheActualSQLExecuted() { @@ -89,7 +88,6 @@ public void ErrorFromDBWillGiveTheActualSQLExecuted() .Add(Projections.Count("child.Key.Area")) ); - var e = Assert.Throws(() => { using (var s = Sfi.OpenSession()) diff --git a/src/NHibernate.Test/PropertyRef/B.cs b/src/NHibernate.Test/PropertyRef/B.cs index 6c3a7299261..6e40d0b3720 100644 --- a/src/NHibernate.Test/PropertyRef/B.cs +++ b/src/NHibernate.Test/PropertyRef/B.cs @@ -18,6 +18,5 @@ public virtual string Name get { return _name; } set { _name = value; } } - - } -} \ No newline at end of file + } +} diff --git a/src/NHibernate.Test/PropertyRef/KeyPropertyRefFixture.cs b/src/NHibernate.Test/PropertyRef/KeyPropertyRefFixture.cs index 2ec67edbaef..35fcf33a53b 100644 --- a/src/NHibernate.Test/PropertyRef/KeyPropertyRefFixture.cs +++ b/src/NHibernate.Test/PropertyRef/KeyPropertyRefFixture.cs @@ -52,7 +52,6 @@ public void PropertyRefUsesOtherColumn() Assert.AreEqual(1, newA.Items.Count); s.Close(); - } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/PropertyTest/FieldClass.cs b/src/NHibernate.Test/PropertyTest/FieldClass.cs index ef518c0e3ce..0bb70cb783e 100644 --- a/src/NHibernate.Test/PropertyTest/FieldClass.cs +++ b/src/NHibernate.Test/PropertyTest/FieldClass.cs @@ -89,7 +89,6 @@ public int CamelUnderscoreFoo } } - public int Blah { get @@ -144,4 +143,4 @@ public int CamelMUnderscore } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/QueryTest/DetachedQueryFixture.cs b/src/NHibernate.Test/QueryTest/DetachedQueryFixture.cs index f55c3298b54..cca72466183 100644 --- a/src/NHibernate.Test/QueryTest/DetachedQueryFixture.cs +++ b/src/NHibernate.Test/QueryTest/DetachedQueryFixture.cs @@ -443,7 +443,6 @@ public void Serializable() [Test, Explicit] public void PerformanceDiffSimplyQuery() { - DateTime sDQStart = DateTime.Now; DetachedQuery dq = new DetachedQuery("from Foo f where f.Name=:pn and f.Description=:pd"); dq.SetString("pn", "N2").SetString("pd", "D2"); @@ -460,7 +459,6 @@ public void PerformanceDiffSimplyQuery() } DateTime sQStop = DateTime.Now; - Console.WriteLine("DetachedQueryCycle={0} QueryCycl={1} Diff={2}", sDQStop - sDQStart, sQStop - sQStart, (sDQStop - sDQStart) - (sQStop - sQStart)); } diff --git a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs index a2bfc7887c0..2b170b95905 100644 --- a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs @@ -544,7 +544,6 @@ public void MultiCriteriaAutoFlush() tx.Commit(); Assert.That(count, Is.EqualTo(0), "Session wasn't auto flushed."); - } } } diff --git a/src/NHibernate.Test/QueryTest/QueryBatchFixture.cs b/src/NHibernate.Test/QueryTest/QueryBatchFixture.cs index 29a901c452f..f28c0af8dce 100644 --- a/src/NHibernate.Test/QueryTest/QueryBatchFixture.cs +++ b/src/NHibernate.Test/QueryTest/QueryBatchFixture.cs @@ -479,7 +479,6 @@ public void TwoMultiHqlWithDifferentPagingGetDifferentResultsWhenUsingCachedQuer [Test] public void CanUseSecondLevelCacheWithPositionalParametersAndHql() { - Sfi.QueryCache.Clear(); CreateItems(); @@ -763,7 +762,6 @@ public void TwoMultiQueriesWithDifferentPagingGetDifferentResultsWhenUsingCached [Test] public void CanUseSecondLevelCacheWithPositionalParameters() { - Sfi.QueryCache.Clear(); CreateItems(); diff --git a/src/NHibernate.Test/ReadOnly/ReadOnlyProxyTest.cs b/src/NHibernate.Test/ReadOnly/ReadOnlyProxyTest.cs index fc963a0f62d..fa76fd3fac2 100644 --- a/src/NHibernate.Test/ReadOnly/ReadOnlyProxyTest.cs +++ b/src/NHibernate.Test/ReadOnly/ReadOnlyProxyTest.cs @@ -1,15 +1,6 @@ using System; -using System.Collections; -using System.Collections.Generic; -using NHibernate.Cfg; -using NHibernate.Dialect; -using NHibernate.Criterion; using NHibernate.Engine; using NHibernate.Proxy; -using NHibernate.SqlCommand; -using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -22,10 +13,10 @@ protected override string[] Mappings get { return new string[] - { - "ReadOnly.DataPoint.hbm.xml", - //"ReadOnly.TextHolder.hbm.xml" - }; + { + "ReadOnly.DataPoint.hbm.xml", + //"ReadOnly.TextHolder.hbm.xml" + }; } } @@ -33,544 +24,612 @@ protected override string[] Mappings public void ReadOnlyViaSessionDoesNotInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Flush(); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Transaction.Commit(); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Flush(); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + t.Commit(); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyViaLazyInitializerDoesNotInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dpLI.ReadOnly = true; - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dpLI.ReadOnly = false; - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Flush(); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Transaction.Commit(); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dpLI.ReadOnly = true; + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dpLI.ReadOnly = false; + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Flush(); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + t.Commit(); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyViaSessionNoChangeAfterInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(dp, Is.InstanceOf()); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - CheckReadOnly(s, dp, true); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + CheckReadOnly(s, dp, false); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(dp, Is.InstanceOf()); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + CheckReadOnly(s, dp, true); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + CheckReadOnly(s, dp, false); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyViaLazyInitializerNoChangeAfterInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - CheckReadOnly(s, dp, false); - Assert.That(dpLI.IsUninitialized); - NHibernateUtil.Initialize(dp); - Assert.That(dpLI.IsUninitialized, Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - dpLI.ReadOnly = true; - CheckReadOnly(s, dp, true); - Assert.That(dpLI.IsUninitialized); - NHibernateUtil.Initialize(dp); - Assert.That(dpLI.IsUninitialized, Is.False); - CheckReadOnly(s, dp, true); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - dpLI.ReadOnly = true; - CheckReadOnly(s, dp, true); - Assert.That(dpLI.IsUninitialized); - dpLI.ReadOnly = false; - CheckReadOnly(s, dp, false); - Assert.That(dpLI.IsUninitialized); - NHibernateUtil.Initialize(dp); - Assert.That(dpLI.IsUninitialized, Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + CheckReadOnly(s, dp, false); + Assert.That(dpLI.IsUninitialized); + NHibernateUtil.Initialize(dp); + Assert.That(dpLI.IsUninitialized, Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + var dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + dpLI.ReadOnly = true; + CheckReadOnly(s, dp, true); + Assert.That(dpLI.IsUninitialized); + NHibernateUtil.Initialize(dp); + Assert.That(dpLI.IsUninitialized, Is.False); + CheckReadOnly(s, dp, true); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + var dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + var dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + dpLI.ReadOnly = true; + CheckReadOnly(s, dp, true); + Assert.That(dpLI.IsUninitialized); + dpLI.ReadOnly = false; + CheckReadOnly(s, dp, false); + Assert.That(dpLI.IsUninitialized); + NHibernateUtil.Initialize(dp); + Assert.That(dpLI.IsUninitialized, Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } [Test] public void ReadOnlyViaSessionBeforeInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - s.SetReadOnly(dp, true); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + s.SetReadOnly(dp, true); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } [Test] public void ModifiableViaSessionBeforeInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, false); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, false); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyViaSessionBeforeInitByModifiableQuery() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - DataPoint dpFromQuery = s.CreateQuery("from DataPoint where id = " + dpOrig.Id).SetReadOnly(false).UniqueResult(); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - CheckReadOnly(s, dp, true); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + DataPoint dpFromQuery = s.CreateQuery("from DataPoint where id = " + dpOrig.Id).SetReadOnly(false) + .UniqueResult(); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + CheckReadOnly(s, dp, true); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } [Test] public void ReadOnlyViaSessionBeforeInitByReadOnlyQuery() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - DataPoint dpFromQuery = s.CreateQuery( "from DataPoint where Id = " + dpOrig.Id).SetReadOnly(true).UniqueResult(); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - CheckReadOnly(s, dp, true); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + DataPoint dpFromQuery = s.CreateQuery("from DataPoint where Id = " + dpOrig.Id).SetReadOnly(true) + .UniqueResult(); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + CheckReadOnly(s, dp, true); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ModifiableViaSessionBeforeInitByModifiableQuery() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - DataPoint dpFromQuery = s.CreateQuery("from DataPoint where Id = " + dpOrig.Id).SetReadOnly(false).UniqueResult(); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + DataPoint dpFromQuery = s.CreateQuery("from DataPoint where Id = " + dpOrig.Id).SetReadOnly(false) + .UniqueResult(); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ModifiableViaSessionBeforeInitByReadOnlyQuery() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - DataPoint dpFromQuery = s.CreateQuery("from DataPoint where id=" + dpOrig.Id).SetReadOnly(true).UniqueResult(); - Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); - Assert.That(dpFromQuery, Is.SameAs(dp)); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + DataPoint dpFromQuery = s.CreateQuery("from DataPoint where id=" + dpOrig.Id).SetReadOnly(true) + .UniqueResult(); + Assert.That(NHibernateUtil.IsInitialized(dpFromQuery), Is.True); + Assert.That(dpFromQuery, Is.SameAs(dp)); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } [Test] public void ReadOnlyViaLazyInitializerBeforeInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dpLI.IsUninitialized); - CheckReadOnly(s, dp, false); - dpLI.ReadOnly = true; - CheckReadOnly(s, dp, true); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dpLI.IsUninitialized); + CheckReadOnly(s, dp, false); + dpLI.ReadOnly = true; + CheckReadOnly(s, dp, true); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } [Test] public void ModifiableViaLazyInitializerBeforeInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dp, Is.InstanceOf()); - Assert.That(dpLI.IsUninitialized); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, false); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dp, Is.InstanceOf()); + Assert.That(dpLI.IsUninitialized); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, false); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyViaLazyInitializerAfterInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dpLI.IsUninitialized); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, false); - dpLI.ReadOnly = true; - CheckReadOnly(s, dp, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dpLI.IsUninitialized); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, false); + dpLI.ReadOnly = true; + CheckReadOnly(s, dp, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ModifiableViaLazyInitializerAfterInit() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - ILazyInitializer dpLI = ((INHibernateProxy)dp).HibernateLazyInitializer; - Assert.That(dpLI.IsUninitialized); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(dpLI.IsUninitialized, Is.False); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, false); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + ILazyInitializer dpLI = ((INHibernateProxy) dp).HibernateLazyInitializer; + Assert.That(dpLI.IsUninitialized); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(dpLI.IsUninitialized, Is.False); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, false); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } [Test] @@ -578,98 +637,108 @@ public void ModifiableViaLazyInitializerAfterInit() public void ModifyToReadOnlyToModifiableIsUpdatedFailureExpected() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - - try + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); Assert.That(dp.Description, Is.EqualTo("changed")); - // should fail due to HHH-4642 + s.Flush(); + t.Commit(); + s.Close(); + } + + try + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + + Assert.That(dp.Description, Is.EqualTo("changed")); + // should fail due to HHH-4642 + } } finally { - s.Transaction.Rollback(); - s.Close(); - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] [Ignore("Failing test. See HHH-4642")] public void ReadOnlyModifiedToModifiableIsUpdatedFailureExpected() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - - try + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); Assert.That(dp.Description, Is.EqualTo("changed")); - // should fail due to HHH-4642 + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + try + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + + Assert.That(dp.Description, Is.EqualTo("changed")); + // should fail due to HHH-4642 + } } finally { - s.Transaction.Rollback(); - s.Close(); - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } @@ -677,1083 +746,1199 @@ public void ReadOnlyModifiedToModifiableIsUpdatedFailureExpected() public void ReadOnlyChangedEvictedUpdate() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Evict(dp); - Assert.That(s.Contains(dp), Is.False); - s.Update(dp); - CheckReadOnly(s, dp, false); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Evict(dp); + Assert.That(s.Contains(dp), Is.False); + s.Update(dp); + CheckReadOnly(s, dp, false); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyToModifiableInitWhenModifiedIsUpdated() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyInitToModifiableModifiedIsUpdated() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - CheckReadOnly(s, dp, true); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + CheckReadOnly(s, dp, true); + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyModifiedUpdate() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, true); - s.Update(dp); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, true); + s.Update(dp); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyDelete() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Delete(dp); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp, Is.Null); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Delete(dp); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Get(dpOrig.Id); + Assert.That(dp, Is.Null); + t.Commit(); + s.Close(); + } } [Test] public void ReadOnlyRefresh() { DataPoint dp = this.CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - ITransaction t = s.BeginTransaction(); - dp = s.Load(dp.Id); - s.SetReadOnly(dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - s.Refresh(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - Assert.That(dp.Description, Is.EqualTo("original")); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(s.IsReadOnly(dp), Is.True); - Assert.That(s.IsReadOnly(((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementation()), Is.True); - s.Refresh(dp); - Assert.That(dp.Description, Is.EqualTo("original")); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(s.IsReadOnly(dp), Is.True); - Assert.That(s.IsReadOnly(((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementation()), Is.True); - t.Commit(); - - s.Clear(); - t = s.BeginTransaction(); - dp = s.Get(dp.Id); - Assert.That(dp.Description, Is.EqualTo("original")); - s.Delete(dp); - t.Commit(); - s.Close(); + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = s.Load(dp.Id); + s.SetReadOnly(dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + s.Refresh(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + Assert.That(dp.Description, Is.EqualTo("original")); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(s.IsReadOnly(dp), Is.True); + Assert.That( + s.IsReadOnly(((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementation()), + Is.True); + s.Refresh(dp); + Assert.That(dp.Description, Is.EqualTo("original")); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(s.IsReadOnly(dp), Is.True); + Assert.That( + s.IsReadOnly(((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementation()), + Is.True); + t.Commit(); + } + + s.Clear(); + using (var t = s.BeginTransaction()) + { + dp = s.Get(dp.Id); + Assert.That(dp.Description, Is.EqualTo("original")); + s.Delete(dp); + t.Commit(); + s.Close(); + } + } } - + [Test] public void ReadOnlyRefreshDeleted() { DataPoint dp = this.CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - ITransaction t = s.BeginTransaction(); - INHibernateProxy dpProxy = (INHibernateProxy)s.Load(dp.Id); - Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); - t.Commit(); - s.Close(); - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - t = s.BeginTransaction(); - dp = s.Get(dp.Id); - s.Delete(dp); - s.Flush(); - + INHibernateProxy dpProxy; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dpProxy = (INHibernateProxy) s.Load(dp.Id); + Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); + t.Commit(); + s.Close(); + } + try { - s.Refresh(dp); - Assert.Fail("should have thrown UnresolvableObjectException" ); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = s.Get(dp.Id); + s.Delete(dp); + s.Flush(); + + s.Refresh(dp); + Assert.Fail("should have thrown UnresolvableObjectException"); + } } catch (UnresolvableObjectException) { // expected } - finally + + DataPoint dpProxyInit; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - t.Rollback(); + s.CacheMode = CacheMode.Ignore; + dpProxyInit = s.Load(dp.Id); + Assert.That(dp.Description, Is.EqualTo("original")); + s.Delete(dpProxyInit); + t.Commit(); s.Close(); } - - s = OpenSession(); - t = s.BeginTransaction(); - s.CacheMode = CacheMode.Ignore; - DataPoint dpProxyInit = s.Load(dp.Id); - Assert.That(dp.Description, Is.EqualTo("original")); - s.Delete(dpProxyInit); - t.Commit(); - s.Close(); - - s = OpenSession(); - t = s.BeginTransaction(); - Assert.That(dpProxyInit, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dpProxyInit), Is.True); try { - s.Refresh(dpProxyInit); - Assert.Fail("should have thrown UnresolvableObjectException"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(dpProxyInit, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dpProxyInit), Is.True); + + s.Refresh(dpProxyInit); + Assert.Fail("should have thrown UnresolvableObjectException"); + } } catch (UnresolvableObjectException) { // expected } - finally - { - t.Rollback(); - s.Close(); - } - - s = OpenSession(); - t = s.BeginTransaction(); - Assert.That(dpProxyInit, Is.InstanceOf()); - + try { - s.Refresh(dpProxy); - Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); - NHibernateUtil.Initialize(dpProxy); - Assert.Fail("should have thrown UnresolvableObjectException"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(dpProxyInit, Is.InstanceOf()); + + s.Refresh(dpProxy); + Assert.That(NHibernateUtil.IsInitialized(dpProxy), Is.False); + NHibernateUtil.Initialize(dpProxy); + Assert.Fail("should have thrown UnresolvableObjectException"); + } } catch (UnresolvableObjectException) { // expected } - finally - { - t.Rollback(); - s.Close(); - } } - + [Test] public void ReadOnlyRefreshDetached() { DataPoint dp = this.CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - ITransaction t = s.BeginTransaction(); - dp = s.Load(dp.Id); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - Assert.That(s.IsReadOnly(dp), Is.False); - s.SetReadOnly(dp, true); - Assert.That(s.IsReadOnly(dp), Is.True); - s.Evict(dp); - s.Refresh(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - Assert.That(s.IsReadOnly(dp), Is.False); - dp.Description = "changed"; - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.SetReadOnly(dp, true); - s.Evict(dp); - s.Refresh(dp); - Assert.That(dp.Description, Is.EqualTo("original")); - Assert.That(s.IsReadOnly(dp), Is.False); - t.Commit(); - - s.Clear(); - t = s.BeginTransaction(); - dp = s.Get(dp.Id); - Assert.That(dp.Description, Is.EqualTo("original")); - s.Delete(dp); - t.Commit(); - s.Close(); + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = s.Load(dp.Id); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + Assert.That(s.IsReadOnly(dp), Is.False); + s.SetReadOnly(dp, true); + Assert.That(s.IsReadOnly(dp), Is.True); + s.Evict(dp); + s.Refresh(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + Assert.That(s.IsReadOnly(dp), Is.False); + dp.Description = "changed"; + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + s.SetReadOnly(dp, true); + s.Evict(dp); + s.Refresh(dp); + Assert.That(dp.Description, Is.EqualTo("original")); + Assert.That(s.IsReadOnly(dp), Is.False); + t.Commit(); + } + + s.Clear(); + using (var t = s.BeginTransaction()) + { + dp = s.Get(dp.Id); + Assert.That(dp.Description, Is.EqualTo("original")); + s.Delete(dp); + t.Commit(); + s.Close(); + } + } } - + [Test] public void ReadOnlyProxyMergeDetachedProxyWithChange() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + t.Commit(); + s.Close(); + } + // modify detached proxy dp.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = s.Load(dpOrig.Id); - Assert.That(dpLoaded, Is.InstanceOf()); - CheckReadOnly(s, dpLoaded, false); - s.SetReadOnly(dpLoaded, true); - CheckReadOnly(s, dpLoaded, true); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - DataPoint dpMerged = (DataPoint)s.Merge(dp); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dpLoaded, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = s.Load(dpOrig.Id); + Assert.That(dpLoaded, Is.InstanceOf()); + CheckReadOnly(s, dpLoaded, false); + s.SetReadOnly(dpLoaded, true); + CheckReadOnly(s, dpLoaded, true); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + DataPoint dpMerged = (DataPoint) s.Merge(dp); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dpLoaded, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyProxyInitMergeDetachedProxyWithChange() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + t.Commit(); + s.Close(); + } + // modify detached proxy dp.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = s.Load(dpOrig.Id); - Assert.That(dpLoaded, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - NHibernateUtil.Initialize(dpLoaded); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - CheckReadOnly(s, dpLoaded, false); - s.SetReadOnly(dpLoaded, true); - CheckReadOnly(s, dpLoaded, true); - DataPoint dpMerged = (DataPoint)s.Merge(dp); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dpLoaded, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = s.Load(dpOrig.Id); + Assert.That(dpLoaded, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + NHibernateUtil.Initialize(dpLoaded); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + CheckReadOnly(s, dpLoaded, false); + s.SetReadOnly(dpLoaded, true); + CheckReadOnly(s, dpLoaded, true); + DataPoint dpMerged = (DataPoint) s.Merge(dp); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dpLoaded, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyProxyMergeDetachedEntityWithChange() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + t.Commit(); + s.Close(); + } + // modify detached proxy target - DataPoint dpEntity = (DataPoint)((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementation(); + DataPoint dpEntity = (DataPoint) ((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementation(); dpEntity.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = s.Load(dpOrig.Id); - Assert.That(dpLoaded, Is.InstanceOf()); - CheckReadOnly(s, dpLoaded, false); - s.SetReadOnly(dpLoaded, true); - CheckReadOnly(s, dpLoaded, true); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - DataPoint dpMerged = (DataPoint)s.Merge(dpEntity); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dpLoaded, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = s.Load(dpOrig.Id); + Assert.That(dpLoaded, Is.InstanceOf()); + CheckReadOnly(s, dpLoaded, false); + s.SetReadOnly(dpLoaded, true); + CheckReadOnly(s, dpLoaded, true); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + DataPoint dpMerged = (DataPoint) s.Merge(dpEntity); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dpLoaded, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyProxyInitMergeDetachedEntityWithChange() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + t.Commit(); + s.Close(); + } + // modify detached proxy target - DataPoint dpEntity = (DataPoint)((INHibernateProxy)dp).HibernateLazyInitializer.GetImplementation(); + DataPoint dpEntity = (DataPoint) ((INHibernateProxy) dp).HibernateLazyInitializer.GetImplementation(); dpEntity.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpLoaded = s.Load(dpOrig.Id); - Assert.That(dpLoaded, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); - NHibernateUtil.Initialize(dpLoaded); - Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); - CheckReadOnly(s, dpLoaded, false); - s.SetReadOnly(dpLoaded, true); - CheckReadOnly(s, dpLoaded, true); - DataPoint dpMerged = (DataPoint)s.Merge(dpEntity); - Assert.That(dpMerged, Is.SameAs(dpLoaded)); - Assert.That(dpLoaded.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dpLoaded, true); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpLoaded = s.Load(dpOrig.Id); + Assert.That(dpLoaded, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.False); + NHibernateUtil.Initialize(dpLoaded); + Assert.That(NHibernateUtil.IsInitialized(dpLoaded), Is.True); + CheckReadOnly(s, dpLoaded, false); + s.SetReadOnly(dpLoaded, true); + CheckReadOnly(s, dpLoaded, true); + DataPoint dpMerged = (DataPoint) s.Merge(dpEntity); + Assert.That(dpMerged, Is.SameAs(dpLoaded)); + Assert.That(dpLoaded.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dpLoaded, true); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void ReadOnlyEntityMergeDetachedProxyWithChange() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - CheckReadOnly(s, dp, false); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - NHibernateUtil.Initialize(dp); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + CheckReadOnly(s, dp, false); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + NHibernateUtil.Initialize(dp); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + t.Commit(); + s.Close(); + } + // modify detached proxy dp.Description = "changed"; - - s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); - DataPoint dpEntity = s.Get(dpOrig.Id); - Assert.That(dpEntity, Is.Not.InstanceOf()); - Assert.That(s.IsReadOnly(dpEntity), Is.False); - s.SetReadOnly(dpEntity, true); - Assert.That(s.IsReadOnly(dpEntity), Is.True); - DataPoint dpMerged = (DataPoint)s.Merge(dp); - Assert.That(dpMerged, Is.SameAs(dpEntity)); - Assert.That(dpEntity.Description, Is.EqualTo("changed")); - Assert.That(s.IsReadOnly(dpEntity), Is.True); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + DataPoint dpEntity = s.Get(dpOrig.Id); + Assert.That(dpEntity, Is.Not.InstanceOf()); + Assert.That(s.IsReadOnly(dpEntity), Is.False); + s.SetReadOnly(dpEntity, true); + Assert.That(s.IsReadOnly(dpEntity), Is.True); + DataPoint dpMerged = (DataPoint) s.Merge(dp); + Assert.That(dpMerged, Is.SameAs(dpEntity)); + Assert.That(dpEntity.Description, Is.EqualTo("changed")); + Assert.That(s.IsReadOnly(dpEntity), Is.True); + s.Flush(); + t.Commit(); + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void SetReadOnlyInTwoTransactionsSameSession() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Flush(); - s.Transaction.Commit(); - - CheckReadOnly(s, dp, true); - - s.BeginTransaction(); - CheckReadOnly(s, dp, true); - dp.Description = "changed again"; - Assert.That(dp.Description, Is.EqualTo("changed again")); - s.Flush(); - s.Transaction.Commit(); - - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + DataPoint dp; + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Flush(); + t.Commit(); + } + + CheckReadOnly(s, dp, true); + + using (var t = s.BeginTransaction()) + { + CheckReadOnly(s, dp, true); + dp.Description = "changed again"; + Assert.That(dp.Description, Is.EqualTo("changed again")); + s.Flush(); + t.Commit(); + } + + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void SetReadOnlyBetweenTwoTransactionsSameSession() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, false); - s.Flush(); - s.Transaction.Commit(); - - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - - s.BeginTransaction(); - CheckReadOnly(s, dp, true); - dp.Description = "changed again"; - Assert.That(dp.Description, Is.EqualTo("changed again")); - s.Flush(); - s.Transaction.Commit(); - - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + DataPoint dp; + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, false); + s.Flush(); + t.Commit(); + } + + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + + using (var t = s.BeginTransaction()) + { + CheckReadOnly(s, dp, true); + dp.Description = "changed again"; + Assert.That(dp.Description, Is.EqualTo("changed again")); + s.Flush(); + t.Commit(); + } + + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void SetModifiableBetweenTwoTransactionsSameSession() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.SetReadOnly(dp, true); - CheckReadOnly(s, dp, true); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); - Assert.That(dp.Description, Is.EqualTo("changed")); - CheckReadOnly(s, dp, true); - s.Flush(); - s.Transaction.Commit(); - - CheckReadOnly(s, dp, true); - s.SetReadOnly(dp, false); - CheckReadOnly(s, dp, false); - - s.BeginTransaction(); - CheckReadOnly(s, dp, false); - Assert.That(dp.Description, Is.EqualTo("changed")); - s.Refresh(dp); - Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); - CheckReadOnly(s, dp, false); - dp.Description = "changed again"; - Assert.That(dp.Description, Is.EqualTo("changed again")); - s.Flush(); - s.Transaction.Commit(); - - s.Close(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Get(dpOrig.Id); - Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); - Assert.That(dp.Description, Is.EqualTo("changed again")); - Assert.That(dp.X, Is.EqualTo(dpOrig.X)); - Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + DataPoint dp; + + using (var s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (var t = s.BeginTransaction()) + { + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.SetReadOnly(dp, true); + CheckReadOnly(s, dp, true); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True); + Assert.That(dp.Description, Is.EqualTo("changed")); + CheckReadOnly(s, dp, true); + s.Flush(); + t.Commit(); + } + + CheckReadOnly(s, dp, true); + s.SetReadOnly(dp, false); + CheckReadOnly(s, dp, false); + + using (var t = s.BeginTransaction()) + { + CheckReadOnly(s, dp, false); + Assert.That(dp.Description, Is.EqualTo("changed")); + s.Refresh(dp); + Assert.That(dp.Description, Is.EqualTo(dpOrig.Description)); + CheckReadOnly(s, dp, false); + dp.Description = "changed again"; + Assert.That(dp.Description, Is.EqualTo("changed again")); + s.Flush(); + t.Commit(); + } + + s.Close(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + dp = s.Get(dpOrig.Id); + Assert.That(dp.Id, Is.EqualTo(dpOrig.Id)); + Assert.That(dp.Description, Is.EqualTo("changed again")); + Assert.That(dp.X, Is.EqualTo(dpOrig.X)); + Assert.That(dp.Y, Is.EqualTo(dpOrig.Y)); + s.Delete(dp); + t.Commit(); + s.Close(); + } } - + [Test] public void IsReadOnlyAfterSessionClosed() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp = null; + try { - s.IsReadOnly(dp); - Assert.Fail("should have failed because session was closed"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + s.Close(); + s.IsReadOnly(dp); + Assert.Fail("should have failed because session was closed"); + } } catch (ObjectDisposedException) // SessionException in Hibernate { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void IsReadOnlyAfterSessionClosedViaLazyInitializer() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - Assert.That(s.Contains(dp), Is.True); - s.Close(); - - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + Assert.That(s.Contains(dp), Is.True); + s.Close(); + } + + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); try { - var value = ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly; + var value = ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly; Assert.Fail("should have failed because session was detached"); } catch (TransientObjectException) { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void DetachedIsReadOnlyAfterEvictViaSession() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - Assert.That(s.Contains(dp), Is.True); - s.Evict(dp); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - - try - { - s.IsReadOnly(dp); - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + Assert.That(s.Contains(dp), Is.True); + s.Evict(dp); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + + try + { + s.IsReadOnly(dp); + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void DetachedIsReadOnlyAfterEvictViaLazyInitializer() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Evict(dp); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - try - { - var value = ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly; - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.Evict(dp); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + try + { + var value = ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly; + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void SetReadOnlyAfterSessionClosed() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - s.Close(); - + DataPoint dp = null; + try { - s.SetReadOnly(dp, true); - Assert.Fail("should have failed because session was closed"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + s.Close(); + s.SetReadOnly(dp, true); + Assert.Fail("should have failed because session was closed"); + } } catch (ObjectDisposedException) // SessionException in Hibernate { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void SetReadOnlyAfterSessionClosedViaLazyInitializer() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - Assert.That(s.Contains(dp), Is.True); - s.Close(); - - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); + DataPoint dp; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + Assert.That(s.Contains(dp), Is.True); + s.Close(); + } + + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); try { - ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly = true; + ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly = true; Assert.Fail("should have failed because session was detached"); } catch (TransientObjectException) { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void SetClosedSessionInLazyInitializer() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Transaction.Commit(); - Assert.That(s.Contains(dp), Is.True); - s.Close(); - - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - Assert.That(((ISessionImplementor)s).IsClosed, Is.True); - + DataPoint dp = null; + try { - ((INHibernateProxy)dp).HibernateLazyInitializer.SetSession((ISessionImplementor)s); - Assert.Fail("should have failed because session was closed"); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CacheMode = CacheMode.Ignore; + + dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + t.Commit(); + Assert.That(s.Contains(dp), Is.True); + s.Close(); + + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + Assert.That(((ISessionImplementor) s).IsClosed, Is.True); + + ((INHibernateProxy) dp).HibernateLazyInitializer.SetSession((ISessionImplementor) s); + Assert.Fail("should have failed because session was closed"); + } } catch (ObjectDisposedException) // SessionException in Hibernate { // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); } finally { - s = OpenSession(); - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void DetachedSetReadOnlyAfterEvictViaSession() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - Assert.That(s.Contains(dp), Is.True); - s.Evict(dp); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - - try - { - s.SetReadOnly(dp, true); - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + Assert.That(s.Contains(dp), Is.True); + s.Evict(dp); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + + try + { + s.SetReadOnly(dp, true); + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + [Test] public void DetachedSetReadOnlyAfterEvictViaLazyInitializer() { DataPoint dpOrig = CreateDataPoint(CacheMode.Ignore); - - ISession s = OpenSession(); - s.CacheMode = CacheMode.Ignore; - - s.BeginTransaction(); - DataPoint dp = s.Load(dpOrig.Id); - Assert.That(dp, Is.InstanceOf()); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); - CheckReadOnly(s, dp, false); - s.Evict(dp); - Assert.That(s.Contains(dp), Is.False); - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.Session, Is.Null); - - try - { - ((INHibernateProxy)dp).HibernateLazyInitializer.ReadOnly = true; - Assert.Fail("should have failed because proxy was detached"); - } - catch (TransientObjectException) - { - // expected - Assert.That(((INHibernateProxy)dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); - } - finally + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - s.Delete(dp); - s.Transaction.Commit(); - s.Close(); + s.CacheMode = CacheMode.Ignore; + + DataPoint dp = s.Load(dpOrig.Id); + Assert.That(dp, Is.InstanceOf()); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False); + CheckReadOnly(s, dp, false); + s.Evict(dp); + Assert.That(s.Contains(dp), Is.False); + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.Session, Is.Null); + + try + { + ((INHibernateProxy) dp).HibernateLazyInitializer.ReadOnly = true; + Assert.Fail("should have failed because proxy was detached"); + } + catch (TransientObjectException) + { + // expected + Assert.That(((INHibernateProxy) dp).HibernateLazyInitializer.IsReadOnlySettingAvailable, Is.False); + } + finally + { + s.Delete(dp); + t.Commit(); + s.Close(); + } } } - + private DataPoint CreateDataPoint(CacheMode mode) { - DataPoint dp = null; - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - - using (ITransaction t = s.BeginTransaction()) - { - dp = new DataPoint(); - dp.X = 0.1M; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - dp.Description = "original"; - s.Save(dp); - t.Commit(); - } + + var dp = new DataPoint(); + dp.X = 0.1M; + dp.Y = (decimal) System.Math.Cos((double) dp.X); + dp.Description = "original"; + s.Save(dp); + t.Commit(); + return dp; } - - return dp; } - + private void CheckReadOnly(ISession s, object proxy, bool expectedReadOnly) { Assert.That(proxy, Is.InstanceOf()); - ILazyInitializer li = ((INHibernateProxy)proxy).HibernateLazyInitializer; + ILazyInitializer li = ((INHibernateProxy) proxy).HibernateLazyInitializer; Assert.That(s, Is.SameAs(li.Session)); Assert.That(s.IsReadOnly(proxy), Is.EqualTo(expectedReadOnly)); Assert.That(li.ReadOnly, Is.EqualTo(expectedReadOnly)); diff --git a/src/NHibernate.Test/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs b/src/NHibernate.Test/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs index dbb628f9556..ad33891ee36 100644 --- a/src/NHibernate.Test/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs +++ b/src/NHibernate.Test/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs @@ -273,7 +273,6 @@ public void ExistingReadOnlyAfterSetSessionModifiable() [Test] public void ExistingReadOnlyAfterSetSessionModifiableExisting() { - Container cOrig = CreateContainer(); ISet expectedInitializedObjects = new HashSet( @@ -365,13 +364,11 @@ public void ExistingReadOnlyAfterSetSessionModifiableExisting() s.CreateQuery("delete from Owner").ExecuteUpdate(); t.Commit(); s.Close(); - } [Test] public void ExistingReadOnlyAfterSetSessionModifiableExistingEntityReadOnly() { - Container cOrig = CreateContainer(); ISet expectedInitializedObjects = new HashSet( @@ -472,7 +469,6 @@ public void ExistingReadOnlyAfterSetSessionModifiableExistingEntityReadOnly() [Test] public void ExistingReadOnlyAfterSetSessionModifiableProxyExisting() { - Container cOrig = CreateContainer(); ISet expectedInitializedObjects = new HashSet( @@ -565,7 +561,6 @@ public void ExistingReadOnlyAfterSetSessionModifiableProxyExisting() s.CreateQuery("delete from Owner").ExecuteUpdate(); t.Commit(); s.Close(); - } [Test] @@ -1387,8 +1382,7 @@ public void DefaultModifiableWithFilterCollectionEntities() t.Commit(); s.Close(); } - - + private Container CreateContainer() { Container c = new Container("container"); diff --git a/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs b/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs index ee7029a6559..b5217f6991d 100644 --- a/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs +++ b/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs @@ -1,14 +1,6 @@ using System.Collections; using System.Collections.Generic; -using NHibernate.Cfg; -using NHibernate.Criterion; -using NHibernate.Dialect; -using NHibernate.Engine; using NHibernate.Proxy; -using NHibernate.SqlCommand; -using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -40,10 +32,10 @@ public void ReadOnlyOnProxies() DataPoint dp = null; long dpId = -1; - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); dp = new DataPoint(); dp.X = 0.1M; @@ -51,13 +43,13 @@ public void ReadOnlyOnProxies() dp.Description = "original"; s.Save(dp); dpId = dp.Id; - s.Transaction.Commit(); + t.Commit(); } - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); s.DefaultReadOnly = true; Assert.That(s.DefaultReadOnly, Is.True); dp = (DataPoint)s.Load(dpId); @@ -69,16 +61,16 @@ public void ReadOnlyOnProxies() Assert.That(NHibernateUtil.IsInitialized(dp), Is.True, "was not initialized during mod"); Assert.That(dp.Description, Is.EqualTo("changed"), "desc not changed in memory"); s.Flush(); - s.Transaction.Commit(); + t.Commit(); } - - using (ISession s = OpenSession()) + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); IList list = s.CreateQuery("from DataPoint where description = 'changed'").List(); Assert.That(list.Count, Is.EqualTo(0), "change written to database"); s.CreateQuery("delete from DataPoint").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } } @@ -1079,7 +1071,6 @@ public void MergeWithReadOnlyEntity() t.Commit(); } } - } [Test] diff --git a/src/NHibernate.Test/ReadOnly/ReadOnlyTest.cs b/src/NHibernate.Test/ReadOnly/ReadOnlyTest.cs index c67203ce8d7..d068b449dcd 100644 --- a/src/NHibernate.Test/ReadOnly/ReadOnlyTest.cs +++ b/src/NHibernate.Test/ReadOnly/ReadOnlyTest.cs @@ -1,7 +1,5 @@ -using System; using System.Collections; using System.Collections.Generic; -using NHibernate.Dialect; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -26,45 +24,52 @@ protected override string[] Mappings public void ReadOnlyOnProxies() { ClearCounts(); - - ISession s = OpenSession(); - s.BeginTransaction(); - DataPoint dp = new DataPoint(); - dp.X = 0.1M; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - dp.Description = "original"; - s.Save(dp); - long dpId = dp.Id; - s.Transaction.Commit(); - s.Close(); - + long dpId; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + DataPoint dp = new DataPoint(); + dp.X = 0.1M; + dp.Y = (decimal) System.Math.Cos((double) dp.X); + dp.Description = "original"; + s.Save(dp); + dpId = dp.Id; + t.Commit(); + s.Close(); + } + AssertInsertCount(1); AssertUpdateCount(0); ClearCounts(); - - s = OpenSession(); - s.BeginTransaction(); - dp = s.Load(dpId); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized"); - s.SetReadOnly(dp, true); - Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized during SetReadOnly"); - dp.Description = "changed"; - Assert.That(NHibernateUtil.IsInitialized(dp), Is.True, "was not initialized during mod"); - Assert.That(dp.Description, Is.EqualTo("changed"), "desc not changed in memory"); - s.Flush(); - s.Transaction.Commit(); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var dp = s.Load(dpId); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized"); + s.SetReadOnly(dp, true); + Assert.That(NHibernateUtil.IsInitialized(dp), Is.False, "was initialized during SetReadOnly"); + dp.Description = "changed"; + Assert.That(NHibernateUtil.IsInitialized(dp), Is.True, "was not initialized during mod"); + Assert.That(dp.Description, Is.EqualTo("changed"), "desc not changed in memory"); + s.Flush(); + t.Commit(); + s.Close(); + } + AssertUpdateCount(0); - - s = OpenSession(); - s.BeginTransaction(); - IList list = s.CreateQuery("from DataPoint where Description = 'changed'").List(); - Assert.That(list.Count, Is.EqualTo(0), "change written to database"); - Assert.That(s.CreateQuery("delete from DataPoint").ExecuteUpdate(), Is.EqualTo(1)); - s.Transaction.Commit(); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + IList list = s.CreateQuery("from DataPoint where Description = 'changed'").List(); + Assert.That(list.Count, Is.EqualTo(0), "change written to database"); + Assert.That(s.CreateQuery("delete from DataPoint").ExecuteUpdate(), Is.EqualTo(1)); + t.Commit(); + s.Close(); + } + AssertUpdateCount(0); //deletes from Query.executeUpdate() are not tracked //AssertDeleteCount(1); @@ -544,38 +549,45 @@ public void ReadOnlyOnTextType() string newText = "some even bigger text string"; ClearCounts(); - - ISession s = OpenSession(); - s.BeginTransaction(); - TextHolder holder = new TextHolder(origText); - s.Save(holder); - long id = holder.Id; - s.Transaction.Commit(); - s.Close(); - + long id; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + TextHolder holder = new TextHolder(origText); + s.Save(holder); + id = holder.Id; + t.Commit(); + s.Close(); + } + AssertInsertCount(1); AssertUpdateCount(0); ClearCounts(); - - s = OpenSession(); - s.BeginTransaction(); - holder = s.Get(id); - s.SetReadOnly(holder, true); - holder.TheText = newText; - s.Flush(); - s.Transaction.Commit(); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var holder = s.Get(id); + s.SetReadOnly(holder, true); + holder.TheText = newText; + s.Flush(); + t.Commit(); + s.Close(); + } + AssertUpdateCount(0); - - s = OpenSession(); - s.BeginTransaction(); - holder = s.Get(id); - Assert.That(origText, Is.EqualTo(holder.TheText), "change written to database"); - s.Delete(holder); - s.Transaction.Commit(); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var holder = s.Get(id); + Assert.That(origText, Is.EqualTo(holder.TheText), "change written to database"); + s.Delete(holder); + t.Commit(); + s.Close(); + } + AssertUpdateCount(0); AssertDeleteCount(1); } diff --git a/src/NHibernate.Test/ReadOnly/ReadOnlyVersionedNodes.cs b/src/NHibernate.Test/ReadOnly/ReadOnlyVersionedNodes.cs index d2db229d516..6ef31d2ae23 100644 --- a/src/NHibernate.Test/ReadOnly/ReadOnlyVersionedNodes.cs +++ b/src/NHibernate.Test/ReadOnly/ReadOnlyVersionedNodes.cs @@ -1,15 +1,4 @@ -using System.Collections; -using System.Collections.Generic; using System.Linq; -using NHibernate.Cfg; -using NHibernate.Criterion; -using NHibernate.Dialect; -using NHibernate.Engine; -using NHibernate.Proxy; -using NHibernate.SqlCommand; -using NHibernate.Transform; -using NHibernate.Type; -using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.ReadOnly @@ -31,22 +20,23 @@ protected override string[] Mappings public void SetReadOnlyTrueAndFalse() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(node); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) { - s.BeginTransaction(); - node = s.Get(node.Id); - s.SetReadOnly(node, true); - node.Name = "node-name"; - s.Transaction.Commit(); + using (var t = s.BeginTransaction()) + { + node = s.Get(node.Id); + s.SetReadOnly(node, true); + node.Name = "node-name"; + t.Commit(); + } AssertUpdateCount(0); AssertInsertCount(0); @@ -54,24 +44,25 @@ public void SetReadOnlyTrueAndFalse() // the changed name is still in node Assert.That(node.Name, Is.EqualTo("node-name")); - s.BeginTransaction(); - node = s.Get(node.Id); - // the changed name is still in the session - Assert.That(node.Name, Is.EqualTo("node-name")); - s.Refresh(node); - // after refresh, the name reverts to the original value - Assert.That(node.Name, Is.EqualTo("node")); - node = s.Get(node.Id); - Assert.That(node.Name, Is.EqualTo("node")); - s.Transaction.Commit(); + using (var t = s.BeginTransaction()) + { + node = s.Get(node.Id); + // the changed name is still in the session + Assert.That(node.Name, Is.EqualTo("node-name")); + s.Refresh(node); + // after refresh, the name reverts to the original value + Assert.That(node.Name, Is.EqualTo("node")); + node = s.Get(node.Id); + Assert.That(node.Name, Is.EqualTo("node")); + t.Commit(); + } } AssertUpdateCount(0); AssertInsertCount(0); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); Assert.That(node.Name, Is.EqualTo("node")); s.SetReadOnly(node, true); @@ -82,22 +73,21 @@ public void SetReadOnlyTrueAndFalse() Assert.That(node.Name, Is.EqualTo("node")); s.SetReadOnly(node, false); node.Name = "diff-node-name"; - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(0); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); Assert.That(node.Name, Is.EqualTo("diff-node-name")); Assert.That(node.Version, Is.EqualTo(2)); s.SetReadOnly(node, true); s.Delete(node); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -108,37 +98,35 @@ public void SetReadOnlyTrueAndFalse() public void UpdateSetReadOnlyTwice() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(node); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); node.Name = "node-name"; s.SetReadOnly(node, true); s.SetReadOnly(node, true); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); AssertInsertCount(0); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); Assert.That(node.Name, Is.EqualTo("node")); Assert.That(node.Version, Is.EqualTo(1)); s.SetReadOnly(node, true); s.Delete(node); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -149,37 +137,35 @@ public void UpdateSetReadOnlyTwice() public void UpdateSetModifiable() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(node); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); node.Name = "node-name"; s.SetReadOnly(node, false); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); Assert.That(node.Name, Is.EqualTo("node-name")); Assert.That(node.Version, Is.EqualTo(2)); //s.SetReadOnly(node, true); s.Delete(node); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -191,36 +177,34 @@ public void UpdateSetModifiable() public void UpdateSetReadOnlySetModifiableFailureExpected() { VersionedNode node = new VersionedNode("node", "node"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(node); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); node.Name = "node-name"; s.SetReadOnly(node, true); s.SetReadOnly(node, false); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(0); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); node = s.Get(node.Id); Assert.That(node.Name, Is.EqualTo("node-name")); Assert.That(node.Version, Is.EqualTo(2)); s.Delete(node); - s.Transaction.Commit(); + t.Commit(); } } @@ -228,72 +212,77 @@ public void UpdateSetReadOnlySetModifiableFailureExpected() [Ignore("Failure expected")] public void SetReadOnlyUpdateSetModifiableFailureExpected() { - ISession s = OpenSession(); - s.BeginTransaction(); VersionedNode node = new VersionedNode("node", "node"); - s.Persist(node); - s.Transaction.Commit(); - s.Close(); - + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Persist(node); + t.Commit(); + s.Close(); + } + ClearCounts(); - - s = OpenSession(); - - s.BeginTransaction(); - node = s.Get(node.Id); - s.SetReadOnly(node, true); - node.Name = "node-name"; - s.SetReadOnly(node, false); - s.Transaction.Commit(); - s.Close(); - + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + node = s.Get(node.Id); + s.SetReadOnly(node, true); + node.Name = "node-name"; + s.SetReadOnly(node, false); + t.Commit(); + s.Close(); + } + AssertUpdateCount(1); AssertInsertCount(0); - - s = OpenSession(); - s.BeginTransaction(); - node = s.Get(node.Id); - Assert.That(node.Name, Is.EqualTo("node-name")); - Assert.That(node.Version, Is.EqualTo(2)); - s.Delete(node); - s.Transaction.Commit(); - s.Close(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + node = s.Get(node.Id); + Assert.That(node.Name, Is.EqualTo("node-name")); + Assert.That(node.Version, Is.EqualTo(2)); + s.Delete(node); + t.Commit(); + s.Close(); + } } [Test] public void AddNewChildToReadOnlyParent() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); VersionedNode parentManaged = s.Get(parent.Id); s.SetReadOnly(parentManaged, true); parentManaged.Name = "new parent name"; parentManaged.AddChild(child); s.Save(parentManaged); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(1); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { s.CacheMode = CacheMode.Ignore; - s.BeginTransaction(); parent = s.Get(parent.Id); Assert.That(parent.Name, Is.EqualTo("parent")); Assert.That(parent.Children.Count, Is.EqualTo(1)); @@ -301,7 +290,7 @@ public void AddNewChildToReadOnlyParent() child = s.Get(child.Id); Assert.That(child, Is.Not.Null); s.Delete(parent); - s.Transaction.Commit(); + t.Commit(); } } @@ -309,11 +298,11 @@ public void AddNewChildToReadOnlyParent() public void UpdateParentWithNewChildCommitWithReadOnlyParent() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -321,22 +310,20 @@ public void UpdateParentWithNewChildCommitWithReadOnlyParent() parent.Name = "new parent name"; VersionedNode child = new VersionedNode("child", "child"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Update(parent); s.SetReadOnly(parent, true); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -349,7 +336,7 @@ public void UpdateParentWithNewChildCommitWithReadOnlyParent() s.SetReadOnly(child, true); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -360,11 +347,11 @@ public void UpdateParentWithNewChildCommitWithReadOnlyParent() public void MergeDetachedParentWithNewChildCommitWithReadOnlyParent() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -372,22 +359,20 @@ public void MergeDetachedParentWithNewChildCommitWithReadOnlyParent() parent.Name = "new parent name"; VersionedNode child = new VersionedNode("child", "child"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = (VersionedNode) s.Merge(parent); s.SetReadOnly(parent, true); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -400,7 +385,7 @@ public void MergeDetachedParentWithNewChildCommitWithReadOnlyParent() s.SetReadOnly(child, true); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -411,11 +396,11 @@ public void MergeDetachedParentWithNewChildCommitWithReadOnlyParent() public void GetParentMakeReadOnlyThenMergeDetachedParentWithNewChildC() { VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -423,24 +408,22 @@ public void GetParentMakeReadOnlyThenMergeDetachedParentWithNewChildC() parent.Name = "new parent name"; VersionedNode child = new VersionedNode("child", "child"); parent.AddChild(child); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode parentManaged = s.Get(parent.Id); s.SetReadOnly(parentManaged, true); VersionedNode parentMerged = (VersionedNode) s.Merge(parent); Assert.That(parentManaged, Is.SameAs(parentMerged)); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(1); AssertInsertCount(1); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -451,7 +434,7 @@ public void GetParentMakeReadOnlyThenMergeDetachedParentWithNewChildC() Assert.That(child.Version, Is.EqualTo(1)); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -463,55 +446,50 @@ public void MergeUnchangedDetachedParentChildren() { VersionedNode parent = new VersionedNode("parent", "parent"); VersionedNode child = new VersionedNode("child", "child"); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent.AddChild(child); s.Persist(parent); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = (VersionedNode) s.Merge(parent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode parentGet = s.Get(parent.Id); s.Merge(parent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode parentLoad = s.Load(parent.Id); s.Merge(parent); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); AssertInsertCount(0); ClearCounts(); - using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(parent.Name, Is.EqualTo("parent")); @@ -522,7 +500,7 @@ public void MergeUnchangedDetachedParentChildren() Assert.That(child.Version, Is.EqualTo(1)); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -533,32 +511,31 @@ public void MergeUnchangedDetachedParentChildren() public void AddNewParentToReadOnlyChild() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(child); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); VersionedNode parent = new VersionedNode("parent", "parent"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode childManaged = s.Get(child.Id); s.SetReadOnly(childManaged, true); childManaged.Name = "new child name"; parent.AddChild(childManaged); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); AssertInsertCount(1); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); child = s.Get(child.Id); Assert.That(child.Name, Is.EqualTo("child")); Assert.That(child.Parent, Is.Null); @@ -567,7 +544,7 @@ public void AddNewParentToReadOnlyChild() Assert.That(parent, Is.Not.Null); s.SetReadOnly(child, true); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -578,11 +555,11 @@ public void AddNewParentToReadOnlyChild() public void UpdateChildWithNewParentCommitWithReadOnlyChild() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(child); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -590,22 +567,20 @@ public void UpdateChildWithNewParentCommitWithReadOnlyChild() child.Name = "new child name"; VersionedNode parent = new VersionedNode("parent", "parent"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Update(child); s.SetReadOnly(child, true); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(child.Name, Is.EqualTo("child")); @@ -618,7 +593,7 @@ public void UpdateChildWithNewParentCommitWithReadOnlyChild() s.SetReadOnly(child, true); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -629,11 +604,11 @@ public void UpdateChildWithNewParentCommitWithReadOnlyChild() public void MergeDetachedChildWithNewParentCommitWithReadOnlyChild() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(child); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -641,22 +616,20 @@ public void MergeDetachedChildWithNewParentCommitWithReadOnlyChild() child.Name = "new child name"; VersionedNode parent = new VersionedNode("parent", "parent"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); child = (VersionedNode) s.Merge(child); s.SetReadOnly(child, true); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); // NH-specific: Hibernate issues a separate UPDATE for the version number AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(child.Name, Is.EqualTo("child")); @@ -669,7 +642,7 @@ public void MergeDetachedChildWithNewParentCommitWithReadOnlyChild() s.SetReadOnly(child, true); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -680,11 +653,11 @@ public void MergeDetachedChildWithNewParentCommitWithReadOnlyChild() public void GetChildMakeReadOnlyThenMergeDetachedChildWithNewParent() { VersionedNode child = new VersionedNode("child", "child"); - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); s.Persist(child); - s.Transaction.Commit(); + t.Commit(); } ClearCounts(); @@ -692,24 +665,22 @@ public void GetChildMakeReadOnlyThenMergeDetachedChildWithNewParent() child.Name = "new child name"; VersionedNode parent = new VersionedNode("parent", "parent"); parent.AddChild(child); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); VersionedNode childManaged = s.Get(child.Id); s.SetReadOnly(childManaged, true); VersionedNode childMerged = (VersionedNode) s.Merge(child); Assert.That(childManaged, Is.SameAs(childMerged)); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); // NH-specific: Hibernate issues a separate UPDATE for the version number AssertInsertCount(1); ClearCounts(); - - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); parent = s.Get(parent.Id); child = s.Get(child.Id); Assert.That(child.Name, Is.EqualTo("child")); @@ -723,7 +694,7 @@ public void GetChildMakeReadOnlyThenMergeDetachedChildWithNewParent() s.SetReadOnly(child, true); s.Delete(parent); s.Delete(child); - s.Transaction.Commit(); + t.Commit(); } AssertUpdateCount(0); @@ -732,14 +703,13 @@ public void GetChildMakeReadOnlyThenMergeDetachedChildWithNewParent() protected override void OnTearDown() { - using (ISession s = OpenSession()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.BeginTransaction(); - s.CreateQuery("delete from VersionedNode where parent is not null").ExecuteUpdate(); s.CreateQuery("delete from VersionedNode").ExecuteUpdate(); - s.Transaction.Commit(); + t.Commit(); } base.OnTearDown(); diff --git a/src/NHibernate.Test/ReadOnly/TextHolder.cs b/src/NHibernate.Test/ReadOnly/TextHolder.cs index f0561a59d5d..94c7f66590f 100644 --- a/src/NHibernate.Test/ReadOnly/TextHolder.cs +++ b/src/NHibernate.Test/ReadOnly/TextHolder.cs @@ -10,10 +10,9 @@ public class TextHolder /// public static bool SupportedForDialect(Dialect.Dialect dialect) { - return !(dialect is FirebirdDialect || dialect is Oracle8iDialect || dialect is MsSqlCeDialect || dialect is HanaRowStoreDialect); + return !(dialect is FirebirdDialect || dialect is Oracle8iDialect || dialect is MsSqlCeDialect || dialect is HanaRowStoreDialect || dialect is DB2Dialect); } - private long id; private string theText; diff --git a/src/NHibernate.Test/ReflectionOptimizerTest/LcgFixture.cs b/src/NHibernate.Test/ReflectionOptimizerTest/LcgFixture.cs index e248515cfc3..06ece20f721 100644 --- a/src/NHibernate.Test/ReflectionOptimizerTest/LcgFixture.cs +++ b/src/NHibernate.Test/ReflectionOptimizerTest/LcgFixture.cs @@ -94,6 +94,5 @@ public void TestGetterTypeMismatch() Assert.AreEqual("str", values[0]); } - } } diff --git a/src/NHibernate.Test/SecondLevelCacheTest/AnotherItem.cs b/src/NHibernate.Test/SecondLevelCacheTest/AnotherItem.cs index d91e0e3df59..5442c9599b4 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/AnotherItem.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/AnotherItem.cs @@ -8,7 +8,6 @@ public class AnotherItem public AnotherItem() { - } public AnotherItem(string name) @@ -34,4 +33,4 @@ public virtual string Description set { description = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index 47b6d76d2bd..3acc648ac64 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -92,7 +92,6 @@ public void InvalidatesEntities() tx.Commit(); } - //Update Item using LINQ using (var tx = session.BeginTransaction()) { diff --git a/src/NHibernate.Test/Settings.StyleCop b/src/NHibernate.Test/Settings.StyleCop new file mode 100644 index 00000000000..12bfa8fb295 --- /dev/null +++ b/src/NHibernate.Test/Settings.StyleCop @@ -0,0 +1,14 @@ + + + + + + + False + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/SqlCommandTest/SqlDeleteBuilderFixture.cs b/src/NHibernate.Test/SqlCommandTest/SqlDeleteBuilderFixture.cs index 746345cbdc8..b3c69b13db5 100644 --- a/src/NHibernate.Test/SqlCommandTest/SqlDeleteBuilderFixture.cs +++ b/src/NHibernate.Test/SqlCommandTest/SqlDeleteBuilderFixture.cs @@ -25,7 +25,6 @@ public void DeleteSqlStringTest() delete.SetTableName("test_delete_builder"); - delete.SetIdentityColumn(new string[] {"decimalColumn"}, NHibernateUtil.Decimal); delete.SetVersionColumn(new string[] {"versionColumn"}, (IVersionType) NHibernateUtil.Int32); @@ -43,4 +42,4 @@ public void DeleteSqlStringTest() Assert.AreEqual(SqlTypeFactory.Int32, actualParameterTypes[1], "secondParam Type"); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/SqlCommandTest/SqlSelectBuilderFixture.cs b/src/NHibernate.Test/SqlCommandTest/SqlSelectBuilderFixture.cs index 901b2cea983..e15e947780c 100644 --- a/src/NHibernate.Test/SqlCommandTest/SqlSelectBuilderFixture.cs +++ b/src/NHibernate.Test/SqlCommandTest/SqlSelectBuilderFixture.cs @@ -44,7 +44,6 @@ public void SelectStringSqlTest() .Append("ORDER BY column1 DESC") .ToString(); - Assert.AreEqual(expectedSql, sqlString.ToString(), "SQL String"); Assert.AreEqual(1, sqlString.GetParameterCount(), "One parameter"); } diff --git a/src/NHibernate.Test/SqlCommandTest/SqlStringFixture.cs b/src/NHibernate.Test/SqlCommandTest/SqlStringFixture.cs index 1fa2aa19df0..361b2b1525e 100644 --- a/src/NHibernate.Test/SqlCommandTest/SqlStringFixture.cs +++ b/src/NHibernate.Test/SqlCommandTest/SqlStringFixture.cs @@ -13,7 +13,6 @@ namespace NHibernate.Test.SqlCommandTest [TestFixture] public class SqlStringFixture { - //[Test] //public void StringPerf() //{ @@ -35,7 +34,6 @@ public class SqlStringFixture // } // Console.WriteLine("Substring average per 10000 iters (ms): " + allSub.Average()); - // double[] allTrim = new double[5]; // for (int a = 0; a < 5; ++a) // { @@ -50,7 +48,6 @@ public class SqlStringFixture // Console.WriteLine("Trim average per 10000 iters (ms): " + allTrim.Average()); //} - [Test] public void Append() { @@ -343,7 +340,6 @@ public void Parse() Assert.AreEqual(SqlString.Empty, SqlString.Parse("")); } - [Test] public void GetSubselectStringSimple() { @@ -441,7 +437,6 @@ public void ParameterPropertyShouldReturnNewInstances() Assert.That(Parameter.Placeholder, Is.Not.SameAs(Parameter.Placeholder)); } - [Test] public void HashcodeEqualForEqualStringsWithDifferentHistory() { diff --git a/src/NHibernate.Test/SqlTest/Custom/CustomSQLSupportTest.cs b/src/NHibernate.Test/SqlTest/Custom/CustomSQLSupportTest.cs index 62dc09ddb02..8ee760763c3 100644 --- a/src/NHibernate.Test/SqlTest/Custom/CustomSQLSupportTest.cs +++ b/src/NHibernate.Test/SqlTest/Custom/CustomSQLSupportTest.cs @@ -81,6 +81,5 @@ public void HandSQL() t.Commit(); s.Close(); } - } } diff --git a/src/NHibernate.Test/SqlTest/Custom/CustomStoredProcSupportTest.cs b/src/NHibernate.Test/SqlTest/Custom/CustomStoredProcSupportTest.cs index 0ab1cc0c2f9..73bb920d571 100644 --- a/src/NHibernate.Test/SqlTest/Custom/CustomStoredProcSupportTest.cs +++ b/src/NHibernate.Test/SqlTest/Custom/CustomStoredProcSupportTest.cs @@ -69,6 +69,5 @@ public void EntityStoredProcedure() t.Commit(); s.Close(); } - } } diff --git a/src/NHibernate.Test/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs b/src/NHibernate.Test/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs index 5adedf4a81c..70fcd1d47e3 100644 --- a/src/NHibernate.Test/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs +++ b/src/NHibernate.Test/SqlTest/Custom/Oracle/OracleCustomSQLFixture.cs @@ -55,6 +55,5 @@ public void RefCursorOutStoredProcedure() t.Commit(); s.Close(); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs index 87c646e8fde..934e3641f03 100644 --- a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -536,7 +536,6 @@ public void AutoDetectAliasing() AssertClassAssignability(o[0].GetType(), typeof(long)); AssertClassAssignability(o[1].GetType(), typeof(Employment)); - IQuery queryWithCollection = s.GetNamedQuery("organizationEmploymentsExplicitAliases"); queryWithCollection.SetInt64("id", jboss.Id); list = queryWithCollection.List(); @@ -784,7 +783,7 @@ public void CanExecuteFutureValue() public void HandlesManualSynchronization() { using (var s = OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { s.SessionFactory.Statistics.IsStatisticsEnabled = true; s.SessionFactory.Statistics.Clear(); @@ -803,7 +802,7 @@ public void HandlesManualSynchronization() // clean up s.Delete(jboss); - s.Transaction.Commit(); + t.Commit(); s.Close(); } } diff --git a/src/NHibernate.Test/Stateless/Fetching/Resource.cs b/src/NHibernate.Test/Stateless/Fetching/Resource.cs index c54f168849b..662042a4e93 100644 --- a/src/NHibernate.Test/Stateless/Fetching/Resource.cs +++ b/src/NHibernate.Test/Stateless/Fetching/Resource.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.Stateless.Fetching { public class Resource diff --git a/src/NHibernate.Test/Stateless/Fetching/StatelessSessionFetchingTest.cs b/src/NHibernate.Test/Stateless/Fetching/StatelessSessionFetchingTest.cs index b0f7c91a4d9..5ca6ddd6ff3 100644 --- a/src/NHibernate.Test/Stateless/Fetching/StatelessSessionFetchingTest.cs +++ b/src/NHibernate.Test/Stateless/Fetching/StatelessSessionFetchingTest.cs @@ -71,5 +71,4 @@ private void cleanup() } } } - } diff --git a/src/NHibernate.Test/Stateless/Fetching/Task.cs b/src/NHibernate.Test/Stateless/Fetching/Task.cs index 8d7b06f8f6d..698bae0dd43 100644 --- a/src/NHibernate.Test/Stateless/Fetching/Task.cs +++ b/src/NHibernate.Test/Stateless/Fetching/Task.cs @@ -1,6 +1,5 @@ using System; - namespace NHibernate.Test.Stateless.Fetching { public class Task @@ -45,7 +44,6 @@ public virtual long? Id } } - public virtual User User { get @@ -59,7 +57,6 @@ public virtual User User } } - public virtual Resource Resource { get @@ -73,7 +70,6 @@ public virtual Resource Resource } } - public virtual string Description { get @@ -87,7 +83,6 @@ public virtual string Description } } - public virtual DateTime DueDate { get @@ -101,7 +96,6 @@ public virtual DateTime DueDate } } - public virtual DateTime? StartDate { get @@ -115,7 +109,6 @@ public virtual DateTime? StartDate } } - public virtual DateTime? CompletionDate { get @@ -128,7 +121,5 @@ public virtual DateTime? CompletionDate this.completionDate = value; } } - } - } diff --git a/src/NHibernate.Test/Stateless/Fetching/User.cs b/src/NHibernate.Test/Stateless/Fetching/User.cs index 6d4a961ec54..78c6472336e 100644 --- a/src/NHibernate.Test/Stateless/Fetching/User.cs +++ b/src/NHibernate.Test/Stateless/Fetching/User.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Test.Stateless.Fetching { public class User @@ -40,6 +39,5 @@ public virtual string Name this.name = value; } } - } } diff --git a/src/NHibernate.Test/Stateless/StatelessWithRelationsFixture.cs b/src/NHibernate.Test/Stateless/StatelessWithRelationsFixture.cs index 7f181c6d029..331d01efd2f 100644 --- a/src/NHibernate.Test/Stateless/StatelessWithRelationsFixture.cs +++ b/src/NHibernate.Test/Stateless/StatelessWithRelationsFixture.cs @@ -86,6 +86,5 @@ public void ShouldWorkLoadingComplexEntities() tx.Commit(); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Stateless/TreeNode.cs b/src/NHibernate.Test/Stateless/TreeNode.cs index ca8614036cc..5afbfdd7b22 100644 --- a/src/NHibernate.Test/Stateless/TreeNode.cs +++ b/src/NHibernate.Test/Stateless/TreeNode.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; - namespace NHibernate.Test.Stateless { public class TreeNode diff --git a/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/BaseInterfaces.cs b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/BaseInterfaces.cs new file mode 100644 index 00000000000..7beade98061 --- /dev/null +++ b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/BaseInterfaces.cs @@ -0,0 +1,16 @@ +using System; + +namespace NHibernate.Test.StaticProxyTest.InterfaceHandling +{ + public interface IEntity + { + Guid Id { get; set; } + + string Name { get; set; } + } + + public interface IEntity2 + { + Guid Id { get; set; } + } +} diff --git a/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/Entities.cs b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/Entities.cs new file mode 100644 index 00000000000..37976a51382 --- /dev/null +++ b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/Entities.cs @@ -0,0 +1,82 @@ +using System; + +namespace NHibernate.Test.StaticProxyTest.InterfaceHandling +{ + public class EntitySimple : IEntity + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + } + + public class EntityMultiInterfaces : IEntity, IEntity2 + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + } + + public class EntityExplicitInterface : IEntity + { + private Guid id; + private string name; + + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + Guid IEntity.Id + { + get => id; + set => id = value; + } + + string IEntity.Name + { + get => name; + set => name = value; + } + } + + //Proxy contains IEntity.Id and IEntity2.Id + public interface IMultiIdProxy : IEntity, IEntity2 + { + } + + public class EntityMultiIdProxy : IMultiIdProxy + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + public class EntityMixExplicitImplicitInterface : IEntity, IEntity2 + { + private Guid id; + private string name; + + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + Guid IEntity.Id + { + get => id; + set => id = value; + } + + string IEntity.Name + { + get => name; + set => name = value; + } + } + + public class EntityWithSuperClassInterfaceLookup + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + public virtual IEntity EntityLookup { get; set; } + } +} diff --git a/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/Fixture.cs b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/Fixture.cs new file mode 100644 index 00000000000..b2e9e36becb --- /dev/null +++ b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/Fixture.cs @@ -0,0 +1,196 @@ +using System; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.StaticProxyTest.InterfaceHandling +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + private readonly Guid _id = Guid.NewGuid(); + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + #region Subclass hierarchy + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.UnionSubclass( + rc => + { + rc.Proxy(typeof(ISubEntityProxy)); + + rc.Property(x => x.AnotherName); + }); + + mapper.UnionSubclass( + rc => + { + rc.Proxy(typeof(IAnotherEntityProxy)); + + rc.Property(x => x.AnotherName); + }); + + mapper.Class( + rc => + { + rc.Table("ClassWithInterfaceLookup"); + rc.Id(x => x.Id); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityClassProxy))); + }); + + #endregion Subclass hierarchy + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Table("multiInterface"); + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class( + rc => + { + rc.Proxy(typeof(IMultiIdProxy)); + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public void ProxyForBaseSubclassCanBeCreated() + { + using (var session = OpenSession()) + { + var entity = session.Load(_id); + } + } + + //Id access via implicit interface should not lead to proxy initialization + [Test] + public void ProxyClassIdAccessByImplicitInterface() + { + using (var session = OpenSession()) + { + var entity = (IEntity) session.Load(_id); + CanAccessIEntityId(entity); + ThrowOnIEntityNameAccess(entity); + Assert.That(entity.Id, Is.EqualTo(_id)); + + var multiInterface = session.Load(_id); + CanAccessIEntityId(multiInterface); + CanAccessIEntity2Id(multiInterface); + Assert.That(multiInterface.Id, Is.EqualTo(_id)); + } + } + + [Test] + public void ProxyClassIdAccessExplicitInterface() + { + using (var session = OpenSession()) + { + var entity = session.Load(_id); + + ThrowOnIEntityIdAccess(entity); + Assert.That(entity.Id, Is.EqualTo(_id)); + } + } + + [Test] + public void ProxyClassIdAccessBothImplicitExplicitInterfaces() + { + using (var session = OpenSession()) + { + var entity = session.Load(_id); + + //IEntity2 is implicit and should be accessible without proxy initialization + CanAccessIEntity2Id(entity); + ThrowOnIEntityIdAccess(entity); + } + } + + [Test] + public void ProxyInterfaceIdAccess() + { + using (var session = OpenSession()) + { + var entity = session.Load(_id); + + CanAccessIEntityId(entity); + } + } + + [KnownBug("GH-2271")] + [Test] + public void ProxyInterfaceIdAccessFromDifferentInterfaces() + { + using (var session = OpenSession()) + { + var entity = session.Load(_id); + + CanAccessIEntityId(entity); + CanAccessIEntity2Id(entity); + } + } + + private void ThrowOnIEntityNameAccess(IEntity entity) + { + Assert.That(() => entity.Name, Throws.TypeOf(), "IEntity.Name access should lead to proxy initialization"); + } + + private void ThrowOnIEntityIdAccess(IEntity entity) + { + Assert.That(() => entity.Id, Throws.TypeOf(), "IEntity.Id access should lead to proxy initialization"); + } + + private void ThrowOnIEntity2IdAccess(IEntity2 entity) + { + Assert.That(() => entity.Id, Throws.TypeOf(), "IEntityId.Id access should lead to proxy initialization"); + } + + private void CanAccessIEntityId(IEntity entity) + { + Assert.That(() => entity.Id, Throws.Nothing, "Failed to access proxy IEntity.Id interface"); + Assert.That(entity.Id, Is.EqualTo(_id)); + } + + private void CanAccessIEntity2Id(IEntity2 entity) + { + Assert.That(() => entity.Id, Throws.Nothing, "Failed to access proxy IEntity2.Id interface"); + Assert.That(entity.Id, Is.EqualTo(_id)); + } + } +} diff --git a/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/SubclassEntities.cs b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/SubclassEntities.cs new file mode 100644 index 00000000000..5a5c54b9015 --- /dev/null +++ b/src/NHibernate.Test/StaticProxyTest/InterfaceHandling/SubclassEntities.cs @@ -0,0 +1,31 @@ +using System; + +namespace NHibernate.Test.StaticProxyTest.InterfaceHandling +{ + public interface ISubEntityProxy : IEntity + { + string AnotherName { get; set; } + } + + public class EntityClassProxy : IEntity + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + } + + class SubEntityInterfaceProxy : EntityClassProxy, ISubEntityProxy + { + public virtual string AnotherName { get; set; } + } + + public interface IAnotherEntityProxy : IEntity + { + string AnotherName { get; set; } + } + + class AnotherSubEntityInterfaceProxy : EntityClassProxy, IAnotherEntityProxy + { + public virtual string AnotherName { get; set; } + } +} diff --git a/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs b/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs index 1977b3f4049..dd7cd6931aa 100644 --- a/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs +++ b/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs @@ -24,6 +24,12 @@ internal interface IInternal { int Id { get; } } + + public interface IWithEqualsAndGetHashCode + { + bool Equals(object that); + int GetHashCode(); + } [Serializable] public class InternalInterfaceTestClass : IInternal @@ -31,6 +37,13 @@ public class InternalInterfaceTestClass : IInternal public virtual int Id { get; set; } } + [Serializable] + internal class InternalTestClass + { + int Id { get; set; } + string Name { get; set; } + } + public interface IPublic { int Id { get; set; } @@ -70,6 +83,30 @@ public PublicExplicitInterfaceTestClass() } } + [Serializable] + public class PublicExplicitInterfaceWithSameMembersTestClass : IPublic + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + int IPublic.Id { get; set; } + string IPublic.Name { get; set; } + + public PublicExplicitInterfaceWithSameMembersTestClass() + { + // Check access to properties from the default constructor do not fail once proxified + Id = -1; + Assert.That(Id, Is.EqualTo(-1)); + Name = "Unknown"; + Assert.That(Name, Is.EqualTo("Unknown")); + IPublic pub = this; + pub.Id = -2; + Assert.That(pub.Id, Is.EqualTo(-2)); + pub.Name = "Unknown2"; + Assert.That(pub.Name, Is.EqualTo("Unknown2")); + } + } + [Serializable] public abstract class AbstractTestClass : IPublic { @@ -218,6 +255,39 @@ public void VerifyProxyForClassWithAdditionalInterface() #endif } + [Test] + public void VerifyProxyForInterfaceWithEqualsAndGetHashCode() + { + var factory = new StaticProxyFactory(); + factory.PostInstantiate( + typeof(IWithEqualsAndGetHashCode).FullName, + typeof(object), + new HashSet {typeof(IWithEqualsAndGetHashCode), typeof(INHibernateProxy)}, + null, null, null, false); + +#if NETFX + VerifyGeneratedAssembly( + () => + { +#endif + var proxy = factory.GetProxy(1, null); + Assert.That(proxy, Is.Not.Null); + Assert.That(proxy, Is.InstanceOf()); + var proxyType = proxy.GetType(); + var proxyMap = proxyType.GetInterfaceMap(typeof(IWithEqualsAndGetHashCode)); + Assert.That( + proxyMap.TargetMethods, + Has.One.Property("Name").EqualTo("Equals").And.Property("IsPublic").EqualTo(true), + "Equals is not implicitly implemented"); + Assert.That( + proxyMap.TargetMethods, + Has.One.Property("Name").EqualTo("GetHashCode").And.Property("IsPublic").EqualTo(true), + "GetHashCode is not implicitly implemented"); +#if NETFX + }); +#endif + } + [Test] public void VerifyProxyForClassWithInterface() { @@ -237,12 +307,30 @@ public void VerifyProxyForClassWithInterface() Assert.That(proxy, Is.Not.Null); Assert.That(proxy, Is.InstanceOf()); Assert.That(proxy, Is.InstanceOf()); + var proxyType = proxy.GetType(); + var proxyMap = proxyType.GetInterfaceMap(typeof(IPublic)); + Assert.That( + proxyMap.TargetMethods, + Has.One.EqualTo(proxyType.GetProperty(nameof(PublicInterfaceTestClass.Name)).GetMethod), + "Name getter does not implement IPublic"); + Assert.That( + proxyMap.TargetMethods, + Has.One.EqualTo(proxyType.GetProperty(nameof(PublicInterfaceTestClass.Name)).SetMethod), + "Name setter does not implement IPublic"); + Assert.That( + proxyMap.TargetMethods, + Has.One.EqualTo(proxyType.GetProperty(nameof(PublicInterfaceTestClass.Id)).GetMethod), + "Id setter does not implement IPublic"); + Assert.That( + proxyMap.TargetMethods, + Has.One.EqualTo(proxyType.GetProperty(nameof(PublicInterfaceTestClass.Id)).SetMethod), + "Id setter does not implement IPublic"); // Check interface and implicit implementations do both call the delegated state var state = new PublicInterfaceTestClass { Id = 5, Name = "State" }; proxy.HibernateLazyInitializer.SetImplementation(state); - var pub = (IPublic) proxy; var ent = (PublicInterfaceTestClass) proxy; + IPublic pub = ent; Assert.That(pub.Id, Is.EqualTo(5), "IPublic.Id"); Assert.That(ent.Id, Is.EqualTo(5), "entity.Id"); Assert.That(pub.Name, Is.EqualTo("State"), "IPublic.Name"); @@ -276,8 +364,13 @@ public void VerifyProxyForClassWithExplicitInterface() Assert.That(proxy, Is.Not.Null); Assert.That(proxy, Is.InstanceOf()); Assert.That(proxy, Is.InstanceOf()); - - // Check interface and implicit implementations do both call the delegated state + var proxyType = proxy.GetType(); + Assert.That(proxyType.GetMethod($"get_{nameof(IPublic.Name)}"), Is.Null, "get Name is implicitly implemented"); + Assert.That(proxyType.GetMethod($"set_{nameof(IPublic.Name)}"), Is.Null, "set Name is implicitly implemented"); + Assert.That(proxyType.GetMethod($"get_{nameof(IPublic.Id)}"), Is.Null, "get Id is implicitly implemented"); + Assert.That(proxyType.GetMethod($"set_{nameof(IPublic.Id)}"), Is.Null, "set Id is implicitly implemented"); + + // Check explicit implementation IPublic state = new PublicExplicitInterfaceTestClass(); state.Id = 5; state.Name = "State"; @@ -285,7 +378,7 @@ public void VerifyProxyForClassWithExplicitInterface() var entity = (IPublic) proxy; Assert.That(entity.Id, Is.EqualTo(5), "Id"); Assert.That(entity.Name, Is.EqualTo("State"), "Name"); - + entity.Id = 10; entity.Name = "Test"; Assert.That(entity.Id, Is.EqualTo(10), "entity.Id"); @@ -297,6 +390,75 @@ public void VerifyProxyForClassWithExplicitInterface() #endif } + [Test] + public void VerifyProxyForClassWithExplicitInterfaceWithSameMembers() + { + var factory = new StaticProxyFactory(); + factory.PostInstantiate( + typeof(PublicExplicitInterfaceWithSameMembersTestClass).FullName, + typeof(PublicExplicitInterfaceWithSameMembersTestClass), + new HashSet {typeof(INHibernateProxy)}, + null, null, null, true); +#if NETFX + VerifyGeneratedAssembly( + () => + { +#endif + var proxy = factory.GetProxy(1, null); + Assert.That(proxy, Is.Not.Null); + Assert.That(proxy, Is.InstanceOf()); + Assert.That(proxy, Is.InstanceOf()); + var proxyType = proxy.GetType(); + var proxyMap = proxyType.GetInterfaceMap(typeof(IPublic)); + Assert.That( + proxyMap.TargetMethods, + Has.None.EqualTo(proxyType.GetProperty(nameof(PublicExplicitInterfaceWithSameMembersTestClass.Name)).GetMethod), + "class Name getter does implement IPublic"); + Assert.That( + proxyMap.TargetMethods, + Has.None.EqualTo(proxyType.GetProperty(nameof(PublicExplicitInterfaceWithSameMembersTestClass.Name)).SetMethod), + "class Name setter does implement IPublic"); + Assert.That( + proxyMap.TargetMethods, + Has.None.EqualTo(proxyType.GetProperty(nameof(PublicExplicitInterfaceWithSameMembersTestClass.Id)).GetMethod), + "class Id setter does implement IPublic"); + Assert.That( + proxyMap.TargetMethods, + Has.None.EqualTo(proxyType.GetProperty(nameof(PublicExplicitInterfaceWithSameMembersTestClass.Id)).SetMethod), + "class Id setter does implement IPublic"); + + // Check interface and implicit implementations do both call the delegated state + var state = new PublicExplicitInterfaceWithSameMembersTestClass(); + IPublic pubState = state; + state.Id = 5; + state.Name = "State"; + pubState.Id = 10; + pubState.Name = "State2"; + proxy.HibernateLazyInitializer.SetImplementation(state); + var entity = (PublicExplicitInterfaceWithSameMembersTestClass) proxy; + IPublic pubEntity = entity; + Assert.That(entity.Id, Is.EqualTo(5), "Id member"); + Assert.That(entity.Name, Is.EqualTo("State"), "Name member"); + Assert.That(pubEntity.Id, Is.EqualTo(10), "Id from interface"); + Assert.That(pubEntity.Name, Is.EqualTo("State2"), "Name from interface"); + + entity.Id = 15; + entity.Name = "Test"; + pubEntity.Id = 20; + pubEntity.Name = "Test2"; + Assert.That(entity.Id, Is.EqualTo(15), "entity.Id"); + Assert.That(state.Id, Is.EqualTo(15), "state.Id"); + Assert.That(entity.Name, Is.EqualTo("Test"), "entity.Name"); + Assert.That(state.Name, Is.EqualTo("Test"), "state.Name"); + Assert.That(pubEntity.Id, Is.EqualTo(20), "pubEntity.Id"); + Assert.That(pubState.Id, Is.EqualTo(20), "pubState.Id"); + Assert.That(pubEntity.Name, Is.EqualTo("Test2"), "pubEntity.Name"); + Assert.That(pubState.Name, Is.EqualTo("Test2"), "pubState.Name"); +#if NETFX + }); +#endif + } + [Test] public void VerifyProxyForRefOutClass() { @@ -362,6 +524,32 @@ public void VerifyProxyForAbstractClass() Assert.That(proxy, Is.Not.Null); Assert.That(proxy, Is.InstanceOf()); Assert.That(proxy, Is.InstanceOf()); +#if NETFX + }); +#endif + } + + [Test] + public void VerifyProxyForInternalClass() + { + var factory = new StaticProxyFactory(); + factory.PostInstantiate( + typeof(InternalTestClass).FullName, + typeof(InternalTestClass), + new HashSet { typeof(INHibernateProxy) }, + null, null, null, true); + +#if NETFX + VerifyGeneratedAssembly( + () => + { +#endif + var proxy = factory.GetProxy(1, null); + Assert.That(proxy, Is.Not.Null); + Assert.That(proxy, Is.InstanceOf()); + + Assert.That(factory.GetFieldInterceptionProxy(), Is.InstanceOf()); + #if NETFX }); #endif diff --git a/src/NHibernate.Test/SystemTransactions/AutoJoinSettingFixture.cs b/src/NHibernate.Test/SystemTransactions/AutoJoinSettingFixture.cs new file mode 100644 index 00000000000..6a5af82e42a --- /dev/null +++ b/src/NHibernate.Test/SystemTransactions/AutoJoinSettingFixture.cs @@ -0,0 +1,55 @@ +using System.Transactions; +using NHibernate.Cfg; +using NUnit.Framework; + +namespace NHibernate.Test.SystemTransactions +{ + [TestFixture(true)] + [TestFixture(false)] + [TestFixture(new object[] { null })] + public class AutoJoinSettingFixture : TestCase + { + private readonly bool? _autoJoinTransaction; + + public AutoJoinSettingFixture(bool? autoJoinTransaction) + { + _autoJoinTransaction = autoJoinTransaction; + } + + protected override string[] Mappings => new[] { "TransactionTest.Person.hbm.xml" }; + + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override void Configure(Configuration configuration) + { + if (_autoJoinTransaction.HasValue) + configuration.SetProperty(Environment.AutoJoinTransaction, _autoJoinTransaction.ToString()); + else + configuration.Properties.Remove(Environment.AutoJoinTransaction); + } + + [Test] + public void CheckTransactionJoined() + { + using (new TransactionScope()) + using (var s = OpenSession()) + { + Assert.That( + s.GetSessionImplementation().TransactionContext, + _autoJoinTransaction == false ? Is.Null : Is.Not.Null); + } + } + + [Theory] + public void CanOverrideAutoJoin(bool autoJoin) + { + using (new TransactionScope()) + using (var s = Sfi.WithOptions().AutoJoinTransaction(autoJoin).OpenSession()) + { + Assert.That( + s.GetSessionImplementation().TransactionContext, + autoJoin ? Is.Not.Null : Is.Null); + } + } + } +} diff --git a/src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs b/src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs index 78e23702e7b..b7d931e0809 100644 --- a/src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs @@ -3,10 +3,8 @@ using System.Threading; using System.Transactions; using log4net; -using log4net.Repository.Hierarchy; using NHibernate.Cfg; using NHibernate.Engine; -using NHibernate.Linq; using NHibernate.Test.TransactionTest; using NUnit.Framework; @@ -711,6 +709,117 @@ public void AdditionalJoinDoesNotThrow() } } + [Theory] + public void CanUseDependentTransaction(bool explicitFlush) + { + if (!TestDialect.SupportsDependentTransaction) + Assert.Ignore("Dialect does not support dependent transactions"); + IgnoreIfUnsupported(explicitFlush); + + try + { + using (var committable = new CommittableTransaction()) + { + System.Transactions.Transaction.Current = committable; + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + + using (var s = OpenSession()) + { + if (!AutoJoinTransaction) + s.JoinTransaction(); + s.Save(new Person()); + + if (explicitFlush) + s.Flush(); + clone.Complete(); + } + } + + System.Transactions.Transaction.Current = committable; + committable.Commit(); + } + } + finally + { + System.Transactions.Transaction.Current = null; + } + } + + [Theory] + public void CanUseSessionWithManyDependentTransaction(bool explicitFlush) + { + if (!TestDialect.SupportsDependentTransaction) + Assert.Ignore("Dialect does not support dependent transactions"); + IgnoreIfUnsupported(explicitFlush); + + try + { + using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession()) + { + using (var committable = new CommittableTransaction()) + { + System.Transactions.Transaction.Current = committable; + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + if (!AutoJoinTransaction) + s.JoinTransaction(); + // Acquire the connection + var count = s.Query().Count(); + Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count."); + clone.Complete(); + } + + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + if (!AutoJoinTransaction) + s.JoinTransaction(); + s.Save(new Person()); + + if (explicitFlush) + s.Flush(); + + clone.Complete(); + } + + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + if (!AutoJoinTransaction) + s.JoinTransaction(); + var count = s.Query().Count(); + Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert."); + clone.Complete(); + } + + System.Transactions.Transaction.Current = committable; + committable.Commit(); + } + } + } + finally + { + System.Transactions.Transaction.Current = null; + } + + DodgeTransactionCompletionDelayIfRequired(); + + using (var s = OpenSession()) + { + using (var tx = new TransactionScope()) + { + if (!AutoJoinTransaction) + s.JoinTransaction(); + var count = s.Query().Count(); + Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit."); + tx.Complete(); + } + } + } + private void DodgeTransactionCompletionDelayIfRequired() { if (Sfi.ConnectionProvider.Driver.HasDelayedDistributedTransactionCompletion) diff --git a/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs b/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs index 0550fc5c08a..3e278f8d8a8 100644 --- a/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/ResourceManagerFixture.cs @@ -582,7 +582,6 @@ public void NonDistributedRollback([Values(false, true)] bool fromConnection, [V } else _log.Info("Scope not completed for triggering rollback"); - } } catch (TransactionAbortedException) diff --git a/src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs b/src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs index b68bbd5d86c..e5fb6fd46c0 100644 --- a/src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Transactions; using NHibernate.Cfg; using NHibernate.Driver; using NHibernate.Engine; -using NHibernate.Linq; using NHibernate.Test.TransactionTest; using NUnit.Framework; @@ -522,6 +522,164 @@ public void AdditionalJoinDoesNotThrow() Assert.DoesNotThrow(() => s.JoinTransaction()); } } + + [Theory] + public void CanUseDependentTransaction(bool explicitFlush) + { + if (!TestDialect.SupportsDependentTransaction) + Assert.Ignore("Dialect does not support dependent transactions"); + IgnoreIfUnsupported(explicitFlush); + + try + { + using (var committable = new CommittableTransaction()) + { + System.Transactions.Transaction.Current = committable; + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + + using (var s = OpenSession()) + { + if (!AutoJoinTransaction) + s.JoinTransaction(); + s.Save(new Person()); + + if (explicitFlush) + s.Flush(); + clone.Complete(); + } + } + + System.Transactions.Transaction.Current = committable; + committable.Commit(); + } + } + finally + { + System.Transactions.Transaction.Current = null; + } + } + + [Theory] + public void CanUseSessionWithManyDependentTransaction(bool explicitFlush) + { + if (!TestDialect.SupportsDependentTransaction) + Assert.Ignore("Dialect does not support dependent transactions"); + IgnoreIfUnsupported(explicitFlush); + // ODBC with SQL-Server always causes system transactions to go distributed, which causes their transaction completion to run + // asynchronously. But ODBC enlistment also check the previous transaction in a way that do not guard against it + // being concurrently disposed of. See https://github.com/nhibernate/nhibernate-core/pull/1505 for more details. + if (Sfi.ConnectionProvider.Driver is OdbcDriver) + Assert.Ignore("ODBC sometimes fails on second scope by checking the previous transaction status, which may yield an object disposed exception"); + // SAP HANA & SQL Anywhere .Net providers always cause system transactions to be distributed, causing them to + // complete on concurrent threads. This creates race conditions when chaining scopes, the subsequent scope usage + // finding the connection still enlisted in the previous transaction, its complete being still not finished + // on its own thread. + if (Sfi.ConnectionProvider.Driver is HanaDriverBase || Sfi.ConnectionProvider.Driver is SapSQLAnywhere17Driver) + Assert.Ignore("SAP HANA and SQL Anywhere scope handling causes concurrency issues preventing chaining scope usages."); + + try + { + using (var s = WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession()) + { + using (var committable = new CommittableTransaction()) + { + System.Transactions.Transaction.Current = committable; + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + if (!AutoJoinTransaction) + s.JoinTransaction(); + // Acquire the connection + var count = s.Query().Count(); + Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count."); + clone.Complete(); + } + + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + if (!AutoJoinTransaction) + s.JoinTransaction(); + s.Save(new Person()); + + if (explicitFlush) + s.Flush(); + + clone.Complete(); + } + + using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete)) + { + System.Transactions.Transaction.Current = clone; + if (!AutoJoinTransaction) + s.JoinTransaction(); + var count = s.Query().Count(); + Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert."); + clone.Complete(); + } + + System.Transactions.Transaction.Current = committable; + committable.Commit(); + } + } + } + finally + { + System.Transactions.Transaction.Current = null; + } + + using (var s = OpenSession()) + { + using (var tx = new TransactionScope()) + { + if (!AutoJoinTransaction) + s.JoinTransaction(); + var count = s.Query().Count(); + Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit."); + tx.Complete(); + } + } + } + + [Theory, Explicit("Bench")] + public void BenchTransactionAccess(bool inTransaction) + { + var currentTransaction = System.Transactions.Transaction.Current; + using (inTransaction ? new TransactionScope() : null) + using (var s = OpenSession()) + { + var impl = s.GetSessionImplementation(); + var transactionContext = impl.TransactionContext; + if (inTransaction) + s.JoinTransaction(); + + // warm-up + for (var i = 0; i < 10; i++) + currentTransaction = System.Transactions.Transaction.Current; + for (var i = 0; i < 10; i++) + transactionContext = impl.TransactionContext; + + var sw = new Stopwatch(); + for (var j = 0; j < 4; j++) + { + sw.Restart(); + for (var i = 0; i < 10000; i++) + currentTransaction = System.Transactions.Transaction.Current; + sw.Stop(); + Assert.That(currentTransaction, inTransaction ? Is.Not.Null : Is.Null); + + Console.WriteLine($"Current transaction reads have taken {sw.Elapsed}"); + sw.Restart(); + for (var i = 0; i < 10000; i++) + transactionContext = impl.TransactionContext; + sw.Stop(); + Assert.That(transactionContext, inTransaction ? Is.Not.Null : Is.Null); + Console.WriteLine($"Transaction context reads have taken {sw.Elapsed}"); + } + } + } } [TestFixture] diff --git a/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs b/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs index 5ac3276bb0f..2033ef061ab 100644 --- a/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/TransactionNotificationFixture.cs @@ -1,15 +1,33 @@ -using System; -using System.Collections; using System.Transactions; using NHibernate.Cfg; +using NHibernate.Mapping.ByCode; using NUnit.Framework; namespace NHibernate.Test.SystemTransactions { public class TransactionNotificationFixture : TestCase { - protected override string[] Mappings - => Array.Empty(); + public class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } + + protected override string[] Mappings => null; + + protected override void AddMappings(Configuration configuration) + { + var modelMapper = new ModelMapper(); + modelMapper.Class( + x => + { + x.Id(e => e.Id); + x.Property(e => e.Name); + x.Table(nameof(Entity)); + }); + + configuration.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities()); + } protected virtual bool UseConnectionOnSystemTransactionPrepare => true; @@ -70,7 +88,6 @@ public void Complete() session.Dispose(); Assert.AreEqual(1, interceptor.beforeTransactionCompletionCalled); Assert.AreEqual(1, interceptor.afterTransactionCompletionCalled); - } [Test] @@ -88,6 +105,8 @@ public void Rollback() [Test] public void TwoTransactionScopesInsideOneSession() { + IgnoreIfTransactionScopeInsideSessionIsNotSupported(); + var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) { @@ -111,6 +130,8 @@ public void TwoTransactionScopesInsideOneSession() [Test] public void OneTransactionScopesInsideOneSession() { + IgnoreIfTransactionScopeInsideSessionIsNotSupported(); + var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) { @@ -125,7 +146,6 @@ public void OneTransactionScopesInsideOneSession() Assert.AreEqual(1, interceptor.afterTransactionCompletionCalled); } - [Description("NH2128, NH3572")] [Theory] public void ShouldNotifyAfterDistributedTransaction(bool doCommit) @@ -137,7 +157,7 @@ public void ShouldNotifyAfterDistributedTransaction(bool doCommit) ISession s1 = null; ISession s2 = null; - using (var tx = new TransactionScope()) + using (var tx = new TransactionScope(TransactionScopeOption.Suppress)) { try { @@ -202,6 +222,12 @@ public void ShouldNotifyAfterDistributedTransactionWithOwnConnection(bool doComm Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); } + + private void IgnoreIfTransactionScopeInsideSessionIsNotSupported() + { + if (!Sfi.ConnectionProvider.Driver.SupportsSystemTransactions || !TestDialect.SupportsDependentTransaction) + Assert.Ignore("Driver does not support dependent transactions. Ignoring test."); + } } [TestFixture] diff --git a/src/NHibernate.Test/TestCase.cs b/src/NHibernate.Test/TestCase.cs index 4c9ea610c6d..e3f4124bd0a 100644 --- a/src/NHibernate.Test/TestCase.cs +++ b/src/NHibernate.Test/TestCase.cs @@ -2,9 +2,10 @@ using System.Collections; using System.Collections.Generic; using System.Data; +using System.Data.Common; +using System.Linq; using System.Reflection; using log4net; -using log4net.Config; using NHibernate.Cfg; using NHibernate.Connection; using NHibernate.Engine; @@ -16,6 +17,9 @@ using System.Text; using NHibernate.Dialect; using NHibernate.Driver; +using NHibernate.Engine.Query; +using NHibernate.Util; +using NSubstitute; namespace NHibernate.Test { @@ -27,6 +31,15 @@ public abstract class TestCase private SchemaExport _schemaExport; private static readonly ILog log = LogManager.GetLogger(typeof(TestCase)); + private static readonly FieldInfo PlanCacheField; + + static TestCase() + { + PlanCacheField = typeof(QueryPlanCache) + .GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new InvalidOperationException( + "planCache field does not exist in QueryPlanCache."); + } protected Dialect.Dialect Dialect { @@ -53,12 +66,6 @@ protected virtual string MappingsAssembly protected SchemaExport SchemaExport => _schemaExport ?? (_schemaExport = new SchemaExport(cfg)); - static TestCase() - { - // Configure log4net here since configuration through an attribute doesn't always work. - XmlConfigurator.Configure(LogManager.GetRepository(typeof(TestCase).Assembly)); - } - /// /// Creates the tables used in this TestCase /// @@ -224,15 +231,22 @@ private string GetCombinedFailureMessage(TestContext.ResultAdapter result, strin protected virtual bool CheckDatabaseWasCleaned() { - if (Sfi.GetAllClassMetadata().Count == 0) + var allClassMetadata = Sfi.GetAllClassMetadata(); + if (allClassMetadata.Count == 0) { // Return early in the case of no mappings, also avoiding // a warning when executing the HQL below. return true; } + var explicitPolymorphismEntities = allClassMetadata.Values.Where(x => x is NHibernate.Persister.Entity.IQueryable queryable && queryable.IsExplicitPolymorphism).ToArray(); + + //TODO: Maybe add explicit load query checks + if (explicitPolymorphismEntities.Length == allClassMetadata.Count) + return true; + bool empty; - using (ISession s = Sfi.OpenSession()) + using (ISession s = OpenSession()) { IList objects = s.CreateQuery("from System.Object o").List(); empty = objects.Count == 0; @@ -286,15 +300,16 @@ protected virtual void AddMappings(Configuration configuration) protected virtual void CreateSchema() { - SchemaExport.Create(OutputDdl, true); + using (var optionalConnection = OpenConnectionForSchemaExport()) + SchemaExport.Create(OutputDdl, true, optionalConnection); } protected virtual void DropSchema() { - DropSchema(OutputDdl, SchemaExport, Sfi); + DropSchema(OutputDdl, SchemaExport, Sfi, OpenConnectionForSchemaExport); } - public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi) + public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFactoryImplementor sfi, Func getConnection = null) { if (sfi?.ConnectionProvider.Driver is FirebirdClientDriver fbDriver) { @@ -307,7 +322,16 @@ public static void DropSchema(bool useStdOut, SchemaExport export, ISessionFacto fbDriver.ClearPool(null); } - export.Drop(useStdOut, true); + using(var optionalConnection = getConnection?.Invoke()) + export.Drop(useStdOut, true, optionalConnection); + } + + /// + /// Specific connection is required only for Database multi-tenancy. In other cases can be null + /// + protected virtual DbConnection OpenConnectionForSchemaExport() + { + return null; } protected virtual DebugSessionFactory BuildSessionFactory() @@ -444,7 +468,6 @@ protected DateTime RoundForDialect(DateTime value) private static readonly Dictionary> DialectsNotSupportingStandardFunction = new Dictionary> { - {"locate", new HashSet {typeof (SQLiteDialect)}}, {"bit_length", new HashSet {typeof (SQLiteDialect)}}, {"extract", new HashSet {typeof (SQLiteDialect)}}, { @@ -457,24 +480,103 @@ protected DateTime RoundForDialect(DateTime value) }} }; + protected bool IsFunctionSupported(string functionName) + { + // We could test Sfi.SQLFunctionRegistry.HasFunction(functionName) which has the advantage of + // accounting for additional functions added in configuration. But Dialect is normally never + // null, while Sfi could be not yet initialized, depending from where this function is called. + // Furthermore there are currently no additional functions added in configuration for NHibernate + // tests. + var dialect = Dialect; + if (!dialect.Functions.ContainsKey(functionName)) + return false; + + return !DialectsNotSupportingStandardFunction.TryGetValue(functionName, out var dialects) || + !dialects.Contains(dialect.GetType()); + } + protected void AssumeFunctionSupported(string functionName) { // We could test Sfi.SQLFunctionRegistry.HasFunction(functionName) which has the advantage of - // accounting for additionnal functions added in configuration. But Dialect is normally never + // accounting for additional functions added in configuration. But Dialect is normally never // null, while Sfi could be not yet initialized, depending from where this function is called. - // Furtermore there are currently no additionnal functions added in configuration for NHibernate + // Furthermore there are currently no additional functions added in configuration for NHibernate // tests. + var dialect = Dialect; Assume.That( - Dialect.Functions, + dialect.Functions, Does.ContainKey(functionName), - $"{Dialect} doesn't support {functionName} function."); + $"{dialect} doesn't support {functionName} function."); if (!DialectsNotSupportingStandardFunction.TryGetValue(functionName, out var dialects)) return; Assume.That( dialects, - Does.Not.Contain(Dialect.GetType()), - $"{Dialect} doesn't support {functionName} standard function."); + Does.Not.Contain(dialect.GetType()), + $"{dialect} doesn't support {functionName} standard function."); + } + + protected SoftLimitMRUCache GetQueryPlanCache() + { + return (SoftLimitMRUCache) PlanCacheField.GetValue(Sfi.QueryPlanCache); + } + + protected void ClearQueryPlanCache() + { + GetQueryPlanCache().Clear(); + } + + protected Substitute SubstituteDialect() + { + var origDialect = Sfi.Settings.Dialect; + var dialectProperty = (PropertyInfo) ReflectHelper.GetProperty(o => o.Dialect); + var forPartsOfMethod = ReflectHelper.GetMethodDefinition(() => Substitute.ForPartsOf()); + var substitute = (Dialect.Dialect) forPartsOfMethod.MakeGenericMethod(origDialect.GetType()) + .Invoke(null, new object[] { new object[0] }); + + dialectProperty.SetValue(Sfi.Settings, substitute); + + return new Substitute(substitute, Dispose); + + void Dispose() + { + dialectProperty.SetValue(Sfi.Settings, origDialect); + } + } + + protected static int GetTotalOccurrences(string content, string substring) + { + if (string.IsNullOrEmpty(substring)) + { + throw new ArgumentNullException(nameof(substring)); + } + + int occurrences = 0, index = 0; + while ((index = content.IndexOf(substring, index)) >= 0) + { + occurrences++; + index += substring.Length; + } + + return occurrences; + } + + protected struct Substitute : IDisposable + { + private readonly System.Action _disposeAction; + + public Substitute(TType value, System.Action disposeAction) + { + Value = value; + _disposeAction = disposeAction; + } + + public TType Value { get; } + + public void Dispose() + { + _disposeAction(); + } } #endregion diff --git a/src/NHibernate.Test/TestDialect.cs b/src/NHibernate.Test/TestDialect.cs index b03e21196ea..de15f8565ef 100644 --- a/src/NHibernate.Test/TestDialect.cs +++ b/src/NHibernate.Test/TestDialect.cs @@ -44,7 +44,6 @@ public bool NativeGeneratorSupportsBulkInsertion public virtual bool SupportsOperatorAll => true; public virtual bool SupportsOperatorSome => true; - public virtual bool SupportsLocate => true; public virtual bool SupportsFullJoin => true; @@ -82,6 +81,8 @@ public bool NativeGeneratorSupportsBulkInsertion public virtual bool SupportsDuplicatedColumnAliases => true; + public virtual bool SupportsAggregateInSubSelect => false; + /// /// Supports inserting in a table without any column specified in the insert. /// @@ -96,7 +97,6 @@ public bool NativeGeneratorSupportsBulkInsertion public bool SupportsEmptyInsertsOrHasNonIdentityNativeGenerator => SupportsEmptyInserts || !HasIdentityNativeGenerator; - /// /// Supports condition not bound to any data, like "where @p1 = @p2". /// @@ -166,6 +166,14 @@ public bool SupportsSqlType(SqlType sqlType) /// public virtual bool SupportsUsingConnectionOnSystemTransactionPrepare => true; + /// + /// Some databases fail with dependent transaction, typically when their driver tries to access the transaction + /// state from its two PC: the dependent transaction is meant to be disposed of before completing the actual + /// transaction, so it is usually disposed at this point, and its state cannot be read. (Drivers should always + /// clone transactions for avoiding this trouble.) + /// + public virtual bool SupportsDependentTransaction => true; + /// /// Some databases (provider?) fails to compute adequate column types for queries which columns /// computing include a parameter value. @@ -176,5 +184,16 @@ public bool SupportsSqlType(SqlType sqlType) /// their type in the query. /// public virtual bool HasBrokenTypeInferenceOnSelectedParameters => false; + + /// + /// Note: Dialect.SupportsRawValueConstructorSyntax is currently disabled for all Dialects (even for ones that support this feature). + /// This flag is added to be able to test this feature selectively + /// + public virtual bool SupportsRowValueConstructorSyntax => _dialect.SupportsRowValueConstructorSyntax; + + /// + /// Returns true if you can modify the same table which you use in the SELECT part. + /// + public virtual bool SupportsModifyAndSelectSameTable => true; } } diff --git a/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs b/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs new file mode 100644 index 00000000000..0ec57f43bc6 --- /dev/null +++ b/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs @@ -0,0 +1,18 @@ +namespace NHibernate.Test.TestDialects +{ + public class MySQL5TestDialect : TestDialect + { + public MySQL5TestDialect(Dialect.Dialect dialect) + : base(dialect) + { + } + + public override bool SupportsAggregateInSubSelect => true; + + /// + /// In MySQL, you can't modify the same table which you use in the SELECT part. + /// This behaviour is documented at: http://dev.mysql.com/doc/refman/5.6/en/update.html + /// + public override bool SupportsModifyAndSelectSameTable => false; + } +} diff --git a/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs b/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs index ef117344223..dcedb6b60ac 100644 --- a/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs +++ b/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs @@ -10,5 +10,7 @@ public Oracle10gTestDialect(Dialect.Dialect dialect) : base(dialect) /// Does not support SELECT FOR UPDATE with paging /// public override bool SupportsSelectForUpdateWithPaging => false; + + public override bool SupportsAggregateInSubSelect => true; } } diff --git a/src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs b/src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs index 78f038ad3e6..ba9a3cc6ff6 100644 --- a/src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs +++ b/src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs @@ -16,5 +16,16 @@ public PostgreSQL83TestDialect(Dialect.Dialect dialect) /// Npgsql 3.2.4.1. /// public override bool SupportsUsingConnectionOnSystemTransactionPrepare => false; + + /// + /// Npgsql does not clone the transaction in its context, and uses it in its prepare phase. When that was a + /// dependent transaction, it is then usually already disposed of, causing Npgsql to crash. + /// + public override bool SupportsDependentTransaction => false; + + public override bool SupportsAggregateInSubSelect => true; + + /// + public override bool SupportsRowValueConstructorSyntax => true; } } diff --git a/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs b/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs index 934425ed12f..b0be2bd35e5 100644 --- a/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs +++ b/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs @@ -22,11 +22,6 @@ public override bool SupportsOperatorSome get { return false; } } - public override bool SupportsLocate - { - get { return false; } - } - public override bool SupportsFullJoin { get { return false; } @@ -51,5 +46,7 @@ public override bool SupportsHavingWithoutGroupBy /// Does not support update locks /// public override bool SupportsSelectForUpdate => false; + + public override bool SupportsAggregateInSubSelect => true; } } diff --git a/src/NHibernate.Test/TestsContext.cs b/src/NHibernate.Test/TestsContext.cs index 41254185afe..e80cca726aa 100644 --- a/src/NHibernate.Test/TestsContext.cs +++ b/src/NHibernate.Test/TestsContext.cs @@ -1,63 +1,34 @@ using NUnit.Framework; - -#if NETCOREAPP2_0 using System.Configuration; -using System.IO; -using log4net.Repository.Hierarchy; +using System.Reflection; +using log4net; +using log4net.Config; using NHibernate.Cfg; -using NHibernate.Cfg.ConfigurationSchema; -#endif namespace NHibernate.Test { -#if NETCOREAPP2_0 [SetUpFixture] -#endif public class TestsContext { - public static bool ExecutingWithVsTest { get; } = - System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name == "testhost"; + private static readonly Assembly TestAssembly = typeof(TestsContext).Assembly; -#if NETCOREAPP2_0 [OneTimeSetUp] public void RunBeforeAnyTests() { + ConfigureLog4Net(); + //When .NET Core App 2.0 tests run from VS/VSTest the entry assembly is "testhost.dll" //so we need to explicitly load the configuration - if (ExecutingWithVsTest) + if (Assembly.GetEntryAssembly() != null) { - Environment.InitializeGlobalProperties(GetTestAssemblyHibernateConfiguration()); + ConfigurationProvider.Current = new SystemConfigurationProvider(ConfigurationManager.OpenExeConfiguration(TestAssembly.Location)); } - - ConfigureLog4Net(); - } - - public static IHibernateConfiguration GetTestAssemblyHibernateConfiguration() - { - var assemblyPath = - Path.Combine(TestContext.CurrentContext.TestDirectory, Path.GetFileName(typeof(TestsContext).Assembly.Location)); - var configuration = ConfigurationManager.OpenExeConfiguration(assemblyPath); - var section = configuration.GetSection(CfgXmlHelper.CfgSectionName); - return HibernateConfiguration.FromAppConfig(section.SectionInformation.GetRawXml()); } private static void ConfigureLog4Net() { - var hierarchy = (Hierarchy)log4net.LogManager.GetRepository(typeof(TestsContext).Assembly); - - var consoleAppender = new log4net.Appender.ConsoleAppender - { - Layout = new log4net.Layout.PatternLayout("%d{ABSOLUTE} %-5p %c{1}:%L - %m%n"), - }; - - ((Logger)hierarchy.GetLogger("NHibernate.Hql.Ast.ANTLR")).Level = log4net.Core.Level.Off; - ((Logger)hierarchy.GetLogger("NHibernate.SQL")).Level = log4net.Core.Level.Off; - ((Logger)hierarchy.GetLogger("NHibernate.AdoNet.AbstractBatcher")).Level = log4net.Core.Level.Off; - ((Logger)hierarchy.GetLogger("NHibernate.Tool.hbm2ddl.SchemaExport")).Level = log4net.Core.Level.Error; - hierarchy.Root.Level = log4net.Core.Level.Warn; - hierarchy.Root.AddAppender(consoleAppender); - hierarchy.Configured = true; + using (var log4NetXml = TestAssembly.GetManifestResourceStream("NHibernate.Test.log4net.xml")) + XmlConfigurator.Configure(LogManager.GetRepository(TestAssembly), log4NetXml); } -#endif } } diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.hbm.xml b/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.hbm.xml index 631c22b0094..bdaaca451e8 100644 --- a/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.hbm.xml +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.hbm.xml @@ -4,16 +4,27 @@ assembly="NHibernate.Test" default-lazy="false"> - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.cs b/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/Order.cs similarity index 79% rename from src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.cs rename to src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/Order.cs index 57057031360..5c589558853 100644 --- a/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/HeavyEntity.cs +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/Order.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace NHibernate.Test.Tools.hbm2ddl.SchemaMetadataUpdaterTest { public class Order @@ -8,5 +10,6 @@ public class Order public string Column { get; set; } public string Name { get; set; } public string Abracadabra { get; set; } + public ISet Rows { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/OrderRow.cs b/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/OrderRow.cs new file mode 100644 index 00000000000..ffc4b651579 --- /dev/null +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaMetadataUpdaterTest/OrderRow.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.Tools.hbm2ddl.SchemaMetadataUpdaterTest +{ + public class OrderRow + { + public int Id { get; set; } + public Order Order { get; set; } + public string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/3_Person.hbm.xml b/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/3_Person.hbm.xml new file mode 100644 index 00000000000..3da0b2345bc --- /dev/null +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/3_Person.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs b/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs index 7ccd282a9cb..129a32eaa0a 100644 --- a/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs @@ -3,6 +3,8 @@ using System.Reflection; using NHibernate.Cfg; using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Exceptions; using NHibernate.Tool.hbm2ddl; using NHibernate.Util; using NUnit.Framework; @@ -13,18 +15,36 @@ namespace NHibernate.Test.Tools.hbm2ddl.SchemaUpdate [TestFixture] public class MigrationFixture { - private void MigrateSchema(string resource1, string resource2) + private Configuration _configurationToDrop; + private FirebirdClientDriver _fireBirdDriver; + + [OneTimeSetUp] + public void OneTimeSetup() { - Configuration v1cfg = TestConfigurationHelper.GetDefaultConfiguration(); - var driverClass = ReflectHelper.ClassForName(v1cfg.GetProperty(Environment.ConnectionDriver)); + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + var driverClass = ReflectHelper.ClassForName(cfg.GetProperty(Environment.ConnectionDriver)); // Odbc is not supported by schema update: System.Data.Odbc.OdbcConnection.GetSchema("ForeignKeys") fails with an ArgumentException: ForeignKeys is undefined. // It seems it would require its own DataBaseSchema, but this is bound to the dialect, not the driver. if (typeof(OdbcDriver).IsAssignableFrom(driverClass)) Assert.Ignore("Test is not compatible with ODBC"); - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource1)) - v1cfg.AddInputStream(stream); - new SchemaExport(v1cfg).Execute(false, true, true); + if (typeof(FirebirdClientDriver).IsAssignableFrom(driverClass)) + _fireBirdDriver = new FirebirdClientDriver(); + } + + [TearDown] + public void TearDown() + { + if (_configurationToDrop != null) + DropSchema(_configurationToDrop); + _configurationToDrop = null; + } + + private void MigrateSchema(string resource1, string resource2) + { + var v1cfg = GetConfigurationForMapping(resource1); + DropSchema(v1cfg); + _configurationToDrop = v1cfg; Tool.hbm2ddl.SchemaUpdate v1schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v1cfg); v1schemaUpdate.Execute(true, true); @@ -34,9 +54,7 @@ private void MigrateSchema(string resource1, string resource2) Assert.AreEqual(0, v1schemaUpdate.Exceptions.Count); - Configuration v2cfg = TestConfigurationHelper.GetDefaultConfiguration(); - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource2)) - v2cfg.AddInputStream(stream); + var v2cfg = GetConfigurationForMapping(resource2); Tool.hbm2ddl.SchemaUpdate v2schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v2cfg); v2schemaUpdate.Execute(true, true); @@ -64,5 +82,64 @@ public void SimpleColumnReplace() MigrateSchema(resource1, resource2); } + + [Test] + public void AutoUpdateFailuresAreThrown() + { + var cfg1 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.1_Person.hbm.xml"); + var sf1 = cfg1.BuildSessionFactory(); + var dialect = Dialect.Dialect.GetDialect(cfg1.Properties); + if (!dialect.SupportsUnique) + Assert.Ignore("This test requires a dialect supporting unique constraints"); + + _configurationToDrop = cfg1; + CreateSchema(cfg1); + + using (var s = sf1.OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new Person()); + s.Save(new Person()); + t.Commit(); + } + + // This schema switches to not-nullable the person properties, which should fail due to an existing person with null properties. + var cfg2 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.3_Person.hbm.xml"); + cfg2.Properties[Environment.Hbm2ddlAuto] = SchemaAutoAction.Update.ToString(); + cfg2.Properties[Environment.Hbm2ddlThrowOnUpdate] = "true"; + Assert.That(() => cfg2.BuildSessionFactory(), Throws.InstanceOf()); + } + + private Configuration GetConfigurationForMapping(string resourcePath) + { + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath)) + cfg.AddInputStream(stream); + return cfg; + } + + private void CreateSchema(Configuration cfg) + { + // Firebird will pool each connection created during the test and will marked as used any table + // referenced by queries. It will at best delays those tables drop until connections are actually + // closed, or immediately fail dropping them. + // This results in other tests failing when they try to create tables with the same name. + // By clearing the connection pool the tables will get dropped. + _fireBirdDriver?.ClearPool(null); + + new SchemaExport(cfg).Create(false, true); + } + + private void DropSchema(Configuration cfg) + { + // Firebird will pool each connection created during the test and will marked as used any table + // referenced by queries. It will at best delays those tables drop until connections are actually + // closed, or immediately fail dropping them. + // This results in other tests failing when they try to create tables with the same name. + // By clearing the connection pool the tables will get dropped. + _fireBirdDriver?.ClearPool(null); + + new SchemaExport(cfg).Drop(false, true); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/Version.cs b/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/Version.cs index 5c6bb6610ec..05c09712892 100644 --- a/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/Version.cs +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/Version.cs @@ -25,7 +25,6 @@ public virtual string Description set { description = value; } } - public virtual Version Previous { get { return previous; } diff --git a/src/NHibernate.Test/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs b/src/NHibernate.Test/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs index 57e056f0dab..ba8686986b4 100644 --- a/src/NHibernate.Test/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs +++ b/src/NHibernate.Test/Tools/hbm2ddl/SchemaValidator/SchemaValidateFixture.cs @@ -41,8 +41,7 @@ public void ShouldVerifySameTable() validator.Validate(); } -#if NETFX - [Test, SetCulture("tr-TR"), SetUICulture("tr-TR")] + [Test, SetCulture("tr-TR"), SetUICulture("tr-TR"), NetFxOnly] public void ShouldVerifySameTableTurkish() { //NH-3063 @@ -70,7 +69,6 @@ public void ShouldVerifySameTableTurkish() export.Drop(true, true); } } -#endif [Test] public void ShouldNotVerifyModifiedTable() diff --git a/src/NHibernate.Test/TransactionTest/TransactionFixture.cs b/src/NHibernate.Test/TransactionTest/TransactionFixture.cs index b06d8d76326..2131dcf46de 100644 --- a/src/NHibernate.Test/TransactionTest/TransactionFixture.cs +++ b/src/NHibernate.Test/TransactionTest/TransactionFixture.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.TransactionTest @@ -99,41 +97,30 @@ public void WasCommittedOrRolledBack() { using (ITransaction t = s.BeginTransaction()) { - Assert.AreSame(t, s.Transaction); - Assert.IsFalse(s.Transaction.WasCommitted); - Assert.IsFalse(s.Transaction.WasRolledBack); + Assert.AreSame(t, s.GetCurrentTransaction()); + Assert.IsFalse(s.GetCurrentTransaction().WasCommitted); + Assert.IsFalse(s.GetCurrentTransaction().WasRolledBack); t.Commit(); - // ISession.Transaction returns a new transaction - // if the previous one completed! - Assert.IsNotNull(s.Transaction); - Assert.IsFalse(t == s.Transaction); + // ISession.GetCurrentTransaction() returns null if the transaction is completed. + Assert.IsNull(s.GetCurrentTransaction()); Assert.IsTrue(t.WasCommitted); Assert.IsFalse(t.WasRolledBack); - Assert.IsFalse(s.Transaction.WasCommitted); - Assert.IsFalse(s.Transaction.WasRolledBack); Assert.IsFalse(t.IsActive); - Assert.IsFalse(s.Transaction.IsActive); } using (ITransaction t = s.BeginTransaction()) { t.Rollback(); - // ISession.Transaction returns a new transaction - // if the previous one completed! - Assert.IsNotNull(s.Transaction); - Assert.IsFalse(t == s.Transaction); + // ISession.GetCurrentTransaction() returns null if the transaction is completed. + Assert.IsNull(s.GetCurrentTransaction()); Assert.IsTrue(t.WasRolledBack); Assert.IsFalse(t.WasCommitted); - Assert.IsFalse(s.Transaction.WasCommitted); - Assert.IsFalse(s.Transaction.WasRolledBack); - Assert.IsFalse(t.IsActive); - Assert.IsFalse(s.Transaction.IsActive); } } } @@ -148,7 +135,8 @@ public void FlushFromTransactionAppliesToSharingSession() using (var s1 = builder.Interceptor(new TestInterceptor(1, flushOrder)).OpenSession()) using (var s2 = builder.Interceptor(new TestInterceptor(2, flushOrder)).OpenSession()) - using (var s3 = s1.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)).OpenSession()) + using (var s3 = s1.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)) + .OpenSession()) using (var t = s.BeginTransaction()) { var p1 = new Person(); @@ -237,9 +225,115 @@ public void WhenCommittingItemsWillAddThemTo2ndLevelCache() using (var s = Sfi.WithOptions().Connection(connection).OpenSession()) { CacheablePerson person = null; - Assert.DoesNotThrow(() => person = s.Load(id), "Failed loading entity from second level cache."); + Assert.DoesNotThrow( + () => person = s.Load(id), + "Failed loading entity from second level cache."); Assert.That(person.NotNullData, Is.EqualTo(notNullData)); } } + + [Test] + public void CanCommitFromSessionTransaction() + { + int id; + using (var s = OpenSession()) + { + Assert.That(s.GetCurrentTransaction(), Is.Null); + using (s.BeginTransaction()) + { + var person = new Person(); + s.Save(person); + id = person.Id; + + s.GetCurrentTransaction().Commit(); + } + Assert.That(s.GetCurrentTransaction(), Is.Null); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var person = s.Get(id); + Assert.That(person, Is.Not.Null); + t.Commit(); + } + } + + [Test] + public void CanRollbackFromSessionTransaction() + { + int id; + using (var s = OpenSession()) + { + Assert.That(s.GetCurrentTransaction(), Is.Null); + using (s.BeginTransaction()) + { + var person = new Person(); + s.Save(person); + id = person.Id; + + s.GetCurrentTransaction().Rollback(); + + // Need to check before leaving the current using, otherwise the rollback could be the result of the + // disposing. + using (var s2 = OpenSession()) + using (var t2 = s2.BeginTransaction()) + { + person = s2.Get(id); + Assert.That(person, Is.Null); + t2.Commit(); + } + } + Assert.That(s.GetCurrentTransaction(), Is.Null); + } + } + + [Test, Obsolete] + public void CanCommitFromSessionObsoleteTransaction() + { + int id; + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var person = new Person(); + s.Save(person); + id = person.Id; + + s.Transaction.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var person = s.Get(id); + Assert.That(person, Is.Not.Null); + t.Commit(); + } + } + + [Test, Obsolete] + public void CanRollbackFromSessionObsoleteTransaction() + { + int id; + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + var person = new Person(); + s.Save(person); + id = person.Id; + + s.Transaction.Rollback(); + + // Need to check before leaving the current using, otherwise the rollback could be the result of the + // disposing. + using (var s2 = OpenSession()) + using (var t2 = s2.BeginTransaction()) + { + person = s2.Get(id); + Assert.That(person, Is.Null); + t2.Commit(); + } + } + } } } diff --git a/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs b/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs index 02d3e5b7e98..350f48955b5 100644 --- a/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs @@ -1,6 +1,8 @@ using System; using System.Data; using System.Data.Common; +using NHibernate.Cfg; +using NHibernate.Mapping.ByCode; using NHibernate.Transaction; using NUnit.Framework; @@ -9,9 +11,29 @@ namespace NHibernate.Test.TransactionTest [TestFixture] public class TransactionNotificationFixture : TestCase { - protected override string[] Mappings => Array.Empty(); + public class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } - [Test] + protected override string[] Mappings => null; + + protected override void AddMappings(Configuration configuration) + { + var modelMapper = new ModelMapper(); + modelMapper.Class( + x => + { + x.Id(e => e.Id); + x.Property(e => e.Name); + x.Table(nameof(Entity)); + }); + + configuration.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities()); + } + + [Test, Obsolete] public void NoTransaction() { var interceptor = new RecordingInterceptor(); diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index c8ea773a7fc..c3e7f5b750a 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -130,25 +130,21 @@ protected override string MappingsAssembly protected override void OnSetUp() { using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { s.Save(new Simple { Name = "Name1" }); s.Save(new Simple { Name = "Name2" }); - s.Transaction.Commit(); + t.Commit(); } - } } protected override void OnTearDown() { using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - using (s.BeginTransaction()) - { s.Delete("from Simple"); - s.Transaction.Commit(); - } + t.Commit(); } } diff --git a/src/NHibernate.Test/TypedManyToOne/Address.cs b/src/NHibernate.Test/TypedManyToOne/Address.cs index 95fb87baccc..5cb79290600 100644 --- a/src/NHibernate.Test/TypedManyToOne/Address.cs +++ b/src/NHibernate.Test/TypedManyToOne/Address.cs @@ -5,7 +5,7 @@ namespace NHibernate.Test.TypedManyToOne [Serializable] public class Address { - public virtual AddressId AddressId {get; set;} + public virtual AddressId AddressId {get; set; } public virtual string Street { get; set; } public virtual string City { get; set; } public virtual string State { get; set; } diff --git a/src/NHibernate.Test/TypedManyToOne/Customer.cs b/src/NHibernate.Test/TypedManyToOne/Customer.cs index d4e6d3f63f6..67b0a402096 100644 --- a/src/NHibernate.Test/TypedManyToOne/Customer.cs +++ b/src/NHibernate.Test/TypedManyToOne/Customer.cs @@ -6,8 +6,8 @@ namespace NHibernate.Test.TypedManyToOne public class Customer { public virtual string CustomerId { get; set; } - public virtual string Name {get; set;} - public virtual Address BillingAddress {get; set;} - public virtual Address ShippingAddress {get; set;} + public virtual string Name {get; set; } + public virtual Address BillingAddress {get; set; } + public virtual Address ShippingAddress {get; set; } } } diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs index 1dca978091e..ec5640460a7 100644 --- a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs @@ -7,5 +7,10 @@ public class ChangeDefaultTypeClass public int Id { get; set; } public DateTime NormalDateTimeValue { get; set; } = DateTime.Today; + + public string StringTypeLengthInType25 { get; set; } + public string StringTypeExplicitLength20 { get; set; } + public decimal CurrencyTypePrecisionInType5And2 { get; set; } + public decimal CurrencyTypeExplicitPrecision6And3 { get; set; } } } diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml index f2cfaf24dec..129615cd2db 100644 --- a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml @@ -12,5 +12,9 @@ + + + + diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeWithLengthFixture.cs b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeWithLengthFixture.cs new file mode 100644 index 00000000000..e8fbd52f2db --- /dev/null +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeWithLengthFixture.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Engine; +using NHibernate.Impl; +using NHibernate.SqlTypes; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.TypesTest +{ + /// + /// TestFixtures for changing a default .Net type. + /// + [TestFixture] + public class ChangeDefaultTypeWithLengthFixture : TypeFixtureBase + { + public class CustomStringType : AbstractStringType + { + public CustomStringType() : base(new StringSqlType()) + { + } + + public CustomStringType(int length) : base(new StringSqlType(length)) + { + } + + public override string Name => "CustomStringType"; + } + + protected override string TypeName => "ChangeDefaultType"; + + private IType _originalDefaultStringType; + private IType _testDefaultStringType; + private static System.Type _replacedType = typeof(string); + + protected override void Configure(Configuration configuration) + { + _originalDefaultStringType = TypeFactory.GetDefaultTypeFor(_replacedType); + Assert.That(_originalDefaultStringType, Is.Not.Null); + _testDefaultStringType = new CustomStringType(); + + TypeFactory.RegisterType( + _replacedType, + _testDefaultStringType, + new[] {"string"}, + length => new CustomStringType(length)); + base.Configure(configuration); + } + + protected override void DropSchema() + { + base.DropSchema(); + TypeFactory.ClearCustomRegistrations(); + Assert.That(TypeFactory.GetDefaultTypeFor(_replacedType), Is.Not.EqualTo(_testDefaultStringType)); + } + + [Test] + public void DefaultType() + { + Assert.That(TypeFactory.GetDefaultTypeFor(_replacedType), Is.EqualTo(_testDefaultStringType)); + } + + [Test] + public void PropertyType() + { + var propertyType25 = Sfi.GetClassMetadata(typeof(ChangeDefaultTypeClass)) + .GetPropertyType(nameof(ChangeDefaultTypeClass.StringTypeLengthInType25)); + Assert.That( + propertyType25.GetType(), + Is.EqualTo(_testDefaultStringType.GetType())); + Assert.That(propertyType25.SqlTypes(Sfi)[0].Length, Is.EqualTo(25)); + + var propertyType20 = Sfi.GetClassMetadata(typeof(ChangeDefaultTypeClass)) + .GetPropertyType(nameof(ChangeDefaultTypeClass.StringTypeExplicitLength20)); + + Assert.That( + propertyType20.GetType(), + Is.EqualTo(_testDefaultStringType.GetType())); + Assert.That(propertyType20.SqlTypes(Sfi)[0].Length, Is.EqualTo(20)); + } + + [Test] + public void GuessType() + { + Assert.That(NHibernateUtil.GuessType(_replacedType), Is.EqualTo(_testDefaultStringType)); + } + + [Test] + public void ParameterType() + { + var namedParametersField = typeof(AbstractQueryImpl) + .GetField("namedParameters", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(namedParametersField, Is.Not.Null, "Missing internal field"); + + using (var s = OpenSession()) + { + // Query where the parameter type cannot be deducted from compared entity property + var q = s.CreateQuery($"from {nameof(ChangeDefaultTypeClass)} where :str1 = :str2 or :str1 = :str3") + .SetParameter("str1", "aaa") + .SetString("str2", "bbb") + .SetAnsiString("str3", "bbb"); + + var namedParameters = namedParametersField.GetValue(q) as Dictionary; + Assert.That(namedParameters, Is.Not.Null, "Unable to retrieve parameters internal field"); + Assert.That(namedParameters["str1"].Type, Is.EqualTo(_testDefaultStringType)); + Assert.That(namedParameters["str2"].Type, Is.EqualTo(NHibernateUtil.String)); + Assert.That(namedParameters["str3"].Type, Is.EqualTo(NHibernateUtil.AnsiString)); + } + } + } +} diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeWithPrecisionFixture.cs b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeWithPrecisionFixture.cs new file mode 100644 index 00000000000..2d803f5b4e5 --- /dev/null +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeWithPrecisionFixture.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; +using System.Data; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Engine; +using NHibernate.Impl; +using NHibernate.SqlTypes; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.TypesTest +{ + /// + /// TestFixtures for changing a default .Net type. + /// + [TestFixture] + public class ChangeDefaultTypeWithPrecisionFixture : TypeFixtureBase + { + public class CustomCurrencyType : DecimalType + { + public CustomCurrencyType() : base(SqlTypeFactory.Currency) + { + } + + public CustomCurrencyType(byte precision, byte scale) : base(new SqlType(DbType.Currency, precision, scale)) + { + } + + public override string Name => "CustomCurrencyType"; + } + + protected override string TypeName => "ChangeDefaultType"; + + private IType _originalDefaultType; + private IType _testDefaultType; + private static System.Type _replacedType = typeof(decimal); + + protected override void Configure(Configuration configuration) + { + _originalDefaultType = TypeFactory.GetDefaultTypeFor(_replacedType); + _testDefaultType = new CustomCurrencyType(); + Assert.That(_originalDefaultType, Is.Not.Null); + Assert.That(_originalDefaultType, Is.Not.EqualTo(_testDefaultType)); + + TypeFactory.RegisterType( + _replacedType, + _testDefaultType, + new[] {"currency"}, + (precision, scale) => new CustomCurrencyType(precision, scale)); + base.Configure(configuration); + } + + protected override void DropSchema() + { + base.DropSchema(); + TypeFactory.ClearCustomRegistrations(); + Assert.That(TypeFactory.GetDefaultTypeFor(_replacedType), Is.Not.EqualTo(_testDefaultType)); + } + + [Test] + public void DefaultType() + { + Assert.That(TypeFactory.GetDefaultTypeFor(_replacedType), Is.EqualTo(_testDefaultType)); + } + + [Test] + public void PropertyType() + { + var propertyType1 = Sfi.GetClassMetadata(typeof(ChangeDefaultTypeClass)) + .GetPropertyType(nameof(ChangeDefaultTypeClass.CurrencyTypeExplicitPrecision6And3)); + Assert.That( + propertyType1.GetType(), + Is.EqualTo(_testDefaultType.GetType())); + Assert.That(propertyType1.SqlTypes(Sfi)[0].Precision, Is.EqualTo(6)); + Assert.That(propertyType1.SqlTypes(Sfi)[0].Scale, Is.EqualTo(3)); + + var propertyType2 = Sfi.GetClassMetadata(typeof(ChangeDefaultTypeClass)) + .GetPropertyType(nameof(ChangeDefaultTypeClass.CurrencyTypePrecisionInType5And2)); + + Assert.That( + propertyType2.GetType(), + Is.EqualTo(_testDefaultType.GetType())); + Assert.That(propertyType2.SqlTypes(Sfi)[0].Precision, Is.EqualTo(5)); + Assert.That(propertyType2.SqlTypes(Sfi)[0].Scale, Is.EqualTo(2)); + } + + [Test] + public void GuessType() + { + Assert.That(NHibernateUtil.GuessType(_replacedType), Is.EqualTo(_testDefaultType)); + } + + [Test] + public void ParameterType() + { + var namedParametersField = typeof(AbstractQueryImpl) + .GetField("namedParameters", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(namedParametersField, Is.Not.Null, "Missing internal field"); + + using (var s = OpenSession()) + { + // Query where the parameter type cannot be deducted from compared entity property + var q = s.CreateQuery($"from {nameof(ChangeDefaultTypeClass)} where :str1 = :str2") + .SetParameter("str1", 1.22m) + .SetDecimal("str2", 1m); + + var namedParameters = namedParametersField.GetValue(q) as Dictionary; + Assert.That(namedParameters, Is.Not.Null, "Unable to retrieve parameters internal field"); + Assert.That(namedParameters["str1"].Type, Is.EqualTo(_testDefaultType)); + Assert.That(namedParameters["str2"].Type, Is.EqualTo(NHibernateUtil.Decimal)); + } + } + } +} diff --git a/src/NHibernate.Test/TypesTest/CurrencyTypeFixture.cs b/src/NHibernate.Test/TypesTest/CurrencyTypeFixture.cs index 8eacc215bda..fd4f81cd660 100644 --- a/src/NHibernate.Test/TypesTest/CurrencyTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/CurrencyTypeFixture.cs @@ -66,6 +66,5 @@ public void UnsavedValue() Assert.AreEqual(0m, mappedValue); Assert.IsTrue(type.IsEqual(mappedValue, 0m), "'0' in the mapping file should have been converted to a 0m"); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TypesTest/EnumCharClass.cs b/src/NHibernate.Test/TypesTest/EnumCharClass.cs index 2d3ee8e2a4e..e256c8cba15 100644 --- a/src/NHibernate.Test/TypesTest/EnumCharClass.cs +++ b/src/NHibernate.Test/TypesTest/EnumCharClass.cs @@ -28,7 +28,6 @@ public enum SampleCharEnum Dimmed = 'D' } - public class EnumCharFoo { private Int32 id; @@ -59,4 +58,4 @@ public virtual SampleCharEnum Type set { type = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TypesTest/EnumCharTypeFixture.cs b/src/NHibernate.Test/TypesTest/EnumCharTypeFixture.cs index b8cb3881564..1528b3b5931 100644 --- a/src/NHibernate.Test/TypesTest/EnumCharTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/EnumCharTypeFixture.cs @@ -14,7 +14,6 @@ protected override string TypeName protected override void OnSetUp() { - EnumCharClass basic = new EnumCharClass(); basic.Id = 1; basic.EnumValue = SampleCharEnum.Dimmed; @@ -143,4 +142,4 @@ public void CanBeUsedAsDiscriminator() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TypesTest/EnumStringTypeFixture.cs b/src/NHibernate.Test/TypesTest/EnumStringTypeFixture.cs index 3b5bbb32ddf..ca3a07797df 100644 --- a/src/NHibernate.Test/TypesTest/EnumStringTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/EnumStringTypeFixture.cs @@ -37,7 +37,6 @@ protected override void OnTearDown() s.Close(); } - [Test] public void ReadFromLoad() { @@ -88,4 +87,4 @@ public void ReadFromQuery() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TypesTest/GenericEnumStringTypeFixture.cs b/src/NHibernate.Test/TypesTest/GenericEnumStringTypeFixture.cs index 4e7586a86d8..ea64fc18a2c 100644 --- a/src/NHibernate.Test/TypesTest/GenericEnumStringTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/GenericEnumStringTypeFixture.cs @@ -39,7 +39,6 @@ protected override void OnTearDown() s.Close(); } - [Test] public void ReadFromLoad() { diff --git a/src/NHibernate.Test/TypesTest/PersistentEnumTypeFixture.cs b/src/NHibernate.Test/TypesTest/PersistentEnumTypeFixture.cs index f950e3326cf..b2a20b042be 100644 --- a/src/NHibernate.Test/TypesTest/PersistentEnumTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/PersistentEnumTypeFixture.cs @@ -37,7 +37,6 @@ protected override void OnSetUp() p = new PersistentEnumClass(1, A.One, B.Two); } - [Test] public void EqualsTrue() { @@ -146,4 +145,4 @@ public void TheNameShouldBeFullNameAndAssembly() Assert.That(enumType.Name, Is.EqualTo(typeof(EnumType).FullName + ", NHibernate")); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TypesTest/TypeFactoryFixture.cs b/src/NHibernate.Test/TypesTest/TypeFactoryFixture.cs index b7b870d3f43..3efb4bbf5b5 100644 --- a/src/NHibernate.Test/TypesTest/TypeFactoryFixture.cs +++ b/src/NHibernate.Test/TypesTest/TypeFactoryFixture.cs @@ -1,6 +1,5 @@ using System; using log4net; -using log4net.Repository.Hierarchy; using NHibernate.Type; using NUnit.Framework; @@ -12,11 +11,6 @@ namespace NHibernate.Test.TypesTest [TestFixture] public class TypeFactoryFixture { - public TypeFactoryFixture() - { - log4net.Config.XmlConfigurator.Configure(LogManager.GetRepository(typeof(TypeFactoryFixture).Assembly)); - } - private static readonly ILog log = LogManager.GetLogger(typeof(TypeFactoryFixture)); /// @@ -59,7 +53,6 @@ public void GetNullableGeneric() Assert.AreEqual( int64Type, TypeFactory.HeuristicType( reflectedType ), "using System.Type should return nh Int64Type" ); Assert.AreEqual( int64Type, TypeFactory.HeuristicType( reflectedType.AssemblyQualifiedName ), "using AQN should return nh Int64Type" ); Assert.AreEqual( int64Type, TypeFactory.HeuristicType( reflectedType.FullName ), "using FullName should return nh Int64Type" ); - } public class GenericPropertyClass diff --git a/src/NHibernate.Test/TypesTest/UriTypeFixture.cs b/src/NHibernate.Test/TypesTest/UriTypeFixture.cs index 2a7c740362f..92ebbe5b389 100644 --- a/src/NHibernate.Test/TypesTest/UriTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/UriTypeFixture.cs @@ -41,7 +41,6 @@ public void ReadWrite() } } - [Test(Description = "NH-2887")] public void ReadWriteRelativeUri() { @@ -98,6 +97,5 @@ public void AutoDiscoverFromNetType() var propertyType = Sfi.GetEntityPersister(typeof(UriClass).FullName).GetPropertyType("AutoUri"); Assert.That(propertyType, Is.InstanceOf()); } - } } diff --git a/src/NHibernate.Test/Unconstrained/UnconstrainedNoLazyTest.cs b/src/NHibernate.Test/Unconstrained/UnconstrainedNoLazyTest.cs index fd96f041b8b..b974a61af8e 100644 --- a/src/NHibernate.Test/Unconstrained/UnconstrainedNoLazyTest.cs +++ b/src/NHibernate.Test/Unconstrained/UnconstrainedNoLazyTest.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections; - using log4net; using NHibernate.Criterion; using NUnit.Framework; @@ -135,30 +132,39 @@ public void Unconstrained() [Test] public void ManyToOneUpdateFalse() { - ISession session = OpenSession(); - ITransaction tx = session.BeginTransaction(); - Person p = new Person("gavin"); - p.EmployeeId = "123456"; - p.Unrelated = 10; - session.Save(p); - tx.Commit(); - - session.BeginTransaction(); - p.Employee = new Employee("456123"); - p.Unrelated = 235; // Force update of the object - session.Save(p.Employee); - session.Transaction.Commit(); - session.Close(); - - session = OpenSession(); - session.BeginTransaction(); - p = (Person) session.Load(typeof (Person), "gavin"); - // Should be null, not Employee#456123 - Assert.IsNull(p.Employee); - session.Delete(p); - session.Delete("from Employee"); - session.Transaction.Commit(); - session.Close(); + using (var session = OpenSession()) + { + Person p = new Person("gavin"); + using (var tx = session.BeginTransaction()) + { + p.EmployeeId = "123456"; + p.Unrelated = 10; + session.Save(p); + tx.Commit(); + } + + using (var tx = session.BeginTransaction()) + { + p.Employee = new Employee("456123"); + p.Unrelated = 235; // Force update of the object + session.Save(p.Employee); + tx.Commit(); + } + + session.Close(); + } + + using (var session = OpenSession()) + using (var tx = session.BeginTransaction()) + { + var p = (Person) session.Load(typeof(Person), "gavin"); + // Should be null, not Employee#456123 + Assert.IsNull(p.Employee); + session.Delete(p); + session.Delete("from Employee"); + tx.Commit(); + session.Close(); + } } } } diff --git a/src/NHibernate.Test/Unionsubclass/Being.cs b/src/NHibernate.Test/Unionsubclass/Being.cs index 2c52d611cc4..7e0115c1ff6 100644 --- a/src/NHibernate.Test/Unionsubclass/Being.cs +++ b/src/NHibernate.Test/Unionsubclass/Being.cs @@ -45,6 +45,5 @@ public virtual string Species get { return null; } set { throw new System.NotSupportedException(); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Unionsubclass2/Person.cs b/src/NHibernate.Test/Unionsubclass2/Person.cs index 4db67a177df..7c8f343c5b3 100644 --- a/src/NHibernate.Test/Unionsubclass2/Person.cs +++ b/src/NHibernate.Test/Unionsubclass2/Person.cs @@ -29,6 +29,5 @@ public virtual Address Address { get { return address; } } - } } diff --git a/src/NHibernate.Test/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs b/src/NHibernate.Test/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs index b1c9ede3ca1..d730dbb8f39 100644 --- a/src/NHibernate.Test/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs +++ b/src/NHibernate.Test/UnionsubclassPolymorphicFormula/UnionSubclassFixture.cs @@ -37,7 +37,6 @@ public void QueryOverPersonTest() s.Delete(result); t.Commit(); } - } } @@ -58,7 +57,6 @@ public void QueryOverCompanyTest() var result = s.QueryOver().Where(p => p.Name == "Limited").SingleOrDefault(); Assert.NotNull(result); } - } } } diff --git a/src/NHibernate.Test/UserCollection/Parameterized/IDefaultableList.cs b/src/NHibernate.Test/UserCollection/Parameterized/IDefaultableList.cs index ead6c69a985..846d4043467 100644 --- a/src/NHibernate.Test/UserCollection/Parameterized/IDefaultableList.cs +++ b/src/NHibernate.Test/UserCollection/Parameterized/IDefaultableList.cs @@ -4,6 +4,6 @@ namespace NHibernate.Test.UserCollection.Parameterized { public interface IDefaultableList : IList { - string DefaultValue { get;} + string DefaultValue { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UserCollection/UserCollectionTypeTest.cs b/src/NHibernate.Test/UserCollection/UserCollectionTypeTest.cs index 8066ee6b1d4..f2fb3950fc2 100644 --- a/src/NHibernate.Test/UserCollection/UserCollectionTypeTest.cs +++ b/src/NHibernate.Test/UserCollection/UserCollectionTypeTest.cs @@ -17,7 +17,6 @@ protected override string[] Mappings get { return new string[] {"UserCollection.UserPermissions.hbm.xml"}; } } - [Test] public void BasicOperation() { @@ -40,4 +39,4 @@ public void BasicOperation() s.Close(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UtilityTest/AsyncReaderWriterLockFixture.cs b/src/NHibernate.Test/UtilityTest/AsyncReaderWriterLockFixture.cs new file mode 100644 index 00000000000..b737b044def --- /dev/null +++ b/src/NHibernate.Test/UtilityTest/AsyncReaderWriterLockFixture.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Util; +using NUnit.Framework; + +namespace NHibernate.Test.UtilityTest +{ + public partial class AsyncReaderWriterLockFixture + { + [Test] + public void TestBlocking() + { + var l = new AsyncReaderWriterLock(); + for (var i = 0; i < 2; i++) + { + var readReleaser = l.ReadLock(); + Assert.That(l.CurrentReaders, Is.EqualTo(1)); + + var readReleaserTask = Task.Run(() => l.ReadLock()); + AssertEqualValue(() => l.CurrentReaders, 2, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + var writeReleaserTask = Task.Run(() => l.WriteLock()); + AssertEqualValue(() => l.AcquiredWriteLock, true, writeReleaserTask); + AssertEqualValue(() => l.Writing, false, writeReleaserTask); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + + readReleaser.Dispose(); + Assert.That(l.CurrentReaders, Is.EqualTo(1)); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + + readReleaserTask.Result.Dispose(); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + AssertEqualValue(() => l.Writing, true, writeReleaserTask); + AssertTaskCompleted(writeReleaserTask); + + readReleaserTask = Task.Run(() => l.ReadLock()); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask); + Assert.That(readReleaserTask.IsCompleted, Is.False); + + var writeReleaserTask2 = Task.Run(() => l.WriteLock()); + AssertEqualValue(() => l.WritersWaiting, 1, writeReleaserTask2); + Assert.That(writeReleaserTask2.IsCompleted, Is.False); + + writeReleaserTask.Result.Dispose(); + AssertEqualValue(() => l.WritersWaiting, 0, writeReleaserTask2); + AssertEqualValue(() => l.Writing, true, writeReleaserTask2); + Assert.That(readReleaserTask.IsCompleted, Is.False); + AssertTaskCompleted(writeReleaserTask2); + + writeReleaserTask2.Result.Dispose(); + AssertEqualValue(() => l.Writing, false, writeReleaserTask2); + AssertEqualValue(() => l.ReadersWaiting, 0, readReleaserTask); + AssertEqualValue(() => l.CurrentReaders, 1, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + readReleaserTask.Result.Dispose(); + Assert.That(l.ReadersWaiting, Is.EqualTo(0)); + Assert.That(l.WritersWaiting, Is.EqualTo(0)); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + Assert.That(l.Writing, Is.False); + } + } + + [Test] + public void TestBlockingAsync() + { + var l = new AsyncReaderWriterLock(); + for (var i = 0; i < 2; i++) + { + var readReleaserTask = l.ReadLockAsync(); + AssertEqualValue(() => l.CurrentReaders, 1, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + var readReleaserTask2 = l.ReadLockAsync(); + AssertEqualValue(() => l.CurrentReaders, 2, readReleaserTask2); + AssertTaskCompleted(readReleaserTask2); + + var writeReleaserTask = l.WriteLockAsync(); + AssertEqualValue(() => l.AcquiredWriteLock, true, writeReleaserTask); + AssertEqualValue(() => l.Writing, false, writeReleaserTask); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + + readReleaserTask.Result.Dispose(); + Assert.That(l.CurrentReaders, Is.EqualTo(1)); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + + readReleaserTask2.Result.Dispose(); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + AssertEqualValue(() => l.Writing, true, writeReleaserTask); + AssertTaskCompleted(writeReleaserTask); + + readReleaserTask = l.ReadLockAsync(); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask); + Assert.That(readReleaserTask.IsCompleted, Is.False); + + var writeReleaserTask2 = l.WriteLockAsync(); + AssertEqualValue(() => l.WritersWaiting, 1, writeReleaserTask2); + Assert.That(writeReleaserTask2.IsCompleted, Is.False); + + writeReleaserTask.Result.Dispose(); + AssertEqualValue(() => l.WritersWaiting, 0, writeReleaserTask2); + AssertEqualValue(() => l.Writing, true, writeReleaserTask2); + Assert.That(readReleaserTask.IsCompleted, Is.False); + AssertTaskCompleted(writeReleaserTask2); + + writeReleaserTask2.Result.Dispose(); + AssertEqualValue(() => l.Writing, false, writeReleaserTask2); + AssertEqualValue(() => l.ReadersWaiting, 0, readReleaserTask); + AssertEqualValue(() => l.CurrentReaders, 1, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + readReleaserTask.Result.Dispose(); + Assert.That(l.ReadersWaiting, Is.EqualTo(0)); + Assert.That(l.WritersWaiting, Is.EqualTo(0)); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + Assert.That(l.Writing, Is.False); + } + } + + [Test, Explicit] + public void TestConcurrentReadWrite() + { + var l = new AsyncReaderWriterLock(); + for (var i = 0; i < 2; i++) + { + var writeReleaser = l.WriteLock(); + Assert.That(l.Writing, Is.True); + + var secondWriteSemaphore = new SemaphoreSlim(0); + var secondWriteReleaser = default(AsyncReaderWriterLock.Releaser); + var secondWriteThread = new Thread( + () => + { + secondWriteSemaphore.Wait(); + secondWriteReleaser = l.WriteLock(); + }); + secondWriteThread.Priority = ThreadPriority.Highest; + secondWriteThread.Start(); + AssertEqualValue(() => secondWriteThread.ThreadState == ThreadState.WaitSleepJoin, true); + + var secondReadThreads = new Thread[20]; + var secondReadReleasers = new AsyncReaderWriterLock.Releaser[secondReadThreads.Length]; + var secondReadSemaphore = new SemaphoreSlim(0); + for (var j = 0; j < secondReadReleasers.Length; j++) + { + var index = j; + var thread = new Thread( + () => + { + secondReadSemaphore.Wait(); + secondReadReleasers[index] = l.ReadLock(); + }); + thread.Priority = ThreadPriority.Highest; + secondReadThreads[j] = thread; + thread.Start(); + } + + AssertEqualValue(() => secondReadThreads.All(o => o.ThreadState == ThreadState.WaitSleepJoin), true); + + var firstReadReleaserTasks = new Task[30]; + var firstReadStopSemaphore = new SemaphoreSlim(0); + for (var j = 0; j < firstReadReleaserTasks.Length; j++) + { + firstReadReleaserTasks[j] = Task.Run(() => + { + var releaser = l.ReadLock(); + firstReadStopSemaphore.Wait(); + releaser.Dispose(); + }); + } + + AssertEqualValue(() => l.ReadersWaiting, firstReadReleaserTasks.Length, waitDelay: 60000); + + writeReleaser.Dispose(); + secondWriteSemaphore.Release(); + secondReadSemaphore.Release(secondReadThreads.Length); + Thread.Sleep(1000); + firstReadStopSemaphore.Release(firstReadReleaserTasks.Length); + + AssertEqualValue(() => firstReadReleaserTasks.All(o => o.IsCompleted), true); + Assert.That(l.ReadersWaiting, Is.EqualTo(secondReadThreads.Length)); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + AssertEqualValue(() => secondWriteThread.IsAlive, false); + AssertEqualValue(() => secondReadThreads.All(o => o.IsAlive), true); + + secondWriteReleaser.Dispose(); + AssertEqualValue(() => secondReadThreads.All(o => !o.IsAlive), true); + + Assert.That(l.ReadersWaiting, Is.EqualTo(0)); + Assert.That(l.CurrentReaders, Is.EqualTo(secondReadThreads.Length)); + + foreach (var secondReadReleaser in secondReadReleasers) + { + secondReadReleaser.Dispose(); + } + + Assert.That(l.ReadersWaiting, Is.EqualTo(0)); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + } + } + + [Test] + public void TestInvaildExitReadLockUsage() + { + var l = new AsyncReaderWriterLock(); + var readReleaser = l.ReadLock(); + var readReleaser2 = l.ReadLock(); + + readReleaser.Dispose(); + readReleaser2.Dispose(); + Assert.Throws(() => readReleaser.Dispose()); + Assert.Throws(() => readReleaser2.Dispose()); + } + + [Test] + public void TestOperationAfterDispose() + { + var l = new AsyncReaderWriterLock(); + l.Dispose(); + + Assert.Throws(() => l.ReadLock()); + Assert.Throws(() => l.WriteLock()); + } + + [Test] + public void TestInvaildExitWriteLockUsage() + { + var l = new AsyncReaderWriterLock(); + var writeReleaser = l.WriteLock(); + + writeReleaser.Dispose(); + Assert.Throws(() => writeReleaser.Dispose()); + } + + [Test] + public void TestMixingSyncAndAsync() + { + var l = new AsyncReaderWriterLock(); + var readReleaser = l.ReadLock(); + Assert.That(l.CurrentReaders, Is.EqualTo(1)); + + var readReleaserTask = l.ReadLockAsync(); + AssertEqualValue(() => l.CurrentReaders, 2, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + readReleaser.Dispose(); + Assert.That(l.CurrentReaders, Is.EqualTo(1)); + + readReleaserTask.Result.Dispose(); + Assert.That(l.CurrentReaders, Is.EqualTo(0)); + + var writeReleaser = l.WriteLock(); + Assert.That(l.AcquiredWriteLock, Is.True); + + var writeReleaserTask = l.WriteLockAsync(); + AssertEqualValue(() => l.WritersWaiting, 1, writeReleaserTask); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + + readReleaserTask = Task.Run(() => l.ReadLock()); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask); + Assert.That(readReleaserTask.IsCompleted, Is.False); + + var readReleaserTask2 = l.ReadLockAsync(); + AssertEqualValue(() => l.ReadersWaiting, 2, readReleaserTask2); + Assert.That(readReleaserTask2.IsCompleted, Is.False); + + writeReleaser.Dispose(); + AssertEqualValue(() => l.WritersWaiting, 0, writeReleaserTask); + AssertEqualValue(() => l.Writing, true, writeReleaserTask); + AssertTaskCompleted(writeReleaserTask); + Assert.That(readReleaserTask.IsCompleted, Is.False); + Assert.That(readReleaserTask2.IsCompleted, Is.False); + + writeReleaserTask.Result.Dispose(); + AssertEqualValue(() => l.CurrentReaders, 2, readReleaserTask); + AssertEqualValue(() => l.ReadersWaiting, 0, readReleaserTask2); + AssertTaskCompleted(readReleaserTask); + AssertTaskCompleted(readReleaserTask2); + } + + [Test] + public void TestWritePriorityOverReadAsync() + { + var l = new AsyncReaderWriterLock(); + for (var i = 0; i < 2; i++) + { + var writeReleaser = l.WriteLock(); + Assert.That(l.AcquiredWriteLock, Is.True); + + var readReleaserTask = l.ReadLockAsync(); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask); + + var writeReleaserTask = l.WriteLockAsync(); + AssertEqualValue(() => l.WritersWaiting, 1, writeReleaserTask); + + writeReleaser.Dispose(); + AssertEqualValue(() => l.WritersWaiting, 0, writeReleaserTask); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask); + AssertTaskCompleted(writeReleaserTask); + + writeReleaserTask.Result.Dispose(); + AssertEqualValue(() => l.ReadersWaiting, 0, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + readReleaserTask.Result.Dispose(); + } + } + + [Test] + public void TestPartialReleasingReadLockAsync() + { + var l = new AsyncReaderWriterLock(); + var readReleaserTask = l.ReadLockAsync(); + AssertEqualValue(() => l.CurrentReaders, 1, readReleaserTask); + AssertTaskCompleted(readReleaserTask); + + var readReleaserTask2 = l.ReadLockAsync(); + AssertEqualValue(() => l.CurrentReaders, 2, readReleaserTask); + AssertTaskCompleted(readReleaserTask2); + + var writeReleaserTask = l.WriteLockAsync(); + AssertEqualValue(() => l.AcquiredWriteLock, true, writeReleaserTask); + AssertEqualValue(() => l.Writing, false, writeReleaserTask); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + + var readReleaserTask3 = l.ReadLockAsync(); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask3); + Assert.That(readReleaserTask3.IsCompleted, Is.False); + + readReleaserTask.Result.Dispose(); + Assert.That(writeReleaserTask.IsCompleted, Is.False); + Assert.That(readReleaserTask3.IsCompleted, Is.False); + + readReleaserTask2.Result.Dispose(); + AssertEqualValue(() => l.Writing, true, writeReleaserTask); + AssertEqualValue(() => l.ReadersWaiting, 1, readReleaserTask3); + AssertTaskCompleted(writeReleaserTask); + Assert.That(readReleaserTask3.IsCompleted, Is.False); + + writeReleaserTask.Result.Dispose(); + AssertEqualValue(() => l.ReadersWaiting, 0, readReleaserTask3); + AssertTaskCompleted(readReleaserTask3); + } + + [Test, Explicit] + public async Task TestLoadSyncAndAsync() + { + var l = new AsyncReaderWriterLock(); + var lockStatistics = new LockStatistics(); + var incorrectLockCount = false; + var tasks = new Task[20]; + var masterRandom = new Random(); + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + for (var i = 0; i < tasks.Length; i++) + { + // Ensure that each random has its own unique seed + var random = new Random(masterRandom.Next()); + tasks[i] = i % 2 == 0 + ? Task.Run(() => Lock(l, random, lockStatistics, CheckLockCount, CanContinue)) + : LockAsync(l, random, lockStatistics, CheckLockCount, CanContinue); + } + + await Task.WhenAll(tasks); + Assert.That(incorrectLockCount, Is.False); + + void CheckLockCount() + { + if (!lockStatistics.Validate()) + { + Volatile.Write(ref incorrectLockCount, true); + } + } + + bool CanContinue() + { + return !cancellationTokenSource.Token.IsCancellationRequested; + } + } + + private class LockStatistics + { + public int ReadLockCount { get; set; } + + public int WriteLockCount { get; set; } + + public bool Validate() + { + return (ReadLockCount == 0 && WriteLockCount == 0) || + (ReadLockCount > 0 && WriteLockCount == 0) || + (ReadLockCount == 0 && WriteLockCount == 1); + } + } + + private static void Lock( + AsyncReaderWriterLock readWriteLock, + Random random, + LockStatistics lockStatistics, + System.Action checkLockAction, + Func canContinue) + { + while (canContinue()) + { + var isRead = random.Next(100) < 80; + var releaser = isRead ? readWriteLock.ReadLock() : readWriteLock.WriteLock(); + lock (readWriteLock) + { + if (isRead) + { + lockStatistics.ReadLockCount++; + } + else + { + lockStatistics.WriteLockCount++; + } + + checkLockAction(); + } + + Thread.Sleep(10); + + lock (readWriteLock) + { + releaser.Dispose(); + if (isRead) + { + lockStatistics.ReadLockCount--; + } + else + { + lockStatistics.WriteLockCount--; + } + + checkLockAction(); + } + } + } + + private static void AssertEqualValue(Func getValueFunc, T value, Task task = null, int waitDelay = 5000) + { + var currentTime = 0; + var step = 5; + while (currentTime < waitDelay) + { + if (task != null) + { + task.Wait(step); + } + else + { + Thread.Sleep(step); + } + + currentTime += step; + if (getValueFunc().Equals(value)) + { + return; + } + + step *= 2; + } + + Assert.That(getValueFunc(), Is.EqualTo(value)); + } + + private static void AssertTaskCompleted(Task task) + { + AssertEqualValue(() => task.IsCompleted, true, task); + } + } +} diff --git a/src/NHibernate.Test/UtilityTest/IdentityMapFixture.cs b/src/NHibernate.Test/UtilityTest/IdentityMapFixture.cs index a5d9250f84a..8b11a385ff8 100644 --- a/src/NHibernate.Test/UtilityTest/IdentityMapFixture.cs +++ b/src/NHibernate.Test/UtilityTest/IdentityMapFixture.cs @@ -191,7 +191,6 @@ public void ContainsDiffObjectWithEquals() Assert.IsFalse(map.Contains(item2), "Even though item1.Equals(item2) IdentityMap should not find by item2"); } - /// /// Add the same MutableHashCode class twice and ensure there is only /// one item in the IdentityMap. @@ -272,4 +271,4 @@ public void MethodMissingException() RuntimeHelpers.GetHashCode(new object()); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UtilityTest/IdentitySetFixture.cs b/src/NHibernate.Test/UtilityTest/IdentitySetFixture.cs index 979aa07ebe2..6a96c8866d7 100644 --- a/src/NHibernate.Test/UtilityTest/IdentitySetFixture.cs +++ b/src/NHibernate.Test/UtilityTest/IdentitySetFixture.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Runtime.CompilerServices; using NHibernate.Util; using NUnit.Framework; @@ -10,6 +9,8 @@ namespace NHibernate.Test.UtilityTest /// /// Test for the IdentityMap. /// + // Since 5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] [TestFixture] public class IdentitySetFixture { @@ -58,7 +59,6 @@ public void AddNoHashCode() Assert.AreEqual(1, set.Count, "The item was added succesfully"); } - /// /// An IdentityMap can not use a ValueType as the Key because of the boxing/unboxing /// that occurs with them. This verifies that an Exception is thrown if a ValueType @@ -134,7 +134,6 @@ public void ContainsDiffObjectWithEquals() Assert.IsFalse(map.Contains(item2), "Even though item1.Equals(item2) IdentitySet should not find by item2"); } - /// /// Add the same MutableHashCode class twice and ensure there is only /// one item in the IdentitySet. @@ -178,6 +177,5 @@ public void SetItemsEqualHashCodeDiffIdentity() Assert.AreEqual(2, actualSet.Count, "The IdentityMap should have 2 elements"); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs b/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs index 678f728332b..c037121dc7c 100644 --- a/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs +++ b/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs @@ -123,7 +123,6 @@ private void WrapsMultipleBreak(int breakIndex) Assert.IsTrue(second.WasDisposed, "second should have been disposed of. "); } - private JoinedEnumerable InitSingle(out EnumerableTester first) { first = new EnumerableTester(new ArrayList(new int[] {1, 2, 3})); @@ -211,4 +210,4 @@ public void Dispose() #endregion } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UtilityTest/JoinedEnumerableGenericFixture.cs b/src/NHibernate.Test/UtilityTest/JoinedEnumerableGenericFixture.cs index c8e5e3a8d56..58dde26a1c2 100644 --- a/src/NHibernate.Test/UtilityTest/JoinedEnumerableGenericFixture.cs +++ b/src/NHibernate.Test/UtilityTest/JoinedEnumerableGenericFixture.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using NHibernate.Util; @@ -6,6 +7,8 @@ namespace NHibernate.Test.UtilityTest { // This test is the same of JoinedEnumerableFixture but for generic version. + // Since 5.3 + [Obsolete] [TestFixture] public class JoinedEnumerableGenericFixture { @@ -102,7 +105,6 @@ private static void WrapsMultipleBreak(int breakIndex) Assert.IsTrue(second.WasDisposed, "second should have been disposed of. "); } - private static JoinedEnumerable InitSingle(out EnumerableTester first) { first = new EnumerableTester(new List(new int[] { 1, 2, 3 })); diff --git a/src/NHibernate.Test/UtilityTest/LinkedHashMapFixture.cs b/src/NHibernate.Test/UtilityTest/LinkedHashMapFixture.cs index 70dac775551..bfa995e190e 100644 --- a/src/NHibernate.Test/UtilityTest/LinkedHashMapFixture.cs +++ b/src/NHibernate.Test/UtilityTest/LinkedHashMapFixture.cs @@ -62,7 +62,6 @@ public void FirstKeyFirstValue() Assert.AreEqual(players[1], lhm.FirstValue); } - [Test] public void Clear() { @@ -261,7 +260,6 @@ public void Serialization() Assert.AreEqual(6, index); } - [Test, Explicit] public void ShowDiff() { @@ -415,4 +413,3 @@ public override string ToString() } } } - diff --git a/src/NHibernate.Test/UtilityTest/ReflectHelperFixture.cs b/src/NHibernate.Test/UtilityTest/ReflectHelperFixture.cs index 86aec92ae2e..d6ee2454010 100644 --- a/src/NHibernate.Test/UtilityTest/ReflectHelperFixture.cs +++ b/src/NHibernate.Test/UtilityTest/ReflectHelperFixture.cs @@ -32,12 +32,10 @@ public interface IMyInheritedWithEqual : IMyBaseWithEqual public interface IEmpty { - } public interface IComplex: IEmpty, IMyInheritedWithEqual { - } [Test] @@ -82,8 +80,7 @@ public void NoTypeFoundReturnsNull() Assert.IsNull(noType); } -#if NETFX - [Test] + [Test, NetFxOnly] public void TypeFoundInNotLoadedAssembly() { System.Type httpRequest = ReflectHelper.TypeFromAssembly("System.Web.HttpRequest", "System.Web", false); @@ -92,7 +89,6 @@ public void TypeFoundInNotLoadedAssembly() System.Type sameType = ReflectHelper.TypeFromAssembly("System.Web.HttpRequest", "System.Web", false); Assert.AreEqual(httpRequest, sameType, "should be the exact same Type"); } -#endif [Test] public void SystemTypes() diff --git a/src/NHibernate.Test/UtilityTest/ReflectionHelperIsMethodOfTests.cs b/src/NHibernate.Test/UtilityTest/ReflectionHelperIsMethodOfTests.cs index 51f21b4563a..7f6b5a01b22 100644 --- a/src/NHibernate.Test/UtilityTest/ReflectionHelperIsMethodOfTests.cs +++ b/src/NHibernate.Test/UtilityTest/ReflectionHelperIsMethodOfTests.cs @@ -31,7 +31,6 @@ public void WhenDeclaringTypeMatchThenTrue() private class MyCollection: List { - } [Test] @@ -73,7 +72,7 @@ private abstract class MyAbstractClass private class MyClass : MyAbstractClass { - public override int MyMethod() {return 0;} + public override int MyMethod() {return 0; } } [Test] diff --git a/src/NHibernate.Test/UtilityTest/SafetyEnumerableFixture.cs b/src/NHibernate.Test/UtilityTest/SafetyEnumerableFixture.cs index 1c0649809f9..59c873e10ab 100644 --- a/src/NHibernate.Test/UtilityTest/SafetyEnumerableFixture.cs +++ b/src/NHibernate.Test/UtilityTest/SafetyEnumerableFixture.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using NHibernate.Util; using NUnit.Framework; @@ -7,6 +8,8 @@ namespace NHibernate.Test.UtilityTest /// /// Test cases for the class. /// + // Since v5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] [TestFixture] public class SafetyEnumerableFixture { diff --git a/src/NHibernate.Test/UtilityTest/SequencedHashMapFixture.cs b/src/NHibernate.Test/UtilityTest/SequencedHashMapFixture.cs index 7b650b1c648..023e84c5bb0 100644 --- a/src/NHibernate.Test/UtilityTest/SequencedHashMapFixture.cs +++ b/src/NHibernate.Test/UtilityTest/SequencedHashMapFixture.cs @@ -52,7 +52,6 @@ public void Add() _shm.Add(newKey, newValue); - int i = 0; foreach (DictionaryEntry de in _shm) { @@ -290,7 +289,6 @@ public void LastValue() Assert.IsNull(_emptyShm.LastValue); } - [Test, Explicit] public void Performance() { @@ -381,7 +379,6 @@ public void Performance() listItemTicks[runIndex] = DateTime.Now.Ticks - listStart; - list.Clear(); } @@ -447,4 +444,4 @@ public void Serialize() Assert.AreEqual(3, index); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs new file mode 100644 index 00000000000..c13478424b0 --- /dev/null +++ b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using NHibernate.Collection.Generic.SetHelpers; +using NUnit.Framework; + +namespace NHibernate.Test.UtilityTest +{ + [TestFixture] + public class SetSnapShotFixture + { + [Test] + public void TestNullValue() + { + var sn = new SetSnapShot(1); + Assert.That(sn, Has.Count.EqualTo(0)); + Assert.That(sn, Is.EquivalentTo(new object[0])); + Assert.That(sn.Contains(null), Is.False); + Assert.That(sn.TryGetValue(null, out _), Is.False); + + sn.Add(null); + Assert.That(sn, Has.Count.EqualTo(1)); + Assert.That(sn, Is.EquivalentTo(new object[] {null})); + + Assert.That(sn.TryGetValue(null, out var value), Is.True); + Assert.That(sn.Contains(null), Is.True); + Assert.That(value, Is.Null); + + Assert.That(sn.Remove(null), Is.True); + Assert.That(sn, Has.Count.EqualTo(0)); + Assert.That(sn, Is.EquivalentTo(new object[0])); + + sn.Add(null); + Assert.That(sn, Has.Count.EqualTo(1)); + + sn.Clear(); + Assert.That(sn, Has.Count.EqualTo(0)); + Assert.That(sn, Is.EquivalentTo(new object[0])); + } + + [Test] + public void TestInitialization() + { + var list = new List {"test1", null, "test2"}; + var sn = new SetSnapShot(list); + Assert.That(sn, Has.Count.EqualTo(list.Count)); + Assert.That(sn, Is.EquivalentTo(list)); + Assert.That(sn.TryGetValue("test1", out _), Is.True); + Assert.That(sn.TryGetValue(null, out _), Is.True); + } + + [Test] + public void TestCopyTo() + { + var list = new List {"test1", null, "test2"}; + var sn = new SetSnapShot(list); + + var array = new string[3]; + sn.CopyTo(array, 0); + Assert.That(list, Is.EquivalentTo(array)); + } + + [Test] + public void TestSerialization() + { + var list = new List {"test1", null, "test2"}; + var sn = new SetSnapShot(list); + + sn = Deserialize>(Serialize(sn)); + Assert.That(sn, Has.Count.EqualTo(list.Count)); + Assert.That(sn, Is.EquivalentTo(list)); + Assert.That(sn.TryGetValue("test1", out var item1), Is.True); + Assert.That(item1, Is.EqualTo("test1")); + Assert.That(sn.TryGetValue(null, out var nullValue), Is.True); + Assert.That(nullValue, Is.Null); + } + + private static byte[] Serialize(T obj) + { + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream()) + { + serializer.Serialize(stream, obj); + return stream.ToArray(); + } + } + + private static T Deserialize(byte[] value) + { + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream(value)) + { + return (T) serializer.Deserialize(stream); + } + } + } +} diff --git a/src/NHibernate.Test/UtilityTest/SingletonEnumerableFixture.cs b/src/NHibernate.Test/UtilityTest/SingletonEnumerableFixture.cs index 4312bceca0f..9051edab9ec 100644 --- a/src/NHibernate.Test/UtilityTest/SingletonEnumerableFixture.cs +++ b/src/NHibernate.Test/UtilityTest/SingletonEnumerableFixture.cs @@ -1,9 +1,12 @@ +using System; using System.Collections; using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.UtilityTest { + // Since 5.3 + [Obsolete] [TestFixture] public class SingletonEnumerableFixture { @@ -46,4 +49,4 @@ public void ShouldWorkAsEnumerator() Assert.That(i, Is.EqualTo(1)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/UtilityTest/SqlStatementLoggerFixture.cs b/src/NHibernate.Test/UtilityTest/SqlStatementLoggerFixture.cs new file mode 100644 index 00000000000..1e8cbefb6e3 --- /dev/null +++ b/src/NHibernate.Test/UtilityTest/SqlStatementLoggerFixture.cs @@ -0,0 +1,44 @@ +using NHibernate.AdoNet.Util; +using NUnit.Framework; + +namespace NHibernate.Test.UtilityTest +{ + [TestFixture] + public class SqlStatementLoggerFixture + { + [Test] + public void SupportsInvalidSql() + { + using (var spy = new SqlLogSpy()) + { + var logger = new SqlStatementLogger(false, true); + using (var cmd = new System.Data.SqlClient.SqlCommand( + "UPDATE Table Set Column = @p0; @p0 = 'Some data with an embedded quote in parentheses and signal word (''UPDATE'') like this: (don't update)'")) + { + Assert.DoesNotThrow(() => logger.LogCommand(cmd, FormatStyle.Basic)); + } + + Assert.That(spy.GetWholeLog(), Contains.Substring("Some data with an embedded quote")); + } + } + [Test] + public void SupportsKeywordInParameter() + { + using (var spy = new SqlLogSpy()) + { + var logger = new SqlStatementLogger(false, true); + using (var cmd = new System.Data.SqlClient.SqlCommand("UPDATE Table Set Column = @p0;")) + { + cmd.Parameters.AddWithValue( + "p0", + "Some data with an embedded quote in parentheses and signal word ('UPDATE') like this: (don't update)"); + Assert.DoesNotThrow(() => logger.LogCommand(cmd, FormatStyle.Basic)); + } + + var log = spy.GetWholeLog(); + Assert.That(log, Contains.Substring("Some data with an embedded quote")); + Assert.That(log.Split('\n'), Has.Length.GreaterThan(2), "SQL seems not formatted"); + } + } + } +} diff --git a/src/NHibernate.Test/UtilityTest/StringHelperFixture.cs b/src/NHibernate.Test/UtilityTest/StringHelperFixture.cs index a8230430007..f949c604b3c 100644 --- a/src/NHibernate.Test/UtilityTest/StringHelperFixture.cs +++ b/src/NHibernate.Test/UtilityTest/StringHelperFixture.cs @@ -138,7 +138,6 @@ public void PurgeBackticksEnclosing() Assert.That(StringHelper.PurgeBackticksEnclosing("`something`"), Is.EqualTo("something")); } - [TestCase("ab", 0, -1, 0)] [TestCase("a\r\nb", 0, 1, 2)] [TestCase("a\nb", 0, 1, 1)] @@ -153,7 +152,6 @@ public void IndexOfAnyNewLineReturnsIndexAndLength(string str, int startIndex, i Assert.That(matchLength, Is.EqualTo(expectedMatchLength)); } - [TestCase("ab", 0, false, 0)] [TestCase("a\r\nb", 0, false, 0)] [TestCase("a\nb", 1, true, 1)] diff --git a/src/NHibernate.Test/UtilityTest/TypeNameParserFixture.cs b/src/NHibernate.Test/UtilityTest/TypeNameParserFixture.cs index caaacaea1e6..f6c47ff38c8 100644 --- a/src/NHibernate.Test/UtilityTest/TypeNameParserFixture.cs +++ b/src/NHibernate.Test/UtilityTest/TypeNameParserFixture.cs @@ -202,12 +202,10 @@ public void ParseGenericTypeNameWithDefaultNamespaceUnused() public class MyGClass { - } public class MyComplexClass { - } [Test] diff --git a/src/NHibernate.Test/VersionTest/Db/MsSQL/ComplexDomainFixture.cs b/src/NHibernate.Test/VersionTest/Db/MsSQL/ComplexDomainFixture.cs index fb7d341c81e..b5c1a23f0da 100644 --- a/src/NHibernate.Test/VersionTest/Db/MsSQL/ComplexDomainFixture.cs +++ b/src/NHibernate.Test/VersionTest/Db/MsSQL/ComplexDomainFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using NHibernate.Dialect; using NUnit.Framework; @@ -52,12 +51,12 @@ public void NH1685() Assert.IsTrue(BinaryTimestamp.Equals(bar.Timestamp, retrievedBar.Timestamp), "Timestamps are different!"); } - using (ISession session = OpenSession()) + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) { - session.BeginTransaction(); session.Delete("from Bar"); - session.Transaction.Commit(); + tran.Commit(); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/log4net.xml b/src/NHibernate.Test/log4net.xml new file mode 100644 index 00000000000..01844a5d696 --- /dev/null +++ b/src/NHibernate.Test/log4net.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj b/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj index f3c29432280..e4639a49a03 100644 --- a/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj +++ b/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj @@ -14,8 +14,12 @@ + + + + - + diff --git a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs index ec08849ce03..5dd5fc8fe22 100644 --- a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs +++ b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs @@ -295,5 +295,3 @@ private static void RunProcess(string processName, string arguments, bool checkS #endif } } - - diff --git a/src/NHibernate/ADOException.cs b/src/NHibernate/ADOException.cs index f8cfe3b7331..f3c5108b5fd 100644 --- a/src/NHibernate/ADOException.cs +++ b/src/NHibernate/ADOException.cs @@ -20,7 +20,6 @@ public class ADOException : HibernateException public ADOException() { - } /// /// Initializes a new instance of the class. diff --git a/src/NHibernate/Action/BulkOperationCleanupAction.cs b/src/NHibernate/Action/BulkOperationCleanupAction.cs index fa52f0f878c..edd5b8f20bd 100644 --- a/src/NHibernate/Action/BulkOperationCleanupAction.cs +++ b/src/NHibernate/Action/BulkOperationCleanupAction.cs @@ -88,7 +88,6 @@ private bool AffectedEntity(ISet querySpaces, string[] entitySpaces) return true; } - return entitySpaces.Any(querySpaces.Contains); } diff --git a/src/NHibernate/Action/CollectionAction.cs b/src/NHibernate/Action/CollectionAction.cs index ca5be713bf6..e676e7d4f18 100644 --- a/src/NHibernate/Action/CollectionAction.cs +++ b/src/NHibernate/Action/CollectionAction.cs @@ -124,7 +124,7 @@ public virtual void BeforeExecutions() public virtual void ExecuteAfterTransactionCompletion(bool success) { - var ck = new CacheKey(key, persister.KeyType, persister.Role, Session.Factory); + var ck = session.GenerateCacheKey(key, persister.KeyType, persister.Role); persister.Cache.Release(ck, softLock); } diff --git a/src/NHibernate/Action/DelayedPostInsertIdentifier.cs b/src/NHibernate/Action/DelayedPostInsertIdentifier.cs index 4f19b4973fd..083e7cd14b2 100644 --- a/src/NHibernate/Action/DelayedPostInsertIdentifier.cs +++ b/src/NHibernate/Action/DelayedPostInsertIdentifier.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace NHibernate.Action { @@ -15,20 +16,12 @@ namespace NHibernate.Action [Serializable] public class DelayedPostInsertIdentifier { - [ThreadStatic] - private static long _Sequence = 0; + private static long GlobalSequence = 0; private readonly long sequence; public DelayedPostInsertIdentifier() { - lock (typeof(DelayedPostInsertIdentifier)) - { - if (_Sequence == long.MaxValue) - { - _Sequence = 0; - } - sequence = _Sequence++; - } + sequence = Interlocked.Increment(ref GlobalSequence); } public override bool Equals(object obj) diff --git a/src/NHibernate/Action/EntityAction.cs b/src/NHibernate/Action/EntityAction.cs index 34b3919786d..70b2b1e60c2 100644 --- a/src/NHibernate/Action/EntityAction.cs +++ b/src/NHibernate/Action/EntityAction.cs @@ -91,7 +91,7 @@ public IEntityPersister Persister get { return persister; } } - protected internal abstract bool HasPostCommitEventListeners { get;} + protected internal abstract bool HasPostCommitEventListeners { get; } #region IExecutable Members diff --git a/src/NHibernate/Action/EntityIdentityInsertAction.cs b/src/NHibernate/Action/EntityIdentityInsertAction.cs index b796bf2be77..d45d89c884c 100644 --- a/src/NHibernate/Action/EntityIdentityInsertAction.cs +++ b/src/NHibernate/Action/EntityIdentityInsertAction.cs @@ -9,7 +9,6 @@ namespace NHibernate.Action [Serializable] public sealed partial class EntityIdentityInsertAction : AbstractEntityInsertAction { - private readonly object lockObject = new object(); private readonly bool isDelayed; private readonly EntityKey delayedEntityKey; //private CacheEntry cacheEntry; @@ -19,7 +18,9 @@ public EntityIdentityInsertAction(object[] state, object instance, IEntityPersis : base(null, state, instance, persister, session) { this.isDelayed = isDelayed; - delayedEntityKey = this.isDelayed ? GenerateDelayedEntityKey() : null; + delayedEntityKey = this.isDelayed + ? Session.GenerateEntityKey(new DelayedPostInsertIdentifier(), Persister) + : null; } public object GeneratedId @@ -32,17 +33,6 @@ public EntityKey DelayedEntityKey get { return delayedEntityKey; } } - private EntityKey GenerateDelayedEntityKey() - { - lock (lockObject) - { - if (!isDelayed) - throw new HibernateException("Cannot request delayed entity-key for non-delayed post-insert-id generation"); - - return Session.GenerateEntityKey(new DelayedPostInsertIdentifier(), Persister); - } - } - protected internal override bool HasPostCommitEventListeners { get diff --git a/src/NHibernate/Action/EntityInsertAction.cs b/src/NHibernate/Action/EntityInsertAction.cs index 71250f48373..0f8e7e6ac24 100644 --- a/src/NHibernate/Action/EntityInsertAction.cs +++ b/src/NHibernate/Action/EntityInsertAction.cs @@ -48,7 +48,6 @@ public override void Execute() // else inserted the same pk first, the insert would fail if (!veto) { - persister.Insert(id, State, instance, Session); EntityEntry entry = Session.PersistenceContext.GetEntry(instance); diff --git a/src/NHibernate/AdoNet/AbstractBatcher.cs b/src/NHibernate/AdoNet/AbstractBatcher.cs index 2d0a50cf11e..520b4e152a3 100644 --- a/src/NHibernate/AdoNet/AbstractBatcher.cs +++ b/src/NHibernate/AdoNet/AbstractBatcher.cs @@ -37,7 +37,7 @@ public abstract partial class AbstractBatcher : IBatcher private SqlType[] _batchCommandParameterTypes; private readonly HashSet _commandsToClose = new HashSet(); private readonly HashSet _readersToClose = new HashSet(); - private readonly IDictionary _readersDuration = new Dictionary(); + private readonly Dictionary _readersDuration = new Dictionary(); private DbCommand _lastQuery; private bool _releasing; @@ -81,14 +81,19 @@ protected DbCommand CurrentCommand public DbCommand Generate(CommandType type, SqlString sqlString, SqlType[] parameterTypes) { - SqlString sql = GetSQL(sqlString); + return Generate(type, sqlString, parameterTypes, false); + } - var cmd = _factory.ConnectionProvider.Driver.GenerateCommand(type, sql, parameterTypes); - LogOpenPreparedCommand(); - if (Log.IsDebugEnabled()) + private DbCommand Generate(CommandType type, SqlString sqlString, SqlType[] parameterTypes, bool batch) + { + var sql = GetSQL(sqlString); + if (batch) { - Log.Debug("Building an DbCommand object for the SqlString: {0}", sql); + OnPreparedBatchStatement(sql); } + + var cmd = _factory.ConnectionProvider.Driver.GenerateCommand(type, sql, parameterTypes); + LogOpenPreparedCommand(sql); _commandsToClose.Add(cmd); return cmd; } @@ -121,7 +126,7 @@ protected void Prepare(DbCommand cmd) cmd.Connection = sessionConnection; } - _connectionManager.Transaction.Enlist(cmd); + _connectionManager.EnlistInTransaction(cmd); Driver.PrepareCommand(cmd); } catch (InvalidOperationException ioe) @@ -141,7 +146,7 @@ public virtual DbCommand PrepareBatchCommand(CommandType type, SqlString sql, Sq } else { - _batchCommand = PrepareCommand(type, sql, parameterTypes); // calls ExecuteBatch() + _batchCommand = PrepareCommand(type, sql, parameterTypes, true); // calls ExecuteBatch() _batchCommandSql = sql; _batchCommandParameterTypes = parameterTypes; } @@ -150,6 +155,11 @@ public virtual DbCommand PrepareBatchCommand(CommandType type, SqlString sql, Sq } public DbCommand PrepareCommand(CommandType type, SqlString sql, SqlType[] parameterTypes) + { + return PrepareCommand(type, sql, parameterTypes, false); + } + + private DbCommand PrepareCommand(CommandType type, SqlString sql, SqlType[] parameterTypes, bool batch) { OnPreparedCommand(); @@ -157,7 +167,7 @@ public DbCommand PrepareCommand(CommandType type, SqlString sql, SqlType[] param // if the command is associated with an ADO.NET Transaction/Connection while // another open one Command is doing something then an exception will be // thrown. - return Generate(type, sql, parameterTypes); + return Generate(type, sql, parameterTypes, batch); } protected virtual void OnPreparedCommand() @@ -167,6 +177,8 @@ protected virtual void OnPreparedCommand() ExecuteBatch(); } + internal virtual void OnPreparedBatchStatement(SqlString sqlString) { } + public DbCommand PrepareQueryCommand(CommandType type, SqlString sql, SqlType[] parameterTypes) { // do not actually prepare the Command here - instead just generate it because @@ -216,7 +228,7 @@ public int ExecuteNonQuery(DbCommand cmd) } finally { - if (Log.IsDebugEnabled() && duration != null) + if (duration != null) Log.Debug("ExecuteNonQuery took {0} ms", duration.ElapsedMilliseconds); } } @@ -226,13 +238,24 @@ public virtual DbDataReader ExecuteReader(DbCommand cmd) CheckReaders(); LogCommand(cmd); Prepare(cmd); - Stopwatch duration = null; - if (Log.IsDebugEnabled()) - duration = Stopwatch.StartNew(); - DbDataReader reader = null; + + var duration = Log.IsDebugEnabled() ? Stopwatch.StartNew() : null; + + var reader = DoExecuteReader(cmd); + + _readersToClose.Add(reader); + LogOpenReader(duration , reader); + return reader; + } + + private DbDataReader DoExecuteReader(DbCommand cmd) + { try { - reader = cmd.ExecuteReader(); + var reader = cmd.ExecuteReader(); + return _factory.ConnectionProvider.Driver.SupportsMultipleOpenReaders + ? reader + : NHybridDataReader.Create(reader); } catch (Exception e) { @@ -240,23 +263,6 @@ public virtual DbDataReader ExecuteReader(DbCommand cmd) Log.Error(e, "Could not execute query: {0}", cmd.CommandText); throw; } - finally - { - if (Log.IsDebugEnabled() && duration != null && reader != null) - { - Log.Debug("ExecuteReader took {0} ms", duration.ElapsedMilliseconds); - _readersDuration[reader] = duration; - } - } - - if (!_factory.ConnectionProvider.Driver.SupportsMultipleOpenReaders) - { - reader = NHybridDataReader.Create(reader); - } - - _readersToClose.Add(reader); - LogOpenReader(); - return reader; } /// @@ -372,8 +378,7 @@ public void CloseReader(DbDataReader reader) var rsw = reader as ResultSetWrapper; var actualReader = rsw == null ? reader : rsw.Target; _readersToClose.Remove(actualReader); - - var duration = GetReaderStopwatch(actualReader); + _readersDuration.Remove(actualReader, out var duration); try { @@ -385,26 +390,7 @@ public void CloseReader(DbDataReader reader) Log.Warn(e, "exception closing reader"); } - LogCloseReader(); - LogDuration(duration); - } - - private Stopwatch GetReaderStopwatch(DbDataReader reader) - { - var nhReader = reader as NHybridDataReader; - var actualReader = nhReader == null ? reader : nhReader.Target; - - Stopwatch duration; - if (_readersDuration.TryGetValue(actualReader, out duration)) - _readersDuration.Remove(actualReader); - return duration; - } - - private static void LogDuration(Stopwatch duration) - { - if (!Log.IsDebugEnabled() || duration == null) return; - - Log.Debug("DataReader was closed after {0} ms", duration.ElapsedMilliseconds); + LogCloseReader(duration); } public void ExecuteBatch() @@ -433,7 +419,7 @@ protected void ExecuteBatchWithTiming(DbCommand ps) duration = Stopwatch.StartNew(); var countBeforeExecutingBatch = CountOfStatementsInCurrentBatch; DoExecuteBatch(ps); - if (Log.IsDebugEnabled() && duration != null) + if (duration != null) Log.Debug("ExecuteBatch for {0} statements took {1} ms", countBeforeExecutingBatch, duration.ElapsedMilliseconds); @@ -490,12 +476,13 @@ protected void LogCommand(DbCommand command) _factory.Settings.SqlStatementLogger.LogCommand(command, FormatStyle.Basic); } - private void LogOpenPreparedCommand() + private void LogOpenPreparedCommand(SqlString sql) { if (Log.IsDebugEnabled()) { int currentOpenCommandCount = Interlocked.Increment(ref _openCommandCount); Log.Debug("Opened new DbCommand, open DbCommands: {0}", currentOpenCommandCount); + Log.Debug("Building an DbCommand object for the SqlString: {0}", sql); } if (_factory.Statistics.IsStatisticsEnabled) @@ -518,22 +505,26 @@ private void LogClosePreparedCommand() } } - private static void LogOpenReader() + private void LogOpenReader(Stopwatch duration, DbDataReader reader) { - if (Log.IsDebugEnabled()) - { - int currentOpenReaderCount = Interlocked.Increment(ref _openReaderCount); - Log.Debug("Opened DbDataReader, open DbDataReaders: {0}", currentOpenReaderCount); - } + if (duration == null) + return; + + Log.Debug("ExecuteReader took {0} ms", duration.ElapsedMilliseconds); + _readersDuration[reader] = duration; + + int currentOpenReaderCount = Interlocked.Increment(ref _openReaderCount); + Log.Debug("Opened DbDataReader, open DbDataReaders: {0}", currentOpenReaderCount); } - private static void LogCloseReader() + private static void LogCloseReader(Stopwatch duration) { - if (Log.IsDebugEnabled()) - { - int currentOpenReaderCount = Interlocked.Decrement(ref _openReaderCount); - Log.Debug("Closed DbDataReader, open DbDataReaders :{0}", currentOpenReaderCount); - } + if (duration == null) + return; + + int currentOpenReaderCount = Interlocked.Decrement(ref _openReaderCount); + Log.Debug("Closed DbDataReader, open DbDataReaders :{0}", currentOpenReaderCount); + Log.Debug("DataReader was closed after {0} ms", duration.ElapsedMilliseconds); } public void CancelLastQuery() @@ -616,13 +607,13 @@ protected virtual void Dispose(bool isDisposing) if (isDisposing) { CloseCommands(); + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } #endregion diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 80674df3cf5..5ebf51a7fb1 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -4,8 +4,9 @@ using System.Data.Common; using System.Runtime.Serialization; using System.Security; - +using NHibernate.Connection; using NHibernate.Engine; +using NHibernate.Impl; namespace NHibernate.AdoNet { @@ -19,6 +20,7 @@ namespace NHibernate.AdoNet [Serializable] public partial class ConnectionManager : ISerializable, IDeserializationCallback { + private readonly IConnectionAccess _connectionAccess; private static readonly INHibernateLogger _log = NHibernateLogger.For(typeof(ConnectionManager)); [NonSerialized] @@ -78,7 +80,8 @@ public ConnectionManager( DbConnection suppliedConnection, ConnectionReleaseMode connectionReleaseMode, IInterceptor interceptor, - bool shouldAutoJoinTransaction) + bool shouldAutoJoinTransaction, + IConnectionAccess connectionAccess) { Session = session; _connection = suppliedConnection; @@ -89,6 +92,18 @@ public ConnectionManager( _ownConnection = suppliedConnection == null; ShouldAutoJoinTransaction = shouldAutoJoinTransaction; + _connectionAccess = connectionAccess ?? throw new ArgumentNullException(nameof(connectionAccess)); + } + + //Since 5.3 + [Obsolete("Use overload with connectionAccess parameter")] + public ConnectionManager( + ISessionImplementor session, + DbConnection suppliedConnection, + ConnectionReleaseMode connectionReleaseMode, + IInterceptor interceptor, + bool shouldAutoJoinTransaction) : this(session, suppliedConnection, connectionReleaseMode, interceptor, shouldAutoJoinTransaction, new NonContextualConnectionAccess(session.Factory)) + { } public void AddDependentSession(ISessionImplementor session) @@ -148,7 +163,7 @@ public DbConnection Close() if (_backupConnection != null) { _log.Warn("Backup connection was still defined at time of closing."); - Factory.ConnectionProvider.CloseConnection(_backupConnection); + _connectionAccess.CloseConnection(_backupConnection); _backupConnection = null; } @@ -205,7 +220,7 @@ public DbConnection Disconnect() private void CloseConnection() { - Factory.ConnectionProvider.CloseConnection(_connection); + _connectionAccess.CloseConnection(_connection); _connection = null; } @@ -239,7 +254,7 @@ public DbConnection GetConnection() { if (_ownConnection) { - _connection = Factory.ConnectionProvider.GetConnection(); + _connection = _connectionAccess.GetConnection(); // Will fail if the connection is already enlisted in another transaction. // Probable case: nested transaction scope with connection auto-enlistment enabled. // That is an user error. @@ -362,6 +377,7 @@ private ConnectionManager(SerializationInfo info, StreamingContext context) _connectionReleaseMode = (ConnectionReleaseMode)info.GetValue("connectionReleaseMode", typeof(ConnectionReleaseMode)); _interceptor = (IInterceptor)info.GetValue("interceptor", typeof(IInterceptor)); + _connectionAccess = (IConnectionAccess) info.GetValue("connectionAccess", typeof(IConnectionAccess)); } [SecurityCritical] @@ -371,6 +387,7 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("session", Session, typeof(ISessionImplementor)); info.AddValue("connectionReleaseMode", _connectionReleaseMode, typeof(ConnectionReleaseMode)); info.AddValue("interceptor", _interceptor, typeof(IInterceptor)); + info.AddValue("connectionAccess", _connectionAccess, typeof(IConnectionAccess)); } #endregion @@ -386,28 +403,42 @@ void IDeserializationCallback.OnDeserialization(object sender) public ITransaction BeginTransaction(IsolationLevel isolationLevel) { - Transaction.Begin(isolationLevel); + EnsureTransactionIsCreated(); + _transaction.Begin(isolationLevel); return _transaction; } public ITransaction BeginTransaction() { - Transaction.Begin(); + EnsureTransactionIsCreated(); + _transaction.Begin(); return _transaction; } + private void EnsureTransactionIsCreated() + { + if (_transaction == null) + { + _transaction = Factory.TransactionFactory.CreateTransaction(Session); + } + } + + // Since v5.3 + [Obsolete("Use CurrentTransaction instead, and check for null.")] public ITransaction Transaction { get { - if (_transaction == null) - { - _transaction = Factory.TransactionFactory.CreateTransaction(Session); - } + EnsureTransactionIsCreated(); return _transaction; } } + /// + /// The current transaction if any is ongoing, else . + /// + public ITransaction CurrentTransaction => _transaction; + public void AfterNonTransactionalQuery(bool success) { _log.Debug("After autocommit"); @@ -444,10 +475,32 @@ public IBatcher Batcher public DbCommand CreateCommand() { var result = GetConnection().CreateCommand(); - Transaction.Enlist(result); + EnlistInTransaction(result); return result; } + /// + /// Enlist a command in the current transaction, if any. + /// + /// The command to enlist. + public void EnlistInTransaction(DbCommand command) + { + if (command == null) + throw new ArgumentNullException(nameof(command)); + + if (_transaction != null) + { + _transaction.Enlist(command); + return; + } + + if (command.Transaction != null) + { + _log.Warn("set a nonnull DbCommand.Transaction to null because the Session had no Transaction"); + command.Transaction = null; + } + } + /// /// Enlist the connection into provided transaction if the connection should be enlisted. /// Do nothing in case an explicit transaction is ongoing. @@ -456,7 +509,13 @@ public DbCommand CreateCommand() public void EnlistIfRequired(System.Transactions.Transaction transaction) { if (transaction == _currentSystemTransaction) + { + // Short-circuit after having stored the transaction : they may be equal, but not the same reference. + // And the previous one may be an already disposed dependent clone, in which case we need to update + // our reference. + _currentSystemTransaction = transaction; return; + } _currentSystemTransaction = transaction; diff --git a/src/NHibernate/AdoNet/Expectations.cs b/src/NHibernate/AdoNet/Expectations.cs index 06a3fa5901f..d8909dfea8a 100644 --- a/src/NHibernate/AdoNet/Expectations.cs +++ b/src/NHibernate/AdoNet/Expectations.cs @@ -144,4 +144,4 @@ public static void VerifyOutcomeBatched(int expectedRowCount, int rowCount, DbCo } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/AdoNet/GenericBatchingBatcher.cs b/src/NHibernate/AdoNet/GenericBatchingBatcher.cs index c7499067cd4..eac197df446 100644 --- a/src/NHibernate/AdoNet/GenericBatchingBatcher.cs +++ b/src/NHibernate/AdoNet/GenericBatchingBatcher.cs @@ -140,6 +140,11 @@ protected override void Dispose(bool isDisposing) _currentBatch.Clear(); } + internal override void OnPreparedBatchStatement(SqlString sqlString) + { + _currentBatch.CurrentStatement = sqlString; + } + private partial class BatchingCommandSet { private readonly string _statementTerminator; @@ -172,6 +177,8 @@ public BatchingCommandSet(GenericBatchingBatcher batcher, char statementTerminat public int CountOfParameters { get; private set; } + public SqlString CurrentStatement { get; set; } + public void Append(DbParameterCollection parameters) { if (CountOfCommands > 0) @@ -183,7 +190,7 @@ public void Append(DbParameterCollection parameters) _commandType = _batcher.CurrentCommand.CommandType; } - _sql.Add(_batcher.CurrentCommandSql.Copy()); + _sql.Add(CurrentStatement); _sqlTypes.AddRange(_batcher.CurrentCommandParameterTypes); foreach (DbParameter parameter in parameters) @@ -207,6 +214,7 @@ public int ExecuteNonQuery() { return 0; } + var batcherCommand = _batcher.Driver.GenerateCommand( _commandType, _sql.ToSqlString(), diff --git a/src/NHibernate/AdoNet/HanaBatchingBatcher.cs b/src/NHibernate/AdoNet/HanaBatchingBatcher.cs index ba098b1771b..9afe0008e21 100644 --- a/src/NHibernate/AdoNet/HanaBatchingBatcher.cs +++ b/src/NHibernate/AdoNet/HanaBatchingBatcher.cs @@ -17,7 +17,7 @@ public partial class HanaBatchingBatcher : AbstractBatcher private int _countOfCommands; private int _totalExpectedRowsAffected; private DbCommand _currentBatch; - private readonly IList _currentBatchCommands = new List(); + private readonly List _currentBatchCommands = new List(); private StringBuilder _currentBatchCommandsLog; public HanaBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor) diff --git a/src/NHibernate/AdoNet/IEmbeddedBatcherFactoryProvider.cs b/src/NHibernate/AdoNet/IEmbeddedBatcherFactoryProvider.cs index 42d96215163..3d380219b5e 100644 --- a/src/NHibernate/AdoNet/IEmbeddedBatcherFactoryProvider.cs +++ b/src/NHibernate/AdoNet/IEmbeddedBatcherFactoryProvider.cs @@ -13,6 +13,6 @@ public interface IEmbeddedBatcherFactoryProvider /// /// The class type. /// - System.Type BatcherFactoryClass { get;} + System.Type BatcherFactoryClass { get; } } } diff --git a/src/NHibernate/AdoNet/NonBatchingBatcher.cs b/src/NHibernate/AdoNet/NonBatchingBatcher.cs index 10b113eb040..134c91ccfad 100644 --- a/src/NHibernate/AdoNet/NonBatchingBatcher.cs +++ b/src/NHibernate/AdoNet/NonBatchingBatcher.cs @@ -56,11 +56,10 @@ protected override int CountOfStatementsInCurrentBatch get { return 1; } } - public override int BatchSize { get { return 1; } set { throw new NotSupportedException("No batch size was defined for the session factory, batching is disabled. Set adonet.batch_size = 1 to enable batching."); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/AdoNet/OracleDataClientBatchingBatcher.cs b/src/NHibernate/AdoNet/OracleDataClientBatchingBatcher.cs index 43256a43391..277e88752c1 100644 --- a/src/NHibernate/AdoNet/OracleDataClientBatchingBatcher.cs +++ b/src/NHibernate/AdoNet/OracleDataClientBatchingBatcher.cs @@ -17,8 +17,8 @@ public partial class OracleDataClientBatchingBatcher : AbstractBatcher private int _countOfCommands; private int _totalExpectedRowsAffected; private DbCommand _currentBatch; - private IDictionary> _parameterValueListHashTable; - private IDictionary _parameterIsAllNullsHashTable; + private Dictionary> _parameterValueListHashTable; + private Dictionary _parameterIsAllNullsHashTable; private StringBuilder _currentBatchCommandsLog; public OracleDataClientBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor) diff --git a/src/NHibernate/AdoNet/SqlClientBatchingBatcher.cs b/src/NHibernate/AdoNet/SqlClientBatchingBatcher.cs index 61eef67666b..88fd9980ca0 100644 --- a/src/NHibernate/AdoNet/SqlClientBatchingBatcher.cs +++ b/src/NHibernate/AdoNet/SqlClientBatchingBatcher.cs @@ -2,13 +2,15 @@ using System; using System.Data.Common; using System.Text; +using System.Threading; +using System.Threading.Tasks; using NHibernate.AdoNet.Util; using NHibernate.Driver; using NHibernate.Exceptions; namespace NHibernate.AdoNet { - public partial class SqlClientBatchingBatcher : AbstractBatcher + public class SqlClientBatchingBatcher : AbstractBatcher { private int _batchSize; private int _totalExpectedRowsAffected; @@ -70,6 +72,47 @@ public override void AddToBatch(IExpectation expectation) } } + public override Task AddToBatchAsync(IExpectation expectation, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + _totalExpectedRowsAffected += expectation.ExpectedRowCount; + var batchUpdate = CurrentCommand; + Driver.AdjustCommand(batchUpdate); + string lineWithParameters = null; + var sqlStatementLogger = Factory.Settings.SqlStatementLogger; + if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled()) + { + lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate); + var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic); + lineWithParameters = formatStyle.Formatter.Format(lineWithParameters); + _currentBatchCommandsLog.Append("command ") + .Append(_currentBatch.CountOfCommands) + .Append(":") + .AppendLine(lineWithParameters); + } + if (Log.IsDebugEnabled()) + { + Log.Debug("Adding to batch:{0}", lineWithParameters); + } + _currentBatch.Append((System.Data.SqlClient.SqlCommand) batchUpdate); + + if (_currentBatch.CountOfCommands >= _batchSize) + { + return ExecuteBatchWithTimingAsync(batchUpdate, cancellationToken); + } + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + protected override void DoExecuteBatch(DbCommand ps) { try @@ -99,6 +142,36 @@ protected override void DoExecuteBatch(DbCommand ps) } } + protected override async Task DoExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + Log.Debug("Executing batch"); + await (CheckReadersAsync(cancellationToken)).ConfigureAwait(false); + await (PrepareAsync(_currentBatch.BatchCommand, cancellationToken)).ConfigureAwait(false); + if (Factory.Settings.SqlStatementLogger.IsDebugEnabled) + { + Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString()); + } + int rowsAffected; + try + { + rowsAffected = _currentBatch.ExecuteNonQuery(); + } + catch (DbException e) + { + throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command."); + } + + Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected, ps); + } + finally + { + ClearCurrentBatch(); + } + } + private SqlClientSqlCommandSet CreateConfiguredBatch() { var result = new SqlClientSqlCommandSet(); diff --git a/src/NHibernate/AdoNet/SqlClientSqlCommandSet.cs b/src/NHibernate/AdoNet/SqlClientSqlCommandSet.cs index 57e681e16e3..bb54028d85e 100644 --- a/src/NHibernate/AdoNet/SqlClientSqlCommandSet.cs +++ b/src/NHibernate/AdoNet/SqlClientSqlCommandSet.cs @@ -76,7 +76,6 @@ private static void AssertHasParameters(SqlCommand command) } } - /// /// Return the batch command to be executed /// diff --git a/src/NHibernate/AdoNet/Util/BasicFormatter.cs b/src/NHibernate/AdoNet/Util/BasicFormatter.cs index 2e601990e06..2bb0ddd48d9 100644 --- a/src/NHibernate/AdoNet/Util/BasicFormatter.cs +++ b/src/NHibernate/AdoNet/Util/BasicFormatter.cs @@ -7,6 +7,8 @@ namespace NHibernate.AdoNet.Util { public class BasicFormatter : IFormatter { + private static readonly INHibernateLogger Logger = NHibernateLogger.For(typeof(BasicFormatter)); + protected const string IndentString = " "; protected const string Initial = "\n "; protected static readonly HashSet beginClauses = new HashSet(); @@ -20,6 +22,7 @@ static BasicFormatter() { beginClauses.Add("left"); beginClauses.Add("right"); + beginClauses.Add("cross"); beginClauses.Add("inner"); beginClauses.Add("outer"); beginClauses.Add("group"); @@ -59,8 +62,16 @@ static BasicFormatter() public virtual string Format(string source) { - using (var fp = new FormatProcess(source)) - return fp.Perform(); + try + { + using (var fp = new FormatProcess(source)) + return fp.Perform(); + } + catch (Exception e) + { + Logger.Warn(e, "Unable to format provided SQL: {0}", source); + return source; + } } #endregion diff --git a/src/NHibernate/AdoNet/Util/DdlFormatter.cs b/src/NHibernate/AdoNet/Util/DdlFormatter.cs index 6e249323ce2..77ebc4a1cca 100644 --- a/src/NHibernate/AdoNet/Util/DdlFormatter.cs +++ b/src/NHibernate/AdoNet/Util/DdlFormatter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Text; using NHibernate.Util; @@ -7,6 +6,8 @@ namespace NHibernate.AdoNet.Util { public class DdlFormatter: IFormatter { + private static readonly INHibernateLogger Logger = NHibernateLogger.For(typeof(DdlFormatter)); + private const string Indent1 = "\n "; private const string Indent2 = "\n "; private const string Indent3 = "\n "; @@ -19,21 +20,20 @@ public class DdlFormatter: IFormatter /// public virtual string Format(string sql) { - if (sql.StartsWith("create table", StringComparison.OrdinalIgnoreCase)) - { - return FormatCreateTable(sql); - } - else if (sql.StartsWith("alter table", StringComparison.OrdinalIgnoreCase)) + try { - return FormatAlterTable(sql); - } - else if (sql.StartsWith("comment on", StringComparison.OrdinalIgnoreCase)) - { - return FormatCommentOn(sql); + if (sql.StartsWith("create table", StringComparison.OrdinalIgnoreCase)) + return FormatCreateTable(sql); + if (sql.StartsWith("alter table", StringComparison.OrdinalIgnoreCase)) + return FormatAlterTable(sql); + if (sql.StartsWith("comment on", StringComparison.OrdinalIgnoreCase)) + return FormatCommentOn(sql); + return Indent1 + sql; } - else + catch (Exception e) { - return Indent1 + sql; + Logger.Warn(e, "Unable to format provided SQL: {0}", sql); + return sql; } } diff --git a/src/NHibernate/AdoNet/Util/SqlStatementLogger.cs b/src/NHibernate/AdoNet/Util/SqlStatementLogger.cs index b911fd9f94b..06a21c491df 100644 --- a/src/NHibernate/AdoNet/Util/SqlStatementLogger.cs +++ b/src/NHibernate/AdoNet/Util/SqlStatementLogger.cs @@ -118,7 +118,7 @@ public string GetParameterLoggableValue(DbParameter parameter) if (IsStringType(parameter.DbType)) { - return string.Concat("'", TruncateWithEllipsis(parameter.Value.ToString(), maxLoggableStringLength), "'"); + return $"'{TruncateWithEllipsis(parameter.Value.ToString(), maxLoggableStringLength).Replace("'", "''")}'"; } if (parameter.Value is DateTime) @@ -134,7 +134,6 @@ public string GetParameterLoggableValue(DbParameter parameter) } return parameter.Value.ToString(); - } private static string GetBufferAsHexString(byte[] buffer) diff --git a/src/NHibernate/AssertionFailure.cs b/src/NHibernate/AssertionFailure.cs index ca4963d7dc6..52cbbdae11d 100644 --- a/src/NHibernate/AssertionFailure.cs +++ b/src/NHibernate/AssertionFailure.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.Serialization; - namespace NHibernate { /// diff --git a/src/NHibernate/Async/Action/CollectionAction.cs b/src/NHibernate/Async/Action/CollectionAction.cs index 16a2a27460e..237d2c34afe 100644 --- a/src/NHibernate/Async/Action/CollectionAction.cs +++ b/src/NHibernate/Async/Action/CollectionAction.cs @@ -74,8 +74,15 @@ public virtual Task ExecuteAfterTransactionCompletionAsync(bool success, Cancell { return Task.FromCanceled(cancellationToken); } - var ck = new CacheKey(key, persister.KeyType, persister.Role, Session.Factory); - return persister.Cache.ReleaseAsync(ck, softLock, cancellationToken); + try + { + var ck = session.GenerateCacheKey(key, persister.KeyType, persister.Role); + return persister.Cache.ReleaseAsync(ck, softLock, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } #endregion diff --git a/src/NHibernate/Async/Action/EntityInsertAction.cs b/src/NHibernate/Async/Action/EntityInsertAction.cs index dced95776c3..17f5c4d0bfb 100644 --- a/src/NHibernate/Async/Action/EntityInsertAction.cs +++ b/src/NHibernate/Async/Action/EntityInsertAction.cs @@ -44,7 +44,6 @@ public override async Task ExecuteAsync(CancellationToken cancellationToken) // else inserted the same pk first, the insert would fail if (!veto) { - await (persister.InsertAsync(id, State, instance, Session, cancellationToken)).ConfigureAwait(false); EntityEntry entry = Session.PersistenceContext.GetEntry(instance); diff --git a/src/NHibernate/Async/AdoNet/AbstractBatcher.cs b/src/NHibernate/Async/AdoNet/AbstractBatcher.cs index 9ac50481b66..ed60b6c704c 100644 --- a/src/NHibernate/Async/AdoNet/AbstractBatcher.cs +++ b/src/NHibernate/Async/AdoNet/AbstractBatcher.cs @@ -58,7 +58,7 @@ protected async Task PrepareAsync(DbCommand cmd, CancellationToken cancellationT cmd.Connection = sessionConnection; } - _connectionManager.Transaction.Enlist(cmd); + _connectionManager.EnlistInTransaction(cmd); Driver.PrepareCommand(cmd); } catch (InvalidOperationException ioe) @@ -79,7 +79,7 @@ public virtual async Task PrepareBatchCommandAsync(CommandType type, } else { - _batchCommand = await (PrepareCommandAsync(type, sql, parameterTypes, cancellationToken)).ConfigureAwait(false); // calls ExecuteBatch() + _batchCommand = await (PrepareCommandAsync(type, sql, parameterTypes, true, cancellationToken)).ConfigureAwait(false); // calls ExecuteBatch() _batchCommandSql = sql; _batchCommandParameterTypes = parameterTypes; } @@ -87,7 +87,16 @@ public virtual async Task PrepareBatchCommandAsync(CommandType type, return _batchCommand; } - public async Task PrepareCommandAsync(CommandType type, SqlString sql, SqlType[] parameterTypes, CancellationToken cancellationToken) + public Task PrepareCommandAsync(CommandType type, SqlString sql, SqlType[] parameterTypes, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return PrepareCommandAsync(type, sql, parameterTypes, false, cancellationToken); + } + + private async Task PrepareCommandAsync(CommandType type, SqlString sql, SqlType[] parameterTypes, bool batch, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); await (OnPreparedCommandAsync(cancellationToken)).ConfigureAwait(false); @@ -96,7 +105,7 @@ public async Task PrepareCommandAsync(CommandType type, SqlString sql // if the command is associated with an ADO.NET Transaction/Connection while // another open one Command is doing something then an exception will be // thrown. - return Generate(type, sql, parameterTypes); + return Generate(type, sql, parameterTypes, batch); } protected virtual Task OnPreparedCommandAsync(CancellationToken cancellationToken) @@ -132,7 +141,7 @@ public async Task ExecuteNonQueryAsync(DbCommand cmd, CancellationToken can } finally { - if (Log.IsDebugEnabled() && duration != null) + if (duration != null) Log.Debug("ExecuteNonQuery took {0} ms", duration.ElapsedMilliseconds); } } @@ -143,13 +152,25 @@ public virtual async Task ExecuteReaderAsync(DbCommand cmd, Cancel await (CheckReadersAsync(cancellationToken)).ConfigureAwait(false); LogCommand(cmd); await (PrepareAsync(cmd, cancellationToken)).ConfigureAwait(false); - Stopwatch duration = null; - if (Log.IsDebugEnabled()) - duration = Stopwatch.StartNew(); - DbDataReader reader = null; + + var duration = Log.IsDebugEnabled() ? Stopwatch.StartNew() : null; + + var reader = await (DoExecuteReaderAsync(cmd, cancellationToken)).ConfigureAwait(false); + + _readersToClose.Add(reader); + LogOpenReader(duration , reader); + return reader; + } + + private async Task DoExecuteReaderAsync(DbCommand cmd, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); try { - reader = await (cmd.ExecuteReaderAsync(cancellationToken)).ConfigureAwait(false); + var reader = await (cmd.ExecuteReaderAsync(cancellationToken)).ConfigureAwait(false); + return _factory.ConnectionProvider.Driver.SupportsMultipleOpenReaders + ? reader + : await (NHybridDataReader.CreateAsync(reader, cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception e) @@ -158,23 +179,6 @@ public virtual async Task ExecuteReaderAsync(DbCommand cmd, Cancel Log.Error(e, "Could not execute query: {0}", cmd.CommandText); throw; } - finally - { - if (Log.IsDebugEnabled() && duration != null && reader != null) - { - Log.Debug("ExecuteReader took {0} ms", duration.ElapsedMilliseconds); - _readersDuration[reader] = duration; - } - } - - if (!_factory.ConnectionProvider.Driver.SupportsMultipleOpenReaders) - { - reader = await (NHybridDataReader.CreateAsync(reader, cancellationToken)).ConfigureAwait(false); - } - - _readersToClose.Add(reader); - LogOpenReader(); - return reader; } /// @@ -225,7 +229,7 @@ protected async Task ExecuteBatchWithTimingAsync(DbCommand ps, CancellationToken duration = Stopwatch.StartNew(); var countBeforeExecutingBatch = CountOfStatementsInCurrentBatch; await (DoExecuteBatchAsync(ps, cancellationToken)).ConfigureAwait(false); - if (Log.IsDebugEnabled() && duration != null) + if (duration != null) Log.Debug("ExecuteBatch for {0} statements took {1} ms", countBeforeExecutingBatch, duration.ElapsedMilliseconds); diff --git a/src/NHibernate/Async/AdoNet/ConnectionManager.cs b/src/NHibernate/Async/AdoNet/ConnectionManager.cs index a7a942d8735..90487416762 100644 --- a/src/NHibernate/Async/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/Async/AdoNet/ConnectionManager.cs @@ -14,8 +14,9 @@ using System.Data.Common; using System.Runtime.Serialization; using System.Security; - +using NHibernate.Connection; using NHibernate.Engine; +using NHibernate.Impl; namespace NHibernate.AdoNet { @@ -61,7 +62,7 @@ async Task InternalGetConnectionAsync() { if (_ownConnection) { - _connection = await (Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); + _connection = await (_connectionAccess.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); // Will fail if the connection is already enlisted in another transaction. // Probable case: nested transaction scope with connection auto-enlistment enabled. // That is an user error. @@ -90,7 +91,7 @@ public async Task CreateCommandAsync(CancellationToken cancellationTo { cancellationToken.ThrowIfCancellationRequested(); var result = (await (GetConnectionAsync(cancellationToken)).ConfigureAwait(false)).CreateCommand(); - Transaction.Enlist(result); + EnlistInTransaction(result); return result; } } diff --git a/src/NHibernate/Async/AdoNet/GenericBatchingBatcher.cs b/src/NHibernate/Async/AdoNet/GenericBatchingBatcher.cs index f769b727a06..549dc3fee4e 100644 --- a/src/NHibernate/Async/AdoNet/GenericBatchingBatcher.cs +++ b/src/NHibernate/Async/AdoNet/GenericBatchingBatcher.cs @@ -89,6 +89,7 @@ public async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) { return 0; } + var batcherCommand = _batcher.Driver.GenerateCommand( _commandType, _sql.ToSqlString(), diff --git a/src/NHibernate/Async/AdoNet/HanaBatchingBatcher.cs b/src/NHibernate/Async/AdoNet/HanaBatchingBatcher.cs index 6deb4b255e9..0fd47edd571 100644 --- a/src/NHibernate/Async/AdoNet/HanaBatchingBatcher.cs +++ b/src/NHibernate/Async/AdoNet/HanaBatchingBatcher.cs @@ -48,9 +48,9 @@ async Task InternalAddToBatchAsync() var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic); lineWithParameters = formatStyle.Formatter.Format(lineWithParameters); _currentBatchCommandsLog.Append("command ") - .Append(_countOfCommands) - .Append(":") - .AppendLine(lineWithParameters); + .Append(_countOfCommands) + .Append(":") + .AppendLine(lineWithParameters); } if (Log.IsDebugEnabled()) { diff --git a/src/NHibernate/Async/AdoNet/NonBatchingBatcher.cs b/src/NHibernate/Async/AdoNet/NonBatchingBatcher.cs index 49d67f22ac5..6b5134c78fb 100644 --- a/src/NHibernate/Async/AdoNet/NonBatchingBatcher.cs +++ b/src/NHibernate/Async/AdoNet/NonBatchingBatcher.cs @@ -66,4 +66,4 @@ protected override Task DoExecuteBatchAsync(DbCommand ps, CancellationToken canc } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/AdoNet/OracleDataClientBatchingBatcher.cs b/src/NHibernate/Async/AdoNet/OracleDataClientBatchingBatcher.cs index 782e2cce4cd..d9363e2d2f3 100644 --- a/src/NHibernate/Async/AdoNet/OracleDataClientBatchingBatcher.cs +++ b/src/NHibernate/Async/AdoNet/OracleDataClientBatchingBatcher.cs @@ -40,9 +40,9 @@ public override Task AddToBatchAsync(IExpectation expectation, CancellationToken var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic); lineWithParameters = formatStyle.Formatter.Format(lineWithParameters); _currentBatchCommandsLog.Append("command ") - .Append(_countOfCommands) - .Append(":") - .AppendLine(lineWithParameters); + .Append(_countOfCommands) + .Append(":") + .AppendLine(lineWithParameters); } if (Log.IsDebugEnabled()) { diff --git a/src/NHibernate/Async/AdoNet/SqlClientBatchingBatcher.cs b/src/NHibernate/Async/AdoNet/SqlClientBatchingBatcher.cs deleted file mode 100644 index c0128ec26ce..00000000000 --- a/src/NHibernate/Async/AdoNet/SqlClientBatchingBatcher.cs +++ /dev/null @@ -1,98 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by AsyncGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -#if NETFX -using System; -using System.Data.Common; -using System.Text; -using NHibernate.AdoNet.Util; -using NHibernate.Driver; -using NHibernate.Exceptions; - -namespace NHibernate.AdoNet -{ - using System.Threading.Tasks; - using System.Threading; - public partial class SqlClientBatchingBatcher : AbstractBatcher - { - - public override Task AddToBatchAsync(IExpectation expectation, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - try - { - _totalExpectedRowsAffected += expectation.ExpectedRowCount; - var batchUpdate = CurrentCommand; - Driver.AdjustCommand(batchUpdate); - string lineWithParameters = null; - var sqlStatementLogger = Factory.Settings.SqlStatementLogger; - if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled()) - { - lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate); - var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic); - lineWithParameters = formatStyle.Formatter.Format(lineWithParameters); - _currentBatchCommandsLog.Append("command ") - .Append(_currentBatch.CountOfCommands) - .Append(":") - .AppendLine(lineWithParameters); - } - if (Log.IsDebugEnabled()) - { - Log.Debug("Adding to batch:{0}", lineWithParameters); - } - _currentBatch.Append((System.Data.SqlClient.SqlCommand)batchUpdate); - - if (_currentBatch.CountOfCommands >= _batchSize) - { - return ExecuteBatchWithTimingAsync(batchUpdate, cancellationToken); - } - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - - protected override async Task DoExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - try - { - Log.Debug("Executing batch"); - await (CheckReadersAsync(cancellationToken)).ConfigureAwait(false); - await (PrepareAsync(_currentBatch.BatchCommand, cancellationToken)).ConfigureAwait(false); - if (Factory.Settings.SqlStatementLogger.IsDebugEnabled) - { - Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString()); - } - int rowsAffected; - try - { - rowsAffected = _currentBatch.ExecuteNonQuery(); - } - catch (DbException e) - { - throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command."); - } - - Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected, ps); - } - finally - { - ClearCurrentBatch(); - } - } - } -} -#endif diff --git a/src/NHibernate/Async/Cache/ICacheConcurrencyStrategy.cs b/src/NHibernate/Async/Cache/ICacheConcurrencyStrategy.cs index 9967ca2dca0..57ad9361beb 100644 --- a/src/NHibernate/Async/Cache/ICacheConcurrencyStrategy.cs +++ b/src/NHibernate/Async/Cache/ICacheConcurrencyStrategy.cs @@ -146,8 +146,8 @@ public static Task GetManyAsync(this ICacheConcurrencyStrategy cache, // PreferMultipleGet yields false if !IBatchableCacheConcurrencyStrategy, no GetMany call should be done // in such case. return ReflectHelper - .CastOrThrow(cache, "batching") - .GetManyAsync(keys, timestamp, cancellationToken); + .CastOrThrow(cache, "batching") + .GetManyAsync(keys, timestamp, cancellationToken); } catch (System.Exception ex) { diff --git a/src/NHibernate/Async/Cache/IQueryCache.cs b/src/NHibernate/Async/Cache/IQueryCache.cs index 5cb2aded412..531f3bf9e2e 100644 --- a/src/NHibernate/Async/Cache/IQueryCache.cs +++ b/src/NHibernate/Async/Cache/IQueryCache.cs @@ -192,19 +192,19 @@ public static Task PutAsync( if (queryCache is IBatchableQueryCache batchableQueryCache) { return batchableQueryCache.PutAsync( - key, queryParameters, - returnTypes, - result, session, cancellationToken); + key, queryParameters, + returnTypes, + result, session, cancellationToken); } #pragma warning disable 618 return queryCache.PutAsync( #pragma warning restore 618 - key, - returnTypes, - result, - queryParameters.NaturalKeyLookup, - session, cancellationToken); + key, + returnTypes, + result, + queryParameters.NaturalKeyLookup, + session, cancellationToken); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Cache/NonstrictReadWriteCache.cs b/src/NHibernate/Async/Cache/NonstrictReadWriteCache.cs index d074aeb3c15..da86e36832a 100644 --- a/src/NHibernate/Async/Cache/NonstrictReadWriteCache.cs +++ b/src/NHibernate/Async/Cache/NonstrictReadWriteCache.cs @@ -31,15 +31,12 @@ public async Task GetAsync(CacheKey key, long txTimestamp, CancellationT log.Debug("Cache lookup: {0}", key); } - object result = await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); - if (result != null) - { - log.Debug("Cache hit"); - } - else + var result = await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); + if (log.IsDebugEnabled()) { - log.Debug("Cache miss"); + log.Debug(result != null ? "Cache hit: {0}" : "Cache miss: {0}", key); } + return result; } @@ -50,15 +47,14 @@ public async Task GetManyAsync(CacheKey[] keys, long timestamp, Cancel { log.Debug("Cache lookup: {0}", string.Join(",", keys.AsEnumerable())); } - var results = await (_cache.GetManyAsync(keys.Select(o => (object) o).ToArray(), cancellationToken)).ConfigureAwait(false); - if (!log.IsDebugEnabled()) - { - return results; - } - for (var i = 0; i < keys.Length; i++) + + var results = await (_cache.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); + if (log.IsDebugEnabled()) { - log.Debug(results[i] != null ? $"Cache hit: {keys[i]}" : $"Cache miss: {keys[i]}"); + log.Debug("Cache hit: {0}", string.Join(",", keys.Where((k, i) => results != null))); + log.Debug("Cache miss: {0}", string.Join(",", keys.Where((k, i) => results == null))); } + return results; } diff --git a/src/NHibernate/Async/Cache/ReadOnlyCache.cs b/src/NHibernate/Async/Cache/ReadOnlyCache.cs index 3d499f0280a..b87852ed39b 100644 --- a/src/NHibernate/Async/Cache/ReadOnlyCache.cs +++ b/src/NHibernate/Async/Cache/ReadOnlyCache.cs @@ -24,12 +24,13 @@ public partial class ReadOnlyCache : IBatchableCacheConcurrencyStrategy public async Task GetAsync(CacheKey key, long timestamp, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - object result = await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); - if (result != null && log.IsDebugEnabled()) + var result = await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); + if (log.IsDebugEnabled()) { - log.Debug("Cache hit: {0}", key); + log.Debug(result != null ? "Cache hit: {0}" : "Cache miss: {0}", key); } - return result; + + return result; } public async Task GetManyAsync(CacheKey[] keys, long timestamp, CancellationToken cancellationToken) @@ -39,15 +40,14 @@ public async Task GetManyAsync(CacheKey[] keys, long timestamp, Cancel { log.Debug("Cache lookup: {0}", string.Join(",", keys.AsEnumerable())); } - var results = await (_cache.GetManyAsync(keys.Select(o => (object) o).ToArray(), cancellationToken)).ConfigureAwait(false); - if (!log.IsDebugEnabled()) - { - return results; - } - for (var i = 0; i < keys.Length; i++) + + var results = await (_cache.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); + if (log.IsDebugEnabled()) { - log.Debug(results[i] != null ? $"Cache hit: {keys[i]}" : $"Cache miss: {keys[i]}"); + log.Debug("Cache hit: {0}", string.Join(",", keys.Where((k, i) => results[i] != null))); + log.Debug("Cache miss: {0}", string.Join(",", keys.Where((k, i) => results[i] == null))); } + return results; } @@ -95,7 +95,7 @@ public async Task PutManyAsync( var skipKeyIndexes = new HashSet(); if (checkKeys.Any()) { - var objects = await (_cache.GetManyAsync(checkKeys.Select(o => (object) o).ToArray(), cancellationToken)).ConfigureAwait(false); + var objects = await (_cache.GetManyAsync(checkKeys.ToArray(), cancellationToken)).ConfigureAwait(false); for (var i = 0; i < objects.Length; i++) { if (objects[i] != null) diff --git a/src/NHibernate/Async/Cache/ReadWriteCache.cs b/src/NHibernate/Async/Cache/ReadWriteCache.cs index 0813d2d8dd4..eac3b2bc339 100644 --- a/src/NHibernate/Async/Cache/ReadWriteCache.cs +++ b/src/NHibernate/Async/Cache/ReadWriteCache.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cache.Access; +using NHibernate.Util; namespace NHibernate.Cache { @@ -19,7 +20,6 @@ namespace NHibernate.Cache using System.Threading; public partial class ReadWriteCache : IBatchableCacheConcurrencyStrategy { - private readonly NHibernate.Util.AsyncLock _lockObjectAsync = new NHibernate.Util.AsyncLock(); /// /// Do not return an item whose timestamp is later than the current @@ -41,7 +41,7 @@ public partial class ReadWriteCache : IBatchableCacheConcurrencyStrategy public async Task GetAsync(CacheKey key, long txTimestamp, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.ReadLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { @@ -52,35 +52,8 @@ public async Task GetAsync(CacheKey key, long txTimestamp, CancellationT /*try { cache.Lock( key );*/ - - ILockable lockable = (ILockable) await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); - - bool gettable = lockable != null && lockable.IsGettable(txTimestamp); - - if (gettable) - { - if (log.IsDebugEnabled()) - { - log.Debug("Cache hit: {0}", key); - } - - return ((CachedItem) lockable).Value; - } - else - { - if (log.IsDebugEnabled()) - { - if (lockable == null) - { - log.Debug("Cache miss: {0}", key); - } - else - { - log.Debug("Cached item was locked: {0}", key); - } - } - return null; - } + var lockable = (ILockable) await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); + return GetValue(txTimestamp, key, lockable); /*} finally { @@ -97,29 +70,14 @@ public async Task GetManyAsync(CacheKey[] keys, long timestamp, Cancel log.Debug("Cache lookup: {0}", string.Join(",", keys.AsEnumerable())); } var result = new object[keys.Length]; - using (await _lockObjectAsync.LockAsync()) + cancellationToken.ThrowIfCancellationRequested(); + using (await (_asyncReaderWriterLock.ReadLockAsync()).ConfigureAwait(false)) { - var lockables = await (_cache.GetManyAsync(keys.Select(o => (object) o).ToArray(), cancellationToken)).ConfigureAwait(false); + var lockables = await (_cache.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); for (var i = 0; i < lockables.Length; i++) { - var lockable = (ILockable) lockables[i]; - var gettable = lockable != null && lockable.IsGettable(timestamp); - - if (gettable) - { - if (log.IsDebugEnabled()) - { - log.Debug("Cache hit: {0}", keys[i]); - } - result[i] = ((CachedItem) lockable).Value; - } - - if (log.IsDebugEnabled()) - { - log.Debug(lockable == null ? "Cache miss: {0}" : "Cached item was locked: {0}", keys[i]); - } - - result[i] = null; + var o = (ILockable) lockables[i]; + result[i] = GetValue(timestamp, keys[i], o); } } return result; @@ -135,7 +93,7 @@ public async Task GetManyAsync(CacheKey[] keys, long timestamp, Cancel public async Task LockAsync(CacheKey key, object version, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { @@ -178,19 +136,20 @@ public async Task PutManyAsync( // MinValue means cache is disabled return result; } + cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { log.Debug("Caching: {0}", string.Join(",", keys.AsEnumerable())); } - var keysArr = keys.Cast().ToArray(); - var lockValue = await (_cache.LockManyAsync(keysArr, cancellationToken)).ConfigureAwait(false); + + var lockValue = await (_cache.LockManyAsync(keys, cancellationToken)).ConfigureAwait(false); try { var putBatch = new Dictionary(); - var lockables = await (_cache.GetManyAsync(keysArr, cancellationToken)).ConfigureAwait(false); + var lockables = await (_cache.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); for (var i = 0; i < keys.Length; i++) { var key = keys[i]; @@ -211,14 +170,9 @@ public async Task PutManyAsync( { if (log.IsDebugEnabled()) { - if (lockable.IsLock) - { - log.Debug("Item was locked: {0}", key); - } - else - { - log.Debug("Item was already cached: {0}", key); - } + log.Debug( + lockable.IsLock ? "Item was locked: {0}" : "Item was already cached: {0}", + key); } result[i] = false; } @@ -231,7 +185,7 @@ public async Task PutManyAsync( } finally { - await (_cache.UnlockManyAsync(keysArr, lockValue, cancellationToken)).ConfigureAwait(false); + await (_cache.UnlockManyAsync(keys, lockValue, cancellationToken)).ConfigureAwait(false); } } return result; @@ -253,8 +207,9 @@ public async Task PutAsync(CacheKey key, object value, long txTimestamp, o // MinValue means cache is disabled return false; } + cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { @@ -282,14 +237,7 @@ public async Task PutAsync(CacheKey key, object value, long txTimestamp, o { if (log.IsDebugEnabled()) { - if (lockable.IsLock) - { - log.Debug("Item was locked: {0}", key); - } - else - { - log.Debug("Item was already cached: {0}", key); - } + log.Debug(lockable.IsLock ? "Item was locked: {0}" : "Item was already cached: {0}", key); } return false; } @@ -325,7 +273,7 @@ private Task DecrementLockAsync(object key, CacheLock @lock, CancellationToken c public async Task ReleaseAsync(CacheKey key, ISoftLock clientLock, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { @@ -398,7 +346,7 @@ public Task RemoveAsync(CacheKey key, CancellationToken cancellationToken) public async Task AfterUpdateAsync(CacheKey key, object value, object version, ISoftLock clientLock, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { @@ -445,7 +393,7 @@ public async Task AfterUpdateAsync(CacheKey key, object value, object vers public async Task AfterInsertAsync(CacheKey key, object value, object version, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _lockObjectAsync.LockAsync()) + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { if (log.IsDebugEnabled()) { @@ -455,7 +403,6 @@ public async Task AfterInsertAsync(CacheKey key, object value, object vers var lockValue = await (_cache.LockAsync(key, cancellationToken)).ConfigureAwait(false); try { - ILockable lockable = (ILockable) await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); if (lockable == null) { diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index ae75c393656..ab7d4f4547e 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -175,14 +175,14 @@ public async Task GetManyAsync( if (Log.IsDebugEnabled()) Log.Debug("checking cached query results in region: '{0}'; {1}", _regionName, StringHelper.CollectionToString(keys)); - var cacheables = (await (_cache.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false)).Cast().ToArray(); + var cacheables = await (_cache.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); var spacesToCheck = new List>(); var checkedSpacesIndexes = new HashSet(); var checkedSpacesTimestamp = new List(); for (var i = 0; i < keys.Length; i++) { - var cacheable = cacheables[i]; + var cacheable = (IList) cacheables[i]; if (cacheable == null) { Log.Debug("query results were not found in cache: {0}", keys[i]); @@ -216,7 +216,7 @@ public async Task GetManyAsync( for (var i = 0; i < keys.Length; i++) { - var cacheable = cacheables[i]; + var cacheable = (IList) cacheables[i]; if (cacheable == null) continue; @@ -253,7 +253,7 @@ public async Task GetManyAsync( { try { - results[i] = await (PerformAssembleAsync(keys[i], finalReturnTypes[i], queryParams.NaturalKeyLookup, session, cacheables[i], cancellationToken)).ConfigureAwait(false); + results[i] = await (PerformAssembleAsync(keys[i], finalReturnTypes[i], queryParams.NaturalKeyLookup, session, (IList) cacheables[i], cancellationToken)).ConfigureAwait(false); } finally { @@ -276,7 +276,7 @@ public async Task GetManyAsync( { try { - await (InitializeCollectionsAsync(finalReturnTypes[i], session, results[i], cacheables[i], cancellationToken)).ConfigureAwait(false); + await (InitializeCollectionsAsync(finalReturnTypes[i], session, results[i], (IList) cacheables[i], cancellationToken)).ConfigureAwait(false); } finally { @@ -470,6 +470,9 @@ protected virtual Task IsUpToDateAsync(ISet spaces, long timestamp { return Task.FromCanceled(cancellationToken); } + if (spaces.Count == 0) + return Task.FromResult(true); + return _updateTimestampsCache.IsUpToDateAsync(spaces, timestamp, cancellationToken); } } diff --git a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs index e5ba6ce0e45..2bfb9719618 100644 --- a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs @@ -22,10 +22,6 @@ namespace NHibernate.Cache using System.Threading; public partial class UpdateTimestampsCache { - private readonly NHibernate.Util.AsyncLock _preInvalidate = new NHibernate.Util.AsyncLock(); - private readonly NHibernate.Util.AsyncLock _invalidate = new NHibernate.Util.AsyncLock(); - private readonly NHibernate.Util.AsyncLock _isUpToDate = new NHibernate.Util.AsyncLock(); - private readonly NHibernate.Util.AsyncLock _areUpToDate = new NHibernate.Util.AsyncLock(); public virtual Task ClearAsync(CancellationToken cancellationToken) { @@ -55,20 +51,20 @@ public Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationTo } } - [MethodImpl()] public virtual async Task PreInvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _preInvalidate.LockAsync()) + if (spaces.Count == 0) + return; + cancellationToken.ThrowIfCancellationRequested(); + + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { //TODO: to handle concurrent writes correctly, this should return a Lock to the client var ts = _updateTimestamps.NextTimestamp() + _updateTimestamps.Timeout; await (SetSpacesTimestampAsync(spaces, ts, cancellationToken)).ConfigureAwait(false); - //TODO: return new Lock(ts); } - - //TODO: return new Lock(ts); } //Since v5.1 @@ -90,11 +86,14 @@ public Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken } } - [MethodImpl()] public virtual async Task InvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _invalidate.LockAsync()) + if (spaces.Count == 0) + return; + cancellationToken.ThrowIfCancellationRequested(); + + using (await (_asyncReaderWriterLock.WriteLockAsync()).ConfigureAwait(false)) { //TODO: to handle concurrent writes correctly, the client should pass in a Lock long ts = _updateTimestamps.NextTimestamp(); @@ -113,16 +112,9 @@ private Task SetSpacesTimestampAsync(IReadOnlyCollection spaces, long ts } try { - if (spaces.Count == 0) - return Task.CompletedTask; - - var timestamps = new object[spaces.Count]; - for (var i = 0; i < timestamps.Length; i++) - { - timestamps[i] = ts; - } - - return _updateTimestamps.PutManyAsync(spaces.ToArray(), timestamps, cancellationToken); + return _updateTimestamps.PutManyAsync( + spaces.ToArray(), + ArrayHelper.Fill(ts, spaces.Count), cancellationToken); } catch (Exception ex) { @@ -130,62 +122,47 @@ private Task SetSpacesTimestampAsync(IReadOnlyCollection spaces, long ts } } - [MethodImpl()] public virtual async Task IsUpToDateAsync(ISet spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _isUpToDate.LockAsync()) - { - if (spaces.Count == 0) - return true; + if (spaces.Count == 0) + return true; + cancellationToken.ThrowIfCancellationRequested(); - var keys = new object[spaces.Count]; - var index = 0; - foreach (var space in spaces) - { - keys[index++] = space; - } - var lastUpdates = await (_updateTimestamps.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); + using (await (_asyncReaderWriterLock.ReadLockAsync()).ConfigureAwait(false)) + { + var lastUpdates = await (_updateTimestamps.GetManyAsync(spaces.ToArray(), cancellationToken)).ConfigureAwait(false); return lastUpdates.All(lastUpdate => !IsOutdated(lastUpdate as long?, timestamp)); } } - [MethodImpl()] public virtual async Task AreUpToDateAsync(ISet[] spaces, long[] timestamps, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _areUpToDate.LockAsync()) + if (spaces.Length == 0) + return Array.Empty(); + + var allSpaces = new HashSet(); + foreach (var sp in spaces) { - var results = new bool[spaces.Length]; - var allSpaces = new HashSet(); - foreach (var sp in spaces) - { - allSpaces.UnionWith(sp); - } + allSpaces.UnionWith(sp); + } - if (allSpaces.Count == 0) - { - for (var i = 0; i < spaces.Length; i++) - { - results[i] = true; - } + if (allSpaces.Count == 0) + return ArrayHelper.Fill(true, spaces.Length); - return results; - } + var keys = allSpaces.ToArray(); + cancellationToken.ThrowIfCancellationRequested(); - var keys = new object[allSpaces.Count]; + using (await (_asyncReaderWriterLock.ReadLockAsync()).ConfigureAwait(false)) + { var index = 0; - foreach (var space in allSpaces) - { - keys[index++] = space; - } - - index = 0; var lastUpdatesBySpace = - (await (_updateTimestamps - .GetManyAsync(keys, cancellationToken)).ConfigureAwait(false)) - .ToDictionary(u => keys[index++], u => u as long?); + (await (_updateTimestamps + .GetManyAsync(keys, cancellationToken)).ConfigureAwait(false)) + .ToDictionary(u => keys[index++], u => u as long?); + var results = new bool[spaces.Length]; for (var i = 0; i < spaces.Length; i++) { var timestamp = timestamps[i]; diff --git a/src/NHibernate/Async/Collection/AbstractPersistentCollection.cs b/src/NHibernate/Async/Collection/AbstractPersistentCollection.cs index 00b1c3cccb3..95897e09f8e 100644 --- a/src/NHibernate/Async/Collection/AbstractPersistentCollection.cs +++ b/src/NHibernate/Async/Collection/AbstractPersistentCollection.cs @@ -12,21 +12,81 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Collection.Generic; +using NHibernate.Collection.Trackers; using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Loader; using NHibernate.Persister.Collection; +using NHibernate.Proxy; using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Collection { - using System.Threading.Tasks; - using System.Threading; public abstract partial class AbstractPersistentCollection : IPersistentCollection, ILazyInitializedCollection { + protected virtual async Task ReadKeyExistenceAsync(TKey elementKey, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!initialized) + { + ThrowLazyInitializationExceptionIfNotConnected(); + CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(this); + ICollectionPersister persister = entry.LoadedPersister; + if (persister.IsExtraLazy) + { + var queueOperationTracker = (AbstractMapQueueOperationTracker) GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) + { + if (HasQueuedOperations) + { + await (session.FlushAsync(cancellationToken)).ConfigureAwait(false); + } + + return persister.IndexExists(entry.LoadedKey, elementKey, session); + } + + if (queueOperationTracker.ContainsKey(elementKey)) + { + return true; + } + + if (queueOperationTracker.Cleared) + { + return false; + } + + if (queueOperationTracker.IsElementKeyQueuedForDelete(elementKey)) + { + return false; + } + + // As keys are unordered we don't have to calculate the current order of the key + return persister.IndexExists(entry.LoadedKey, elementKey, session); + } + Read(); + } + return null; + } + + internal async Task CanSkipElementExistenceCheckAsync(object element, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var queryableCollection = (IQueryableCollection) Session.Factory.GetCollectionPersister(Role); + return + queryableCollection != null && + queryableCollection.ElementType.IsEntityType && + !queryableCollection.ElementPersister.EntityMetamodel.OverridesEquals && + !element.IsProxy() && + !Session.PersistenceContext.IsEntryFor(element) && + await (ForeignKeys.IsTransientFastAsync(queryableCollection.ElementPersister.EntityName, element, Session, cancellationToken)).ConfigureAwait(false) == true; + } + /// /// Initialize the collection, if possible, wrapping any exceptions /// in a runtime exception @@ -92,41 +152,6 @@ public virtual Task ForceInitializationAsync(CancellationToken cancellationToken return Task.CompletedTask; } - public Task GetQueuedOrphansAsync(string entityName, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - try - { - if (HasQueuedOperations) - { - List additions = new List(operationQueue.Count); - List removals = new List(operationQueue.Count); - for (int i = 0; i < operationQueue.Count; i++) - { - IDelayedOperation op = operationQueue[i]; - if (op.AddedInstance != null) - { - additions.Add(op.AddedInstance); - } - if (op.Orphan != null) - { - removals.Add(op.Orphan); - } - } - return GetOrphansAsync(removals, additions, entityName, session, cancellationToken); - } - - return Task.FromResult(CollectionHelper.EmptyCollection); - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - /// /// Called before inserting rows, to ensure that any surrogate keys are fully generated /// @@ -149,60 +174,8 @@ public virtual Task PreInsertAsync(ICollectionPersister persister, CancellationT } } - /// - /// Get all "orphaned" elements - /// - public abstract Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken); - - /// - /// Given a collection of entity instances that used to - /// belong to the collection, and a collection of instances - /// that currently belong, return a collection of orphans - /// - protected virtual async Task GetOrphansAsync(ICollection oldElements, ICollection currentElements, string entityName, ISessionImplementor session, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - // short-circuit(s) - if (currentElements.Count == 0) - { - // no new elements, the old list contains only Orphans - return oldElements; - } - if (oldElements.Count == 0) - { - // no old elements, so no Orphans neither - return oldElements; - } - - IType idType = session.Factory.GetEntityPersister(entityName).IdentifierType; - - // create the collection holding the orphans - List res = new List(); - - // collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access - var currentIds = new HashSet(); - foreach (object current in currentElements) - { - if (current != null && await (ForeignKeys.IsNotTransientSlowAsync(entityName, current, session, cancellationToken)).ConfigureAwait(false)) - { - object currentId = await (ForeignKeys.GetEntityIdentifierIfNotUnsavedAsync(entityName, current, session, cancellationToken)).ConfigureAwait(false); - currentIds.Add(new TypedValue(idType, currentId, false)); - } - } - - // iterate over the *old* list - foreach (object old in oldElements) - { - object oldId = await (ForeignKeys.GetEntityIdentifierIfNotUnsavedAsync(entityName, old, session, cancellationToken)).ConfigureAwait(false); - if (!currentIds.Contains(new TypedValue(idType, oldId, false))) - { - res.Add(old); - } - } - - return res; - } - + // Since 5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] public async Task IdentityRemoveAsync(IList list, object obj, string entityName, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs index fc1d89befe1..3b229ac54b8 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs @@ -15,6 +15,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -27,7 +28,7 @@ namespace NHibernate.Collection.Generic { using System.Threading.Tasks; using System.Threading; - public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IList, IQueryable + public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IReadOnlyList, IList, IQueryable { public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) @@ -76,6 +77,9 @@ public override Task GetDeletesAsync(ICollectionPersister persister } } + //Since 5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -84,8 +88,7 @@ public override Task GetOrphansAsync(object snapshot, string entity } try { - var sn = (ICollection) snapshot; - return GetOrphansAsync(sn, (ICollection) _gbag, entityName, Session, cancellationToken); + return Task.FromResult(GetOrphans(snapshot, entityName)); } catch (Exception ex) { @@ -154,11 +157,9 @@ public override async Task ReadFromAsync(DbDataReader reader, ICollectio // note that if we load this collection from a cartesian product // the multiplicity would be broken ... so use an idbag instead var element = await (role.ReadElementAsync(reader, owner, descriptor.SuffixedElementAliases, Session, cancellationToken)).ConfigureAwait(false); - // NH Different behavior : we don't check for null - // The NH-750 test show how checking for null we are ignoring the not-found tag and - // the DB may have some records ignored by NH. This issue may need some more deep consideration. - //if (element != null) - _gbag.Add((T) element); + + if (element != null) + _gbag.Add((T) element); return element; } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs index 6573a8f8509..6e3ea111d01 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -22,12 +22,13 @@ using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Collection.Generic { using System.Threading.Tasks; using System.Threading; - public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IList, IQueryable + public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IReadOnlyList, IList, IQueryable { /// @@ -155,6 +156,9 @@ public override async Task ReadFromAsync(DbDataReader reader, ICollectio return element; } + //Since 5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -163,8 +167,7 @@ public override Task GetOrphansAsync(object snapshot, string entity } try { - var sn = (ISet)GetSnapshot(); - return GetOrphansAsync(sn.Select(x => x.Value).ToArray(), (ICollection) _values, entityName, Session, cancellationToken); + return Task.FromResult(GetOrphans(snapshot, entityName)); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs index 260877f54b3..1417c98e361 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs @@ -15,6 +15,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -27,9 +28,12 @@ namespace NHibernate.Collection.Generic { using System.Threading.Tasks; using System.Threading; - public partial class PersistentGenericList : AbstractPersistentCollection, IList, IList, IQueryable + public partial class PersistentGenericList : AbstractPersistentCollection, IList, IReadOnlyList, IList, IQueryable { + //Since 5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -38,8 +42,7 @@ public override Task GetOrphansAsync(object snapshot, string entity } try { - var sn = (IList)snapshot; - return GetOrphansAsync((ICollection)sn, (ICollection) WrappedList, entityName, Session, cancellationToken); + return Task.FromResult(GetOrphans(snapshot, entityName)); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index 32819bb279b..e574577a111 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -15,6 +15,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -27,9 +28,12 @@ namespace NHibernate.Collection.Generic { using System.Threading.Tasks; using System.Threading; - public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, ICollection + public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, IReadOnlyDictionary, ICollection { + //Since 5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -38,8 +42,7 @@ public override Task GetOrphansAsync(object snapshot, string entity } try { - var sn = (IDictionary) snapshot; - return GetOrphansAsync((ICollection)sn.Values, (ICollection)WrappedMap.Values, entityName, Session, cancellationToken); + return Task.FromResult(GetOrphans(snapshot, entityName)); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs index f20da3da39e..91f2f86f626 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Linq.Expressions; using NHibernate.Collection.Generic.SetHelpers; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -28,9 +29,12 @@ namespace NHibernate.Collection.Generic { using System.Threading.Tasks; using System.Threading; - public partial class PersistentGenericSet : AbstractPersistentCollection, ISet, IQueryable + public partial class PersistentGenericSet : AbstractPersistentCollection, ISet, IReadOnlyCollection, IQueryable { + //Since 5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -39,12 +43,7 @@ public override Task GetOrphansAsync(object snapshot, string entity } try { - var sn = new SetSnapShot((IEnumerable)snapshot); - - // TODO: Avoid duplicating shortcuts and array copy, by making base class GetOrphans() more flexible - if (WrappedSet.Count == 0) return Task.FromResult(sn); - if (((ICollection)sn).Count == 0) return Task.FromResult(sn); - return GetOrphansAsync(sn, WrappedSet.ToArray(), entityName, Session, cancellationToken); + return Task.FromResult(GetOrphans(snapshot, entityName)); } catch (Exception ex) { @@ -56,13 +55,12 @@ public override async Task EqualsSnapshotAsync(ICollectionPersister persis { cancellationToken.ThrowIfCancellationRequested(); var elementType = persister.ElementType; - var snapshot = (ISetSnapshot)GetSnapshot(); + var snapshot = (SetSnapShot)GetSnapshot(); if (((ICollection)snapshot).Count != WrappedSet.Count) { return false; } - foreach (T obj in WrappedSet) { T oldValue; @@ -125,12 +123,11 @@ public override async Task GetDeletesAsync(ICollectionPersister per { cancellationToken.ThrowIfCancellationRequested(); IType elementType = persister.ElementType; - var sn = (ISetSnapshot)GetSnapshot(); + var sn = (SetSnapShot)GetSnapshot(); var deletes = new List(((ICollection)sn).Count); deletes.AddRange(sn.Where(obj => !WrappedSet.Contains(obj))); - foreach (var obj in WrappedSet) { T oldValue; @@ -144,7 +141,7 @@ public override async Task GetDeletesAsync(ICollectionPersister per public override async Task NeedsInsertingAsync(object entry, int i, IType elemType, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var sn = (ISetSnapshot)GetSnapshot(); + var sn = (SetSnapShot)GetSnapshot(); T oldKey; // note that it might be better to iterate the snapshot but this is safe, @@ -169,4 +166,4 @@ public override Task NeedsUpdatingAsync(object entry, int i, IType elemTyp } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Collection/ILazyInitializedCollection.cs b/src/NHibernate/Async/Collection/ILazyInitializedCollection.cs index d853a513792..f09c28bb698 100644 --- a/src/NHibernate/Async/Collection/ILazyInitializedCollection.cs +++ b/src/NHibernate/Async/Collection/ILazyInitializedCollection.cs @@ -20,6 +20,5 @@ public partial interface ILazyInitializedCollection /// /// A cancellation token that can be used to cancel the work Task ForceInitializationAsync(CancellationToken cancellationToken); - } } diff --git a/src/NHibernate/Async/Collection/IPersistentCollection.cs b/src/NHibernate/Async/Collection/IPersistentCollection.cs index 14bdcec49fe..cd2bb132dcb 100644 --- a/src/NHibernate/Async/Collection/IPersistentCollection.cs +++ b/src/NHibernate/Async/Collection/IPersistentCollection.cs @@ -11,6 +11,8 @@ using System; using System.Collections; using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Collection.Generic; using NHibernate.Engine; using NHibernate.Loader; @@ -19,8 +21,6 @@ namespace NHibernate.Collection { - using System.Threading.Tasks; - using System.Threading; public partial interface IPersistentCollection { @@ -92,27 +92,11 @@ public partial interface IPersistentCollection /// Task GetDeletesAsync(ICollectionPersister persister, bool indexIsFormula, CancellationToken cancellationToken); - /// Get the "queued" orphans - Task GetQueuedOrphansAsync(string entityName, CancellationToken cancellationToken); - /// /// Called before inserting rows, to ensure that any surrogate keys are fully generated /// /// /// A cancellation token that can be used to cancel the work Task PreInsertAsync(ICollectionPersister persister, CancellationToken cancellationToken); - - /// - /// Get all "orphaned" elements - /// - /// The snapshot of the collection. - /// The persistent class whose objects - /// the collection is expected to contain. - /// A cancellation token that can be used to cancel the work - /// - /// An that contains all of the elements - /// that have been orphaned. - /// - Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken); } } diff --git a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs index 4a8665de6f9..448309f2f9e 100644 --- a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs @@ -27,17 +27,23 @@ namespace NHibernate.Collection public partial class PersistentArrayHolder : AbstractPersistentCollection, ICollection { - public override async Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) + //Since 5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] + public override Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - object[] sn = (object[]) snapshot; - object[] arr = (object[]) array; - List result = new List(sn); - for (int i = 0; i < sn.Length; i++) + if (cancellationToken.IsCancellationRequested) { - await (IdentityRemoveAsync(result, arr[i], entityName, Session, cancellationToken)).ConfigureAwait(false); + return Task.FromCanceled(cancellationToken); + } + try + { + return Task.FromResult(GetOrphans(snapshot, entityName)); + } + catch (Exception ex) + { + return Task.FromException(ex); } - return result; } public override async Task EqualsSnapshotAsync(ICollectionPersister persister, CancellationToken cancellationToken) @@ -147,4 +153,4 @@ public override async Task NeedsUpdatingAsync(object entry, int i, IType e && await (elemType.IsDirtyAsync(array.GetValue(i), sn.GetValue(i), Session, cancellationToken)).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Connection/ConnectionProvider.cs b/src/NHibernate/Async/Connection/ConnectionProvider.cs index 72638e38277..df3ab7cced7 100644 --- a/src/NHibernate/Async/Connection/ConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/ConnectionProvider.cs @@ -10,7 +10,6 @@ using System; using System.Collections; -using System.Configuration; using System.Data.Common; using NHibernate.Driver; @@ -30,6 +29,23 @@ public abstract partial class ConnectionProvider : IConnectionProvider /// /// A cancellation token that can be used to cancel the work /// An open . - public abstract Task GetConnectionAsync(CancellationToken cancellationToken); + public virtual Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return GetConnectionAsync(ConnectionString, cancellationToken); + } + + //TODO 6.0: Make abstract + /// + /// Gets an open for given connectionString + /// + /// An open . + public virtual Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) + { + throw new NotImplementedException("This method must be overriden."); + } } } diff --git a/src/NHibernate/Async/Connection/DriverConnectionProvider.cs b/src/NHibernate/Async/Connection/DriverConnectionProvider.cs index 4c3358a5943..c0c21554592 100644 --- a/src/NHibernate/Async/Connection/DriverConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/DriverConnectionProvider.cs @@ -22,21 +22,20 @@ public partial class DriverConnectionProvider : ConnectionProvider /// Gets a new open through /// the . /// - /// A cancellation token that can be used to cancel the work /// /// An Open . /// /// /// If there is any problem creating or opening the . /// - public override async Task GetConnectionAsync(CancellationToken cancellationToken) + public override async Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); log.Debug("Obtaining DbConnection from Driver"); var conn = Driver.CreateConnection(); try { - conn.ConnectionString = ConnectionString; + conn.ConnectionString = connectionString; await (conn.OpenAsync(cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } @@ -49,4 +48,4 @@ public override async Task GetConnectionAsync(CancellationToken ca return conn; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Connection/IConnectionAccess.cs b/src/NHibernate/Async/Connection/IConnectionAccess.cs new file mode 100644 index 00000000000..eb8c1ebd506 --- /dev/null +++ b/src/NHibernate/Async/Connection/IConnectionAccess.cs @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Data.Common; + +namespace NHibernate.Connection +{ + using System.Threading.Tasks; + using System.Threading; + public partial interface IConnectionAccess + { + + //ObtainConnection in hibernate + /// + /// Gets the database connection. + /// + /// A cancellation token that can be used to cancel the work + /// The database connection. + Task GetConnectionAsync(CancellationToken cancellationToken); + } +} diff --git a/src/NHibernate/Async/Connection/IConnectionProvider.cs b/src/NHibernate/Async/Connection/IConnectionProvider.cs index 61587ec2e6f..3a74648f9fd 100644 --- a/src/NHibernate/Async/Connection/IConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/IConnectionProvider.cs @@ -12,11 +12,31 @@ using System.Collections.Generic; using System.Data.Common; using NHibernate.Driver; +using NHibernate.Util; namespace NHibernate.Connection { using System.Threading.Tasks; using System.Threading; + public static partial class ConnectionProviderExtensions + { + internal static Task GetConnectionAsync(this IConnectionProvider connectionProvider, string connectionString, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return ReflectHelper.CastOrThrow(connectionProvider, "open connection by connectionString").GetConnectionAsync(connectionString, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } + public partial interface IConnectionProvider : IDisposable { @@ -27,4 +47,4 @@ public partial interface IConnectionProvider : IDisposable /// An open . Task GetConnectionAsync(CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs b/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs index 45ce11aa0e0..a4950405bf4 100644 --- a/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs +++ b/src/NHibernate/Async/Connection/UserSuppliedConnectionProvider.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Data.Common; - namespace NHibernate.Connection { using System.Threading.Tasks; @@ -24,7 +23,6 @@ public partial class UserSuppliedConnectionProvider : ConnectionProvider /// Throws an if this method is called /// because the user is responsible for creating s. /// - /// A cancellation token that can be used to cancel the work /// /// No value is returned because an is thrown. /// @@ -32,9 +30,9 @@ public partial class UserSuppliedConnectionProvider : ConnectionProvider /// Thrown when this method is called. User is responsible for creating /// s. /// - public override Task GetConnectionAsync(CancellationToken cancellationToken) + public override Task GetConnectionAsync(string connectionString, CancellationToken cancellationToken) { throw new InvalidOperationException("The user must provide an ADO.NET connection - NHibernate is not creating it."); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Context/ThreadLocalSessionContext.cs b/src/NHibernate/Async/Context/ThreadLocalSessionContext.cs index 01fada343b8..f31006d976b 100644 --- a/src/NHibernate/Async/Context/ThreadLocalSessionContext.cs +++ b/src/NHibernate/Async/Context/ThreadLocalSessionContext.cs @@ -32,17 +32,16 @@ private static async Task CleanupAnyOrphanedSessionAsync(ISessionFactory factory try { - if (orphan.Transaction != null && orphan.Transaction.IsActive) + try { - try - { - await (orphan.Transaction.RollbackAsync(cancellationToken)).ConfigureAwait(false); - } - catch (OperationCanceledException) { throw; } - catch (Exception ex) - { - log.Debug(ex, "Unable to rollback transaction for orphaned session"); - } + var transaction = orphan.GetCurrentTransaction(); + if (transaction?.IsActive == true) + await (transaction.RollbackAsync(cancellationToken)).ConfigureAwait(false); + } + catch (OperationCanceledException) { throw; } + catch (Exception ex) + { + log.Debug(ex, "Unable to rollback transaction for orphaned session"); } orphan.Close(); } diff --git a/src/NHibernate/Async/Criterion/QueryOver.cs b/src/NHibernate/Async/Criterion/QueryOver.cs index afd2e581858..00b1cf97452 100644 --- a/src/NHibernate/Async/Criterion/QueryOver.cs +++ b/src/NHibernate/Async/Criterion/QueryOver.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Linq; @@ -20,6 +19,7 @@ using NHibernate.Loader; using NHibernate.SqlCommand; using NHibernate.Transform; +using NHibernate.Util; namespace NHibernate.Criterion { @@ -63,7 +63,6 @@ private Task SingleOrDefaultAsync(CancellationToken cancellationToken) return criteria.UniqueResultAsync(cancellationToken); } - Task> IQueryOver.ListAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -131,7 +130,5 @@ Task IQueryOver.SingleOrDefaultAsync(CancellationToken cancellation } return SingleOrDefaultAsync(cancellationToken); } - } - } diff --git a/src/NHibernate/Async/Dialect/Lock/UpdateLockingStrategy.cs b/src/NHibernate/Async/Dialect/Lock/UpdateLockingStrategy.cs index 0d39ebf758f..4487f5944b4 100644 --- a/src/NHibernate/Async/Dialect/Lock/UpdateLockingStrategy.cs +++ b/src/NHibernate/Async/Dialect/Lock/UpdateLockingStrategy.cs @@ -78,13 +78,13 @@ async Task InternalLockAsync() catch (Exception sqle) { var exceptionContext = new AdoExceptionContextInfo - { - SqlException = sqle, - Message = "could not lock: " + MessageHelper.InfoString(lockable, id, factory), - Sql = sql.ToString(), - EntityName = lockable.EntityName, - EntityId = id - }; + { + SqlException = sqle, + Message = "could not lock: " + MessageHelper.InfoString(lockable, id, factory), + Sql = sql.ToString(), + EntityName = lockable.EntityName, + EntityId = id + }; throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, exceptionContext); } } diff --git a/src/NHibernate/Async/Driver/BasicResultSetsCommand.cs b/src/NHibernate/Async/Driver/BasicResultSetsCommand.cs index e703a079a88..83560a9bfd0 100644 --- a/src/NHibernate/Async/Driver/BasicResultSetsCommand.cs +++ b/src/NHibernate/Async/Driver/BasicResultSetsCommand.cs @@ -31,7 +31,7 @@ public virtual async Task GetReaderAsync(int? commandTimeout, Canc var batcher = Session.Batcher; SqlType[] sqlTypes = Commands.SelectMany(c => c.ParameterTypes).ToArray(); ForEachSqlCommand((sqlLoaderCommand, offset) => sqlLoaderCommand.ResetParametersIndexesForTheCommand(offset)); - var command = batcher.PrepareQueryCommand(CommandType.Text, sqlString, sqlTypes); + var command = batcher.PrepareQueryCommand(CommandType.Text, Sql, sqlTypes); if (commandTimeout.HasValue) { command.CommandTimeout = commandTimeout.Value; diff --git a/src/NHibernate/Async/Engine/ActionQueue.cs b/src/NHibernate/Async/Engine/ActionQueue.cs index 9a405ab0087..3a0ab25b1f5 100644 --- a/src/NHibernate/Async/Engine/ActionQueue.cs +++ b/src/NHibernate/Async/Engine/ActionQueue.cs @@ -11,28 +11,27 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using NHibernate.Action; using NHibernate.Cache; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Engine { public partial class ActionQueue { - private async Task ExecuteActionsAsync(IList list, CancellationToken cancellationToken) + private async Task ExecuteActionsAsync(List list, CancellationToken cancellationToken) where T: IExecutable { cancellationToken.ThrowIfCancellationRequested(); // Actions may raise events to which user code can react and cause changes to action list. // It will then fail here due to list being modified. (Some previous code was dodging the // trouble with a for loop which was not failing provided the list was not getting smaller. // But then it was clearing it without having executed added actions (if any), ...) - - foreach (IExecutable executable in list) + foreach (var executable in list) { await (InnerExecuteAsync(executable, cancellationToken)).ConfigureAwait(false); } @@ -46,7 +45,7 @@ private Task PreInvalidateCachesAsync(CancellationToken cancellationToken) { return Task.FromCanceled(cancellationToken); } - if (session.Factory.Settings.IsQueryCacheEnabled) + if (session.Factory.Settings.IsQueryCacheEnabled && executedSpaces.Count > 0) { return session.Factory.UpdateTimestampsCache.PreInvalidateAsync(executedSpaces, cancellationToken); } @@ -118,10 +117,10 @@ public async Task ExecuteActionsAsync(CancellationToken cancellationToken) } } - private static async Task PrepareActionsAsync(IList queue, CancellationToken cancellationToken) + private static async Task PrepareActionsAsync(List queue, CancellationToken cancellationToken) where T: IExecutable { cancellationToken.ThrowIfCancellationRequested(); - foreach (IExecutable executable in queue) + foreach (var executable in queue) await (executable.BeforeExecutionsAsync(cancellationToken)).ConfigureAwait(false); } @@ -166,7 +165,7 @@ public async Task AfterTransactionCompletionAsync(bool success, CancellationToke private async Task InvalidateCachesAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (session.Factory.Settings.IsQueryCacheEnabled) + if (session.Factory.Settings.IsQueryCacheEnabled && executedSpaces.Count > 0) { await (session.Factory.UpdateTimestampsCache.InvalidateAsync(executedSpaces, cancellationToken)).ConfigureAwait(false); } diff --git a/src/NHibernate/Async/Engine/Cascade.cs b/src/NHibernate/Async/Engine/Cascade.cs index 77e8b93bfb1..c0165e5cf66 100644 --- a/src/NHibernate/Async/Engine/Cascade.cs +++ b/src/NHibernate/Async/Engine/Cascade.cs @@ -26,7 +26,6 @@ namespace NHibernate.Engine public sealed partial class Cascade { - /// Cascade an action from the parent entity instance to all its children. /// The parent's entity persister /// The parent reference. @@ -117,7 +116,8 @@ private async Task CascadePropertyAsync(object parent, object child, IType type, // value is orphaned if loaded state for this property shows not null // because it is currently null. EntityEntry entry = eventSource.PersistenceContext.GetEntry(parent); - if (entry != null && entry.Status != Status.Saving) + //LoadedState is null when detached entity is cascaded from session.Update context + if (entry?.LoadedState != null && entry.Status != Status.Saving) { object loadedValue; if (componentPathStack.Count == 0) @@ -284,11 +284,11 @@ private async Task DeleteOrphansAsync(string entityName, IPersistentCollection p if (pc.WasInitialized) { CollectionEntry ce = eventSource.PersistenceContext.GetCollectionEntry(pc); - orphans = ce == null ? CollectionHelper.EmptyCollection : await (ce.GetOrphansAsync(entityName, pc, cancellationToken)).ConfigureAwait(false); + orphans = ce == null ? CollectionHelper.EmptyCollection : ce.GetOrphans(entityName, pc); } else { - orphans = await (pc.GetQueuedOrphansAsync(entityName, cancellationToken)).ConfigureAwait(false); + orphans = pc.GetQueuedOrphans(entityName); } foreach (object orphan in orphans) diff --git a/src/NHibernate/Async/Engine/CollectionEntry.cs b/src/NHibernate/Async/Engine/CollectionEntry.cs index 97d590c3d37..ea5694df0fd 100644 --- a/src/NHibernate/Async/Engine/CollectionEntry.cs +++ b/src/NHibernate/Async/Engine/CollectionEntry.cs @@ -10,6 +10,8 @@ using System; using System.Collections; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Action; using NHibernate.Collection; using NHibernate.Impl; @@ -17,8 +19,6 @@ namespace NHibernate.Engine { - using System.Threading.Tasks; - using System.Threading; public partial class CollectionEntry { @@ -70,18 +70,5 @@ public async Task PreFlushAsync(IPersistentCollection collection, CancellationTo reached = false; processed = false; } - - public Task GetOrphansAsync(string entityName, IPersistentCollection collection, CancellationToken cancellationToken) - { - if (snapshot == null) - { - throw new AssertionFailure("no collection snapshot for orphan delete"); - } - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return collection.GetOrphansAsync(snapshot, entityName, cancellationToken); - } } } diff --git a/src/NHibernate/Async/Engine/Collections.cs b/src/NHibernate/Async/Engine/Collections.cs index b87792fea7c..6219e1c3320 100644 --- a/src/NHibernate/Async/Engine/Collections.cs +++ b/src/NHibernate/Async/Engine/Collections.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Collection; using NHibernate.Impl; using NHibernate.Persister.Collection; @@ -204,13 +203,13 @@ private static Task PrepareCollectionForUpdateAsync(IPersistentCollection collec { // it is or was referenced _somewhere_ bool ownerChanged = loadedPersister != currentPersister || - !currentPersister.KeyType.IsEqual(entry.LoadedKey, entry.CurrentKey, factory); + !currentPersister.KeyType.IsEqual(entry.LoadedKey, entry.CurrentKey, factory); if (ownerChanged) { // do a check bool orphanDeleteAndRoleChanged = loadedPersister != null && - currentPersister != null && loadedPersister.HasOrphanDelete; + currentPersister != null && loadedPersister.HasOrphanDelete; if (orphanDeleteAndRoleChanged) { diff --git a/src/NHibernate/Async/Engine/ForeignKeys.cs b/src/NHibernate/Async/Engine/ForeignKeys.cs index 441dbcb87d4..8185a996af4 100644 --- a/src/NHibernate/Async/Engine/ForeignKeys.cs +++ b/src/NHibernate/Async/Engine/ForeignKeys.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Id; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -99,7 +98,6 @@ private async Task NullifyTransientReferencesAsync(object value, IType t private async Task IsNullifiableAsync(string entityName, object obj, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - //if (obj == org.hibernate.intercept.LazyPropertyInitializer_Fields.UNFETCHED_PROPERTY) // return false; //this is kinda the best we can do... @@ -157,10 +155,6 @@ private async Task IsNullifiableAsync(string entityName, object obj, Cance public static async Task IsNotTransientSlowAsync(string entityName, object entity, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (entity.IsProxy()) - return true; - if (session.PersistenceContext.IsEntryFor(entity)) - return true; return !await (IsTransientSlowAsync(entityName, entity, session, cancellationToken)).ConfigureAwait(false); } @@ -271,21 +265,10 @@ public static async Task GetEntityIdentifierIfNotUnsavedAsync(string ent if ((await (IsTransientFastAsync(entityName, entity, session, cancellationToken)).ConfigureAwait(false)).GetValueOrDefault()) { - /***********************************************/ - // TODO NH verify the behavior of NH607 test - // these lines are only to pass test NH607 during PersistenceContext porting - // i'm not secure that NH607 is a test for a right behavior - EntityEntry entry = session.PersistenceContext.GetEntry(entity); - if (entry != null) - return entry.Id; - // the check was put here to have les possible impact - /**********************************************/ - entityName = entityName ?? session.GuessEntityName(entity); string entityString = entity.ToString(); throw new TransientObjectException( string.Format("object references an unsaved transient instance - save the transient instance before flushing or set cascade action for the property to something that would make it autosave. Type: {0}, Entity: {1}", entityName, entityString)); - } id = session.GetEntityPersister(entityName, entity).GetIdentifier(entity); } diff --git a/src/NHibernate/Async/Engine/ISessionImplementor.cs b/src/NHibernate/Async/Engine/ISessionImplementor.cs index d5108c3e8f8..0036bb9918e 100644 --- a/src/NHibernate/Async/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Async/Engine/ISessionImplementor.cs @@ -30,7 +30,7 @@ namespace NHibernate.Engine { using System.Threading.Tasks; using System.Threading; - internal static partial class SessionImplementorExtensions + public static partial class SessionImplementorExtensions { internal static async Task AutoFlushIfRequiredAsync(this ISessionImplementor implementor, ISet querySpaces, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs b/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs index 853d1ad1261..f0e5674ea69 100644 --- a/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs +++ b/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs @@ -58,7 +58,7 @@ public async Task PerformListAsync(QueryParameters queryParameters, ISessionImpl } IList combinedResults = results ?? new List(); - IdentitySet distinction = new IdentitySet(); + var distinction = new HashSet(ReferenceComparer.Instance); int includedCount = -1; for (int i = 0; i < Translators.Length; i++) { @@ -128,7 +128,7 @@ public async Task PerformIterateAsync(QueryParameters queryParamete public async Task> PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return new SafetyEnumerable(await (PerformIterateAsync(queryParameters, session, cancellationToken)).ConfigureAwait(false)); + return (await (PerformIterateAsync(queryParameters, session, cancellationToken)).ConfigureAwait(false)).CastOrDefault(); } public async Task PerformExecuteUpdateAsync(QueryParameters queryParameters, ISessionImplementor session, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Engine/Versioning.cs b/src/NHibernate/Async/Engine/Versioning.cs index 69db5134409..061f598a9b8 100644 --- a/src/NHibernate/Async/Engine/Versioning.cs +++ b/src/NHibernate/Async/Engine/Versioning.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Persister.Entity; using NHibernate.Type; diff --git a/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs index fed30c185b7..6b53cb2685a 100644 --- a/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultAutoFlushEventListener.cs @@ -56,7 +56,6 @@ public virtual async Task OnAutoFlushAsync(AutoFlushEvent @event, CancellationTo } else { - if (log.IsDebugEnabled()) log.Debug("Dont need to execute flush"); source.ActionQueue.ClearFromFlushNeededCheck(oldSize); diff --git a/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs index be81efc248f..58be2bfad01 100644 --- a/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs @@ -37,7 +37,7 @@ public virtual Task OnDeleteAsync(DeleteEvent @event, CancellationToken cancella { return Task.FromCanceled(cancellationToken); } - return OnDeleteAsync(@event, new IdentitySet(), cancellationToken); + return OnDeleteAsync(@event, new HashSet(ReferenceComparer.Instance), cancellationToken); } public virtual async Task OnDeleteAsync(DeleteEvent @event, ISet transientEntities, CancellationToken cancellationToken) @@ -145,7 +145,7 @@ protected virtual async Task DeleteTransientEntityAsync(IEventSource session, ob // NH different impl : NH-1895 if(transientEntities == null) { - transientEntities = new HashSet(); + transientEntities = new HashSet(ReferenceComparer.Instance); } if (!transientEntities.Add(entity)) { diff --git a/src/NHibernate/Async/Event/Default/DefaultDirtyCheckEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultDirtyCheckEventListener.cs index 5862db48115..f4b47a26a9c 100644 --- a/src/NHibernate/Async/Event/Default/DefaultDirtyCheckEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultDirtyCheckEventListener.cs @@ -10,7 +10,6 @@ using System; - namespace NHibernate.Event.Default { using System.Threading.Tasks; diff --git a/src/NHibernate/Async/Event/Default/DefaultEvictEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultEvictEventListener.cs index 8a21bbe5602..12f93dc6289 100644 --- a/src/NHibernate/Async/Event/Default/DefaultEvictEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultEvictEventListener.cs @@ -65,7 +65,6 @@ public virtual async Task OnEvictAsync(EvictEvent @event, CancellationToken canc protected virtual async Task DoEvictAsync(object obj, EntityKey key, IEntityPersister persister, IEventSource session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (log.IsDebugEnabled()) { log.Debug("evicting {0}", MessageHelper.InfoString(persister)); diff --git a/src/NHibernate/Async/Event/Default/DefaultFlushEntityEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultFlushEntityEventListener.cs index 1341bccd9d2..d70bbb4b562 100644 --- a/src/NHibernate/Async/Event/Default/DefaultFlushEntityEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultFlushEntityEventListener.cs @@ -351,7 +351,6 @@ protected Task IsUpdateNecessaryAsync(FlushEntityEvent @event, Cancellatio } else { - int[] dirtyProperties = @event.DirtyProperties; if (dirtyProperties != null && dirtyProperties.Length != 0) { @@ -455,7 +454,6 @@ protected virtual async Task DirtyCheckAsync(FlushEntityEvent @event, Cancellati @event.DirtyCheckPossible = !cannotDirtyCheck; } - private async Task GetDatabaseSnapshotAsync(ISessionImplementor session, IEntityPersister persister, object id, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -483,6 +481,5 @@ private async Task GetDatabaseSnapshotAsync(ISessionImplementor sessio return session.PersistenceContext.GetCachedDatabaseSnapshot(entityKey); } } - } } diff --git a/src/NHibernate/Async/Event/Default/DefaultLoadEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultLoadEventListener.cs index a351b6f0437..e71e91aee2b 100644 --- a/src/NHibernate/Async/Event/Default/DefaultLoadEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultLoadEventListener.cs @@ -47,7 +47,6 @@ public virtual async Task OnLoadAsync(LoadEvent @event, LoadType loadType, Cance if (persister == null) { - var message = new StringBuilder(512); message.AppendLine(string.Format("Unable to locate persister for the entity named '{0}'.", @event.EntityClassName)); message.AppendLine("The persister define the persistence strategy for an entity."); @@ -393,7 +392,6 @@ protected virtual async Task LoadFromSessionCacheAsync(LoadEvent @event, return old; } - /// Attempts to load the entity from the second-level cache. /// The load event /// The persister for the entity being requested for load diff --git a/src/NHibernate/Async/Event/Default/DefaultMergeEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultMergeEventListener.cs index 9384b786b79..abb74ca8b20 100644 --- a/src/NHibernate/Async/Event/Default/DefaultMergeEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultMergeEventListener.cs @@ -19,7 +19,6 @@ using NHibernate.Proxy; using NHibernate.Type; - namespace NHibernate.Event.Default { using System.Threading.Tasks; diff --git a/src/NHibernate/Async/Event/Default/DefaultRefreshEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultRefreshEventListener.cs index 780ebf7dd73..7d745ec2fa3 100644 --- a/src/NHibernate/Async/Event/Default/DefaultRefreshEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultRefreshEventListener.cs @@ -130,8 +130,7 @@ public virtual async Task OnRefreshAsync(RefreshEvent @event, IDictionary refres if (!persister.IsMutable) source.SetReadOnly(result, true); else - source.SetReadOnly(result, (e == null ? source.DefaultReadOnly : e.IsReadOnly)); - + source.SetReadOnly(result, e == null ? source.DefaultReadOnly : !e.IsModifiableEntity()); source.FetchProfile = previousFetchProfile; // NH Different behavior : we are ignoring transient entities without throw any kind of exception diff --git a/src/NHibernate/Async/Event/Default/DefaultSaveOrUpdateEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultSaveOrUpdateEventListener.cs index 99aa1b95e7a..1b7dd4c99d7 100644 --- a/src/NHibernate/Async/Event/Default/DefaultSaveOrUpdateEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultSaveOrUpdateEventListener.cs @@ -71,7 +71,7 @@ protected virtual async Task PerformSaveOrUpdateAsync(SaveOrUpdateEvent case EntityState.Persistent: return EntityIsPersistent(@event); - default: //TRANSIENT or DELETED + default: //TRANSIENT or DELETED return await (EntityIsTransientAsync(@event, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Event/Default/EvictVisitor.cs b/src/NHibernate/Async/Event/Default/EvictVisitor.cs index c6432848862..7bf193fca0b 100644 --- a/src/NHibernate/Async/Event/Default/EvictVisitor.cs +++ b/src/NHibernate/Async/Event/Default/EvictVisitor.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Impl; diff --git a/src/NHibernate/Async/Event/Default/ReattachVisitor.cs b/src/NHibernate/Async/Event/Default/ReattachVisitor.cs index c6a81a49ee5..9bee8f1b652 100644 --- a/src/NHibernate/Async/Event/Default/ReattachVisitor.cs +++ b/src/NHibernate/Async/Event/Default/ReattachVisitor.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Action; using NHibernate.Engine; using NHibernate.Impl; diff --git a/src/NHibernate/Async/Event/Default/WrapVisitor.cs b/src/NHibernate/Async/Event/Default/WrapVisitor.cs index 42c9442d258..8423f1a2d43 100644 --- a/src/NHibernate/Async/Event/Default/WrapVisitor.cs +++ b/src/NHibernate/Async/Event/Default/WrapVisitor.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Persister.Collection; diff --git a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs index 3629a87f063..26e84fd0b65 100644 --- a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs +++ b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs @@ -68,9 +68,9 @@ protected virtual async Task DropTemporaryTableIfNecessaryAsync(IQueryable persi { IIsolatedWork work = new TmpIdTableDropIsolatedWork(persister, log, session); - if (ShouldIsolateTemporaryTableDDL()) + if (ShouldIsolateTemporaryTableDDL() && session.ConnectionManager.CurrentTransaction != null) { - session.ConnectionManager.Transaction.RegisterSynchronization( + session.ConnectionManager.CurrentTransaction.RegisterSynchronization( new IsolatedWorkAfterTransaction(work, session)); } else diff --git a/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs b/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs index e36847d2988..8c78a478fca 100644 --- a/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs +++ b/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs @@ -77,7 +77,7 @@ public async Task ListAsync(ISessionImplementor session, QueryParameters int size = results.Count; var tmp = new List(); - var distinction = new IdentitySet(); + var distinction = new HashSet(ReferenceComparer.Instance); for ( int i = 0; i < size; i++ ) { diff --git a/src/NHibernate/Async/Hql/IQueryTranslator.cs b/src/NHibernate/Async/Hql/IQueryTranslator.cs index 879d940e3a7..fd11e81d03b 100644 --- a/src/NHibernate/Async/Hql/IQueryTranslator.cs +++ b/src/NHibernate/Async/Hql/IQueryTranslator.cs @@ -47,4 +47,4 @@ public partial interface IQueryTranslator /// Task ExecuteUpdateAsync(QueryParameters queryParameters, ISessionImplementor session, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/IQueryOver.cs b/src/NHibernate/Async/IQueryOver.cs index 78f1f2b1fe1..28109dc0ec7 100644 --- a/src/NHibernate/Async/IQueryOver.cs +++ b/src/NHibernate/Async/IQueryOver.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -66,7 +65,5 @@ public partial interface IQueryOver : IQueryOver /// /// A cancellation token that can be used to cancel the work Task SingleOrDefaultAsync(CancellationToken cancellationToken = default(CancellationToken)); - } - } diff --git a/src/NHibernate/Async/ISession.cs b/src/NHibernate/Async/ISession.cs index fbe6f6d19a6..71cf958c903 100644 --- a/src/NHibernate/Async/ISession.cs +++ b/src/NHibernate/Async/ISession.cs @@ -26,6 +26,76 @@ namespace NHibernate { using System.Threading.Tasks; using System.Threading; + public static partial class SessionExtensions + { + + /// + /// Return the persistent instance of the given entity class with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The session. + /// The entity name. + /// The entity identifier. + /// The lock mode to use for getting the entity. + /// A cancellation token that can be used to cancel the work + /// A persistent instance, or . + public static Task GetAsync(this ISession session, string entityName, object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return + ReflectHelper + .CastOrThrow(session, "Get with entityName and lockMode") + .GetAsync(entityName, id, lockMode, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// The lock mode to use for getting the entity. + /// A cancellation token that can be used to cancel the work + /// A persistent instance, or . + public static async Task GetAsync(this ISession session, string entityName, object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return (T) await (session.GetAsync(entityName, id, lockMode, cancellationToken)).ConfigureAwait(false); + } + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// A cancellation token that can be used to cancel the work + /// A persistent instance, or . + public static async Task GetAsync(this ISession session, string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return (T) await (session.GetAsync(entityName, id, cancellationToken)).ConfigureAwait(false); + } + } public partial interface ISession : IDisposable { diff --git a/src/NHibernate/Async/ISessionFactory.cs b/src/NHibernate/Async/ISessionFactory.cs index 744c59b3bde..482f8af4ea5 100644 --- a/src/NHibernate/Async/ISessionFactory.cs +++ b/src/NHibernate/Async/ISessionFactory.cs @@ -17,6 +17,7 @@ using NHibernate.Impl; using NHibernate.Metadata; using NHibernate.Stat; +using NHibernate.Util; namespace NHibernate { @@ -24,6 +25,44 @@ namespace NHibernate using System.Threading; public static partial class SessionFactoryExtension { + /// + /// Evict an entry from the second-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// The name of the entity to evict. + /// + /// Tenant identifier + /// A cancellation token that can be used to cancel the work + public static async Task EvictEntityAsync(this ISessionFactory factory, string entityName, object id, string tenantIdentifier, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (tenantIdentifier == null) + await (factory.EvictEntityAsync(entityName, id, cancellationToken)).ConfigureAwait(false); + + await (ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictEntityAsync(entityName, id, tenantIdentifier, cancellationToken)).ConfigureAwait(false); + } + + /// + /// Evict an entry from the process-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// Collection role name. + /// Collection id + /// Tenant identifier + /// A cancellation token that can be used to cancel the work + public static async Task EvictCollectionAsync(this ISessionFactory factory, string roleName, object id, string tenantIdentifier, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (tenantIdentifier == null) + await (factory.EvictCollectionAsync(roleName, id, cancellationToken)).ConfigureAwait(false); + + await (ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictCollectionAsync(roleName, id, tenantIdentifier, cancellationToken)).ConfigureAwait(false); + } + /// /// Evict all entries from the process-level cache. This method occurs outside /// of any transaction; it performs an immediate "hard" remove, so does not respect diff --git a/src/NHibernate/Async/IStatelessSession.cs b/src/NHibernate/Async/IStatelessSession.cs index 5e86ca768c8..040f5b92e7c 100644 --- a/src/NHibernate/Async/IStatelessSession.cs +++ b/src/NHibernate/Async/IStatelessSession.cs @@ -22,6 +22,46 @@ namespace NHibernate { using System.Threading.Tasks; using System.Threading; + public static partial class StatelessSessionExtensions + { + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// The lock mode to use for getting the entity. + /// A cancellation token that can be used to cancel the work + /// A persistent instance, or . + public static async Task GetAsync(this IStatelessSession session, string entityName, object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return (T) await (session.GetAsync(entityName, id, lockMode, cancellationToken)).ConfigureAwait(false); + } + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// A cancellation token that can be used to cancel the work + /// A persistent instance, or . + public static async Task GetAsync(this IStatelessSession session, string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return (T) await (session.GetAsync(entityName, id, cancellationToken)).ConfigureAwait(false); + } + } public partial interface IStatelessSession : IDisposable { diff --git a/src/NHibernate/Async/Id/Enhanced/OptimizerFactory.cs b/src/NHibernate/Async/Id/Enhanced/OptimizerFactory.cs index 63f63c9ff6c..5ba08f4a31e 100644 --- a/src/NHibernate/Async/Id/Enhanced/OptimizerFactory.cs +++ b/src/NHibernate/Async/Id/Enhanced/OptimizerFactory.cs @@ -24,13 +24,11 @@ public partial class OptimizerFactory public partial class HiLoOptimizer : OptimizerSupport { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); - [MethodImpl()] public override async Task GenerateAsync(IAccessCallback callback, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { if (_lastSourceValue < 0) { @@ -51,6 +49,7 @@ public override async Task GenerateAsync(IAccessCallback callback, Cance _lastSourceValue = await (callback.GetNextValueAsync(cancellationToken)).ConfigureAwait(false); _upperLimit = (_lastSourceValue * IncrementSize) + 1; } + return Make(_value++); } } @@ -101,13 +100,11 @@ public abstract partial class OptimizerSupport : IOptimizer public partial class PooledOptimizer : OptimizerSupport, IInitialValueAwareOptimizer { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); - [MethodImpl()] public override async Task GenerateAsync(IAccessCallback callback, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { if (_hiValue < 0) { @@ -134,6 +131,7 @@ public override async Task GenerateAsync(IAccessCallback callback, Cance _hiValue = await (callback.GetNextValueAsync(cancellationToken)).ConfigureAwait(false); _value = _hiValue - IncrementSize; } + return Make(_value++); } } @@ -145,13 +143,11 @@ public override async Task GenerateAsync(IAccessCallback callback, Cance public partial class PooledLoOptimizer : OptimizerSupport { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); - [MethodImpl()] public override async Task GenerateAsync(IAccessCallback callback, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { if (_lastSourceValue < 0 || _value >= (_lastSourceValue + IncrementSize)) { @@ -161,6 +157,7 @@ public override async Task GenerateAsync(IAccessCallback callback, Cance while (_value < 1) _value++; } + return Make(_value++); } } diff --git a/src/NHibernate/Async/Id/Enhanced/TableGenerator.cs b/src/NHibernate/Async/Id/Enhanced/TableGenerator.cs index be2da466fff..0ca2de00611 100644 --- a/src/NHibernate/Async/Id/Enhanced/TableGenerator.cs +++ b/src/NHibernate/Async/Id/Enhanced/TableGenerator.cs @@ -26,20 +26,16 @@ namespace NHibernate.Id.Enhanced using System.Threading; public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGenerator, IConfigurable { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); - - [MethodImpl()] public virtual async Task GenerateAsync(ISessionImplementor session, object obj, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { return await (Optimizer.GenerateAsync(new TableAccessCallback(session, this), cancellationToken)).ConfigureAwait(false); } } - private partial class TableAccessCallback : IAccessCallback { @@ -54,7 +50,6 @@ public async Task GetNextValueAsync(CancellationToken cancellationToken) #endregion } - public override async Task DoWorkInCurrentTransactionAsync(ISessionImplementor session, DbConnection conn, DbTransaction transaction, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -108,7 +103,6 @@ public override async Task DoWorkInCurrentTransactionAsync(ISessionImple throw; } - try { var updateCmd = session.Factory.ConnectionProvider.Driver.GenerateCommand(CommandType.Text, updateQuery, updateParameterTypes); diff --git a/src/NHibernate/Async/Id/IIdentifierGenerator.cs b/src/NHibernate/Async/Id/IIdentifierGenerator.cs index 12d7141668c..d9e06bee10f 100644 --- a/src/NHibernate/Async/Id/IIdentifierGenerator.cs +++ b/src/NHibernate/Async/Id/IIdentifierGenerator.cs @@ -15,7 +15,6 @@ namespace NHibernate.Id using System.Threading.Tasks; using System.Threading; - public partial interface IIdentifierGenerator { /// @@ -27,4 +26,4 @@ public partial interface IIdentifierGenerator /// The new identifier Task GenerateAsync(ISessionImplementor session, object obj, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Id/IPostInsertIdentityPersister.cs b/src/NHibernate/Async/Id/IPostInsertIdentityPersister.cs index d1c6a87a6cf..886bee0cf25 100644 --- a/src/NHibernate/Async/Id/IPostInsertIdentityPersister.cs +++ b/src/NHibernate/Async/Id/IPostInsertIdentityPersister.cs @@ -17,6 +17,7 @@ using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Id { diff --git a/src/NHibernate/Async/Id/IdentityGenerator.cs b/src/NHibernate/Async/Id/IdentityGenerator.cs index d70e2f80e40..6908290a417 100644 --- a/src/NHibernate/Async/Id/IdentityGenerator.cs +++ b/src/NHibernate/Async/Id/IdentityGenerator.cs @@ -62,4 +62,4 @@ protected internal override Task GetResultAsync(ISessionImplementor sess } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Id/IncrementGenerator.cs b/src/NHibernate/Async/Id/IncrementGenerator.cs index 0fd915d15cf..4df097d6624 100644 --- a/src/NHibernate/Async/Id/IncrementGenerator.cs +++ b/src/NHibernate/Async/Id/IncrementGenerator.cs @@ -19,6 +19,7 @@ using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Id { @@ -26,7 +27,6 @@ namespace NHibernate.Id using System.Threading; public partial class IncrementGenerator : IIdentifierGenerator, IConfigurable { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); /// /// @@ -35,16 +35,16 @@ public partial class IncrementGenerator : IIdentifierGenerator, IConfigurable /// /// A cancellation token that can be used to cancel the work /// - [MethodImpl()] public async Task GenerateAsync(ISessionImplementor session, object obj, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { if (_sql != null) { await (GetNextAsync(session, cancellationToken)).ConfigureAwait(false); } + return IdentifierGeneratorFactory.CreateNumber(_next++, _returnClass); } } diff --git a/src/NHibernate/Async/Id/Insert/IBinder.cs b/src/NHibernate/Async/Id/Insert/IBinder.cs index 394c8491836..18a008641e6 100644 --- a/src/NHibernate/Async/Id/Insert/IBinder.cs +++ b/src/NHibernate/Async/Id/Insert/IBinder.cs @@ -18,4 +18,4 @@ public partial interface IBinder { Task BindValuesAsync(DbCommand cm, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Id/SelectGenerator.cs b/src/NHibernate/Async/Id/SelectGenerator.cs index 0cf33c629f4..5e52da2d44f 100644 --- a/src/NHibernate/Async/Id/SelectGenerator.cs +++ b/src/NHibernate/Async/Id/SelectGenerator.cs @@ -18,6 +18,7 @@ using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Id { diff --git a/src/NHibernate/Async/Id/SequenceHiLoGenerator.cs b/src/NHibernate/Async/Id/SequenceHiLoGenerator.cs index 94ee6d72da5..75992f456ed 100644 --- a/src/NHibernate/Async/Id/SequenceHiLoGenerator.cs +++ b/src/NHibernate/Async/Id/SequenceHiLoGenerator.cs @@ -23,7 +23,6 @@ namespace NHibernate.Id using System.Threading; public partial class SequenceHiLoGenerator : SequenceGenerator { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); #region IIdentifierGenerator Members @@ -35,11 +34,10 @@ public partial class SequenceHiLoGenerator : SequenceGenerator /// The entity for which the id is being generated. /// A cancellation token that can be used to cancel the work /// The new identifier as a , , or . - [MethodImpl()] public override async Task GenerateAsync(ISessionImplementor session, object obj, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { if (maxLo < 1) { diff --git a/src/NHibernate/Async/Id/TableGenerator.cs b/src/NHibernate/Async/Id/TableGenerator.cs index b2731653a92..3ad468a09be 100644 --- a/src/NHibernate/Async/Id/TableGenerator.cs +++ b/src/NHibernate/Async/Id/TableGenerator.cs @@ -29,7 +29,6 @@ namespace NHibernate.Id using System.Threading; public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGenerator, IConfigurable { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); #region IIdentifierGenerator Members @@ -41,11 +40,10 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe /// The entity for which the id is being generated. /// A cancellation token that can be used to cancel the work /// The new identifier as a , , or . - [MethodImpl()] public virtual async Task GenerateAsync(ISessionImplementor session, object obj, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { // This has to be done using a different connection to the containing // transaction becase the new hi value must remain valid even if the diff --git a/src/NHibernate/Async/Id/TableHiLoGenerator.cs b/src/NHibernate/Async/Id/TableHiLoGenerator.cs index 8302dad5e98..663733f6b39 100644 --- a/src/NHibernate/Async/Id/TableHiLoGenerator.cs +++ b/src/NHibernate/Async/Id/TableHiLoGenerator.cs @@ -23,7 +23,6 @@ namespace NHibernate.Id using System.Threading; public partial class TableHiLoGenerator : TableGenerator { - private readonly NHibernate.Util.AsyncLock _generate = new NHibernate.Util.AsyncLock(); #region IIdentifierGenerator Members @@ -34,11 +33,10 @@ public partial class TableHiLoGenerator : TableGenerator /// The entity for which the id is being generated. /// A cancellation token that can be used to cancel the work /// The new identifier as a . - [MethodImpl()] public override async Task GenerateAsync(ISessionImplementor session, object obj, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (await _generate.LockAsync()) + using (await (_asyncLock.LockAsync()).ConfigureAwait(false)) { if (maxLo < 1) { diff --git a/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs b/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs index 00fad92ccd3..f61abfe4c17 100644 --- a/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs +++ b/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs @@ -119,6 +119,7 @@ public abstract partial class AbstractQueryImpl2 : AbstractQueryImpl } // Since v5.2 + /// [Obsolete("This method has no usages and will be removed in a future version")] protected internal override Task> GetTranslatorsAsync(ISessionImplementor sessionImplementor, QueryParameters queryParameters, CancellationToken cancellationToken) { diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index 2d28c429495..ed3948ad38e 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -17,6 +17,7 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; +using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -27,9 +28,11 @@ using NHibernate.Loader.Custom; using NHibernate.Loader.Custom.Sql; using NHibernate.Multi; +using NHibernate.MultiTenancy; using NHibernate.Persister.Entity; using NHibernate.Transaction; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Impl { @@ -68,6 +71,7 @@ public virtual async Task> ListAsync(IQueryExpression query, QueryPa } } + //TODO 6.0: Make abstract public virtual async Task> ListAsync(CriteriaImpl criteria, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -79,17 +83,16 @@ public virtual async Task> ListAsync(CriteriaImpl criteria, Cancella } } + //TODO 6.0: Make virtual public abstract Task ListAsync(CriteriaImpl criteria, IList results, CancellationToken cancellationToken); + //{ + // ArrayHelper.AddAll(results, List(criteria)); + //} public virtual async Task ListAsync(CriteriaImpl criteria, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) - { - var results = new List(); - await (ListAsync(criteria, results, cancellationToken)).ConfigureAwait(false); - return results; - } + return (await (ListAsync(criteria, cancellationToken)).ConfigureAwait(false)).ToIList(); } public abstract Task ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); diff --git a/src/NHibernate/Async/Impl/CollectionFilterImpl.cs b/src/NHibernate/Async/Impl/CollectionFilterImpl.cs index 46a53d0219a..af6b061848e 100644 --- a/src/NHibernate/Async/Impl/CollectionFilterImpl.cs +++ b/src/NHibernate/Async/Impl/CollectionFilterImpl.cs @@ -61,6 +61,7 @@ public partial class CollectionFilterImpl : QueryImpl } // Since v5.2 + /// [Obsolete("This method has no usages and will be removed in a future version")] protected internal override Task> GetTranslatorsAsync(ISessionImplementor session, QueryParameters queryParameters, CancellationToken cancellationToken) { diff --git a/src/NHibernate/Async/Impl/CriteriaImpl.cs b/src/NHibernate/Async/Impl/CriteriaImpl.cs index 81e735bc052..478ad6b58b6 100644 --- a/src/NHibernate/Async/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Async/Impl/CriteriaImpl.cs @@ -29,18 +29,22 @@ public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria, ISupp public async Task ListAsync(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - var results = new List(); - await (ListAsync(results, cancellationToken)).ConfigureAwait(false); - return results; + return (await (ListAsync(cancellationToken)).ConfigureAwait(false)).ToIList(); } public async Task ListAsync(IList results, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ArrayHelper.AddAll(results, await (ListAsync(cancellationToken)).ConfigureAwait(false)); + } + + public async Task> ListAsync(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); Before(); try { - await (session.ListAsync(this, results, cancellationToken)).ConfigureAwait(false); + return await (session.ListAsync(this, cancellationToken)).ConfigureAwait(false); } finally { @@ -48,14 +52,6 @@ public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria, ISupp } } - public async Task> ListAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - List results = new List(); - await (ListAsync(results, cancellationToken)).ConfigureAwait(false); - return results; - } - public async Task UniqueResultAsync(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs b/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs index a473450df66..d98e200c5e9 100644 --- a/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs +++ b/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs @@ -81,6 +81,7 @@ internal partial class ExpressionFilterImpl : ExpressionQueryImpl } // Since v5.2 + /// [Obsolete("This method has no usages and will be removed in a future version")] protected internal override Task> GetTranslatorsAsync(ISessionImplementor session, QueryParameters queryParameters, CancellationToken cancellationToken) { diff --git a/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs new file mode 100644 index 00000000000..99e8efa7c31 --- /dev/null +++ b/src/NHibernate/Async/Impl/NonContextualConnectionAccess.cs @@ -0,0 +1,33 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.Impl +{ + using System.Threading.Tasks; + using System.Threading; + partial class NonContextualConnectionAccess : IConnectionAccess + { + + /// + public Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _sessionFactory.ConnectionProvider.GetConnectionAsync(cancellationToken); + } + } +} diff --git a/src/NHibernate/Async/Impl/SessionFactoryImpl.cs b/src/NHibernate/Async/Impl/SessionFactoryImpl.cs index af52013f08b..50d4a4d6a11 100644 --- a/src/NHibernate/Async/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Async/Impl/SessionFactoryImpl.cs @@ -31,6 +31,7 @@ using NHibernate.Id; using NHibernate.Mapping; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -63,6 +64,16 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb public async Task CloseAsync(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); + if (isClosed) + { + if (log.IsDebugEnabled()) + { + log.Debug("Already closed"); + } + + return; + } + log.Info("Closing"); isClosed = true; @@ -121,24 +132,7 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb { return Task.FromCanceled(cancellationToken); } - try - { - IEntityPersister p = GetEntityPersister(persistentClass.FullName); - if (p.HasCache) - { - if (log.IsDebugEnabled()) - { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id)); - } - CacheKey ck = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); - return p.Cache.RemoveAsync(ck, cancellationToken); - } - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } + return EvictEntityAsync(persistentClass.FullName, id, cancellationToken); } public Task EvictAsync(System.Type persistentClass, CancellationToken cancellationToken = default(CancellationToken)) @@ -226,7 +220,7 @@ async Task InternalEvictEntityAsync() if (log.IsDebugEnabled()) { log.Debug("evicting second-level cache for: {0}", - string.Join(", ", cacheGroup.Select(p => p.EntityName))); + string.Join(", ", cacheGroup.Select(p => p.EntityName))); } await (cacheGroup.Key.ClearAsync(cancellationToken)).ConfigureAwait(false); } @@ -234,6 +228,15 @@ async Task InternalEvictEntityAsync() } public Task EvictEntityAsync(string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return EvictEntityAsync(entityName, id, null, cancellationToken); + } + + public Task EvictEntityAsync(string entityName, object id, string tenantIdentifier, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -246,9 +249,9 @@ async Task InternalEvictEntityAsync() { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id, this)); + LogEvict(tenantIdentifier, MessageHelper.InfoString(p, id, this)); } - CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); + CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName, tenantIdentifier); return p.Cache.RemoveAsync(cacheKey, cancellationToken); } return Task.CompletedTask; @@ -260,6 +263,15 @@ async Task InternalEvictEntityAsync() } public Task EvictCollectionAsync(string roleName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return EvictCollectionAsync(roleName, id, null, cancellationToken); + } + + public Task EvictCollectionAsync(string roleName, object id, string tenantIdentifier, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -272,9 +284,10 @@ async Task InternalEvictEntityAsync() { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.CollectionInfoString(p, id)); + LogEvict(tenantIdentifier, MessageHelper.CollectionInfoString(p, id)); } - CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role); + + CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role, tenantIdentifier); return p.Cache.RemoveAsync(ck, cancellationToken); } return Task.CompletedTask; @@ -327,7 +340,7 @@ async Task InternalEvictCollectionAsync() if (log.IsDebugEnabled()) { log.Debug("evicting second-level cache for: {0}", - string.Join(", ", cacheGroup.Select(p => p.Role))); + string.Join(", ", cacheGroup.Select(p => p.Role))); } await (cacheGroup.Key.ClearAsync(cancellationToken)).ConfigureAwait(false); } diff --git a/src/NHibernate/Async/Impl/SessionImpl.cs b/src/NHibernate/Async/Impl/SessionImpl.cs index 5905e6801a9..b219757f21f 100644 --- a/src/NHibernate/Async/Impl/SessionImpl.cs +++ b/src/NHibernate/Async/Impl/SessionImpl.cs @@ -26,6 +26,7 @@ using NHibernate.Intercept; using NHibernate.Loader.Criteria; using NHibernate.Loader.Custom; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -803,24 +804,21 @@ public override async Task AutoFlushIfRequiredAsync(ISet querySpac return LoadAsync(entityClass.FullName, id, cancellationToken); } + /// public async Task GetAsync(object id, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) - { - return (T)await (GetAsync(typeof(T), id, cancellationToken)).ConfigureAwait(false); - } + return (T) await (GetAsync(typeof(T), id, cancellationToken)).ConfigureAwait(false); } + /// public async Task GetAsync(object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) - { - return (T)await (GetAsync(typeof(T), id, lockMode, cancellationToken)).ConfigureAwait(false); - } + return (T) await (GetAsync(typeof(T), id, lockMode, cancellationToken)).ConfigureAwait(false); } + /// public Task GetAsync(System.Type entityClass, object id, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) @@ -830,29 +828,50 @@ public override async Task AutoFlushIfRequiredAsync(ISet querySpac return GetAsync(entityClass.FullName, id, cancellationToken); } - /// - /// Load the data for the object with the specified id into a newly created object - /// using "for update", if supported. A new key will be assigned to the object. - /// This should return an existing proxy where appropriate. - /// - /// If the object does not exist in the database, null is returned. - /// - /// - /// - /// - /// A cancellation token that can be used to cancel the work - /// - public async Task GetAsync(System.Type clazz, object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) + /// + public Task GetAsync(System.Type clazz, object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return GetAsync(clazz.FullName, id, lockMode, cancellationToken); + } + + /// + public async Task GetAsync(string entityName, object id, LockMode lockMode, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (BeginProcess()) { - LoadEvent loadEvent = new LoadEvent(id, clazz.FullName, lockMode, this); + LoadEvent loadEvent = new LoadEvent(id, entityName, lockMode, this); await (FireLoadAsync(loadEvent, LoadEventListener.Get, cancellationToken)).ConfigureAwait(false); + //Note: AfterOperation call is skipped to avoid releasing the lock when outside of a transaction. return loadEvent.Result; } } + /// + public async Task GetAsync(string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + using (BeginProcess()) + { + LoadEvent loadEvent = new LoadEvent(id, entityName, null, this); + bool success = false; + try + { + await (FireLoadAsync(loadEvent, LoadEventListener.Get, cancellationToken)).ConfigureAwait(false); + success = true; + return loadEvent.Result; + } + finally + { + await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false); + } + } + } + public async Task GetEntityNameAsync(object obj, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -882,26 +901,6 @@ public override async Task AutoFlushIfRequiredAsync(ISet querySpac } } - public async Task GetAsync(string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) - { - LoadEvent loadEvent = new LoadEvent(id, entityName, false, this); - bool success = false; - try - { - await (FireLoadAsync(loadEvent, LoadEventListener.Get, cancellationToken)).ConfigureAwait(false); - success = true; - return loadEvent.Result; - } - finally - { - await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false); - } - } - } - /// /// Load the data for the object with the specified id into a newly created object. /// This is only called when lazily initializing a proxy. @@ -924,7 +923,6 @@ public override async Task ImmediateLoadAsync(string entityName, object } } - /// /// Return the object with the specified id or throw exception if no row with that id exists. Defer the load, /// return a new proxy or return an existing proxy if possible. Do not check if the object was deleted. @@ -1122,20 +1120,22 @@ public override async Task> EnumerableFilterAsync(object colle } } - public override async Task ListAsync(CriteriaImpl criteria, IList results, CancellationToken cancellationToken) + public override async Task> ListAsync(CriteriaImpl criteria, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (BeginProcess()) { string[] implementors = Factory.GetImplementors(criteria.EntityOrClassName); int size = implementors.Length; + if (size == 0) + throw new QueryException(criteria.EntityOrClassName + " is not mapped"); CriteriaLoader[] loaders = new CriteriaLoader[size]; ISet spaces = new HashSet(); for (int i = 0; i < size; i++) { - loaders[i] = new CriteriaLoader( + var loader = new CriteriaLoader( GetOuterJoinLoadable(implementors[i]), Factory, criteria, @@ -1143,7 +1143,8 @@ public override async Task ListAsync(CriteriaImpl criteria, IList results, Cance enabledFilters ); - spaces.UnionWith(loaders[i].QuerySpaces); + spaces.UnionWith(loader.QuerySpaces); + loaders[size - 1 - i] = loader; } await (AutoFlushIfRequiredAsync(spaces, cancellationToken)).ConfigureAwait(false); @@ -1153,11 +1154,9 @@ public override async Task ListAsync(CriteriaImpl criteria, IList results, Cance { try { - for (int i = size - 1; i >= 0; i--) - { - ArrayHelper.AddAll(results, await (loaders[i].ListAsync(this, cancellationToken)).ConfigureAwait(false)); - } + var results = await (loaders.LoadAllToListAsync(this, cancellationToken)).ConfigureAwait(false); success = true; + return results; } catch (OperationCanceledException) { throw; } catch (HibernateException) @@ -1177,6 +1176,13 @@ public override async Task ListAsync(CriteriaImpl criteria, IList results, Cance } } + //TODO 6.0: Remove (use base class implementation) + public override async Task ListAsync(CriteriaImpl criteria, IList results, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ArrayHelper.AddAll(results, await (ListAsync(criteria, cancellationToken)).ConfigureAwait(false)); + } + /// /// remove any hard references to the entity that are held by the infrastructure /// (references held by application or other persistant instances are okay) diff --git a/src/NHibernate/Async/Impl/SqlQueryImpl.cs b/src/NHibernate/Async/Impl/SqlQueryImpl.cs index 31b2edb0dc7..f4a0342ce37 100644 --- a/src/NHibernate/Async/Impl/SqlQueryImpl.cs +++ b/src/NHibernate/Async/Impl/SqlQueryImpl.cs @@ -11,6 +11,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -98,6 +99,7 @@ public partial class SqlQueryImpl : AbstractQueryImpl, ISQLQuery, ISynchronizabl Before(); try { + ComputeFlattenedParameters(); return await (Session.ExecuteNativeUpdateAsync(GenerateQuerySpecification(namedParams), GetQueryParameters(namedParams), cancellationToken)).ConfigureAwait(false); } finally @@ -107,6 +109,7 @@ public partial class SqlQueryImpl : AbstractQueryImpl, ISQLQuery, ISynchronizabl } // Since v5.2 + /// [Obsolete("This method has no usages and will be removed in a future version")] protected internal override Task> GetTranslatorsAsync(ISessionImplementor sessionImplementor, QueryParameters queryParameters, CancellationToken cancellationToken) { diff --git a/src/NHibernate/Async/Impl/StatelessSessionImpl.cs b/src/NHibernate/Async/Impl/StatelessSessionImpl.cs index f1ba521b612..162baafddc2 100644 --- a/src/NHibernate/Async/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Async/Impl/StatelessSessionImpl.cs @@ -122,7 +122,7 @@ public override async Task ListAsync(IQueryExpression queryExpression, QueryPara } } - public override async Task ListAsync(CriteriaImpl criteria, IList results, CancellationToken cancellationToken) + public override async Task> ListAsync(CriteriaImpl criteria, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (BeginProcess()) @@ -133,18 +133,16 @@ public override async Task ListAsync(CriteriaImpl criteria, IList results, Cance CriteriaLoader[] loaders = new CriteriaLoader[size]; for (int i = 0; i < size; i++) { - loaders[i] = new CriteriaLoader(GetOuterJoinLoadable(implementors[i]), Factory, + loaders[size - 1 - i] = new CriteriaLoader(GetOuterJoinLoadable(implementors[i]), Factory, criteria, implementors[i], EnabledFilters); } bool success = false; try { - for (int i = size - 1; i >= 0; i--) - { - ArrayHelper.AddAll(results, await (loaders[i].ListAsync(this, cancellationToken)).ConfigureAwait(false)); - } + var results = await (loaders.LoadAllToListAsync(this, cancellationToken)).ConfigureAwait(false); success = true; + return results; } catch (OperationCanceledException) { throw; } catch (HibernateException) @@ -159,11 +157,18 @@ public override async Task ListAsync(CriteriaImpl criteria, IList results, Cance finally { await (AfterOperationAsync(success, cancellationToken)).ConfigureAwait(false); + temporaryPersistenceContext.Clear(); } - temporaryPersistenceContext.Clear(); } } + //TODO 6.0: Remove (use base class implementation) + public override async Task ListAsync(CriteriaImpl criteria, IList results, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ArrayHelper.AddAll(results, await (ListAsync(criteria, cancellationToken)).ConfigureAwait(false)); + } + public override Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -434,7 +439,7 @@ public async Task ManagedFlushAsync(CancellationToken cancellationToken) } } - /// Retrieve a entity. + /// Retrieve an entity. /// a detached entity instance public Task GetAsync(string entityName, object id, CancellationToken cancellationToken = default(CancellationToken)) { @@ -445,31 +450,19 @@ public async Task ManagedFlushAsync(CancellationToken cancellationToken) return GetAsync(entityName, id, LockMode.None, cancellationToken); } - /// Retrieve a entity. - /// + /// + /// Retrieve an entity. /// /// a detached entity instance /// public async Task GetAsync(object id, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) - { - return (T)await (GetAsync(typeof(T), id, cancellationToken)).ConfigureAwait(false); - } - } - - private Task GetAsync(System.Type persistentClass, object id, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return GetAsync(persistentClass.FullName, id, cancellationToken); + return (T) await (GetAsync(typeof(T).FullName, id, cancellationToken)).ConfigureAwait(false); } /// - /// Retrieve a entity, obtaining the specified lock mode. + /// Retrieve an entity, obtaining the specified lock mode. /// /// a detached entity instance public async Task GetAsync(string entityName, object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) @@ -477,7 +470,7 @@ private Task GetAsync(System.Type persistentClass, object id, Cancellati cancellationToken.ThrowIfCancellationRequested(); using (BeginProcess()) { - object result = await (Factory.GetEntityPersister(entityName).LoadAsync(id, null, lockMode, this, cancellationToken)).ConfigureAwait(false); + object result = await (Factory.GetEntityPersister(entityName).LoadAsync(id, null, lockMode ?? LockMode.None, this, cancellationToken)).ConfigureAwait(false); if (temporaryPersistenceContext.IsLoadFinished) { temporaryPersistenceContext.Clear(); @@ -487,16 +480,13 @@ private Task GetAsync(System.Type persistentClass, object id, Cancellati } /// - /// Retrieve a entity, obtaining the specified lock mode. + /// Retrieve an entity, obtaining the specified lock mode. /// /// a detached entity instance public async Task GetAsync(object id, LockMode lockMode, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) - { - return (T)await (GetAsync(typeof(T).FullName, id, lockMode, cancellationToken)).ConfigureAwait(false); - } + return (T) await (GetAsync(typeof(T).FullName, id, lockMode, cancellationToken)).ConfigureAwait(false); } /// diff --git a/src/NHibernate/Async/Linq/DefaultQueryProvider.cs b/src/NHibernate/Async/Linq/DefaultQueryProvider.cs index d1e3549e354..2cc4b8863b4 100644 --- a/src/NHibernate/Async/Linq/DefaultQueryProvider.cs +++ b/src/NHibernate/Async/Linq/DefaultQueryProvider.cs @@ -21,6 +21,7 @@ using NHibernate.Util; using System.Threading.Tasks; using NHibernate.Multi; +using NHibernate.Param; namespace NHibernate.Linq { @@ -32,6 +33,23 @@ public partial interface INhQueryProvider : IQueryProvider public partial class DefaultQueryProvider : INhQueryProvider, IQueryProviderWithOptions, ISupportFutureBatchNhQueryProvider { + //TODO 6.0: Add to INhQueryProvider interface + public virtual async Task> ExecuteListAsync(Expression expression, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var linqExpression = PrepareQuery(expression, out var query); + var resultTransformer = linqExpression.ExpressionToHqlTranslationResults?.PostExecuteTransformer; + if (resultTransformer == null) + { + return await (query.ListAsync(cancellationToken)).ConfigureAwait(false); + } + + return new List + { + (TResult) resultTransformer.DynamicInvoke((await (query.ListAsync(cancellationToken)).ConfigureAwait(false)).AsQueryable()) + }; + } + // Since v5.1 [Obsolete("Use ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery query) instead")] protected virtual async Task ExecuteQueryAsync(NhLinqExpression nhLinqExpression, IQuery query, NhLinqExpression nhQuery, CancellationToken cancellationToken) @@ -86,7 +104,7 @@ public Task ExecuteDmlAsync(QueryMode queryMode, Expression expression, var query = Session.CreateQuery(nhLinqExpression); - SetParameters(query, nhLinqExpression.ParameterValuesByName); + SetParameters(query, nhLinqExpression.NamedParameters); _options?.Apply(query); return query.ExecuteUpdateAsync(cancellationToken); } diff --git a/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs b/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs index ccae5a22c23..ea6f8c3a5ce 100644 --- a/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs +++ b/src/NHibernate/Async/Loader/Criteria/CriteriaLoader.cs @@ -11,6 +11,7 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Linq; using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Param; @@ -24,6 +25,29 @@ namespace NHibernate.Loader.Criteria { using System.Threading.Tasks; using System.Threading; + internal static partial class CriteriaLoaderExtensions + { + /// + /// Loads all loaders results to single typed list + /// + internal static async Task> LoadAllToListAsync(this IList loaders, ISessionImplementor session, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var subresults = new List(loaders.Count); + foreach(var l in loaders) + { + subresults.Add(await (l.ListAsync(session, cancellationToken)).ConfigureAwait(false)); + } + + var results = new List(subresults.Sum(r => r.Count)); + foreach(var list in subresults) + { + ArrayHelper.AddAll(results, list); + } + return results; + } + } + public partial class CriteriaLoader : OuterJoinLoader { @@ -51,7 +75,6 @@ protected override async Task GetResultColumnOrRowAsync(object[] row, IR .TransformTuple(await (GetResultRowAsync(row, rs, session, cancellationToken)).ConfigureAwait(false), ResultRowAliases); } - protected override async Task GetResultRowAsync(object[] row, DbDataReader rs, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Loader/Hql/QueryLoader.cs b/src/NHibernate/Async/Loader/Hql/QueryLoader.cs index 9135a5b8481..83832ddf38b 100644 --- a/src/NHibernate/Async/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Async/Loader/Hql/QueryLoader.cs @@ -76,7 +76,9 @@ protected override async Task GetResultRowAsync(object[] row, DbDataRe resultRow = new object[queryCols]; for (int i = 0; i < queryCols; i++) { - resultRow[i] = await (ResultTypes[i].NullSafeGetAsync(rs, scalarColumns[i], session, null, cancellationToken)).ConfigureAwait(false); + resultRow[i] = _entityByResultTypeDic.TryGetValue(i, out var rowIndex) + ? row[rowIndex] + : await (ResultTypes[i].NullSafeGetAsync(rs, scalarColumns[i], session, null, cancellationToken)).ConfigureAwait(false); } } else diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index 58c6f066d45..a8a235d48c3 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -27,7 +27,6 @@ using NHibernate.Exceptions; using NHibernate.Hql.Util; using NHibernate.Impl; -using NHibernate.Intercept; using NHibernate.Param; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -59,7 +58,6 @@ private Task DoQueryAndInitializeNonLazyCollectionsAsync(ISessionImplemen return DoQueryAndInitializeNonLazyCollectionsAsync(session, queryParameters, returnProxies, null, null, cancellationToken); } - private async Task DoQueryAndInitializeNonLazyCollectionsAsync(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder, CancellationToken cancellationToken) @@ -429,7 +427,7 @@ private Task EndCollectionLoadAsync(DbDataReader reader, ISessionImplementor ses { //this is a query and we are loading multiple instances of the same collection role return session.PersistenceContext.LoadContexts.GetCollectionLoadContext(reader).EndLoadingCollectionsAsync( - collectionPersister, !IsCollectionPersisterCacheable(collectionPersister), cacheBatcher, cancellationToken); + collectionPersister, !IsCollectionPersisterCacheable(collectionPersister), cacheBatcher, cancellationToken); } catch (Exception ex) { @@ -437,7 +435,6 @@ private Task EndCollectionLoadAsync(DbDataReader reader, ISessionImplementor ses } } - /// /// Get the actual object that is returned in the user-visible result list. /// diff --git a/src/NHibernate/Async/Multi/IQueryBatch.cs b/src/NHibernate/Async/Multi/IQueryBatch.cs index 85bdc444411..f9c260dd725 100644 --- a/src/NHibernate/Async/Multi/IQueryBatch.cs +++ b/src/NHibernate/Async/Multi/IQueryBatch.cs @@ -21,7 +21,7 @@ public partial interface IQueryBatch /// Executes the batch. /// /// A cancellation token that can be used to cancel the work - Task ExecuteAsync(CancellationToken cancellationToken); + Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); /// /// Gets a query result, triggering execution of the batch if it was not already executed. @@ -32,7 +32,7 @@ public partial interface IQueryBatch /// A query result. /// is 0 based and matches the order in which queries have been /// added into the batch. - Task> GetResultAsync(int queryIndex, CancellationToken cancellationToken); + Task> GetResultAsync(int queryIndex, CancellationToken cancellationToken = default(CancellationToken)); /// /// Gets a query result, triggering execution of the batch if it was not already executed. @@ -41,6 +41,6 @@ public partial interface IQueryBatch /// A cancellation token that can be used to cancel the work /// The type of the result elements of the query. /// A query result. - Task> GetResultAsync(string querykey, CancellationToken cancellationToken); + Task> GetResultAsync(string querykey, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/NHibernate/Async/Multi/IQueryBatchItem.cs b/src/NHibernate/Async/Multi/IQueryBatchItem.cs index 257f6673964..c8e4fa5a2a1 100644 --- a/src/NHibernate/Async/Multi/IQueryBatchItem.cs +++ b/src/NHibernate/Async/Multi/IQueryBatchItem.cs @@ -36,4 +36,9 @@ public partial interface IQueryBatchItem /// A cancellation token that can be used to cancel the work Task ExecuteNonBatchedAsync(CancellationToken cancellationToken); } + + internal partial interface IQueryBatchItemWithAsyncProcessResults + { + Task ProcessResultsAsync(CancellationToken cancellationToken); + } } diff --git a/src/NHibernate/Async/Multi/LinqBatchItem.cs b/src/NHibernate/Async/Multi/LinqBatchItem.cs index 638948f3cf5..d27fface8e3 100644 --- a/src/NHibernate/Async/Multi/LinqBatchItem.cs +++ b/src/NHibernate/Async/Multi/LinqBatchItem.cs @@ -14,7 +14,6 @@ using System.Linq; using System.Linq.Expressions; using NHibernate.Linq; -using NHibernate.Util; using Remotion.Linq.Parsing.ExpressionVisitors; namespace NHibernate.Multi @@ -22,7 +21,7 @@ namespace NHibernate.Multi using System.Threading.Tasks; using System.Threading; - public partial class LinqBatchItem : QueryBatchItem + public partial class LinqBatchItem : QueryBatchItem, ILinqBatchItem { protected override async Task> GetResultsNonBatchedAsync(CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Multi/QueryBatch.cs b/src/NHibernate/Async/Multi/QueryBatch.cs index ba3e1e0fa39..addf76a22c3 100644 --- a/src/NHibernate/Async/Multi/QueryBatch.cs +++ b/src/NHibernate/Async/Multi/QueryBatch.cs @@ -27,7 +27,7 @@ public partial class QueryBatch : IQueryBatch { /// - public async Task ExecuteAsync(CancellationToken cancellationToken) + public async Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); if (_queries.Count == 0) @@ -70,7 +70,7 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) } /// - public Task> GetResultAsync(int queryIndex, CancellationToken cancellationToken) + public Task> GetResultAsync(int queryIndex, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -87,7 +87,7 @@ public Task> GetResultAsync(int queryIndex, Cancellation } /// - public Task> GetResultAsync(string querykey, CancellationToken cancellationToken) + public Task> GetResultAsync(string querykey, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -139,18 +139,19 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) } var rowCount = 0; + CacheBatcher cacheBatcher = null; try { if (resultSetsCommand.HasQueries) { + cacheBatcher = new CacheBatcher(Session); using (var reader = await (resultSetsCommand.GetReaderAsync(Timeout, cancellationToken)).ConfigureAwait(false)) { - var cacheBatcher = new CacheBatcher(Session); foreach (var query in _queries) { if (query.CachingInformation != null) { - foreach (var cachingInfo in query.CachingInformation.Where(ci => ci.IsCacheable)) + foreach (var cachingInfo in query.CachingInformation) { cachingInfo.SetCacheBatcher(cacheBatcher); } @@ -158,18 +159,28 @@ protected async Task ExecuteBatchedAsync(CancellationToken cancellationToken) rowCount += await (query.ProcessResultsSetAsync(reader, cancellationToken)).ConfigureAwait(false); } - await (cacheBatcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false); } } - // Query cacheable results must be cached untransformed: the put does not need to wait for - // the ProcessResults. - await (PutCacheableResultsAsync(cancellationToken)).ConfigureAwait(false); - foreach (var query in _queries) { - query.ProcessResults(); + //TODO 6.0: Replace with query.ProcessResults(); + if (query is IQueryBatchItemWithAsyncProcessResults q) + await (q.ProcessResultsAsync(cancellationToken)).ConfigureAwait(false); + else + query.ProcessResults(); } + + var executeBatchTask = cacheBatcher?.ExecuteBatchAsync(cancellationToken); + + if (executeBatchTask != null) + + { + + await (executeBatchTask).ConfigureAwait(false); + + } + await (PutCacheableResultsAsync(cancellationToken)).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception sqle) diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index 37fbaff3558..e590cc77f6f 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -23,7 +23,7 @@ namespace NHibernate.Multi { using System.Threading.Tasks; using System.Threading; - public abstract partial class QueryBatchItemBase : IQueryBatchItem + public abstract partial class QueryBatchItemBase : IQueryBatchItem, IQueryBatchItemWithAsyncProcessResults { /// @@ -34,6 +34,7 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT var dialect = Session.Factory.Dialect; var hydratedObjects = new List[_queryInfos.Count]; + var isDebugLog = Log.IsDebugEnabled(); using (Session.SwitchCacheMode(_cacheMode)) { @@ -75,14 +76,21 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT var lockModeArray = loader.GetLockModes(queryParameters.LockModes); var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); var tmpResults = new List(); - var queryCacheBuilder = new QueryCacheResultBuilder(loader); + var queryCacheBuilder = queryInfo.IsCacheable ? new QueryCacheResultBuilder(loader) : null; var cacheBatcher = queryInfo.CacheBatcher; var ownCacheBatcher = cacheBatcher == null; if (ownCacheBatcher) cacheBatcher = new CacheBatcher(Session); - for (var count = 0; count < maxRows && await (reader.ReadAsync(cancellationToken)).ConfigureAwait(false); count++) + if (isDebugLog) + Log.Debug("processing result set"); + + int count; + for (count = 0; count < maxRows && await (reader.ReadAsync(cancellationToken)).ConfigureAwait(false); count++) { + if (isDebugLog) + Log.Debug("result set row: {0}", count); + rowCount++; var o = @@ -108,6 +116,9 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT tmpResults.Add(o); } + if (isDebugLog) + Log.Debug("done processing result set ({0} rows)", count); + queryInfo.Result = tmpResults; if (queryInfo.CanPutToCache) queryInfo.ResultToCache = queryCacheBuilder.Result; @@ -118,12 +129,47 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT await (reader.NextResultAsync(cancellationToken)).ConfigureAwait(false); } - await (InitializeEntitiesAndCollectionsAsync(reader, hydratedObjects, cancellationToken)).ConfigureAwait(false); - + StopLoadingCollections(reader); + _reader = reader; + _hydratedObjects = hydratedObjects; return rowCount; } } + /// + public async Task ProcessResultsAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfNotInitialized(); + + using (Session.SwitchCacheMode(_cacheMode)) + await (InitializeEntitiesAndCollectionsAsync(_reader, _hydratedObjects, cancellationToken)).ConfigureAwait(false); + + for (var i = 0; i < _queryInfos.Count; i++) + { + var queryInfo = _queryInfos[i]; + if (_subselectResultKeys[i] != null) + { + queryInfo.Loader.CreateSubselects(_subselectResultKeys[i], queryInfo.Parameters, Session); + } + + if (queryInfo.IsCacheable) + { + if (queryInfo.IsResultFromCache) + { + var queryCacheBuilder = new QueryCacheResultBuilder(queryInfo.Loader); + queryInfo.Result = queryCacheBuilder.GetResultList(queryInfo.Result); + } + + // This transformation must not be applied to ResultToCache. + queryInfo.Result = + queryInfo.Loader.TransformCacheableResults( + queryInfo.Parameters, queryInfo.CacheKey.ResultTransformer, queryInfo.Result); + } + } + AfterLoadCallback?.Invoke(GetResults()); + } + /// public async Task ExecuteNonBatchedAsync(CancellationToken cancellationToken) { diff --git a/src/NHibernate/Async/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..123bcad9827 --- /dev/null +++ b/src/NHibernate/Async/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs @@ -0,0 +1,36 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.MultiTenancy +{ + using System.Threading.Tasks; + using System.Threading; + public abstract partial class AbstractMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider + { + partial class ContextualConnectionAccess : IConnectionAccess + { + + /// + public Task GetConnectionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return _sessionFactory.ConnectionProvider.GetConnectionAsync(ConnectionString, cancellationToken); + } + } + } +} diff --git a/src/NHibernate/Async/NHibernateUtil.cs b/src/NHibernate/Async/NHibernateUtil.cs index 29fb3e3bf1e..fd7959973ac 100644 --- a/src/NHibernate/Async/NHibernateUtil.cs +++ b/src/NHibernate/Async/NHibernateUtil.cs @@ -25,7 +25,6 @@ namespace NHibernate public static partial class NHibernateUtil { - /// /// Force initialization of a proxy or persistent collection. /// @@ -58,7 +57,6 @@ public static partial class NHibernateUtil return persistent.ForceInitializationAsync(cancellationToken); } return Task.CompletedTask; - } catch (Exception ex) { diff --git a/src/NHibernate/Async/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs b/src/NHibernate/Async/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs index 2610e8c09fc..57893dade62 100644 --- a/src/NHibernate/Async/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs +++ b/src/NHibernate/Async/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs @@ -34,7 +34,6 @@ public partial class AggregatedIndexCollectionSelectorParameterSpecifications : // return bindCount; //} - public Task BindAsync(DbCommand command, IList sqlQueryParametersList, QueryParameters queryParameters, ISessionImplementor session, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -45,4 +44,4 @@ public Task BindAsync(DbCommand command, IList multiSqlQueryParameter throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs index eb15d667603..da37c811661 100644 --- a/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs @@ -40,7 +40,7 @@ namespace NHibernate.Persister.Collection using System.Threading.Tasks; using System.Threading; public abstract partial class AbstractCollectionPersister : ICollectionMetadata, ISqlLoadableCollection, - IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister + IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable { public Task InitializeAsync(object key, ISessionImplementor session, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Persister/Collection/NamedQueryCollectionInitializer.cs b/src/NHibernate/Async/Persister/Collection/NamedQueryCollectionInitializer.cs index 7349bbdf4c8..128b0f23d27 100644 --- a/src/NHibernate/Async/Persister/Collection/NamedQueryCollectionInitializer.cs +++ b/src/NHibernate/Async/Persister/Collection/NamedQueryCollectionInitializer.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Loader.Collection; diff --git a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs index ff5580194f5..09f2076dd3d 100644 --- a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs @@ -45,7 +45,7 @@ namespace NHibernate.Persister.Entity using System.Threading; public abstract partial class AbstractEntityPersister : IOuterJoinLoadable, IQueryable, IClassMetadata, IUniqueKeyLoadable, ISqlLoadable, ILazyPropertyInitializer, IPostInsertIdentityPersister, ILockable, - ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister + ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable { private partial class GeneratedIdentifierBinder : IBinder @@ -87,7 +87,12 @@ async Task InternalInitializeLazyPropertiesAsync() int[] lazyIndexes; if (allLazyProperties) { - lazyIndexes = indexes = lazyPropertyNumbers; + indexes = lazyPropertyNumbers; + lazyIndexes = new int[lazyPropertyNumbers.Length]; + for(var i = 0; i < lazyIndexes.Length; i++) + { + lazyIndexes[i] = i; + } } else { @@ -201,9 +206,9 @@ async Task InternalForceVersionIncrementAsync() if (log.IsDebugEnabled()) { log.Debug("Forcing version increment [{0}; {1} -> {2}]", - MessageHelper.InfoString(this, id, Factory), - VersionType.ToLoggableString(currentVersion, Factory), - VersionType.ToLoggableString(nextVersion, Factory)); + MessageHelper.InfoString(this, id, Factory), + VersionType.ToLoggableString(currentVersion, Factory), + VersionType.ToLoggableString(nextVersion, Factory)); } IExpectation expectation = Expectations.AppropriateExpectation(updateResultCheckStyles[0]); @@ -227,13 +232,13 @@ async Task InternalForceVersionIncrementAsync() catch (DbException sqle) { var exceptionContext = new AdoExceptionContextInfo - { - SqlException = sqle, - Message = "could not retrieve version: " + MessageHelper.InfoString(this, id, Factory), - Sql = VersionSelectString.ToString(), - EntityName = EntityName, - EntityId = id - }; + { + SqlException = sqle, + Message = "could not retrieve version: " + MessageHelper.InfoString(this, id, Factory), + Sql = VersionSelectString.ToString(), + EntityName = EntityName, + EntityId = id + }; throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext); } return nextVersion; @@ -566,7 +571,7 @@ public async Task BindSelectByUniqueKeyAsync( { cancellationToken.ThrowIfCancellationRequested(); var propertyNames = GetUniqueKeyPropertyNames(suppliedPropertyNames); - var parameterTypes = propertyNames.Select(GetPropertyType).ToArray(); + var parameterTypes = propertyNames.ToArray(p => GetPropertyType(p)); var entity = binder.Entity; for (var i = 0; i < propertyNames.Length; i++) { @@ -1388,9 +1393,9 @@ async Task InternalGetNaturalIdentifierSnapshotAsync() string[] aliasedIdColumns = StringHelper.Qualify(RootAlias, IdentifierColumnNames); SqlString whereClause = new SqlString( - SqlStringHelper.Join(new SqlString("=", Parameter.Placeholder, " and "), aliasedIdColumns), - "=", Parameter.Placeholder, - WhereJoinFragment(RootAlias, true, false)); + SqlStringHelper.Join(new SqlString("=", Parameter.Placeholder, " and "), aliasedIdColumns), + "=", Parameter.Placeholder, + WhereJoinFragment(RootAlias, true, false)); SqlString sql = select.SetOuterJoins(SqlString.Empty, SqlString.Empty).SetWhereClause(whereClause).ToStatementString(); /////////////////////////////////////////////////////////////////////// @@ -1414,7 +1419,7 @@ async Task InternalGetNaturalIdentifierSnapshotAsync() for (int i = 0; i < naturalIdPropertyCount; i++) { snapshot[i] = - await (extractionTypes[i].HydrateAsync(rs, GetPropertyAliases(string.Empty, naturalIdPropertyIndexes[i]), session, null, cancellationToken)).ConfigureAwait(false); + await (extractionTypes[i].HydrateAsync(rs, GetPropertyAliases(string.Empty, naturalIdPropertyIndexes[i]), session, null, cancellationToken)).ConfigureAwait(false); if (extractionTypes[i].IsEntityType) { snapshot[i] = await (extractionTypes[i].ResolveIdentifierAsync(snapshot[i], session, null, cancellationToken)).ConfigureAwait(false); @@ -1430,13 +1435,13 @@ async Task InternalGetNaturalIdentifierSnapshotAsync() catch (DbException sqle) { var exceptionContext = new AdoExceptionContextInfo - { - SqlException = sqle, - Message = "could not retrieve snapshot: " + MessageHelper.InfoString(this, id, Factory), - Sql = sql.ToString(), - EntityName = EntityName, - EntityId = id - }; + { + SqlException = sqle, + Message = "could not retrieve snapshot: " + MessageHelper.InfoString(this, id, Factory), + Sql = sql.ToString(), + EntityName = EntityName, + EntityId = id + }; throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext); } } diff --git a/src/NHibernate/Async/Persister/Entity/ILoadable.cs b/src/NHibernate/Async/Persister/Entity/ILoadable.cs index 08729f3ad44..bab57223561 100644 --- a/src/NHibernate/Async/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Async/Persister/Entity/ILoadable.cs @@ -50,12 +50,12 @@ public static Task HydrateAsync( if (loadable is AbstractEntityPersister abstractEntityPersister) { return abstractEntityPersister.HydrateAsync( - rs, id, obj, suffixedPropertyColumns, fetchedLazyProperties, allProperties, session, cancellationToken); + rs, id, obj, suffixedPropertyColumns, fetchedLazyProperties, allProperties, session, cancellationToken); } var rootLoadable = loadable.RootEntityName == loadable.EntityName - ? loadable - : (ILoadable) loadable.Factory.GetEntityPersister(loadable.RootEntityName); + ? loadable + : (ILoadable) loadable.Factory.GetEntityPersister(loadable.RootEntityName); #pragma warning disable 618 // Fallback to the old behavior diff --git a/src/NHibernate/Async/Persister/Entity/NamedQueryLoader.cs b/src/NHibernate/Async/Persister/Entity/NamedQueryLoader.cs index 67bb866837f..ed130ad0603 100644 --- a/src/NHibernate/Async/Persister/Entity/NamedQueryLoader.cs +++ b/src/NHibernate/Async/Persister/Entity/NamedQueryLoader.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ - using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Loader.Entity; diff --git a/src/NHibernate/Async/Proxy/ILazyInitializer.cs b/src/NHibernate/Async/Proxy/ILazyInitializer.cs index 2e86311b819..4008180a426 100644 --- a/src/NHibernate/Async/Proxy/ILazyInitializer.cs +++ b/src/NHibernate/Async/Proxy/ILazyInitializer.cs @@ -32,4 +32,4 @@ public partial interface ILazyInitializer /// The persistent object this proxy is proxying. Task GetImplementationAsync(CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Tool/hbm2ddl/IConnectionHelper.cs b/src/NHibernate/Async/Tool/hbm2ddl/IConnectionHelper.cs index 5f71dee295e..7d53932c317 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/IConnectionHelper.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/IConnectionHelper.cs @@ -22,5 +22,4 @@ public partial interface IConnectionHelper /// A cancellation token that can be used to cancel the work Task PrepareAsync(CancellationToken cancellationToken); } - } diff --git a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs index 5f3ac5a485e..6c44940aefa 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs @@ -17,6 +17,7 @@ using NHibernate.AdoNet.Util; using NHibernate.Cfg; using NHibernate.Connection; +using NHibernate.MultiTenancy; using NHibernate.Util; using Environment=NHibernate.Cfg.Environment; @@ -46,9 +47,11 @@ public partial class SchemaExport dropSQL = cfg.GenerateDropSchemaScript(dialect); createSQL = cfg.GenerateSchemaCreationScript(dialect); formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter; + _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenancy, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; wasInitialized = true; } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -65,9 +68,39 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(useStdOut, execute, false, cancellationToken); + return CreateAsync(useStdOut, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the schema creation script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public Task CreateAsync(bool useStdOut, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, false, connection, null, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -84,9 +117,32 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(scriptAction, execute, false, cancellationToken); + return CreateAsync(scriptAction, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the schema creation script + /// + /// an action that will be called for each line of the generated ddl. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public Task CreateAsync(Action scriptAction, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(scriptAction, execute, false, connection, null, cancellationToken); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -103,9 +159,32 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(null, execute, false, exportOutput, cancellationToken); + return CreateAsync(exportOutput, execute, null, cancellationToken); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the schema creation script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public Task CreateAsync(TextWriter exportOutput, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(null, execute, false, connection, exportOutput, cancellationToken); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -122,9 +201,39 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(useStdOut, execute, true, cancellationToken); + return DropAsync(useStdOut, execute, null, cancellationToken); + } + + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the drop schema script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public Task DropAsync(bool useStdOut, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, true, connection, null, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -141,7 +250,29 @@ public partial class SchemaExport { return Task.FromCanceled(cancellationToken); } - return ExecuteAsync(null, execute, true, exportOutput, cancellationToken); + return DropAsync(exportOutput, execute, null, cancellationToken); + } + + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the drop schema script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// A cancellation token that can be used to cancel the work + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public Task DropAsync(TextWriter exportOutput, bool execute, DbConnection connection, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(null, execute, true, connection, exportOutput, cancellationToken); } private async Task ExecuteInitializedAsync(Action scriptAction, bool execute, bool throwOnError, TextWriter exportOutput, @@ -228,14 +359,7 @@ public Task ExecuteAsync(bool useStdOut, bool execute, bool justDrop, DbConnecti } try { - if (useStdOut) - { - return ExecuteAsync(Console.WriteLine, execute, justDrop, connection, exportOutput, cancellationToken); - } - else - { - return ExecuteAsync(null, execute, justDrop, connection, exportOutput, cancellationToken); - } + return ExecuteAsync(GetAction(useStdOut), execute, justDrop, connection, exportOutput, cancellationToken); } catch (Exception ex) { @@ -319,14 +443,7 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j } try { - if (useStdOut) - { - return ExecuteAsync(Console.WriteLine, execute, justDrop, cancellationToken); - } - else - { - return ExecuteAsync(null, execute, justDrop, cancellationToken); - } + return InitConnectionAndExecuteAsync(GetAction(useStdOut), execute, justDrop, null, null, cancellationToken); } catch (Exception ex) { @@ -334,7 +451,6 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j } } - public Task ExecuteAsync(Action scriptAction, bool execute, bool justDrop, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) @@ -344,12 +460,19 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j return ExecuteAsync(scriptAction, execute, justDrop, null, cancellationToken); } + public Task ExecuteAsync(Action scriptAction, bool execute, bool justDrop, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InitConnectionAndExecuteAsync(scriptAction, execute, justDrop, null, exportOutput, cancellationToken); + } - public async Task ExecuteAsync(Action scriptAction, bool execute, bool justDrop, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken)) + private async Task InitConnectionAndExecuteAsync(Action scriptAction, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); await (InitializeAsync(cancellationToken)).ConfigureAwait(false); - DbConnection connection = null; TextWriter fileOutput = exportOutput; IConnectionProvider connectionProvider = null; @@ -360,8 +483,13 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j fileOutput = new StreamWriter(outputFile); } - if (execute) + if (execute && connection == null) { + if (_requireTenantConnection) + { + throw new ArgumentException("When Database multi-tenancy is enabled you need to provide explicit connection. Please use overload with connection parameter."); + } + var props = new Dictionary(); foreach (var de in dialect.DefaultProperties) { @@ -395,7 +523,7 @@ public async Task ExecuteAsync(Action scriptAction, bool execute, bool j } finally { - if (connection != null) + if (connectionProvider != null) { connectionProvider.CloseConnection(connection); connectionProvider.Dispose(); diff --git a/src/NHibernate/Async/Tool/hbm2ddl/SchemaMetadataUpdater.cs b/src/NHibernate/Async/Tool/hbm2ddl/SchemaMetadataUpdater.cs index be454a8bb34..b17f66bac3d 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/SchemaMetadataUpdater.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/SchemaMetadataUpdater.cs @@ -13,6 +13,7 @@ using NHibernate.Engine; using NHibernate.Mapping; using System.Collections.Generic; +using System.Linq; namespace NHibernate.Tool.hbm2ddl { @@ -40,8 +41,8 @@ public static Task UpdateAsync(Configuration configuration, Dialect.Dialect dial try { return UpdateDialectKeywordsAsync( - dialect, - new ManagedProviderConnectionHelper(configuration.GetDerivedProperties()), cancellationToken); + dialect, + new ManagedProviderConnectionHelper(configuration.GetDerivedProperties()), cancellationToken); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs b/src/NHibernate/Async/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs index 861a527556c..6a4af39710f 100644 --- a/src/NHibernate/Async/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs +++ b/src/NHibernate/Async/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs @@ -15,7 +15,6 @@ namespace NHibernate.Tool.hbm2ddl { using System.Threading.Tasks; using System.Threading; - public partial class SuppliedConnectionProviderConnectionHelper : IConnectionHelper { diff --git a/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs b/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs index ec3ea6bc578..d6d5c59925d 100644 --- a/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs +++ b/src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs @@ -16,7 +16,6 @@ using NHibernate.Engine; using NHibernate.Engine.Transaction; using NHibernate.Exceptions; -using NHibernate.Impl; namespace NHibernate.Transaction { @@ -42,25 +41,17 @@ async Task InternalExecuteWorkInIsolationAsync() DbConnection connection = null; DbTransaction trans = null; - // bool wasAutoCommit = false; try { // We make an exception for SQLite and use the session's connection, // since SQLite only allows one connection to the database. - if (session.Factory.Dialect is SQLiteDialect) - connection = session.Connection; - else - connection = await (session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); + connection = session.Factory.Dialect is SQLiteDialect + ? session.Connection + : await (session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); if (transacted) { trans = connection.BeginTransaction(); - // TODO NH: a way to read the autocommit state is needed - //if (TransactionManager.GetAutoCommit(connection)) - //{ - // wasAutoCommit = true; - // TransactionManager.SetAutoCommit(connection, false); - //} } await (work.DoWorkAsync(connection, trans, cancellationToken)).ConfigureAwait(false); @@ -68,7 +59,6 @@ async Task InternalExecuteWorkInIsolationAsync() if (transacted) { trans.Commit(); - //TransactionManager.Commit(connection); } } catch (Exception t) @@ -84,49 +74,33 @@ async Task InternalExecuteWorkInIsolationAsync() } catch (Exception ignore) { - isolaterLog.Debug(ignore, "Unable to rollback transaction"); + _isolatorLog.Debug(ignore, "Unable to rollback transaction"); } - if (t is HibernateException) + switch (t) { - throw; - } - else if (t is DbException) - { - throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, - "error performing isolated work"); - } - else - { - throw new HibernateException("error performing isolated work", t); + case HibernateException _: + throw; + case DbException _: + throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, + "error performing isolated work"); + default: + throw new HibernateException("error performing isolated work", t); } } } finally { - //if (transacted && wasAutoCommit) - //{ - // try - // { - // // TODO NH: reset autocommit - // // TransactionManager.SetAutoCommit(connection, true); - // } - // catch (Exception) - // { - // log.Debug("was unable to reset connection back to auto-commit"); - // } - //} - try { trans?.Dispose(); } catch (Exception ignore) { - isolaterLog.Warn(ignore, "Unable to dispose transaction"); + _isolatorLog.Warn(ignore, "Unable to dispose transaction"); } - if (session.Factory.Dialect is SQLiteDialect == false) + if (connection != null && session.Factory.Dialect is SQLiteDialect == false) session.Factory.ConnectionProvider.CloseConnection(connection); } } diff --git a/src/NHibernate/Async/Transaction/AdoNetWithSystemTransactionFactory.cs b/src/NHibernate/Async/Transaction/AdoNetWithSystemTransactionFactory.cs index 599752d270c..91558147f86 100644 --- a/src/NHibernate/Async/Transaction/AdoNetWithSystemTransactionFactory.cs +++ b/src/NHibernate/Async/Transaction/AdoNetWithSystemTransactionFactory.cs @@ -16,7 +16,6 @@ using NHibernate.AdoNet; using NHibernate.Engine; using NHibernate.Engine.Transaction; -using NHibernate.Impl; using NHibernate.Util; namespace NHibernate.Transaction diff --git a/src/NHibernate/Async/Transaction/AdoTransaction.cs b/src/NHibernate/Async/Transaction/AdoTransaction.cs index 82437382703..70ebb8c7684 100644 --- a/src/NHibernate/Async/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Async/Transaction/AdoTransaction.cs @@ -164,6 +164,7 @@ protected virtual async Task DisposeAsync(bool isDisposing, CancellationToken ca // don't dispose of multiple times. return; } + _isAlreadyDisposed = true; // free managed resources that are being managed by the AdoTransaction if we // know this call came through Dispose() @@ -181,13 +182,11 @@ protected virtual async Task DisposeAsync(bool isDisposing, CancellationToken ca // Assume we are rolled back await (AfterTransactionCompletionAsync(false, cancellationToken)).ConfigureAwait(false); } + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here - - _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } } diff --git a/src/NHibernate/Async/Type/AnyType.cs b/src/NHibernate/Async/Type/AnyType.cs index 996e25fa6bf..ff030de2f1a 100644 --- a/src/NHibernate/Async/Type/AnyType.cs +++ b/src/NHibernate/Async/Type/AnyType.cs @@ -218,6 +218,5 @@ private Task ResolveAnyAsync(string entityName, object id, ISessionImple } return entityName == null || id == null ? Task.FromResult(null ): session.InternalLoadAsync(entityName, id, false, false, cancellationToken); } - } } diff --git a/src/NHibernate/Async/Type/CollectionType.cs b/src/NHibernate/Async/Type/CollectionType.cs index b103b445c22..432c11957a8 100644 --- a/src/NHibernate/Async/Type/CollectionType.cs +++ b/src/NHibernate/Async/Type/CollectionType.cs @@ -104,6 +104,11 @@ public override async Task DisassembleAsync(object value, ISessionImplem public override async Task BeforeAssembleAsync(object oid, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + if (oid == null) + { + return; + } + var queryCacheQueue = session.PersistenceContext.BatchFetchQueue.QueryCacheQueue; if (queryCacheQueue == null) { diff --git a/src/NHibernate/Async/Type/CompositeCustomType.cs b/src/NHibernate/Async/Type/CompositeCustomType.cs index 10e6590f7a6..0fffef1b131 100644 --- a/src/NHibernate/Async/Type/CompositeCustomType.cs +++ b/src/NHibernate/Async/Type/CompositeCustomType.cs @@ -17,6 +17,7 @@ using NHibernate.SqlTypes; using NHibernate.UserTypes; using System.Collections.Generic; +using NHibernate.Util; namespace NHibernate.Type { @@ -179,6 +180,5 @@ public override Task ReplaceAsync(object original, object current, ISess return Task.FromException(ex); } } - } } diff --git a/src/NHibernate/Async/Type/EntityType.cs b/src/NHibernate/Async/Type/EntityType.cs index 944bb2bace8..95fa8eaa2bb 100644 --- a/src/NHibernate/Async/Type/EntityType.cs +++ b/src/NHibernate/Async/Type/EntityType.cs @@ -235,6 +235,10 @@ public async Task LoadByUniqueKeyAsync(string entityName, string uniqueK if (result == null) { result = await (persister.LoadByUniqueKeyAsync(uniqueKeyPropertyName, key, session, cancellationToken)).ConfigureAwait(false); + if (result == null && !IsNullable) + { + factory.EntityNotFoundDelegate.HandleEntityNotFound(entityName, uniqueKeyPropertyName, key); + } } return result == null ? null : persistenceContext.ProxyFor(result); } diff --git a/src/NHibernate/Async/Type/IAbstractComponentType.cs b/src/NHibernate/Async/Type/IAbstractComponentType.cs index fe4aa0392ea..fe6de170698 100644 --- a/src/NHibernate/Async/Type/IAbstractComponentType.cs +++ b/src/NHibernate/Async/Type/IAbstractComponentType.cs @@ -26,4 +26,4 @@ public partial interface IAbstractComponentType : IType Task GetPropertyValueAsync(object component, int i, ISessionImplementor session, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Type/IType.cs b/src/NHibernate/Async/Type/IType.cs index bbac991db9b..4b439734786 100644 --- a/src/NHibernate/Async/Type/IType.cs +++ b/src/NHibernate/Async/Type/IType.cs @@ -17,7 +17,7 @@ namespace NHibernate.Type { using System.Threading.Tasks; using System.Threading; - + public partial interface IType : ICacheAssembler { diff --git a/src/NHibernate/Async/Type/ImmutableType.cs b/src/NHibernate/Async/Type/ImmutableType.cs index b7e24bed407..b2663974643 100644 --- a/src/NHibernate/Async/Type/ImmutableType.cs +++ b/src/NHibernate/Async/Type/ImmutableType.cs @@ -36,6 +36,5 @@ public override Task ReplaceAsync(object original, object current, ISess return Task.FromException(ex); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Type/MutableType.cs b/src/NHibernate/Async/Type/MutableType.cs index dfcc79632e8..3e5f06cb84c 100644 --- a/src/NHibernate/Async/Type/MutableType.cs +++ b/src/NHibernate/Async/Type/MutableType.cs @@ -36,6 +36,5 @@ public override Task ReplaceAsync(object original, object target, ISessi return Task.FromException(ex); } } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Bytecode/IBytecodeProvider.cs b/src/NHibernate/Bytecode/IBytecodeProvider.cs index d17daaa32b3..264b1c7c29e 100644 --- a/src/NHibernate/Bytecode/IBytecodeProvider.cs +++ b/src/NHibernate/Bytecode/IBytecodeProvider.cs @@ -10,7 +10,7 @@ public interface IBytecodeProvider /// The specific factory for this provider capable of /// generating run-time proxies for lazy-loading purposes. /// - IProxyFactoryFactory ProxyFactoryFactory { get;} + IProxyFactoryFactory ProxyFactoryFactory { get; } /// /// Retrieve the delegate for this provider diff --git a/src/NHibernate/Bytecode/Lightweight/AccessOptimizer.cs b/src/NHibernate/Bytecode/Lightweight/AccessOptimizer.cs index cf79e2d0c82..b6b5aa2da13 100644 --- a/src/NHibernate/Bytecode/Lightweight/AccessOptimizer.cs +++ b/src/NHibernate/Bytecode/Lightweight/AccessOptimizer.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Security; using NHibernate.Properties; +using NHibernate.Util; namespace NHibernate.Bytecode.Lightweight { @@ -26,8 +27,8 @@ public AccessOptimizer( : this( getDelegate, setDelegate, - getters.Select(o => (GetPropertyValueInvoker) o.Get).ToArray(), - setters.Select(o => (SetPropertyValueInvoker) o.Set).ToArray(), + getters.ToArray(o => (GetPropertyValueInvoker) o.Get), + setters.ToArray(o => (SetPropertyValueInvoker) o.Set), null, null) { diff --git a/src/NHibernate/Bytecode/Lightweight/BytecodeProviderImpl.cs b/src/NHibernate/Bytecode/Lightweight/BytecodeProviderImpl.cs index 09877c20409..bcc3ac6f5c6 100644 --- a/src/NHibernate/Bytecode/Lightweight/BytecodeProviderImpl.cs +++ b/src/NHibernate/Bytecode/Lightweight/BytecodeProviderImpl.cs @@ -12,7 +12,6 @@ namespace NHibernate.Bytecode.Lightweight /// public class BytecodeProviderImpl : AbstractBytecodeProvider { - #region IBytecodeProvider Members /// diff --git a/src/NHibernate/Bytecode/UnableToLoadProxyFactoryFactoryException.cs b/src/NHibernate/Bytecode/UnableToLoadProxyFactoryFactoryException.cs index fc9bbe48284..d37d031e370 100644 --- a/src/NHibernate/Bytecode/UnableToLoadProxyFactoryFactoryException.cs +++ b/src/NHibernate/Bytecode/UnableToLoadProxyFactoryFactoryException.cs @@ -7,7 +7,6 @@ namespace NHibernate.Bytecode [Serializable] public class UnableToLoadProxyFactoryFactoryException : HibernateByteCodeException { - public UnableToLoadProxyFactoryFactoryException(string typeName, Exception inner) : base("", inner) { diff --git a/src/NHibernate/Cache/CacheKey.cs b/src/NHibernate/Cache/CacheKey.cs index 3b0a46d7266..f3f050175dd 100644 --- a/src/NHibernate/Cache/CacheKey.cs +++ b/src/NHibernate/Cache/CacheKey.cs @@ -20,6 +20,7 @@ public class CacheKey : IDeserializationCallback [NonSerialized] private int? _hashCode; private readonly ISessionFactoryImplementor _factory; + private readonly string _tenantIdentifier; /// /// Construct a new key for a collection or entity instance. @@ -30,28 +31,40 @@ public class CacheKey : IDeserializationCallback /// The Hibernate type mapping /// The entity or collection-role name. /// The session factory for which we are caching - public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory) + /// + public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory, string tenantIdentifier) { key = id; this.type = type; this.entityOrRoleName = entityOrRoleName; _factory = factory; + _tenantIdentifier = tenantIdentifier; _hashCode = GenerateHashCode(); } + //Since 5.3 + [Obsolete("Use constructor with tenantIdentifier")] + public CacheKey(object id, IType type, string entityOrRoleName, ISessionFactoryImplementor factory) + : this(id, type, entityOrRoleName, factory, null) + { + } + //Mainly for SysCache and Memcache public override String ToString() { // For Component the user can override ToString - return entityOrRoleName + '#' + key; + return + string.IsNullOrEmpty(_tenantIdentifier) + ? entityOrRoleName + "#" + key + : string.Join("#", entityOrRoleName, key, _tenantIdentifier); } public override bool Equals(object obj) { CacheKey that = obj as CacheKey; if (that == null) return false; - return entityOrRoleName.Equals(that.entityOrRoleName) && type.IsEqual(key, that.key); + return entityOrRoleName.Equals(that.entityOrRoleName) && type.IsEqual(key, that.key) && _tenantIdentifier == that._tenantIdentifier; } public override int GetHashCode() @@ -71,7 +84,14 @@ public void OnDeserialization(object sender) private int GenerateHashCode() { - return type.GetHashCode(key, _factory); + var hashCode = type.GetHashCode(key, _factory); + + if (_tenantIdentifier != null) + { + hashCode = (37 * hashCode) + _tenantIdentifier.GetHashCode(); + } + + return hashCode; } public object Key diff --git a/src/NHibernate/Cache/IOptimisticCacheSource.cs b/src/NHibernate/Cache/IOptimisticCacheSource.cs index 840ad8f022f..08b27d4d93f 100644 --- a/src/NHibernate/Cache/IOptimisticCacheSource.cs +++ b/src/NHibernate/Cache/IOptimisticCacheSource.cs @@ -15,10 +15,10 @@ public interface IOptimisticCacheSource /// Does this source represent versioned (i.e., and thus optimistically lockable) data? /// /// True if this source represents versioned data; false otherwise. - bool IsVersioned { get;} + bool IsVersioned { get; } /// Get the comparator used to compare two different version values together. /// An appropriate comparator. - IComparer VersionComparator { get;} + IComparer VersionComparator { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cache/NoCacheProvider.cs b/src/NHibernate/Cache/NoCacheProvider.cs index 9c927369028..f4afc9b40aa 100644 --- a/src/NHibernate/Cache/NoCacheProvider.cs +++ b/src/NHibernate/Cache/NoCacheProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; - namespace NHibernate.Cache { /// @@ -41,7 +40,7 @@ public long NextTimestamp() // This is used by SessionFactoryImpl to hand to the generated SessionImpl; // was the only reason I could see that we cannot just use null as // Settings.CacheProvider - return DateTime.Now.Ticks / (100 * TimeSpan.TicksPerMillisecond); + return DateTime.UtcNow.Ticks / (100 * TimeSpan.TicksPerMillisecond); } /// diff --git a/src/NHibernate/Cache/NonstrictReadWriteCache.cs b/src/NHibernate/Cache/NonstrictReadWriteCache.cs index 065d7189ac6..011a2c9b7be 100644 --- a/src/NHibernate/Cache/NonstrictReadWriteCache.cs +++ b/src/NHibernate/Cache/NonstrictReadWriteCache.cs @@ -53,15 +53,12 @@ public object Get(CacheKey key, long txTimestamp) log.Debug("Cache lookup: {0}", key); } - object result = Cache.Get(key); - if (result != null) - { - log.Debug("Cache hit"); - } - else + var result = Cache.Get(key); + if (log.IsDebugEnabled()) { - log.Debug("Cache miss"); + log.Debug(result != null ? "Cache hit: {0}" : "Cache miss: {0}", key); } + return result; } @@ -71,15 +68,14 @@ public object[] GetMany(CacheKey[] keys, long timestamp) { log.Debug("Cache lookup: {0}", string.Join(",", keys.AsEnumerable())); } - var results = _cache.GetMany(keys.Select(o => (object) o).ToArray()); - if (!log.IsDebugEnabled()) - { - return results; - } - for (var i = 0; i < keys.Length; i++) + + var results = _cache.GetMany(keys); + if (log.IsDebugEnabled()) { - log.Debug(results[i] != null ? $"Cache hit: {keys[i]}" : $"Cache miss: {keys[i]}"); + log.Debug("Cache hit: {0}", string.Join(",", keys.Where((k, i) => results != null))); + log.Debug("Cache miss: {0}", string.Join(",", keys.Where((k, i) => results == null))); } + return results; } diff --git a/src/NHibernate/Cache/QueryCacheResultBuilder.cs b/src/NHibernate/Cache/QueryCacheResultBuilder.cs index f3f145ddad3..fff6fe08d75 100644 --- a/src/NHibernate/Cache/QueryCacheResultBuilder.cs +++ b/src/NHibernate/Cache/QueryCacheResultBuilder.cs @@ -1,12 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NHibernate.Collection; -using NHibernate.Engine; -using NHibernate.Persister.Collection; using NHibernate.Type; namespace NHibernate.Cache @@ -17,56 +12,34 @@ namespace NHibernate.Cache public sealed class QueryCacheResultBuilder { private readonly IType[] _resultTypes; - private readonly IType[] _cacheTypes; - private readonly List _entityFetchIndexes = new List(); - private readonly List _collectionFetchIndexes = new List(); - private readonly bool _hasFetches; + private readonly Loader.Loader.QueryCacheInfo _cacheInfo; + public static bool IsCacheWithFetches(Loader.Loader loader) + { + return loader.CacheTypes.Length > loader.ResultTypes.Length; + } + internal QueryCacheResultBuilder(Loader.Loader loader) { _resultTypes = loader.ResultTypes; - _cacheTypes = loader.CacheTypes; - if (loader.EntityFetches != null) + if (IsCacheWithFetches(loader)) { - for (var i = 0; i < loader.EntityFetches.Length; i++) - { - if (loader.EntityFetches[i]) - { - _entityFetchIndexes.Add(i); - } - } - - _hasFetches = _entityFetchIndexes.Count > 0; - } - - if (loader.CollectionFetches == null) - { - return; + _cacheInfo = loader.CacheInfo; } - - for (var i = 0; i < loader.CollectionFetches.Length; i++) - { - if (loader.CollectionFetches[i]) - { - _collectionFetchIndexes.Add(i); - } - } - - _hasFetches = _hasFetches || _collectionFetchIndexes.Count > 0; } internal IList Result { get; } = new List(); internal void AddRow(object result, object[] entities, IPersistentCollection[] collections) { - if (!_hasFetches) + if (_cacheInfo == null) { Result.Add(result); return; } - var row = new object[_cacheTypes.Length]; + var row = new object[_cacheInfo.CacheTypes.Length]; if (_resultTypes.Length == 1) { row[0] = result; @@ -77,14 +50,17 @@ internal void AddRow(object result, object[] entities, IPersistentCollection[] c } var i = _resultTypes.Length; - foreach (var index in _entityFetchIndexes) + foreach (var index in _cacheInfo.AdditionalEntities) { row[i++] = entities[index]; } - foreach (var index in _collectionFetchIndexes) + if (collections != null) { - row[i++] = collections[index]; + foreach (var collection in collections) + { + row[i++] = collection; + } } Result.Add(row); @@ -92,7 +68,7 @@ internal void AddRow(object result, object[] entities, IPersistentCollection[] c internal IList GetResultList(IList cacheList) { - if (!_hasFetches) + if (_cacheInfo == null) { return cacheList; } diff --git a/src/NHibernate/Cache/QueryKey.cs b/src/NHibernate/Cache/QueryKey.cs index cdd8ed44179..d336a58d323 100644 --- a/src/NHibernate/Cache/QueryKey.cs +++ b/src/NHibernate/Cache/QueryKey.cs @@ -13,7 +13,7 @@ namespace NHibernate.Cache { [Serializable] - public class QueryKey : IDeserializationCallback + public class QueryKey : IDeserializationCallback, IEquatable { private readonly ISessionFactoryImplementor _factory; private readonly SqlString _sqlQueryString; @@ -21,6 +21,7 @@ public class QueryKey : IDeserializationCallback private readonly object[] _values; private readonly int _firstRow = RowSelection.NoValue; private readonly int _maxRows = RowSelection.NoValue; + private readonly string _tenantIdentifier; // Sets and dictionaries are populated last during deserialization, causing them to be potentially empty // during the deserialization callback. This causes them to be unreliable when used in hashcode or equals @@ -47,8 +48,9 @@ public class QueryKey : IDeserializationCallback /// The query parameters. /// The filters. /// The result transformer; should be null if data is not transformed before being cached. + /// Tenant identifier or null public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, QueryParameters queryParameters, - ISet filters, CacheableResultTransformer customTransformer) + ISet filters, CacheableResultTransformer customTransformer, string tenantIdentifier) { _factory = factory; _sqlQueryString = queryString; @@ -70,10 +72,19 @@ public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, Query _namedParameters = queryParameters.NamedParameters?.ToArray(); _filters = filters?.ToArray(); _customTransformer = customTransformer; + _tenantIdentifier = tenantIdentifier; _hashCode = ComputeHashCode(); } + //Since 5.3 + [Obsolete("Please use overload with tenantIdentifier")] + public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, QueryParameters queryParameters, + ISet filters, CacheableResultTransformer customTransformer) + : this(factory, queryString, queryParameters, filters, customTransformer, null) + { + } + public CacheableResultTransformer ResultTransformer { get { return _customTransformer; } @@ -93,47 +104,51 @@ public QueryKey SetMaxRows(int[] maxRows) public override bool Equals(object other) { - QueryKey that = (QueryKey)other; - if (!_sqlQueryString.Equals(that._sqlQueryString)) + return Equals(other as QueryKey); + } + + public bool Equals(QueryKey other) + { + if (other == null || !_sqlQueryString.Equals(other._sqlQueryString)) { return false; } - if (_firstRow != that._firstRow - || _maxRows != that._maxRows) + + if (_firstRow != other._firstRow || _maxRows != other._maxRows) { return false; } - if (!Equals(_customTransformer, that._customTransformer)) + if (!Equals(_customTransformer, other._customTransformer)) { return false; } if (_types == null) { - if (that._types != null) + if (other._types != null) { return false; } } else { - if (that._types == null) + if (other._types == null) { return false; } - if (_types.Length != that._types.Length) + if (_types.Length != other._types.Length) { return false; } for (int i = 0; i < _types.Length; i++) { - if (!_types[i].Equals(that._types[i])) + if (!_types[i].Equals(other._types[i])) { return false; } - if (!Equals(_values[i], that._values[i])) + if (!Equals(_values[i], other._values[i])) { return false; } @@ -144,16 +159,21 @@ public override bool Equals(object other) // issues on deserialization if GetHashCode or Equals are called in its deserialization callback. And // building sets or dictionaries on the fly will in most cases be worst than BagEquals, unless re-coding // its short-circuits. - if (!CollectionHelper.BagEquals(_filters, that._filters)) + if (!CollectionHelper.BagEquals(_filters, other._filters)) return false; - if (!CollectionHelper.BagEquals(_namedParameters, that._namedParameters, NamedParameterComparer.Instance)) + if (!CollectionHelper.BagEquals(_namedParameters, other._namedParameters, NamedParameterComparer.Instance)) return false; - if (!CollectionHelper.SequenceEquals(_multiQueriesFirstRows, that._multiQueriesFirstRows)) + if (!CollectionHelper.SequenceEquals(_multiQueriesFirstRows, other._multiQueriesFirstRows)) { return false; } - if (!CollectionHelper.SequenceEquals(_multiQueriesMaxRows, that._multiQueriesMaxRows)) + if (!CollectionHelper.SequenceEquals(_multiQueriesMaxRows, other._multiQueriesMaxRows)) + { + return false; + } + + if (_tenantIdentifier != other._tenantIdentifier) { return false; } @@ -219,6 +239,10 @@ public int ComputeHashCode() result = 37 * result + (_customTransformer == null ? 0 : _customTransformer.GetHashCode()); result = 37 * result + _sqlQueryString.GetHashCode(); + if (_tenantIdentifier != null) + { + result = (37 * result) + _tenantIdentifier.GetHashCode(); + } return result; } } @@ -280,6 +304,10 @@ public override string ToString() buf.Append("; "); } + if (_tenantIdentifier != null) + { + buf.Append("; tenantIdentifier=").Append(_tenantIdentifier).Append("; "); + } return buf.ToString(); } diff --git a/src/NHibernate/Cache/ReadOnlyCache.cs b/src/NHibernate/Cache/ReadOnlyCache.cs index 0f71872282f..e95ec9c3475 100644 --- a/src/NHibernate/Cache/ReadOnlyCache.cs +++ b/src/NHibernate/Cache/ReadOnlyCache.cs @@ -41,12 +41,13 @@ CacheBase IBatchableCacheConcurrencyStrategy.Cache public object Get(CacheKey key, long timestamp) { - object result = Cache.Get(key); - if (result != null && log.IsDebugEnabled()) + var result = Cache.Get(key); + if (log.IsDebugEnabled()) { - log.Debug("Cache hit: {0}", key); + log.Debug(result != null ? "Cache hit: {0}" : "Cache miss: {0}", key); } - return result; + + return result; } public object[] GetMany(CacheKey[] keys, long timestamp) @@ -55,15 +56,14 @@ public object[] GetMany(CacheKey[] keys, long timestamp) { log.Debug("Cache lookup: {0}", string.Join(",", keys.AsEnumerable())); } - var results = _cache.GetMany(keys.Select(o => (object) o).ToArray()); - if (!log.IsDebugEnabled()) - { - return results; - } - for (var i = 0; i < keys.Length; i++) + + var results = _cache.GetMany(keys); + if (log.IsDebugEnabled()) { - log.Debug(results[i] != null ? $"Cache hit: {keys[i]}" : $"Cache miss: {keys[i]}"); + log.Debug("Cache hit: {0}", string.Join(",", keys.Where((k, i) => results[i] != null))); + log.Debug("Cache miss: {0}", string.Join(",", keys.Where((k, i) => results[i] == null))); } + return results; } @@ -100,7 +100,7 @@ public bool[] PutMany( var skipKeyIndexes = new HashSet(); if (checkKeys.Any()) { - var objects = _cache.GetMany(checkKeys.Select(o => (object) o).ToArray()); + var objects = _cache.GetMany(checkKeys.ToArray()); for (var i = 0; i < objects.Length; i++) { if (objects[i] != null) diff --git a/src/NHibernate/Cache/ReadWriteCache.cs b/src/NHibernate/Cache/ReadWriteCache.cs index 1b0ca1f879f..9bb25e51048 100644 --- a/src/NHibernate/Cache/ReadWriteCache.cs +++ b/src/NHibernate/Cache/ReadWriteCache.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cache.Access; +using NHibernate.Util; namespace NHibernate.Cache { @@ -33,9 +34,9 @@ public interface ILockable private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(ReadWriteCache)); - private readonly object _lockObject = new object(); private CacheBase _cache; private int _nextLockId; + private readonly AsyncReaderWriterLock _asyncReaderWriterLock = new AsyncReaderWriterLock(); /// /// Gets the cache region name. @@ -95,7 +96,7 @@ private int NextLockId() /// public object Get(CacheKey key, long txTimestamp) { - lock (_lockObject) + using (_asyncReaderWriterLock.ReadLock()) { if (log.IsDebugEnabled()) { @@ -106,35 +107,8 @@ public object Get(CacheKey key, long txTimestamp) /*try { cache.Lock( key );*/ - - ILockable lockable = (ILockable) Cache.Get(key); - - bool gettable = lockable != null && lockable.IsGettable(txTimestamp); - - if (gettable) - { - if (log.IsDebugEnabled()) - { - log.Debug("Cache hit: {0}", key); - } - - return ((CachedItem) lockable).Value; - } - else - { - if (log.IsDebugEnabled()) - { - if (lockable == null) - { - log.Debug("Cache miss: {0}", key); - } - else - { - log.Debug("Cached item was locked: {0}", key); - } - } - return null; - } + var lockable = (ILockable) Cache.Get(key); + return GetValue(txTimestamp, key, lockable); /*} finally { @@ -150,32 +124,38 @@ public object[] GetMany(CacheKey[] keys, long timestamp) log.Debug("Cache lookup: {0}", string.Join(",", keys.AsEnumerable())); } var result = new object[keys.Length]; - lock (_lockObject) + using (_asyncReaderWriterLock.ReadLock()) { - var lockables = _cache.GetMany(keys.Select(o => (object) o).ToArray()); + var lockables = _cache.GetMany(keys); for (var i = 0; i < lockables.Length; i++) { - var lockable = (ILockable) lockables[i]; - var gettable = lockable != null && lockable.IsGettable(timestamp); - - if (gettable) - { - if (log.IsDebugEnabled()) - { - log.Debug("Cache hit: {0}", keys[i]); - } - result[i] = ((CachedItem) lockable).Value; - } + var o = (ILockable) lockables[i]; + result[i] = GetValue(timestamp, keys[i], o); + } + } + return result; + } - if (log.IsDebugEnabled()) - { - log.Debug(lockable == null ? "Cache miss: {0}" : "Cached item was locked: {0}", keys[i]); - } + private static object GetValue(long timestamp, CacheKey key, ILockable lockable) + { + var gettable = lockable != null && lockable.IsGettable(timestamp); - result[i] = null; + if (gettable) + { + if (log.IsDebugEnabled()) + { + log.Debug("Cache hit: {0}", key); } + + return ((CachedItem) lockable).Value; } - return result; + + if (log.IsDebugEnabled()) + { + log.Debug(lockable == null ? "Cache miss: {0}" : "Cached item was locked: {0}", key); + } + + return null; } /// @@ -187,7 +167,7 @@ public object[] GetMany(CacheKey[] keys, long timestamp) /// public ISoftLock Lock(CacheKey key, object version) { - lock (_lockObject) + using (_asyncReaderWriterLock.WriteLock()) { if (log.IsDebugEnabled()) { @@ -230,18 +210,18 @@ public bool[] PutMany( return result; } - lock (_lockObject) + using (_asyncReaderWriterLock.WriteLock()) { if (log.IsDebugEnabled()) { log.Debug("Caching: {0}", string.Join(",", keys.AsEnumerable())); } - var keysArr = keys.Cast().ToArray(); - var lockValue = _cache.LockMany(keysArr); + + var lockValue = _cache.LockMany(keys); try { var putBatch = new Dictionary(); - var lockables = _cache.GetMany(keysArr); + var lockables = _cache.GetMany(keys); for (var i = 0; i < keys.Length; i++) { var key = keys[i]; @@ -262,14 +242,9 @@ public bool[] PutMany( { if (log.IsDebugEnabled()) { - if (lockable.IsLock) - { - log.Debug("Item was locked: {0}", key); - } - else - { - log.Debug("Item was already cached: {0}", key); - } + log.Debug( + lockable.IsLock ? "Item was locked: {0}" : "Item was already cached: {0}", + key); } result[i] = false; } @@ -282,7 +257,7 @@ public bool[] PutMany( } finally { - _cache.UnlockMany(keysArr, lockValue); + _cache.UnlockMany(keys, lockValue); } } return result; @@ -304,7 +279,7 @@ public bool Put(CacheKey key, object value, long txTimestamp, object version, IC return false; } - lock (_lockObject) + using (_asyncReaderWriterLock.WriteLock()) { if (log.IsDebugEnabled()) { @@ -332,14 +307,7 @@ public bool Put(CacheKey key, object value, long txTimestamp, object version, IC { if (log.IsDebugEnabled()) { - if (lockable.IsLock) - { - log.Debug("Item was locked: {0}", key); - } - else - { - log.Debug("Item was already cached: {0}", key); - } + log.Debug(lockable.IsLock ? "Item was locked: {0}" : "Item was already cached: {0}", key); } return false; } @@ -363,7 +331,7 @@ private void DecrementLock(object key, CacheLock @lock) public void Release(CacheKey key, ISoftLock clientLock) { - lock (_lockObject) + using (_asyncReaderWriterLock.WriteLock()) { if (log.IsDebugEnabled()) { @@ -415,6 +383,7 @@ public void Destroy() // The cache is externally provided and may be shared. Destroying the cache is // not the responsibility of this class. Cache = null; + _asyncReaderWriterLock.Dispose(); } /// @@ -423,7 +392,7 @@ public void Destroy() /// public bool AfterUpdate(CacheKey key, object value, object version, ISoftLock clientLock) { - lock (_lockObject) + using (_asyncReaderWriterLock.WriteLock()) { if (log.IsDebugEnabled()) { @@ -469,7 +438,7 @@ public bool AfterUpdate(CacheKey key, object value, object version, ISoftLock cl public bool AfterInsert(CacheKey key, object value, object version) { - lock (_lockObject) + using (_asyncReaderWriterLock.WriteLock()) { if (log.IsDebugEnabled()) { @@ -479,7 +448,6 @@ public bool AfterInsert(CacheKey key, object value, object version) var lockValue = _cache.Lock(key); try { - ILockable lockable = (ILockable) Cache.Get(key); if (lockable == null) { diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index d63134d3ec8..8cda99f3815 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -205,14 +205,14 @@ public IList[] GetMany( if (Log.IsDebugEnabled()) Log.Debug("checking cached query results in region: '{0}'; {1}", _regionName, StringHelper.CollectionToString(keys)); - var cacheables = _cache.GetMany(keys).Cast().ToArray(); + var cacheables = _cache.GetMany(keys); var spacesToCheck = new List>(); var checkedSpacesIndexes = new HashSet(); var checkedSpacesTimestamp = new List(); for (var i = 0; i < keys.Length; i++) { - var cacheable = cacheables[i]; + var cacheable = (IList) cacheables[i]; if (cacheable == null) { Log.Debug("query results were not found in cache: {0}", keys[i]); @@ -246,7 +246,7 @@ public IList[] GetMany( for (var i = 0; i < keys.Length; i++) { - var cacheable = cacheables[i]; + var cacheable = (IList) cacheables[i]; if (cacheable == null) continue; @@ -283,7 +283,7 @@ public IList[] GetMany( { try { - results[i] = PerformAssemble(keys[i], finalReturnTypes[i], queryParams.NaturalKeyLookup, session, cacheables[i]); + results[i] = PerformAssemble(keys[i], finalReturnTypes[i], queryParams.NaturalKeyLookup, session, (IList) cacheables[i]); } finally { @@ -306,7 +306,7 @@ public IList[] GetMany( { try { - InitializeCollections(finalReturnTypes[i], session, results[i], cacheables[i]); + InitializeCollections(finalReturnTypes[i], session, results[i], (IList) cacheables[i]); } finally { @@ -551,6 +551,9 @@ private static ICacheAssembler[] GuessTypes(IList cacheable) protected virtual bool IsUpToDate(ISet spaces, long timestamp) { + if (spaces.Count == 0) + return true; + return _updateTimestampsCache.IsUpToDate(spaces, timestamp); } } diff --git a/src/NHibernate/Cache/Timestamper.cs b/src/NHibernate/Cache/Timestamper.cs index a960041861d..c904b9108b4 100644 --- a/src/NHibernate/Cache/Timestamper.cs +++ b/src/NHibernate/Cache/Timestamper.cs @@ -34,7 +34,7 @@ public static long Next() { // Ticks is accurate down to 100 nanoseconds - hibernate uses milliseconds // to help calculate next time so drop the nanoseconds portion.(1ms==1000000ns) - long newTime = ((DateTime.Now.Ticks / 10000) - baseDateMs) << BinDigits; + long newTime = ((DateTime.UtcNow.Ticks / 10000) - baseDateMs) << BinDigits; if (time < newTime) { time = newTime; @@ -48,4 +48,4 @@ public static long Next() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Cache/UpdateTimestampsCache.cs index 21f37cf8d25..f6851f5ed44 100644 --- a/src/NHibernate/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Cache/UpdateTimestampsCache.cs @@ -19,6 +19,7 @@ public partial class UpdateTimestampsCache { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(UpdateTimestampsCache)); private readonly CacheBase _updateTimestamps; + private readonly AsyncReaderWriterLock _asyncReaderWriterLock = new AsyncReaderWriterLock(); public virtual void Clear() { @@ -54,14 +55,18 @@ public void PreInvalidate(object[] spaces) PreInvalidate(spaces.OfType().ToList()); } - [MethodImpl(MethodImplOptions.Synchronized)] public virtual void PreInvalidate(IReadOnlyCollection spaces) { - //TODO: to handle concurrent writes correctly, this should return a Lock to the client - var ts = _updateTimestamps.NextTimestamp() + _updateTimestamps.Timeout; - SetSpacesTimestamp(spaces, ts); + if (spaces.Count == 0) + return; - //TODO: return new Lock(ts); + using (_asyncReaderWriterLock.WriteLock()) + { + //TODO: to handle concurrent writes correctly, this should return a Lock to the client + var ts = _updateTimestamps.NextTimestamp() + _updateTimestamps.Timeout; + SetSpacesTimestamp(spaces, ts); + //TODO: return new Lock(ts); + } } //Since v5.1 @@ -72,51 +77,46 @@ public void Invalidate(object[] spaces) Invalidate(spaces.OfType().ToList()); } - [MethodImpl(MethodImplOptions.Synchronized)] public virtual void Invalidate(IReadOnlyCollection spaces) - { - //TODO: to handle concurrent writes correctly, the client should pass in a Lock - long ts = _updateTimestamps.NextTimestamp(); - //TODO: if lock.getTimestamp().equals(ts) - if (log.IsDebugEnabled()) - log.Debug("Invalidating spaces [{0}]", StringHelper.CollectionToString(spaces)); - SetSpacesTimestamp(spaces, ts); - } - - private void SetSpacesTimestamp(IReadOnlyCollection spaces, long ts) { if (spaces.Count == 0) return; - var timestamps = new object[spaces.Count]; - for (var i = 0; i < timestamps.Length; i++) + using (_asyncReaderWriterLock.WriteLock()) { - timestamps[i] = ts; + //TODO: to handle concurrent writes correctly, the client should pass in a Lock + long ts = _updateTimestamps.NextTimestamp(); + //TODO: if lock.getTimestamp().equals(ts) + if (log.IsDebugEnabled()) + log.Debug("Invalidating spaces [{0}]", StringHelper.CollectionToString(spaces)); + SetSpacesTimestamp(spaces, ts); } + } - _updateTimestamps.PutMany(spaces.ToArray(), timestamps); + private void SetSpacesTimestamp(IReadOnlyCollection spaces, long ts) + { + _updateTimestamps.PutMany( + spaces.ToArray(), + ArrayHelper.Fill(ts, spaces.Count)); } - [MethodImpl(MethodImplOptions.Synchronized)] public virtual bool IsUpToDate(ISet spaces, long timestamp /* H2.1 has Long here */) { if (spaces.Count == 0) return true; - var keys = new object[spaces.Count]; - var index = 0; - foreach (var space in spaces) + using (_asyncReaderWriterLock.ReadLock()) { - keys[index++] = space; + var lastUpdates = _updateTimestamps.GetMany(spaces.ToArray()); + return lastUpdates.All(lastUpdate => !IsOutdated(lastUpdate as long?, timestamp)); } - var lastUpdates = _updateTimestamps.GetMany(keys); - return lastUpdates.All(lastUpdate => !IsOutdated(lastUpdate as long?, timestamp)); } - [MethodImpl(MethodImplOptions.Synchronized)] public virtual bool[] AreUpToDate(ISet[] spaces, long[] timestamps) { - var results = new bool[spaces.Length]; + if (spaces.Length == 0) + return Array.Empty(); + var allSpaces = new HashSet(); foreach (var sp in spaces) { @@ -124,35 +124,27 @@ public virtual bool[] AreUpToDate(ISet[] spaces, long[] timestamps) } if (allSpaces.Count == 0) + return ArrayHelper.Fill(true, spaces.Length); + + var keys = allSpaces.ToArray(); + + using (_asyncReaderWriterLock.ReadLock()) { + var index = 0; + var lastUpdatesBySpace = + _updateTimestamps + .GetMany(keys) + .ToDictionary(u => keys[index++], u => u as long?); + + var results = new bool[spaces.Length]; for (var i = 0; i < spaces.Length; i++) { - results[i] = true; + var timestamp = timestamps[i]; + results[i] = spaces[i].All(space => !IsOutdated(lastUpdatesBySpace[space], timestamp)); } return results; } - - var keys = new object[allSpaces.Count]; - var index = 0; - foreach (var space in allSpaces) - { - keys[index++] = space; - } - - index = 0; - var lastUpdatesBySpace = - _updateTimestamps - .GetMany(keys) - .ToDictionary(u => keys[index++], u => u as long?); - - for (var i = 0; i < spaces.Length; i++) - { - var timestamp = timestamps[i]; - results[i] = spaces[i].All(space => !IsOutdated(lastUpdatesBySpace[space], timestamp)); - } - - return results; } // Since v5.3 @@ -161,9 +153,10 @@ public virtual void Destroy() { // The cache is externally provided and may be shared. Destroying the cache is // not the responsibility of this class. + _asyncReaderWriterLock.Dispose(); } - private bool IsOutdated(long? lastUpdate, long timestamp) + private static bool IsOutdated(long? lastUpdate, long timestamp) { if (!lastUpdate.HasValue) { diff --git a/src/NHibernate/Cfg/AppSettings.cs b/src/NHibernate/Cfg/AppSettings.cs new file mode 100644 index 00000000000..510f12ee3ec --- /dev/null +++ b/src/NHibernate/Cfg/AppSettings.cs @@ -0,0 +1,13 @@ +namespace NHibernate.Cfg +{ + /// + /// Configuration App Settings + /// + public static class AppSettings + { + /// + /// Type that implements + /// + public const string LoggerFactoryClassName = "nhibernate-logger"; + } +} diff --git a/src/NHibernate/Cfg/Configuration.cs b/src/NHibernate/Cfg/Configuration.cs index cf6a0623c42..6b52f6008ba 100644 --- a/src/NHibernate/Cfg/Configuration.cs +++ b/src/NHibernate/Cfg/Configuration.cs @@ -47,7 +47,7 @@ namespace NHibernate.Cfg /// /// [Serializable] - public class Configuration : ISerializable + public partial class Configuration : ISerializable { /// Default name for hibernate configuration file. public const string DefaultHibernateCfgFileName = "hibernate.cfg.xml"; @@ -818,7 +818,7 @@ public Configuration AddAssembly(Assembly assembly) return this; } - private static IList GetAllHbmXmlResourceNames(Assembly assembly) + private static List GetAllHbmXmlResourceNames(Assembly assembly) { var result = new List(); @@ -2229,7 +2229,6 @@ public void SetListeners(ListenerType type, object[] listeners) } } - /// /// Append the listeners to the end of the currently configured /// listeners diff --git a/src/NHibernate/Cfg/ConfigurationExtensions.cs b/src/NHibernate/Cfg/ConfigurationExtensions.cs index 84b16969f14..381c47dfae5 100644 --- a/src/NHibernate/Cfg/ConfigurationExtensions.cs +++ b/src/NHibernate/Cfg/ConfigurationExtensions.cs @@ -1,7 +1,6 @@ using System; using NHibernate.Cfg.Loquacious; using NHibernate.Context; -using NHibernate.Engine; using NHibernate.Hql; using NHibernate.Linq; using NHibernate.Linq.Functions; @@ -9,25 +8,35 @@ namespace NHibernate.Cfg { + //Since 5.3 + [Obsolete] public static class ConfigurationExtensions { + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static IFluentSessionFactoryConfiguration SessionFactory(this Configuration configuration) { return new FluentSessionFactoryConfiguration(configuration); } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration SessionFactoryName(this Configuration configuration, string sessionFactoryName) { configuration.SetProperty(Environment.SessionFactoryName, sessionFactoryName); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration Cache(this Configuration configuration, Action cacheProperties) { cacheProperties(new CacheConfigurationProperties(configuration)); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration CollectionTypeFactory(this Configuration configuration) { configuration.SetProperty(Environment.CollectionTypeFactoryClass, @@ -35,48 +44,64 @@ public static Configuration CollectionTypeFactory(this Confi return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration Proxy(this Configuration configuration, Action proxyProperties) { proxyProperties(new ProxyConfigurationProperties(configuration)); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration HqlQueryTranslator(this Configuration configuration) where TQueryTranslator : IQueryTranslatorFactory { configuration.SetProperty(Environment.QueryTranslator, typeof(TQueryTranslator).AssemblyQualifiedName); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration LinqQueryProvider(this Configuration configuration) where TQueryProvider : INhQueryProvider { configuration.SetProperty(Environment.QueryLinqProvider, typeof(TQueryProvider).AssemblyQualifiedName); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration LinqToHqlGeneratorsRegistry(this Configuration configuration) where TLinqToHqlGeneratorsRegistry : ILinqToHqlGeneratorsRegistry { configuration.SetProperty(Environment.LinqToHqlGeneratorsRegistry, typeof(TLinqToHqlGeneratorsRegistry).AssemblyQualifiedName); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration CurrentSessionContext(this Configuration configuration) where TCurrentSessionContext : ICurrentSessionContext { configuration.SetProperty(Environment.CurrentSessionContextClass, typeof(TCurrentSessionContext).AssemblyQualifiedName); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration Mappings(this Configuration configuration, Action mappingsProperties) { mappingsProperties(new MappingsConfigurationProperties(configuration)); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration DataBaseIntegration(this Configuration configuration, Action dataBaseIntegration) { dataBaseIntegration(new DbIntegrationConfigurationProperties(configuration)); return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration EntityCache(this Configuration configuration, Action> entityCacheConfiguration) where TEntity : class { @@ -126,6 +151,8 @@ public static Configuration EntityCache(this Configuration configuratio /// /// /// + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration TypeDefinition(this Configuration configuration, Action typeDefConfiguration) where TDef : class { @@ -133,7 +160,7 @@ public static Configuration TypeDefinition(this Configuration configuratio { return configuration; } - var tdConfiguration = new TypeDefConfigurationProperties(); + var tdConfiguration = TypeDefConfigurationProperties.Create(); typeDefConfiguration(tdConfiguration); if(string.IsNullOrEmpty(tdConfiguration.Alias)) { @@ -145,6 +172,8 @@ public static Configuration TypeDefinition(this Configuration configuratio return configuration; } + //Since 5.3 + [Obsolete("Please use Configuration instance method instead")] public static Configuration AddNamedQuery(this Configuration configuration, string queryIdentifier, Action namedQueryDefinition) { if (configuration == null) @@ -165,4 +194,4 @@ public static Configuration AddNamedQuery(this Configuration configuration, stri return configuration; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/ConfigurationProvider.cs b/src/NHibernate/Cfg/ConfigurationProvider.cs new file mode 100644 index 00000000000..d793e906ee8 --- /dev/null +++ b/src/NHibernate/Cfg/ConfigurationProvider.cs @@ -0,0 +1,29 @@ +namespace NHibernate.Cfg +{ + /// + /// Base class for NHibernate configuration settings + /// + public abstract class ConfigurationProvider + { + private static ConfigurationProvider _current = new StaticConfigurationManagerProvider(); + + /// + /// Provides ability to override default with custom implementation. + /// Can be set to null if all configuration is specified by code + /// + public static ConfigurationProvider Current + { + get => _current; + set => _current = value ?? new NullConfigurationProvider(); + } + + public abstract IHibernateConfiguration GetConfiguration(); + + public abstract string GetNamedConnectionString(string name); + + /// + /// Type that implements + /// + public abstract string GetLoggerFactoryClassName(); + } +} diff --git a/src/NHibernate/Cfg/ConfigurationSchema/CfgXmlHelper.cs b/src/NHibernate/Cfg/ConfigurationSchema/CfgXmlHelper.cs index d2cb08ecdd2..caa9c4c2191 100644 --- a/src/NHibernate/Cfg/ConfigurationSchema/CfgXmlHelper.cs +++ b/src/NHibernate/Cfg/ConfigurationSchema/CfgXmlHelper.cs @@ -280,5 +280,4 @@ internal static string ListenerTypeConvertToString(ListenerType listenerType) } } } - } diff --git a/src/NHibernate/Cfg/ConfigurationSchema/ClassCacheConfiguration.cs b/src/NHibernate/Cfg/ConfigurationSchema/ClassCacheConfiguration.cs index b5ca288c32b..75341d5b228 100644 --- a/src/NHibernate/Cfg/ConfigurationSchema/ClassCacheConfiguration.cs +++ b/src/NHibernate/Cfg/ConfigurationSchema/ClassCacheConfiguration.cs @@ -127,7 +127,6 @@ public string Region get { return region; } } - private EntityCacheUsage usage; /// /// Cache strategy. @@ -149,6 +148,5 @@ public ClassCacheInclude Include { get { return include; } } - } } diff --git a/src/NHibernate/Cfg/ConfigurationSchema/EventConfiguration.cs b/src/NHibernate/Cfg/ConfigurationSchema/EventConfiguration.cs index 6a1edec38c1..a45165c3afb 100644 --- a/src/NHibernate/Cfg/ConfigurationSchema/EventConfiguration.cs +++ b/src/NHibernate/Cfg/ConfigurationSchema/EventConfiguration.cs @@ -49,7 +49,7 @@ public ListenerType Type get { return type; } } - private IList listeners = new List(); + private List listeners = new List(); /// /// Listeners for this event. /// @@ -57,6 +57,5 @@ public IList Listeners { get { return listeners; } } - } } diff --git a/src/NHibernate/Cfg/ConfigurationSchema/HibernateConfiguration.cs b/src/NHibernate/Cfg/ConfigurationSchema/HibernateConfiguration.cs index 5208e7428e8..1631a31a226 100644 --- a/src/NHibernate/Cfg/ConfigurationSchema/HibernateConfiguration.cs +++ b/src/NHibernate/Cfg/ConfigurationSchema/HibernateConfiguration.cs @@ -2,7 +2,6 @@ using System.Xml; using System.Xml.XPath; - namespace NHibernate.Cfg.ConfigurationSchema { /// diff --git a/src/NHibernate/Cfg/ConfigurationSchema/ListenerConfiguration.cs b/src/NHibernate/Cfg/ConfigurationSchema/ListenerConfiguration.cs index 4a4fc977cb7..de341beed70 100644 --- a/src/NHibernate/Cfg/ConfigurationSchema/ListenerConfiguration.cs +++ b/src/NHibernate/Cfg/ConfigurationSchema/ListenerConfiguration.cs @@ -84,6 +84,5 @@ public ListenerType Type { get { return type; } } - } } diff --git a/src/NHibernate/Cfg/ConfigurationSchema/SessionFactoryConfiguration.cs b/src/NHibernate/Cfg/ConfigurationSchema/SessionFactoryConfiguration.cs index 3e96946feca..7b565d7ff1c 100644 --- a/src/NHibernate/Cfg/ConfigurationSchema/SessionFactoryConfiguration.cs +++ b/src/NHibernate/Cfg/ConfigurationSchema/SessionFactoryConfiguration.cs @@ -83,7 +83,6 @@ private void ParseClassesCache(XPathNavigator navigator) } } - private void ParseCollectionsCache(XPathNavigator navigator) { XPathNodeIterator xpni = navigator.Select(CfgXmlHelper.SessionFactoryCollectionsCacheExpression); @@ -93,7 +92,6 @@ private void ParseCollectionsCache(XPathNavigator navigator) } } - private void ParseListeners(XPathNavigator navigator) { XPathNodeIterator xpni = navigator.Select(CfgXmlHelper.SessionFactoryListenersExpression); diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index 5e5dc8238be..973657f803d 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.Reflection; using NHibernate.Bytecode; using NHibernate.Cfg.ConfigurationSchema; using NHibernate.Engine; using NHibernate.Linq; +using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Util; namespace NHibernate.Cfg @@ -123,6 +124,10 @@ public static string Version [Obsolete("This setting has no usages and will be removed in a future version")] public const string OutputStylesheet = "xml.output_stylesheet"; + /// + /// The class name of a custom implementation. Defaults to the + /// built-in . + /// public const string TransactionStrategy = "transaction.factory_class"; /// /// Timeout duration in milliseconds for the system transaction completion lock. @@ -144,6 +149,14 @@ public static string Version /// transaction preparation, while still benefiting from on querying. /// public const string UseConnectionOnSystemTransactionPrepare = "transaction.use_connection_on_system_prepare"; + /// + /// Should sessions check on every operation whether there is an ongoing system transaction or not, and enlist + /// into it if any? Default is . It can also be controlled at session opening, see + /// . A session can also be instructed to explicitly join the current + /// transaction by calling . This setting has no effect when using a + /// transaction factory that is not system transactions aware. + /// + public const string AutoJoinTransaction = "transaction.auto_join"; // Since v5.0.1 [Obsolete("This setting has no usages and will be removed in a future version")] @@ -182,6 +195,12 @@ public static string Version [Obsolete("This setting has no usages and will be removed in a future version")] public const string QueryImports = "query.imports"; public const string Hbm2ddlAuto = "hbm2ddl.auto"; + + // 6.0 TODO default should become true + /// + /// Whether to throw or not on schema auto-update failures. false by default. + /// + public const string Hbm2ddlThrowOnUpdate = "hbm2ddl.throw_on_update"; public const string Hbm2ddlKeyWords = "hbm2ddl.keywords"; public const string SqlExceptionConverter = "sql_exception_converter"; @@ -215,6 +234,48 @@ public static string Version public const string LinqToHqlGeneratorsRegistry = "linqtohql.generatorsregistry"; + /// + /// Whether to use the legacy pre-evaluation or not in Linq queries. true by default. + /// + /// + /// + /// Legacy pre-evaluation is causing special properties or functions like DateTime.Now or + /// Guid.NewGuid() to be always evaluated with the .Net runtime and replaced in the query by + /// parameter values. + /// + /// + /// The new pre-evaluation allows them to be converted to HQL function calls which will be run on the db + /// side. This allows for example to retrieve the server time instead of the client time, or to generate + /// UUIDs for each row instead of an unique one for all rows. (This does not happen if the dialect does + /// not support the required HQL function.) + /// + /// + /// The new pre-evaluation will likely be enabled by default in the next major version (6.0). + /// + /// + public const string LinqToHqlLegacyPreEvaluation = "linqtohql.legacy_preevaluation"; + + /// + /// When the new pre-evaluation is enabled, should methods which translation is not supported by the current + /// dialect fallback to pre-evaluation? false by default. + /// + /// + /// + /// When this fallback option is enabled while legacy pre-evaluation is disabled, properties or functions + /// like DateTime.Now or Guid.NewGuid() used in Linq expressions will not fail when the dialect does not + /// support them, but will instead be pre-evaluated. + /// + /// + /// When this fallback option is disabled while legacy pre-evaluation is disabled, properties or functions + /// like DateTime.Now or Guid.NewGuid() used in Linq expressions will fail when the dialect does not + /// support them. + /// + /// + /// This option has no effect if the legacy pre-evaluation is enabled. + /// + /// + public const string LinqToHqlFallbackOnPreEvaluation = "linqtohql.fallback_on_preevaluation"; + /// Enable ordering of insert statements for the purpose of more efficient batching. public const string OrderInserts = "order_inserts"; @@ -223,6 +284,11 @@ public static string Version public const string QueryModelRewriterFactory = "query.query_model_rewriter_factory"; + /// + /// The class name of the LINQ query pre-transformer registrar, implementing . + /// + public const string PreTransformerRegistrar = "query.pre_transformer_registrar"; + /// /// Set the default length used in casting when the target type is length bound and /// does not specify it. 4000 by default, automatically trimmed down according to dialect type registration. @@ -266,6 +332,20 @@ public static string Version /// public const string OracleUseNPrefixedTypesForUnicode = "oracle.use_n_prefixed_types_for_unicode"; + /// + /// Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET + /// and types, where FLOAT and DOUBLE are not. Oracle + /// FLOAT and DOUBLE types do not conform to the IEEE standard as they are internally implemented as + /// NUMBER type, which makes them an exact numeric type. + /// + /// by default. + /// + /// + /// + /// See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126 + /// + public const string OracleUseBinaryFloatingPointTypes = "oracle.use_binary_floating_point_types"; + /// /// /// Firebird with FirebirdSql.Data.FirebirdClient may be unable to determine the type @@ -280,6 +360,18 @@ public static string Version /// public const string FirebirdDisableParameterCasting = "firebird.disable_parameter_casting"; + /// + /// + /// SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid + /// connection string parameter (default is 'true'). The BinaryGuid setting will affect + /// how to cast GUID to string in SQL. NHibernate will attempt to detect this + /// setting automatically from the connection string, but if the connection + /// or connection string is being handled by the application instead of by NHibernate, + /// you can use the 'sqlite.binaryguid' NHibernate setting to override the behavior. + /// + /// + public const string SqliteBinaryGuid = "sqlite.binaryguid"; + /// /// Set whether tracking the session id or not. When , each session /// will have an unique that can be retrieved by , @@ -294,6 +386,16 @@ public static string Version private static readonly Dictionary GlobalProperties = new Dictionary(); + /// + /// Strategy for multi-tenancy. + /// See also + public const string MultiTenancy = "multi_tenancy.strategy"; + + /// + /// Connection provider for given multi-tenancy strategy. Class name implementing IMultiTenancyConnectionProvider. + /// + public const string MultiTenancyConnectionProvider = "multi_tenancy.connection_provider"; + private static IBytecodeProvider BytecodeProviderInstance; private static bool EnableReflectionOptimizer; @@ -358,19 +460,10 @@ public static void InitializeGlobalProperties(IHibernateConfiguration config) private static IHibernateConfiguration GetHibernateConfiguration() { - object config = ConfigurationManager.GetSection(CfgXmlHelper.CfgSectionName); - if (config == null) + var nhConfig = ConfigurationProvider.Current.GetConfiguration(); + if (nhConfig == null && log.IsInfoEnabled()) { log.Info("{0} section not found in application configuration file", CfgXmlHelper.CfgSectionName); - return null; - } - - var nhConfig = config as IHibernateConfiguration; - if (nhConfig == null) - { - log.Info( - "{0} section handler, in application configuration file, is not IHibernateConfiguration, section ignored", - CfgXmlHelper.CfgSectionName); } return nhConfig; @@ -542,5 +635,34 @@ private static IObjectsFactory CreateCustomObjectsFactory(string assemblyQualifi } } + /// + /// Get a named connection string, if configured. + /// + /// + /// Thrown when a was found + /// in the settings parameter but could not be found in the app.config. + /// + internal static string GetNamedConnectionString(IDictionary settings) + { + if (!settings.TryGetValue(ConnectionStringName, out var connStringName)) + return null; + + return ConfigurationProvider.Current.GetNamedConnectionString(connStringName) + ?? throw new HibernateException($"Could not find named connection string '{connStringName}'."); + } + + /// + /// Get the configured connection string, from if that + /// is set, otherwise from , or null if that isn't + /// set either. + /// + internal static string GetConfiguredConnectionString(IDictionary settings) + { + // Connection string in the configuration overrides named connection string. + if (!settings.TryGetValue(ConnectionString, out string connString)) + connString = GetNamedConnectionString(settings); + + return connString; + } } } diff --git a/src/NHibernate/Cfg/FilterSecondPassArgs.cs b/src/NHibernate/Cfg/FilterSecondPassArgs.cs index e1026286743..2db9e888371 100644 --- a/src/NHibernate/Cfg/FilterSecondPassArgs.cs +++ b/src/NHibernate/Cfg/FilterSecondPassArgs.cs @@ -22,7 +22,7 @@ public FilterSecondPassArgs(IFilterable filterable, string filterName) FilterName = filterName; } - public IFilterable Filterable{ get; private set;} + public IFilterable Filterable{ get; private set; } public string FilterName { get; private set; } } } diff --git a/src/NHibernate/Cfg/Loquacious/CacheConfiguration.cs b/src/NHibernate/Cfg/Loquacious/CacheConfiguration.cs index 7b4da5f6447..3562eed3096 100644 --- a/src/NHibernate/Cfg/Loquacious/CacheConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/CacheConfiguration.cs @@ -3,7 +3,10 @@ namespace NHibernate.Cfg.Loquacious { - internal class CacheConfigurationProperties : ICacheConfigurationProperties + public class CacheConfigurationProperties +#pragma warning disable 618 + : ICacheConfigurationProperties +#pragma warning restore 618 { private readonly Configuration cfg; @@ -46,6 +49,8 @@ public void QueryCache() where TFactory : IQueryCache throw new InvalidOperationException("This method is invalid and should not be used. Use QueryCacheFactory method instead."); } + #endregion + public void QueryCacheFactory() where TFactory : IQueryCacheFactory { UseSecondLevelCache = true; @@ -57,10 +62,12 @@ private bool UseSecondLevelCache { set { cfg.SetProperty(Environment.UseSecondLevelCache, value.ToString().ToLowerInvariant()); } } - #endregion } - internal class CacheConfiguration : ICacheConfiguration + public class CacheConfiguration +#pragma warning disable 618 + : ICacheConfiguration +#pragma warning restore 618 { private readonly FluentSessionFactoryConfiguration fc; @@ -75,39 +82,66 @@ internal Configuration Configuration get { return fc.Configuration; } } - #region Implementation of ICacheConfiguration - - public ICacheConfiguration Through() where TProvider : ICacheProvider + public CacheConfiguration Through() where TProvider : ICacheProvider { fc.Configuration.SetProperty(Environment.UseSecondLevelCache, "true"); fc.Configuration.SetProperty(Environment.CacheProvider, typeof(TProvider).AssemblyQualifiedName); return this; } - public ICacheConfiguration PrefixingRegionsWith(string regionPrefix) + public CacheConfiguration PrefixingRegionsWith(string regionPrefix) { fc.Configuration.SetProperty(Environment.CacheRegionPrefix, regionPrefix); return this; } - public ICacheConfiguration UsingMinimalPuts() + public CacheConfiguration UsingMinimalPuts() { fc.Configuration.SetProperty(Environment.UseMinimalPuts, true.ToString().ToLowerInvariant()); return this; } - public IFluentSessionFactoryConfiguration WithDefaultExpiration(int seconds) + public FluentSessionFactoryConfiguration WithDefaultExpiration(int seconds) { fc.Configuration.SetProperty(Environment.CacheDefaultExpiration, seconds.ToString()); return fc; } - public IQueryCacheConfiguration Queries { get; private set; } + public QueryCacheConfiguration Queries { get; } + +#pragma warning disable 618 + #region Implementation of ICacheConfiguration + + ICacheConfiguration ICacheConfiguration.Through() + { + return Through(); + } + + ICacheConfiguration ICacheConfiguration.PrefixingRegionsWith(string regionPrefix) + { + return PrefixingRegionsWith(regionPrefix); + } + + ICacheConfiguration ICacheConfiguration.UsingMinimalPuts() + { + return UsingMinimalPuts(); + } + + IFluentSessionFactoryConfiguration ICacheConfiguration.WithDefaultExpiration(int seconds) + { + return WithDefaultExpiration(seconds); + } + + IQueryCacheConfiguration ICacheConfiguration.Queries => Queries; #endregion +#pragma warning restore 618 } - internal class QueryCacheConfiguration : IQueryCacheConfiguration + public class QueryCacheConfiguration +#pragma warning disable 618 + : IQueryCacheConfiguration +#pragma warning restore 618 { private readonly CacheConfiguration cc; @@ -116,10 +150,8 @@ public QueryCacheConfiguration(CacheConfiguration cc) this.cc = cc; } - #region Implementation of IQueryCacheConfiguration - // 6.0 TODO: enable constraint and remove runtime type check - public ICacheConfiguration Through() // where TFactory : IQueryCacheFactory + public CacheConfiguration Through() //where TFactory : IQueryCacheFactory { if (!typeof(IQueryCacheFactory).IsAssignableFrom(typeof(TFactory))) throw new ArgumentException($"{nameof(TFactory)} must be an {nameof(IQueryCacheFactory)}", nameof(TFactory)); @@ -130,6 +162,15 @@ public ICacheConfiguration Through() // where TFactory : IQueryCacheFa return cc; } + #region Implementation of IQueryCacheConfiguration +#pragma warning disable 618 + + ICacheConfiguration IQueryCacheConfiguration.Through() + { + return Through(); + } + +#pragma warning restore 618 #endregion } } diff --git a/src/NHibernate/Cfg/Loquacious/Configuration.cs b/src/NHibernate/Cfg/Loquacious/Configuration.cs new file mode 100644 index 00000000000..fd1df32aa5a --- /dev/null +++ b/src/NHibernate/Cfg/Loquacious/Configuration.cs @@ -0,0 +1,180 @@ +using System; +using NHibernate.Cfg.Loquacious; +using NHibernate.Context; +using NHibernate.Hql; +using NHibernate.Linq; +using NHibernate.Linq.Functions; +using NHibernate.Util; + +// ReSharper disable once CheckNamespace +namespace NHibernate.Cfg +{ + // "Loquacious" part of Configuration + public partial class Configuration + { + public FluentSessionFactoryConfiguration SessionFactory() + { + return new FluentSessionFactoryConfiguration(this); + } + + public Configuration SessionFactory(Action configure) + { + configure(SessionFactory()); + return this; + } + + public Configuration SessionFactoryName(string sessionFactoryName) + { + SetProperty(Environment.SessionFactoryName, sessionFactoryName); + return this; + } + + public Configuration Cache(Action cacheProperties) + { + cacheProperties(new CacheConfigurationProperties(this)); + return this; + } + + public Configuration CollectionTypeFactory() + { + SetProperty( + Environment.CollectionTypeFactoryClass, + typeof(TCollectionsFactory).AssemblyQualifiedName); + return this; + } + + public Configuration Proxy(Action proxyProperties) + { + proxyProperties(new ProxyConfigurationProperties(this)); + return this; + } + + public Configuration HqlQueryTranslator() where TQueryTranslator : IQueryTranslatorFactory + { + SetProperty(Environment.QueryTranslator, typeof(TQueryTranslator).AssemblyQualifiedName); + return this; + } + + public Configuration LinqQueryProvider() where TQueryProvider : INhQueryProvider + { + SetProperty(Environment.QueryLinqProvider, typeof(TQueryProvider).AssemblyQualifiedName); + return this; + } + + public Configuration LinqToHqlGeneratorsRegistry() where TLinqToHqlGeneratorsRegistry : ILinqToHqlGeneratorsRegistry + { + SetProperty(Environment.LinqToHqlGeneratorsRegistry, typeof(TLinqToHqlGeneratorsRegistry).AssemblyQualifiedName); + return this; + } + + public Configuration CurrentSessionContext() where TCurrentSessionContext : ICurrentSessionContext + { + SetProperty(Environment.CurrentSessionContextClass, typeof(TCurrentSessionContext).AssemblyQualifiedName); + return this; + } + + public Configuration Mappings(Action mappingsProperties) + { + mappingsProperties(new MappingsConfigurationProperties(this)); + return this; + } + + public Configuration DataBaseIntegration(Action dataBaseIntegration) + { + dataBaseIntegration(new DbIntegrationConfigurationProperties(this)); + return this; + } + + public Configuration EntityCache(Action> entityCacheConfiguration) + where TEntity : class + { + var ecc = new EntityCacheConfigurationProperties(); + entityCacheConfiguration(ecc); + if (ecc.Strategy.HasValue) + { + SetCacheConcurrencyStrategy( + typeof(TEntity).FullName, + EntityCacheUsageParser.ToString(ecc.Strategy.Value), + ecc.RegionName); + } + + foreach (var collection in ecc.Collections) + { + SetCollectionCacheConcurrencyStrategy( + collection.Key, + EntityCacheUsageParser.ToString(collection.Value.Strategy), + collection.Value.RegionName); + } + + return this; + } + + /// + /// Add a type-definition for mappings. + /// + /// The persistent type. + /// The custom configuration action. + /// The . + /// + /// + /// + /// + /// Depending on where you will use the type-definition in the mapping the + /// can be : + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Configuration TypeDefinition(Action typeDefConfiguration) + where TDef : class + { + if (typeDefConfiguration == null) + { + return this; + } + + var tdConfiguration = TypeDefConfigurationProperties.Create(); + typeDefConfiguration(tdConfiguration); + if (string.IsNullOrEmpty(tdConfiguration.Alias)) + { + return this; + } + + var mappings = CreateMappings(); + mappings.LazyDialect = new Lazy(() => Dialect.Dialect.GetDialect(Properties)); + mappings.AddTypeDef(tdConfiguration.Alias, typeof(TDef).AssemblyQualifiedName, tdConfiguration.Properties.ToTypeParameters()); + return this; + } + + public Configuration AddNamedQuery(string queryIdentifier, Action namedQueryDefinition) + { + if (queryIdentifier == null) + { + throw new ArgumentNullException(nameof(queryIdentifier)); + } + + if (namedQueryDefinition == null) + { + throw new ArgumentNullException(nameof(namedQueryDefinition)); + } + + var builder = new NamedQueryDefinitionBuilder(); + namedQueryDefinition(builder); + NamedQueries.Add(queryIdentifier, builder.Build()); + return this; + } + } +} diff --git a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfiguration.cs b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfiguration.cs index 4782cf29ee5..c0a2fb4ae34 100644 --- a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfiguration.cs @@ -9,7 +9,10 @@ namespace NHibernate.Cfg.Loquacious { - internal class DbIntegrationConfiguration : IDbIntegrationConfiguration + public class DbIntegrationConfiguration +#pragma warning disable 618 + : IDbIntegrationConfiguration +#pragma warning restore 618 { private readonly Configuration configuration; @@ -23,57 +26,99 @@ public DbIntegrationConfiguration(Configuration configuration) Schema = new DbSchemaIntegrationConfiguration(this); } - public Configuration Configuration - { - get { return configuration; } - } - - #region Implementation of IDbIntegrationConfiguration + public Configuration Configuration => configuration; - public IDbIntegrationConfiguration Using() where TDialect : Dialect.Dialect + /// + /// Define and configure the dialect to use. + /// + /// The dialect implementation inherited from . + /// The fluent configuration itself. + public DbIntegrationConfiguration Using() where TDialect : Dialect.Dialect { configuration.SetProperty(Environment.Dialect, typeof(TDialect).AssemblyQualifiedName); return this; } - public IDbIntegrationConfiguration DisableKeywordsAutoImport() + public DbIntegrationConfiguration DisableKeywordsAutoImport() { configuration.SetProperty(Environment.Hbm2ddlKeyWords, "none"); return this; } - public IDbIntegrationConfiguration AutoQuoteKeywords() + public DbIntegrationConfiguration AutoQuoteKeywords() { configuration.SetProperty(Environment.Hbm2ddlKeyWords, "auto-quote"); return this; } - public IDbIntegrationConfiguration LogSqlInConsole() + public DbIntegrationConfiguration LogSqlInConsole() { configuration.SetProperty(Environment.ShowSql, "true"); return this; } - public IDbIntegrationConfiguration EnableLogFormattedSql() + public DbIntegrationConfiguration EnableLogFormattedSql() { configuration.SetProperty(Environment.FormatSql, "true"); return this; } - public IConnectionConfiguration Connected { get; private set; } + public ConnectionConfiguration Connected { get; } + + public BatcherConfiguration BatchingQueries { get; } + + public TransactionConfiguration Transactions { get; } + + public CommandsConfiguration CreateCommands { get; } + + public DbSchemaIntegrationConfiguration Schema { get; } + + #region Implementation of IDbIntegrationConfiguration +#pragma warning disable 618 + + IDbIntegrationConfiguration IDbIntegrationConfiguration.Using() + { + return Using(); + } + + IDbIntegrationConfiguration IDbIntegrationConfiguration.DisableKeywordsAutoImport() + { + return DisableKeywordsAutoImport(); + } + + IDbIntegrationConfiguration IDbIntegrationConfiguration.AutoQuoteKeywords() + { + return AutoQuoteKeywords(); + } - public IBatcherConfiguration BatchingQueries { get; private set; } + IDbIntegrationConfiguration IDbIntegrationConfiguration.LogSqlInConsole() + { + return LogSqlInConsole(); + } - public ITransactionConfiguration Transactions { get; private set; } + IDbIntegrationConfiguration IDbIntegrationConfiguration.EnableLogFormattedSql() + { + return EnableLogFormattedSql(); + } - public ICommandsConfiguration CreateCommands { get; private set; } + IConnectionConfiguration IDbIntegrationConfiguration.Connected => Connected; - public IDbSchemaIntegrationConfiguration Schema { get; private set; } + IBatcherConfiguration IDbIntegrationConfiguration.BatchingQueries => BatchingQueries; + ITransactionConfiguration IDbIntegrationConfiguration.Transactions => Transactions; + + ICommandsConfiguration IDbIntegrationConfiguration.CreateCommands => CreateCommands; + + IDbSchemaIntegrationConfiguration IDbIntegrationConfiguration.Schema => Schema; + +#pragma warning restore 618 #endregion } - internal class DbSchemaIntegrationConfiguration : IDbSchemaIntegrationConfiguration + public class DbSchemaIntegrationConfiguration +#pragma warning disable 618 + : IDbSchemaIntegrationConfiguration +#pragma warning restore 618 { private readonly DbIntegrationConfiguration dbc; @@ -82,36 +127,73 @@ public DbSchemaIntegrationConfiguration(DbIntegrationConfiguration dbc) this.dbc = dbc; } - #region Implementation of IDbSchemaIntegrationConfiguration - - public IDbIntegrationConfiguration Recreating() + public DbIntegrationConfiguration Recreating() { dbc.Configuration.SetProperty(Environment.Hbm2ddlAuto, SchemaAutoAction.Recreate.ToString()); return dbc; } - public IDbIntegrationConfiguration Creating() + public DbIntegrationConfiguration Creating() { dbc.Configuration.SetProperty(Environment.Hbm2ddlAuto, SchemaAutoAction.Create.ToString()); return dbc; } - public IDbIntegrationConfiguration Updating() + public DbIntegrationConfiguration Updating() { dbc.Configuration.SetProperty(Environment.Hbm2ddlAuto, SchemaAutoAction.Update.ToString()); return dbc; } - public IDbIntegrationConfiguration Validating() + public DbIntegrationConfiguration Validating() { dbc.Configuration.SetProperty(Environment.Hbm2ddlAuto, SchemaAutoAction.Validate.ToString()); return dbc; } + // 6.0 TODO default should become true + /// + /// Whether to throw or not on schema auto-update failures. by default. + /// + /// to throw in case any failure is reported during schema auto-update, + /// to ignore failures. + public DbIntegrationConfiguration ThrowOnSchemaUpdate(bool @throw) + { + dbc.Configuration.SetProperty(Environment.Hbm2ddlThrowOnUpdate, @throw.ToString().ToLowerInvariant()); + return dbc; + } + + #region Implementation of IDbSchemaIntegrationConfiguration +#pragma warning disable 618 + + IDbIntegrationConfiguration IDbSchemaIntegrationConfiguration.Recreating() + { + return Recreating(); + } + + IDbIntegrationConfiguration IDbSchemaIntegrationConfiguration.Creating() + { + return Creating(); + } + + IDbIntegrationConfiguration IDbSchemaIntegrationConfiguration.Updating() + { + return Updating(); + } + + IDbIntegrationConfiguration IDbSchemaIntegrationConfiguration.Validating() + { + return Validating(); + } + +#pragma warning restore 618 #endregion } - internal class CommandsConfiguration : ICommandsConfiguration + public class CommandsConfiguration +#pragma warning disable 618 + : ICommandsConfiguration +#pragma warning restore 618 { private readonly DbIntegrationConfiguration dbc; @@ -120,54 +202,100 @@ public CommandsConfiguration(DbIntegrationConfiguration dbc) this.dbc = dbc; } - #region Implementation of ICommandsConfiguration - - public ICommandsConfiguration Preparing() + public CommandsConfiguration Preparing() { dbc.Configuration.SetProperty(Environment.PrepareSql, "true"); return this; } - public ICommandsConfiguration WithTimeout(byte seconds) + public CommandsConfiguration WithTimeout(byte seconds) { dbc.Configuration.SetProperty(Environment.CommandTimeout, seconds.ToString()); return this; } - public ICommandsConfiguration ConvertingExceptionsThrough() + public CommandsConfiguration ConvertingExceptionsThrough() where TExceptionConverter : ISQLExceptionConverter { dbc.Configuration.SetProperty(Environment.SqlExceptionConverter, typeof(TExceptionConverter).AssemblyQualifiedName); return this; } - public ICommandsConfiguration AutoCommentingSql() + public CommandsConfiguration AutoCommentingSql() { dbc.Configuration.SetProperty(Environment.UseSqlComments, "true"); return this; } - public IDbIntegrationConfiguration WithHqlToSqlSubstitutions(string csvQuerySubstitutions) + public DbIntegrationConfiguration WithHqlToSqlSubstitutions(string csvQuerySubstitutions) { dbc.Configuration.SetProperty(Environment.QuerySubstitutions, csvQuerySubstitutions); return dbc; } - public IDbIntegrationConfiguration WithDefaultHqlToSqlSubstitutions() + public DbIntegrationConfiguration WithDefaultHqlToSqlSubstitutions() { return dbc; } - public ICommandsConfiguration WithMaximumDepthOfOuterJoinFetching(byte maxFetchDepth) + /// + /// Maximum depth of outer join fetching + /// + /// + /// 0 (zero) disable the usage of OuterJoinFetching + /// + public CommandsConfiguration WithMaximumDepthOfOuterJoinFetching(byte maxFetchDepth) { dbc.Configuration.SetProperty(Environment.MaxFetchDepth, maxFetchDepth.ToString()); return this; } + #region Implementation of ICommandsConfiguration +#pragma warning disable 618 + + ICommandsConfiguration ICommandsConfiguration.Preparing() + { + return Preparing(); + } + + ICommandsConfiguration ICommandsConfiguration.WithTimeout(byte seconds) + { + return WithTimeout(seconds); + } + + ICommandsConfiguration ICommandsConfiguration.ConvertingExceptionsThrough() + { + return ConvertingExceptionsThrough(); + } + + ICommandsConfiguration ICommandsConfiguration.AutoCommentingSql() + { + return AutoCommentingSql(); + } + + IDbIntegrationConfiguration ICommandsConfiguration.WithHqlToSqlSubstitutions(string csvQuerySubstitutions) + { + return WithHqlToSqlSubstitutions(csvQuerySubstitutions); + } + + IDbIntegrationConfiguration ICommandsConfiguration.WithDefaultHqlToSqlSubstitutions() + { + return WithDefaultHqlToSqlSubstitutions(); + } + + ICommandsConfiguration ICommandsConfiguration.WithMaximumDepthOfOuterJoinFetching(byte maxFetchDepth) + { + return WithMaximumDepthOfOuterJoinFetching(maxFetchDepth); + } + +#pragma warning restore 618 #endregion } - internal class TransactionConfiguration : ITransactionConfiguration + public class TransactionConfiguration +#pragma warning disable 618 + : ITransactionConfiguration +#pragma warning restore 618 { private readonly DbIntegrationConfiguration dbc; @@ -176,18 +304,28 @@ public TransactionConfiguration(DbIntegrationConfiguration dbc) this.dbc = dbc; } - #region Implementation of ITransactionConfiguration - - public IDbIntegrationConfiguration Through() where TFactory : ITransactionFactory + public DbIntegrationConfiguration Through() where TFactory : ITransactionFactory { dbc.Configuration.SetProperty(Environment.TransactionStrategy, typeof(TFactory).AssemblyQualifiedName); return dbc; } + #region Implementation of ITransactionConfiguration +#pragma warning disable 618 + + IDbIntegrationConfiguration ITransactionConfiguration.Through() + { + return Through(); + } + +#pragma warning restore 618 #endregion } - internal class BatcherConfiguration : IBatcherConfiguration + public class BatcherConfiguration +#pragma warning disable 618 + : IBatcherConfiguration +#pragma warning restore 618 { private readonly DbIntegrationConfiguration dbc; @@ -196,36 +334,61 @@ public BatcherConfiguration(DbIntegrationConfiguration dbc) this.dbc = dbc; } - #region Implementation of IBatcherConfiguration - - public IBatcherConfiguration Through() where TBatcher : IBatcherFactory + public BatcherConfiguration Through() where TBatcher : IBatcherFactory { dbc.Configuration.SetProperty(Environment.BatchStrategy, typeof(TBatcher).AssemblyQualifiedName); return this; } - public IDbIntegrationConfiguration Each(short batchSize) + public DbIntegrationConfiguration Each(short batchSize) { dbc.Configuration.SetProperty(Environment.BatchSize, batchSize.ToString()); return dbc; } - public IBatcherConfiguration OrderingInserts() + public BatcherConfiguration OrderingInserts() { dbc.Configuration.SetProperty(Environment.OrderInserts, true.ToString().ToLowerInvariant()); return this; } - public IBatcherConfiguration DisablingInsertsOrdering() + public BatcherConfiguration DisablingInsertsOrdering() { dbc.Configuration.SetProperty(Environment.OrderInserts, false.ToString().ToLowerInvariant()); return this; } + #region Implementation of IBatcherConfiguration +#pragma warning disable 618 + + IBatcherConfiguration IBatcherConfiguration.Through() + { + return Through(); + } + + IDbIntegrationConfiguration IBatcherConfiguration.Each(short batchSize) + { + return Each(batchSize); + } + + IBatcherConfiguration IBatcherConfiguration.OrderingInserts() + { + return OrderingInserts(); + } + + IBatcherConfiguration IBatcherConfiguration.DisablingInsertsOrdering() + { + return DisablingInsertsOrdering(); + } + +#pragma warning restore 618 #endregion } - internal class ConnectionConfiguration : IConnectionConfiguration + public class ConnectionConfiguration +#pragma warning disable 618 + : IConnectionConfiguration +#pragma warning restore 618 { private readonly DbIntegrationConfiguration dbc; @@ -234,50 +397,87 @@ public ConnectionConfiguration(DbIntegrationConfiguration dbc) this.dbc = dbc; } - #region Implementation of IConnectionConfiguration - - public IConnectionConfiguration Through() where TProvider : IConnectionProvider + public ConnectionConfiguration Through() where TProvider : IConnectionProvider { dbc.Configuration.SetProperty(Environment.ConnectionProvider, typeof(TProvider).AssemblyQualifiedName); return this; } - public IConnectionConfiguration By() where TDriver : IDriver + public ConnectionConfiguration By() where TDriver : IDriver { dbc.Configuration.SetProperty(Environment.ConnectionDriver, typeof(TDriver).AssemblyQualifiedName); return this; } - public IConnectionConfiguration With(IsolationLevel level) + public ConnectionConfiguration With(IsolationLevel level) { dbc.Configuration.SetProperty(Environment.Isolation, level.ToString()); return this; } - public IConnectionConfiguration Releasing(ConnectionReleaseMode releaseMode) + public ConnectionConfiguration Releasing(ConnectionReleaseMode releaseMode) { dbc.Configuration.SetProperty(Environment.ReleaseConnections, ConnectionReleaseModeParser.ToString(releaseMode)); return this; } - public IDbIntegrationConfiguration Using(string connectionString) + public DbIntegrationConfiguration Using(string connectionString) { dbc.Configuration.SetProperty(Environment.ConnectionString, connectionString); return dbc; } - public IDbIntegrationConfiguration Using(DbConnectionStringBuilder connectionStringBuilder) + public DbIntegrationConfiguration Using(DbConnectionStringBuilder connectionStringBuilder) { dbc.Configuration.SetProperty(Environment.ConnectionString, connectionStringBuilder.ConnectionString); return dbc; } - public IDbIntegrationConfiguration ByAppConfing(string connectionStringName) + public DbIntegrationConfiguration ByAppConfing(string connectionStringName) { dbc.Configuration.SetProperty(Environment.ConnectionStringName, connectionStringName); return dbc; } + #region Implementation of IConnectionConfiguration +#pragma warning disable 618 + + IConnectionConfiguration IConnectionConfiguration.Through() + { + return Through(); + } + + IConnectionConfiguration IConnectionConfiguration.By() + { + return By(); + } + + IConnectionConfiguration IConnectionConfiguration.With(IsolationLevel level) + { + return With(level); + } + + IConnectionConfiguration IConnectionConfiguration.Releasing(ConnectionReleaseMode releaseMode) + { + return Releasing(releaseMode); + } + + IDbIntegrationConfiguration IConnectionConfiguration.Using(string connectionString) + { + return Using(connectionString); + } + + IDbIntegrationConfiguration IConnectionConfiguration.Using(DbConnectionStringBuilder connectionStringBuilder) + { + return Using(connectionStringBuilder); + } + + IDbIntegrationConfiguration IConnectionConfiguration.ByAppConfing(string connectionStringName) + { + return ByAppConfing(connectionStringName); + } + +#pragma warning restore 618 #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs index 55131818ab4..f19619bc251 100644 --- a/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/DbIntegrationConfigurationProperties.cs @@ -4,11 +4,15 @@ using NHibernate.Driver; using NHibernate.Exceptions; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Transaction; namespace NHibernate.Cfg.Loquacious { - internal class DbIntegrationConfigurationProperties: IDbIntegrationConfigurationProperties + public class DbIntegrationConfigurationProperties +#pragma warning disable 618 + : IDbIntegrationConfigurationProperties +#pragma warning restore 618 { private readonly Configuration configuration; @@ -94,6 +98,9 @@ public bool PrepareCommands set { configuration.SetProperty(Environment.PrepareSql, value.ToString().ToLowerInvariant()); } } + /// + /// Set the default timeout in seconds for ADO.NET queries. + /// public byte Timeout { set { configuration.SetProperty(Environment.CommandTimeout, value.ToString()); } @@ -124,11 +131,39 @@ public SchemaAutoAction SchemaAction set { configuration.SetProperty(Environment.Hbm2ddlAuto, value.ToString()); } } + // 6.0 TODO default should become true + /// + /// Whether to throw or not on schema auto-update failures. by default. + /// + public bool ThrowOnSchemaUpdate + { + set { configuration.SetProperty(Environment.Hbm2ddlThrowOnUpdate, value.ToString().ToLowerInvariant()); } + } + public void QueryModelRewriterFactory() where TFactory : IQueryModelRewriterFactory { configuration.SetProperty(Environment.QueryModelRewriterFactory, typeof(TFactory).AssemblyQualifiedName); } + /// + /// Set the class of the LINQ query pre-transformer registrar. + /// + /// The class of the LINQ query pre-transformer registrar. + public void PreTransformerRegistrar() where TRegistrar : IExpressionTransformerRegistrar + { + configuration.SetProperty(Environment.PreTransformerRegistrar, typeof(TRegistrar).AssemblyQualifiedName); + } + + public MultiTenancy.MultiTenancyStrategy MultiTenancy + { + set { configuration.SetProperty(Environment.MultiTenancy, value.ToString()); } + } + + public void MultiTenancyConnectionProvider() where TProvider : IMultiTenancyConnectionProvider + { + configuration.SetProperty(Environment.MultiTenancyConnectionProvider, typeof(TProvider).AssemblyQualifiedName); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/EntityCacheConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/EntityCacheConfigurationProperties.cs index d1aa17be91a..e9a13e38881 100644 --- a/src/NHibernate/Cfg/Loquacious/EntityCacheConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/EntityCacheConfigurationProperties.cs @@ -6,49 +6,63 @@ namespace NHibernate.Cfg.Loquacious { - internal class EntityCacheConfigurationProperties : IEntityCacheConfigurationProperties + public class EntityCacheConfigurationProperties +#pragma warning disable 618 + : IEntityCacheConfigurationProperties +#pragma warning restore 618 where TEntity : class { - private readonly Dictionary collections; + private readonly Dictionary collections; public EntityCacheConfigurationProperties() { - collections = new Dictionary(10); + collections = new Dictionary(10); Strategy = null; } - #region Implementation of IEntityCacheConfigurationProperties - public EntityCacheUsage? Strategy { set; get; } public string RegionName { set; get; } public void Collection(Expression> collectionProperty, - Action collectionCacheConfiguration) + Action collectionCacheConfiguration) where TCollection : IEnumerable { if (collectionProperty == null) { - throw new ArgumentNullException("collectionProperty"); + throw new ArgumentNullException(nameof(collectionProperty)); } var mi = ExpressionsHelper.DecodeMemberAccessExpression(collectionProperty); if(mi.DeclaringType != typeof(TEntity)) { - throw new ArgumentOutOfRangeException("collectionProperty", "Collection not owned by " + typeof (TEntity).FullName); + throw new ArgumentOutOfRangeException(nameof(collectionProperty), "Collection not owned by " + typeof (TEntity).FullName); } var ecc = new EntityCollectionCacheConfigurationProperties(); collectionCacheConfiguration(ecc); collections.Add(typeof (TEntity).FullName + "." + mi.Name, ecc); } +#pragma warning disable 618 + #region Implementation of IEntityCacheConfigurationProperties + + void IEntityCacheConfigurationProperties.Collection(Expression> collectionProperty, + Action collectionCacheConfiguration) + { + Collection(collectionProperty, collectionCacheConfiguration); + } + #endregion +#pragma warning restore 618 - public IDictionary Collections + internal IDictionary Collections { get { return collections; } } } - internal class EntityCollectionCacheConfigurationProperties : IEntityCollectionCacheConfigurationProperties + public class EntityCollectionCacheConfigurationProperties +#pragma warning disable 618 + : IEntityCollectionCacheConfigurationProperties +#pragma warning restore 618 { public EntityCollectionCacheConfigurationProperties() { @@ -62,4 +76,4 @@ public EntityCollectionCacheConfigurationProperties() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/FluentSessionFactoryConfiguration.cs b/src/NHibernate/Cfg/Loquacious/FluentSessionFactoryConfiguration.cs index e9ecf07d823..be570d8fb9a 100644 --- a/src/NHibernate/Cfg/Loquacious/FluentSessionFactoryConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/FluentSessionFactoryConfiguration.cs @@ -4,7 +4,10 @@ namespace NHibernate.Cfg.Loquacious { - internal class FluentSessionFactoryConfiguration : IFluentSessionFactoryConfiguration + public class FluentSessionFactoryConfiguration +#pragma warning disable 618 + : IFluentSessionFactoryConfiguration +#pragma warning restore 618 { private readonly Configuration configuration; @@ -23,52 +26,106 @@ internal Configuration Configuration get { return configuration; } } - #region Implementation of IFluentSessionFactoryConfiguration - - public IFluentSessionFactoryConfiguration Named(string sessionFactoryName) + /// + /// Set the SessionFactory mnemonic name. + /// + /// The mnemonic name. + /// The fluent configuration itself. + /// + /// The SessionFactory mnemonic name can be used as a surrogate key in a multi-DB application. + /// + public FluentSessionFactoryConfiguration Named(string sessionFactoryName) { configuration.SetProperty(Environment.SessionFactoryName, sessionFactoryName); return this; } - public IDbIntegrationConfiguration Integrate { get; private set; } + /// + /// DataBase integration configuration. + /// + public DbIntegrationConfiguration Integrate { get; } - public ICacheConfiguration Caching { get; private set; } + /// + /// Cache configuration. + /// + public CacheConfiguration Caching { get; } - public IFluentSessionFactoryConfiguration GenerateStatistics() + public FluentSessionFactoryConfiguration GenerateStatistics() { configuration.SetProperty(Environment.GenerateStatistics, "true"); return this; } - public IFluentSessionFactoryConfiguration DefaultFlushMode(FlushMode flushMode) + public FluentSessionFactoryConfiguration DefaultFlushMode(FlushMode flushMode) { configuration.SetProperty(Environment.DefaultFlushMode, flushMode.ToString()); return this; } - public IFluentSessionFactoryConfiguration ParsingHqlThrough() + public FluentSessionFactoryConfiguration ParsingHqlThrough() where TQueryTranslator : IQueryTranslatorFactory { configuration.SetProperty(Environment.QueryTranslator, typeof (TQueryTranslator).AssemblyQualifiedName); return this; } - public IFluentSessionFactoryConfiguration ParsingLinqThrough() + public FluentSessionFactoryConfiguration ParsingLinqThrough() where TQueryProvider : INhQueryProvider { configuration.SetProperty(Environment.QueryLinqProvider, typeof(TQueryProvider).AssemblyQualifiedName); return this; } - public IProxyConfiguration Proxy { get; private set; } - public ICollectionFactoryConfiguration GeneratingCollections { get; private set; } - public IMappingsConfiguration Mapping { get; private set; } + public ProxyConfiguration Proxy { get; } + public CollectionFactoryConfiguration GeneratingCollections { get; } + public MappingsConfiguration Mapping { get; } + + #region Implementation of IFluentSessionFactoryConfiguration +#pragma warning disable 618 + + IFluentSessionFactoryConfiguration IFluentSessionFactoryConfiguration.Named(string sessionFactoryName) + { + return Named(sessionFactoryName); + } + + IDbIntegrationConfiguration IFluentSessionFactoryConfiguration.Integrate => Integrate; + ICacheConfiguration IFluentSessionFactoryConfiguration.Caching => Caching; + + IFluentSessionFactoryConfiguration IFluentSessionFactoryConfiguration.GenerateStatistics() + { + return GenerateStatistics(); + } + + IFluentSessionFactoryConfiguration IFluentSessionFactoryConfiguration.DefaultFlushMode(FlushMode flushMode) + { + return DefaultFlushMode(flushMode); + } + + IFluentSessionFactoryConfiguration IFluentSessionFactoryConfiguration.ParsingHqlThrough() + { + return ParsingHqlThrough(); + } + + IFluentSessionFactoryConfiguration IFluentSessionFactoryConfiguration.ParsingLinqThrough() + { + return ParsingLinqThrough(); + } + + IProxyConfiguration IFluentSessionFactoryConfiguration.Proxy => Proxy; + + ICollectionFactoryConfiguration IFluentSessionFactoryConfiguration.GeneratingCollections => GeneratingCollections; + + IMappingsConfiguration IFluentSessionFactoryConfiguration.Mapping => Mapping; + +#pragma warning restore 618 #endregion } - internal class CollectionFactoryConfiguration : ICollectionFactoryConfiguration + public class CollectionFactoryConfiguration +#pragma warning disable 618 + : ICollectionFactoryConfiguration +#pragma warning restore 618 { private readonly FluentSessionFactoryConfiguration fc; @@ -77,16 +134,23 @@ public CollectionFactoryConfiguration(FluentSessionFactoryConfiguration parent) fc = parent; } - #region Implementation of ICollectionFactoryConfiguration - - public IFluentSessionFactoryConfiguration Through() - where TCollecionsFactory : ICollectionTypeFactory + public FluentSessionFactoryConfiguration Through() + where TCollectionsFactory : ICollectionTypeFactory { fc.Configuration.SetProperty(Environment.CollectionTypeFactoryClass, - typeof (TCollecionsFactory).AssemblyQualifiedName); + typeof (TCollectionsFactory).AssemblyQualifiedName); return fc; } + #region Implementation of ICollectionFactoryConfiguration +#pragma warning disable 618 + + IFluentSessionFactoryConfiguration ICollectionFactoryConfiguration.Through() + { + return Through(); + } + +#pragma warning restore 618 #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IBatcherConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IBatcherConfiguration.cs index 5c9ec09fa25..b8a32d3d591 100644 --- a/src/NHibernate/Cfg/Loquacious/IBatcherConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IBatcherConfiguration.cs @@ -1,6 +1,8 @@ +using System; using NHibernate.AdoNet; namespace NHibernate.Cfg.Loquacious { + [Obsolete("Replaced by direct class usage")] public interface IBatcherConfiguration { IBatcherConfiguration Through() where TBatcher : IBatcherFactory; @@ -8,4 +10,4 @@ public interface IBatcherConfiguration IBatcherConfiguration OrderingInserts(); IBatcherConfiguration DisablingInsertsOrdering(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/ICacheConfiguration.cs b/src/NHibernate/Cfg/Loquacious/ICacheConfiguration.cs index 5345731926c..3f4ac08024d 100644 --- a/src/NHibernate/Cfg/Loquacious/ICacheConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/ICacheConfiguration.cs @@ -4,6 +4,8 @@ namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface ICacheConfiguration { ICacheConfiguration Through() where TProvider : ICacheProvider; @@ -13,6 +15,8 @@ public interface ICacheConfiguration IQueryCacheConfiguration Queries { get; } } + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface ICacheConfigurationProperties { bool UseMinimalPuts { set; } @@ -24,9 +28,11 @@ public interface ICacheConfigurationProperties void QueryCache() where TFactory : IQueryCache; } - // 6.0 TODO: merge into ICacheConfigurationProperties + // 6.0 TODO: Remove public static class CacheConfigurationPropertiesExtensions { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public static void QueryCacheFactory(this ICacheConfigurationProperties config) where TFactory : IQueryCacheFactory { ReflectHelper diff --git a/src/NHibernate/Cfg/Loquacious/ICollectionFactoryConfiguration.cs b/src/NHibernate/Cfg/Loquacious/ICollectionFactoryConfiguration.cs index afe2ffa0aaa..bb98d087826 100644 --- a/src/NHibernate/Cfg/Loquacious/ICollectionFactoryConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/ICollectionFactoryConfiguration.cs @@ -1,8 +1,11 @@ +using System; using NHibernate.Bytecode; namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface ICollectionFactoryConfiguration { IFluentSessionFactoryConfiguration Through() where TCollecionsFactory : ICollectionTypeFactory; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/ICommandsConfiguration.cs b/src/NHibernate/Cfg/Loquacious/ICommandsConfiguration.cs index f33a692c2c5..0471a185370 100644 --- a/src/NHibernate/Cfg/Loquacious/ICommandsConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/ICommandsConfiguration.cs @@ -1,6 +1,9 @@ +using System; using NHibernate.Exceptions; namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface ICommandsConfiguration { ICommandsConfiguration Preparing(); @@ -18,4 +21,4 @@ public interface ICommandsConfiguration /// ICommandsConfiguration WithMaximumDepthOfOuterJoinFetching(byte maxFetchDepth); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IConnectionConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IConnectionConfiguration.cs index c1ccaf3f557..bf04db998dd 100644 --- a/src/NHibernate/Cfg/Loquacious/IConnectionConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IConnectionConfiguration.cs @@ -1,3 +1,4 @@ +using System; using System.Data; using System.Data.Common; using NHibernate.Driver; @@ -5,6 +6,8 @@ namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IConnectionConfiguration { IConnectionConfiguration Through() where TProvider : IConnectionProvider; @@ -15,4 +18,4 @@ public interface IConnectionConfiguration IDbIntegrationConfiguration Using(DbConnectionStringBuilder connectionStringBuilder); IDbIntegrationConfiguration ByAppConfing(string connectionStringName); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfiguration.cs index e2e99494bb9..a01193aa6c2 100644 --- a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfiguration.cs @@ -2,6 +2,8 @@ namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IDbIntegrationConfiguration { /// @@ -29,4 +31,4 @@ public interface IDbIntegrationConfiguration IDbSchemaIntegrationConfiguration Schema { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs index 496ebf58433..a1c1c546bb4 100644 --- a/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs @@ -1,3 +1,4 @@ +using System; using System.Data; using NHibernate.AdoNet; using NHibernate.Connection; @@ -8,6 +9,8 @@ namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IDbIntegrationConfigurationProperties { void Dialect() where TDialect : Dialect.Dialect; diff --git a/src/NHibernate/Cfg/Loquacious/IDbSchemaIntegrationConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IDbSchemaIntegrationConfiguration.cs index d2a2326f3f4..4c662d19dc5 100644 --- a/src/NHibernate/Cfg/Loquacious/IDbSchemaIntegrationConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IDbSchemaIntegrationConfiguration.cs @@ -1,5 +1,9 @@ +using System; + namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IDbSchemaIntegrationConfiguration { IDbIntegrationConfiguration Recreating(); @@ -7,4 +11,4 @@ public interface IDbSchemaIntegrationConfiguration IDbIntegrationConfiguration Updating(); IDbIntegrationConfiguration Validating(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IEntityCacheConfigurationProperties.cs b/src/NHibernate/Cfg/Loquacious/IEntityCacheConfigurationProperties.cs index f666007ae4d..909042bf0b4 100644 --- a/src/NHibernate/Cfg/Loquacious/IEntityCacheConfigurationProperties.cs +++ b/src/NHibernate/Cfg/Loquacious/IEntityCacheConfigurationProperties.cs @@ -4,12 +4,16 @@ namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IEntityCollectionCacheConfigurationProperties { EntityCacheUsage Strategy { get; set; } string RegionName { get; set; } } - + + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IEntityCacheConfigurationProperties where TEntity: class { EntityCacheUsage? Strategy { get; set; } @@ -18,4 +22,4 @@ public interface IEntityCacheConfigurationProperties where TEntity: cla void Collection(Expression> collectionProperty, Action collectionCacheConfiguration) where TCollection : IEnumerable; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IFluentSessionFactoryConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IFluentSessionFactoryConfiguration.cs index 08f682e1b2c..3277194c3cd 100644 --- a/src/NHibernate/Cfg/Loquacious/IFluentSessionFactoryConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IFluentSessionFactoryConfiguration.cs @@ -1,7 +1,10 @@ +using System; using NHibernate.Hql; using NHibernate.Linq; namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IFluentSessionFactoryConfiguration { /// @@ -36,4 +39,4 @@ public interface IFluentSessionFactoryConfiguration IMappingsConfiguration Mapping { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IMappingsConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IMappingsConfiguration.cs index 4cf6de3f797..d82b4fccda7 100644 --- a/src/NHibernate/Cfg/Loquacious/IMappingsConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IMappingsConfiguration.cs @@ -1,14 +1,20 @@ +using System; + namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IMappingsConfiguration { IMappingsConfiguration UsingDefaultCatalog(string defaultCatalogName); IFluentSessionFactoryConfiguration UsingDefaultSchema(string defaultSchemaName); } + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IMappingsConfigurationProperties { string DefaultCatalog { set; } string DefaultSchema { set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/INamedQueryDefinitionBuilder.cs b/src/NHibernate/Cfg/Loquacious/INamedQueryDefinitionBuilder.cs index e8253a8a2e1..aeece856916 100644 --- a/src/NHibernate/Cfg/Loquacious/INamedQueryDefinitionBuilder.cs +++ b/src/NHibernate/Cfg/Loquacious/INamedQueryDefinitionBuilder.cs @@ -1,8 +1,11 @@ +using System; using System.Collections.Generic; using NHibernate.Engine; namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface INamedQueryDefinitionBuilder { bool IsCacheable { get; set; } @@ -19,7 +22,10 @@ public interface INamedQueryDefinitionBuilder CacheMode? CacheMode { get; set; } } - internal class NamedQueryDefinitionBuilder : INamedQueryDefinitionBuilder + public class NamedQueryDefinitionBuilder +#pragma warning disable 618 + : INamedQueryDefinitionBuilder +#pragma warning restore 618 { private int fetchSize = -1; private int timeout = -1; @@ -50,6 +56,9 @@ public int FetchSize } } + /// + /// The timeout in seconds for the underlying ADO.NET query. + /// public int Timeout { get { return timeout; } @@ -74,7 +83,7 @@ public int Timeout #endregion - public NamedQueryDefinition Build() + internal NamedQueryDefinition Build() { return new NamedQueryDefinition(Query, IsCacheable, CacheRegion, Timeout, FetchSize, FlushMode, CacheMode ,IsReadOnly, Comment, new Dictionary(1)); } diff --git a/src/NHibernate/Cfg/Loquacious/IProxyConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IProxyConfiguration.cs index 8750db07933..7bb8c736931 100644 --- a/src/NHibernate/Cfg/Loquacious/IProxyConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IProxyConfiguration.cs @@ -1,15 +1,20 @@ +using System; using NHibernate.Bytecode; namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IProxyConfiguration { IProxyConfiguration DisableValidation(); IFluentSessionFactoryConfiguration Through() where TProxyFactoryFactory : IProxyFactoryFactory; } + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IProxyConfigurationProperties { bool Validation { set; } void ProxyFactoryFactory() where TProxyFactoryFactory : IProxyFactoryFactory; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/IQueryCacheConfiguration.cs b/src/NHibernate/Cfg/Loquacious/IQueryCacheConfiguration.cs index 6b5d9ba0648..98fd5d33d72 100644 --- a/src/NHibernate/Cfg/Loquacious/IQueryCacheConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/IQueryCacheConfiguration.cs @@ -1,5 +1,9 @@ +using System; + namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface IQueryCacheConfiguration { // 6.0 TODO: enable constraint diff --git a/src/NHibernate/Cfg/Loquacious/ITransactionConfiguration.cs b/src/NHibernate/Cfg/Loquacious/ITransactionConfiguration.cs index 195a785ef76..7a379d03e83 100644 --- a/src/NHibernate/Cfg/Loquacious/ITransactionConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/ITransactionConfiguration.cs @@ -1,8 +1,11 @@ +using System; using NHibernate.Transaction; namespace NHibernate.Cfg.Loquacious { + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface ITransactionConfiguration { IDbIntegrationConfiguration Through() where TFactory : ITransactionFactory; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/ITypeDefConfiguration.cs b/src/NHibernate/Cfg/Loquacious/ITypeDefConfiguration.cs index fefe04b5254..d9a447b82a1 100644 --- a/src/NHibernate/Cfg/Loquacious/ITypeDefConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/ITypeDefConfiguration.cs @@ -1,9 +1,13 @@ +using System; + namespace NHibernate.Cfg.Loquacious { /// /// Properties of TypeDef configuration. /// /// + //Since 5.3 + [Obsolete("Replaced by direct class usage")] public interface ITypeDefConfigurationProperties { /// @@ -30,12 +34,18 @@ public interface ITypeDefConfigurationProperties object Properties { get; set; } } - internal class TypeDefConfigurationProperties : ITypeDefConfigurationProperties - where T: class + /// + /// Properties of TypeDef configuration. + /// + /// + public class TypeDefConfigurationProperties +#pragma warning disable 618 + : ITypeDefConfigurationProperties +#pragma warning restore 618 { - public TypeDefConfigurationProperties() + internal static TypeDefConfigurationProperties Create() { - Alias = typeof(T).Name; + return new TypeDefConfigurationProperties {Alias = typeof(T).Name}; } #region Implementation of ITypeDefConfigurationProperties @@ -45,4 +55,4 @@ public TypeDefConfigurationProperties() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/MappingsConfiguration.cs b/src/NHibernate/Cfg/Loquacious/MappingsConfiguration.cs index 609f5ca5416..b903ef56b2b 100644 --- a/src/NHibernate/Cfg/Loquacious/MappingsConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/MappingsConfiguration.cs @@ -1,8 +1,9 @@ -using System; - namespace NHibernate.Cfg.Loquacious { - internal class MappingsConfiguration : IMappingsConfiguration + public class MappingsConfiguration +#pragma warning disable 618 + : IMappingsConfiguration +#pragma warning restore 618 { private readonly FluentSessionFactoryConfiguration fc; @@ -11,24 +12,39 @@ public MappingsConfiguration(FluentSessionFactoryConfiguration parent) fc = parent; } - #region Implementation of IMappingsConfiguration - - public IMappingsConfiguration UsingDefaultCatalog(string defaultCatalogName) + public MappingsConfiguration UsingDefaultCatalog(string defaultCatalogName) { fc.Configuration.SetProperty(Environment.DefaultCatalog, defaultCatalogName); return this; } - public IFluentSessionFactoryConfiguration UsingDefaultSchema(string defaultSchemaName) + public FluentSessionFactoryConfiguration UsingDefaultSchema(string defaultSchemaName) { fc.Configuration.SetProperty(Environment.DefaultSchema, defaultSchemaName); return fc; } + #region Implementation of IMappingsConfiguration +#pragma warning disable 618 + + IMappingsConfiguration IMappingsConfiguration.UsingDefaultCatalog(string defaultCatalogName) + { + return UsingDefaultCatalog(defaultCatalogName); + } + + IFluentSessionFactoryConfiguration IMappingsConfiguration.UsingDefaultSchema(string defaultSchemaName) + { + return UsingDefaultSchema(defaultSchemaName); + } + +#pragma warning restore 618 #endregion } - internal class MappingsConfigurationProperties:IMappingsConfigurationProperties + public class MappingsConfigurationProperties +#pragma warning disable 618 + :IMappingsConfigurationProperties +#pragma warning restore 618 { private readonly Configuration configuration; @@ -51,4 +67,4 @@ public string DefaultSchema #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Loquacious/ProxyConfiguration.cs b/src/NHibernate/Cfg/Loquacious/ProxyConfiguration.cs index c3de2953a1a..a5fce9fb843 100644 --- a/src/NHibernate/Cfg/Loquacious/ProxyConfiguration.cs +++ b/src/NHibernate/Cfg/Loquacious/ProxyConfiguration.cs @@ -2,7 +2,10 @@ namespace NHibernate.Cfg.Loquacious { - internal class ProxyConfiguration : IProxyConfiguration + public class ProxyConfiguration +#pragma warning disable 618 + : IProxyConfiguration +#pragma warning restore 618 { private readonly FluentSessionFactoryConfiguration fc; @@ -11,26 +14,41 @@ public ProxyConfiguration(FluentSessionFactoryConfiguration parent) fc = parent; } - #region Implementation of IProxyConfiguration - - public IProxyConfiguration DisableValidation() + public ProxyConfiguration DisableValidation() { fc.Configuration.SetProperty(Environment.UseProxyValidator, "false"); return this; } - public IFluentSessionFactoryConfiguration Through() + public FluentSessionFactoryConfiguration Through() where TProxyFactoryFactory : IProxyFactoryFactory { fc.Configuration.SetProperty(Environment.ProxyFactoryFactoryClass, - typeof(TProxyFactoryFactory).AssemblyQualifiedName); + typeof(TProxyFactoryFactory).AssemblyQualifiedName); return fc; } + #region Implementation of IProxyConfiguration +#pragma warning disable 618 + + IProxyConfiguration IProxyConfiguration.DisableValidation() + { + return DisableValidation(); + } + + IFluentSessionFactoryConfiguration IProxyConfiguration.Through() + { + return Through(); + } + +#pragma warning restore 618 #endregion } - internal class ProxyConfigurationProperties: IProxyConfigurationProperties + public class ProxyConfigurationProperties +#pragma warning disable 618 + : IProxyConfigurationProperties +#pragma warning restore 618 { private readonly Configuration configuration; @@ -54,4 +72,4 @@ public void ProxyFactoryFactory() where TProxyFactoryFacto #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmComponent.cs b/src/NHibernate/Cfg/MappingSchema/HbmComponent.cs index 44acc41d84e..a861b281428 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmComponent.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmComponent.cs @@ -64,6 +64,5 @@ protected override HbmMeta[] Metadatas } #endregion - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmCompositeElement.cs b/src/NHibernate/Cfg/MappingSchema/HbmCompositeElement.cs index aac2748a7c8..5c5608cdfe1 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmCompositeElement.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmCompositeElement.cs @@ -21,7 +21,7 @@ public HbmParent Parent public string EmbeddedNode { - get { return node;} + get { return node; } } public string Name @@ -50,4 +50,4 @@ public IEnumerable Properties #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmDatabaseObject.cs b/src/NHibernate/Cfg/MappingSchema/HbmDatabaseObject.cs index a1286ba5ad8..4aa4ecd3fe9 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmDatabaseObject.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmDatabaseObject.cs @@ -17,7 +17,7 @@ public HbmDefinition FindDefinition() public IList FindDialectScopeNames() { - IList dialectScopeNames = new List(); + var dialectScopeNames = new List(); if (dialectscope != null) foreach (HbmDialectScope dialectScopeSchema in dialectscope) @@ -37,4 +37,4 @@ public bool HasDefinition() return FindDefinition() != null; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmDynamicComponent.cs b/src/NHibernate/Cfg/MappingSchema/HbmDynamicComponent.cs index 238a755f985..6af94596c57 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmDynamicComponent.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmDynamicComponent.cs @@ -64,6 +64,5 @@ protected override HbmMeta[] Metadatas } #endregion - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmElement.cs b/src/NHibernate/Cfg/MappingSchema/HbmElement.cs index 16cadfb532b..2573da02b07 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmElement.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmElement.cs @@ -62,7 +62,6 @@ private IEnumerable AsFormulas() #endregion - #region Implementation of ITypeMapping public HbmType Type diff --git a/src/NHibernate/Cfg/MappingSchema/HbmIdbag.cs b/src/NHibernate/Cfg/MappingSchema/HbmIdbag.cs index e4916d44220..48513187ac8 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmIdbag.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmIdbag.cs @@ -189,6 +189,5 @@ public HbmCache Cache } #endregion - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmKeyManyToOne.cs b/src/NHibernate/Cfg/MappingSchema/HbmKeyManyToOne.cs index f5d68ff7282..afde48b427c 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmKeyManyToOne.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmKeyManyToOne.cs @@ -6,7 +6,6 @@ namespace NHibernate.Cfg.MappingSchema { public partial class HbmKeyManyToOne : AbstractDecoratable, IColumnsMapping, IRelationship, IEntityPropertyMapping { - #region Implementation of IColumnsMapping [XmlIgnore] @@ -89,4 +88,4 @@ public bool OptimisticLock #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmKeyProperty.cs b/src/NHibernate/Cfg/MappingSchema/HbmKeyProperty.cs index d6bd5217f4d..fcc9f87543f 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmKeyProperty.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmKeyProperty.cs @@ -6,7 +6,6 @@ namespace NHibernate.Cfg.MappingSchema { public partial class HbmKeyProperty : AbstractDecoratable, IColumnsMapping, ITypeMapping, IEntityPropertyMapping { - #region Implementation of IColumnsMapping [XmlIgnore] @@ -75,4 +74,4 @@ public bool OptimisticLock #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmManyToAny.cs b/src/NHibernate/Cfg/MappingSchema/HbmManyToAny.cs index bad08a8ee94..1587869c19e 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmManyToAny.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmManyToAny.cs @@ -6,7 +6,6 @@ namespace NHibernate.Cfg.MappingSchema { public partial class HbmManyToAny : IColumnsMapping, IAnyMapping { - #region Implementation of IColumnsMapping [XmlIgnore] @@ -49,4 +48,4 @@ public ICollection MetaValues #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/MappingSchema/HbmManyToOne.cs b/src/NHibernate/Cfg/MappingSchema/HbmManyToOne.cs index f9bc6ae35ef..274e6e320f0 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmManyToOne.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmManyToOne.cs @@ -135,7 +135,7 @@ public IEnumerable ColumnsAndFormulas public HbmLaziness? Lazy { - get { return lazySpecified ? lazy : (HbmLaziness?) null;} + get { return lazySpecified ? lazy : (HbmLaziness?) null; } } } } diff --git a/src/NHibernate/Cfg/MappingSchema/HbmMapKey.cs b/src/NHibernate/Cfg/MappingSchema/HbmMapKey.cs index ffe0082034c..15ed2c6e2b8 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmMapKey.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmMapKey.cs @@ -7,7 +7,6 @@ namespace NHibernate.Cfg.MappingSchema { public partial class HbmMapKey: IColumnsMapping, ITypeMapping { - #region Implementation of IColumnsMapping [XmlIgnore] diff --git a/src/NHibernate/Cfg/MappingSchema/HbmMapKeyManyToMany.cs b/src/NHibernate/Cfg/MappingSchema/HbmMapKeyManyToMany.cs index 96619ed8302..017e0b63aab 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmMapKeyManyToMany.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmMapKeyManyToMany.cs @@ -7,7 +7,6 @@ namespace NHibernate.Cfg.MappingSchema { public partial class HbmMapKeyManyToMany: IColumnsMapping, IFormulasMapping, IRelationship { - #region Implementation of IColumnsMapping [XmlIgnore] diff --git a/src/NHibernate/Cfg/MappingSchema/HbmNestedCompositeElement.cs b/src/NHibernate/Cfg/MappingSchema/HbmNestedCompositeElement.cs index a941789f592..8162f93f029 100644 --- a/src/NHibernate/Cfg/MappingSchema/HbmNestedCompositeElement.cs +++ b/src/NHibernate/Cfg/MappingSchema/HbmNestedCompositeElement.cs @@ -55,7 +55,7 @@ public bool OptimisticLock protected override HbmMeta[] Metadatas { - get { return Array.Empty();} + get { return Array.Empty(); } } #endregion diff --git a/src/NHibernate/Cfg/MappingsQueue.cs b/src/NHibernate/Cfg/MappingsQueue.cs index b14e10526eb..538904b2fc6 100644 --- a/src/NHibernate/Cfg/MappingsQueue.cs +++ b/src/NHibernate/Cfg/MappingsQueue.cs @@ -11,7 +11,7 @@ namespace NHibernate.Cfg public class MappingsQueue { private readonly Queue availableEntries = new Queue(); - private readonly ISet processedClassNames = new HashSet(); + private readonly HashSet processedClassNames = new HashSet(); private readonly List unavailableEntries = new List(); @@ -109,4 +109,4 @@ private static string FormatExceptionMessage(IEnumerable res return message.ToString(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/NullConfigurationProvider.cs b/src/NHibernate/Cfg/NullConfigurationProvider.cs new file mode 100644 index 00000000000..7954d738ddf --- /dev/null +++ b/src/NHibernate/Cfg/NullConfigurationProvider.cs @@ -0,0 +1,20 @@ +namespace NHibernate.Cfg +{ + class NullConfigurationProvider : ConfigurationProvider + { + public override IHibernateConfiguration GetConfiguration() + { + return null; + } + + public override string GetNamedConnectionString(string name) + { + return null; + } + + public override string GetLoggerFactoryClassName() + { + return null; + } + } +} diff --git a/src/NHibernate/Cfg/SessionFactoryConfigurationBase.cs b/src/NHibernate/Cfg/SessionFactoryConfigurationBase.cs index 162ae3b96d9..09da7223888 100644 --- a/src/NHibernate/Cfg/SessionFactoryConfigurationBase.cs +++ b/src/NHibernate/Cfg/SessionFactoryConfigurationBase.cs @@ -6,12 +6,12 @@ namespace NHibernate.Cfg public class SessionFactoryConfigurationBase : ISessionFactoryConfiguration { private string name = string.Empty; - private readonly IDictionary properties = new Dictionary(); - private readonly IList mappings = new List(); - private readonly IList classesCache= new List(); - private readonly IList collectionsCache= new List(); - private readonly IList events= new List(); - private readonly IList listeners= new List(); + private readonly Dictionary properties = new Dictionary(); + private readonly List mappings = new List(); + private readonly List classesCache= new List(); + private readonly List collectionsCache= new List(); + private readonly List events= new List(); + private readonly List listeners= new List(); /// /// The session factory name. @@ -70,4 +70,4 @@ public IList Listeners get { return listeners; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/Settings.cs b/src/NHibernate/Cfg/Settings.cs index 878b9b60605..f973766013e 100644 --- a/src/NHibernate/Cfg/Settings.cs +++ b/src/NHibernate/Cfg/Settings.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq.Expressions; using NHibernate.AdoNet; using NHibernate.AdoNet.Util; using NHibernate.Cache; @@ -9,6 +10,7 @@ using NHibernate.Hql; using NHibernate.Linq.Functions; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Transaction; namespace NHibernate.Cfg @@ -53,6 +55,15 @@ public Settings() public string SessionFactoryName { get; internal set; } + /// + /// Should sessions check on every operation whether there is an ongoing system transaction or not, and enlist + /// into it if any? Default is . It can also be controlled at session opening, see + /// . A session can also be instructed to explicitly join the current + /// transaction by calling . This setting has no effect if using a + /// transaction factory that is not system transactions aware. + /// + public bool AutoJoinTransaction { get; internal set; } + public bool IsAutoCreateSchema { get; internal set; } public bool IsAutoDropSchema { get; internal set; } @@ -120,6 +131,13 @@ public Settings() public bool IsNamedQueryStartupCheckingEnabled { get; internal set; } public bool IsBatchVersionedDataEnabled { get; internal set; } + + // 6.0 TODO : should throw by default + /// + /// to throw in case any failure is reported during schema auto-update, + /// to ignore failures. + /// + public bool ThrowOnSchemaUpdate { get; internal set; } #region NH specific @@ -134,8 +152,53 @@ public Settings() /// public ILinqToHqlGeneratorsRegistry LinqToHqlGeneratorsRegistry { get; internal set; } + /// + /// Whether to use the legacy pre-evaluation or not in Linq queries. true by default. + /// + /// + /// + /// Legacy pre-evaluation is causing special properties or functions like DateTime.Now or + /// Guid.NewGuid() to be always evaluated with the .Net runtime and replaced in the query by + /// parameter values. + /// + /// + /// The new pre-evaluation allows them to be converted to HQL function calls which will be run on the db + /// side. This allows for example to retrieve the server time instead of the client time, or to generate + /// UUIDs for each row instead of an unique one for all rows. + /// + /// + public bool LinqToHqlLegacyPreEvaluation { get; internal set; } + + /// + /// When the new pre-evaluation is enabled, should methods which translation is not supported by the current + /// dialect fallback to pre-evaluation? false by default. + /// + /// + /// + /// When this fallback option is enabled while legacy pre-evaluation is disabled, properties or functions + /// like DateTime.Now or Guid.NewGuid() used in Linq expressions will not fail when the dialect does not + /// support them, but will instead be pre-evaluated. + /// + /// + /// When this fallback option is disabled while legacy pre-evaluation is disabled, properties or functions + /// like DateTime.Now or Guid.NewGuid() used in Linq expressions will fail when the dialect does not + /// support them. + /// + /// + /// This option has no effect if the legacy pre-evaluation is enabled. + /// + /// + public bool LinqToHqlFallbackOnPreEvaluation { get; internal set; } + public IQueryModelRewriterFactory QueryModelRewriterFactory { get; internal set; } - + + /// + /// The pre-transformer registrar used to register custom expression transformers. + /// + public IExpressionTransformerRegistrar PreTransformerRegistrar { get; internal set; } + + internal Func LinqPreTransformer { get; set; } + #endregion internal string GetFullCacheRegionName(string name) @@ -145,5 +208,9 @@ internal string GetFullCacheRegionName(string name) return prefix + '.' + name; return name; } + + public MultiTenancyStrategy MultiTenancyStrategy { get; internal set; } + + public IMultiTenancyConnectionProvider MultiTenancyConnectionProvider { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/SettingsFactory.cs b/src/NHibernate/Cfg/SettingsFactory.cs index dd3195350c4..f1f1a0ffb29 100644 --- a/src/NHibernate/Cfg/SettingsFactory.cs +++ b/src/NHibernate/Cfg/SettingsFactory.cs @@ -12,6 +12,7 @@ using NHibernate.Linq; using NHibernate.Linq.Functions; using NHibernate.Linq.Visitors; +using NHibernate.MultiTenancy; using NHibernate.Transaction; using NHibernate.Util; @@ -54,6 +55,15 @@ public Settings BuildSettings(IDictionary properties) settings.Dialect = dialect; settings.LinqToHqlGeneratorsRegistry = LinqToHqlGeneratorsRegistryFactory.CreateGeneratorsRegistry(properties); + // 6.0 TODO: default to false instead of true, and adjust documentation in xsd, xml comment on Environment + // and Setting properties, and doc\reference. + settings.LinqToHqlLegacyPreEvaluation = PropertiesHelper.GetBoolean( + Environment.LinqToHqlLegacyPreEvaluation, + properties, + true); + settings.LinqToHqlFallbackOnPreEvaluation = PropertiesHelper.GetBoolean( + Environment.LinqToHqlFallbackOnPreEvaluation, + properties); #region SQL Exception converter @@ -187,6 +197,8 @@ public Settings BuildSettings(IDictionary properties) settings.IsAutoQuoteEnabled = false; } + settings.ThrowOnSchemaUpdate = PropertiesHelper.GetBoolean(Environment.Hbm2ddlThrowOnUpdate, properties, false); + #endregion bool useSecondLevelCache = PropertiesHelper.GetBoolean(Environment.UseSecondLevelCache, properties, true); @@ -207,7 +219,6 @@ public Settings BuildSettings(IDictionary properties) if (string.IsNullOrEmpty(cacheRegionPrefix)) cacheRegionPrefix = null; if (cacheRegionPrefix != null) log.Info("Cache region prefix: {0}", cacheRegionPrefix); - if (useQueryCache) { string queryCacheFactoryClassName = PropertiesHelper.GetString(Environment.QueryCacheFactory, properties, @@ -291,6 +302,7 @@ public Settings BuildSettings(IDictionary properties) settings.TransactionFactory = transactionFactory; // Not ported - TransactionManagerLookup settings.SessionFactoryName = sessionFactoryName; + settings.AutoJoinTransaction = PropertiesHelper.GetBoolean(Environment.AutoJoinTransaction, properties, true); settings.MaximumFetchDepth = maxFetchDepth; settings.IsQueryCacheEnabled = useQueryCache; settings.IsSecondLevelCacheEnabled = useSecondLevelCache; @@ -299,7 +311,14 @@ public Settings BuildSettings(IDictionary properties) // Not ported - JdbcBatchVersionedData settings.QueryModelRewriterFactory = CreateQueryModelRewriterFactory(properties); - + settings.PreTransformerRegistrar = CreatePreTransformerRegistrar(properties); + + // Avoid dependency on re-linq assembly when PreTransformerRegistrar is null + if (settings.PreTransformerRegistrar != null) + { + settings.LinqPreTransformer = NhRelinqQueryParser.CreatePreTransformer(settings.PreTransformerRegistrar); + } + // NHibernate-specific: settings.IsolationLevel = isolation; @@ -307,6 +326,14 @@ public Settings BuildSettings(IDictionary properties) log.Debug("Track session id: " + EnabledDisabled(trackSessionId)); settings.TrackSessionId = trackSessionId; + var multiTenancyStrategy = PropertiesHelper.GetEnum(Environment.MultiTenancy, properties, MultiTenancyStrategy.None); + settings.MultiTenancyStrategy = multiTenancyStrategy; + if (multiTenancyStrategy != MultiTenancyStrategy.None) + { + log.Debug("multi-tenancy strategy : " + multiTenancyStrategy); + settings.MultiTenancyConnectionProvider = CreateMultiTenancyConnectionProvider(properties); + } + return settings; } @@ -393,6 +420,29 @@ private static System.Type CreateLinqQueryProviderType(IDictionary properties) + { + string className = PropertiesHelper.GetString( + Environment.MultiTenancyConnectionProvider, + properties, + null); + log.Info("Multi-tenancy connection provider: {0}", className); + if (className == null) + { + return null; + } + + try + { + return (IMultiTenancyConnectionProvider) + Environment.ObjectsFactory.CreateInstance(System.Type.GetType(className, true)); + } + catch (Exception cnfe) + { + throw new HibernateException("could not find Multi-tenancy connection provider class: " + className, cnfe); + } + } + private static ITransactionFactory CreateTransactionFactory(IDictionary properties) { string className = PropertiesHelper.GetString( @@ -433,5 +483,25 @@ private static IQueryModelRewriterFactory CreateQueryModelRewriterFactory(IDicti throw new HibernateException("could not instantiate IQueryModelRewriterFactory: " + className, cnfe); } } + + private static IExpressionTransformerRegistrar CreatePreTransformerRegistrar(IDictionary properties) + { + var className = PropertiesHelper.GetString(Environment.PreTransformerRegistrar, properties, null); + if (className == null) + return null; + + log.Info("Pre-transformer registrar: {0}", className); + + try + { + return + (IExpressionTransformerRegistrar) + Environment.ObjectsFactory.CreateInstance(ReflectHelper.ClassForName(className)); + } + catch (Exception e) + { + throw new HibernateException("could not instantiate IExpressionTransformerRegistrar: " + className, e); + } + } } } diff --git a/src/NHibernate/Cfg/StaticConfigurationManagerProvider.cs b/src/NHibernate/Cfg/StaticConfigurationManagerProvider.cs new file mode 100644 index 00000000000..8bc4abaa2d8 --- /dev/null +++ b/src/NHibernate/Cfg/StaticConfigurationManagerProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Configuration; +using System.Linq; +using NHibernate.Cfg.ConfigurationSchema; + +namespace NHibernate.Cfg +{ + class StaticConfigurationManagerProvider : ConfigurationProvider + { + public override IHibernateConfiguration GetConfiguration() + { + //TODO 6.0: Throw if not null and not IHibernateConfiguration + return ConfigurationManager.GetSection(CfgXmlHelper.CfgSectionName) as IHibernateConfiguration; + } + + public override string GetNamedConnectionString(string name) + { + return ConfigurationManager.ConnectionStrings[name]?.ConnectionString; + } + + public override string GetLoggerFactoryClassName() + { + var name = AppSettings.LoggerFactoryClassName; + var value = ConfigurationManager.AppSettings[name]; + + //TODO 6.0: Return value right away. Don't do ignore case search and document it as possible breaking change. + if (value != null) + return value; + + return GetAppSettingIgnoreCase(name); + } + + //TODO 6.0: Remove it + private static string GetAppSettingIgnoreCase(string name) + { + var key = ConfigurationManager.AppSettings.Keys.Cast().FirstOrDefault(k => name.Equals(k, StringComparison.OrdinalIgnoreCase)); + return string.IsNullOrEmpty(key) + ? null + : ConfigurationManager.AppSettings[key]; + } + } +} diff --git a/src/NHibernate/Cfg/SystemConfigurationProvider.cs b/src/NHibernate/Cfg/SystemConfigurationProvider.cs new file mode 100644 index 00000000000..87499d3cff8 --- /dev/null +++ b/src/NHibernate/Cfg/SystemConfigurationProvider.cs @@ -0,0 +1,40 @@ +using System.Configuration; +using NHibernate.Cfg.ConfigurationSchema; + +namespace NHibernate.Cfg +{ + /// + /// Configuration manager that supports user provided configuration + /// + public class SystemConfigurationProvider : ConfigurationProvider + { + private readonly System.Configuration.Configuration _configuration; + + public SystemConfigurationProvider(System.Configuration.Configuration configuration) + { + _configuration = configuration; + } + + public override IHibernateConfiguration GetConfiguration() + { + ConfigurationSection configurationSection = _configuration.GetSection(CfgXmlHelper.CfgSectionName); + var xml = configurationSection?.SectionInformation.GetRawXml(); + return xml == null ? null : HibernateConfiguration.FromAppConfig(xml); + } + + public override string GetNamedConnectionString(string name) + { + return _configuration.ConnectionStrings.ConnectionStrings[name]?.ConnectionString; + } + + public override string GetLoggerFactoryClassName() + { + return GetAppSetting(AppSettings.LoggerFactoryClassName); + } + + private string GetAppSetting(string name) + { + return _configuration.AppSettings.Settings[name]?.Value; + } + } +} diff --git a/src/NHibernate/Cfg/XmlHbmBinding/ClassBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/ClassBinder.cs index 5e111956a15..a8db6c1638f 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/ClassBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/ClassBinder.cs @@ -306,7 +306,6 @@ protected void BindComponent(IComponentMapping componentMapping, Component model model.IsEmbedded = false; model.IsDynamic = true; } - else if (reflectedClass != null) { model.ComponentClass = reflectedClass; diff --git a/src/NHibernate/Cfg/XmlHbmBinding/ClassCompositeIdBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/ClassCompositeIdBinder.cs index fff9351682c..00e384c5e76 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/ClassCompositeIdBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/ClassCompositeIdBinder.cs @@ -153,12 +153,10 @@ private System.Type GetPropertyType(System.Type containingType, string propertyN { return ClassForNameChecked(idSchema.@class, mappings, "could not find class: {0}"); } - else if (containingType == null) { return null; } - else { string access = idSchema.access ?? mappings.DefaultAccess; diff --git a/src/NHibernate/Cfg/XmlHbmBinding/CollectionBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/CollectionBinder.cs index edf40d8cf00..8984459d119 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/CollectionBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/CollectionBinder.cs @@ -942,7 +942,6 @@ private void BindManyToManySubelements(HbmManyToMany manyToManyMapping, Mapping. string.Format( "many-to-many defining filter or where without join fetching not valid within collection using join fetching [{0}]", collection.Role)); - } new FiltersBinder(collection, Mappings).Bind(filters, collection.AddManyToManyFilter); diff --git a/src/NHibernate/Cfg/XmlHbmBinding/MappingLogExtensions.cs b/src/NHibernate/Cfg/XmlHbmBinding/MappingLogExtensions.cs index d212e89e5ed..afbb135c143 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/MappingLogExtensions.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/MappingLogExtensions.cs @@ -3,7 +3,6 @@ using NHibernate.Mapping; using NHibernate.Type; - namespace NHibernate.Cfg.XmlHbmBinding { public static class MappingLogExtensions diff --git a/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs index 9779af07b0b..e9bb3f89081 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs @@ -33,8 +33,8 @@ public void AddSqlQuery(HbmSqlQuery querySchema) ? querySchema.cachemode.ToCacheMode() : null; - IDictionary parameterTypes = new LinkedHashMap(); - IList synchronizedTables = GetSynchronizedTables(querySchema); + var parameterTypes = new LinkedHashMap(); + var synchronizedTables = GetSynchronizedTables(querySchema); NamedSQLQueryDefinition namedQuery; @@ -58,9 +58,9 @@ public void AddSqlQuery(HbmSqlQuery querySchema) }); } - private static IList GetSynchronizedTables(HbmSqlQuery querySchema) + private static List GetSynchronizedTables(HbmSqlQuery querySchema) { - IList synchronizedTables = new List(); + var synchronizedTables = new List(); foreach (object item in querySchema.Items ?? Array.Empty()) { diff --git a/src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs index f78252b1760..3fbbc63aa57 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs @@ -372,12 +372,12 @@ private void BindComponentProperty(HbmComponent componentMapping, Property prope HbmTuplizer[] tuplizers = componentMapping.tuplizer; if (tuplizers != null) { - Array.ForEach(tuplizers.Select(tuplizer => new - { - TuplizerClassName = FullQualifiedClassName(tuplizer.@class, mappings), - Mode = tuplizer.entitymode.ToEntityMode() - }).ToArray(), - x => model.AddTuplizer(x.Mode, x.TuplizerClassName)); + foreach (var tuplizer in tuplizers) + { + var mode = tuplizer.entitymode.ToEntityMode(); + var tuplizerClassName = FullQualifiedClassName(tuplizer.@class, mappings); + model.AddTuplizer(mode, tuplizerClassName); + } } } diff --git a/src/NHibernate/Cfg/XmlHbmBinding/ResultSetMappingBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/ResultSetMappingBinder.cs index f168868b621..9de85b622e9 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/ResultSetMappingBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/ResultSetMappingBinder.cs @@ -53,20 +53,20 @@ private INativeSQLQueryReturn CreateQueryReturn(object item, int count) HbmReturnScalar returnScalarSchema = item as HbmReturnScalar; if (returnScalarSchema != null) - return CreateScalarReturn(returnScalarSchema); + return CreateScalarReturn(returnScalarSchema); else if (returnSchema != null) - return CreateReturn(returnSchema, count); + return CreateReturn(returnSchema, count); else if (returnJoinSchema != null) - return CreateJoinReturn(returnJoinSchema); + return CreateJoinReturn(returnJoinSchema); else if (loadCollectionSchema != null) - return CreateLoadCollectionReturn(loadCollectionSchema); + return CreateLoadCollectionReturn(loadCollectionSchema); else - return null; - } + return null; + } private INativeSQLQueryReturn CreateScalarReturn(HbmReturnScalar returnScalarSchema) { diff --git a/src/NHibernate/Cfg/XmlHbmBinding/RootClassBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/RootClassBinder.cs index 73ccc107087..c4aa765fee9 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/RootClassBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/RootClassBinder.cs @@ -90,7 +90,7 @@ private void BindNaturalId(HbmNaturalId naturalid, PersistentClass rootClass, ID property.IsUpdateable = false; property.IsNaturalIdentifier = true; - uk.AddColumns(property.ColumnIterator.OfType()); + uk.AddColumns(property.ColumnIterator); }); rootClass.Table.AddUniqueKey(uk); diff --git a/src/NHibernate/Cfg/XmlHbmBinding/SubclassBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/SubclassBinder.cs index 00c57e4d000..bd1b133b3c2 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/SubclassBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/SubclassBinder.cs @@ -60,6 +60,5 @@ public void HandleSubclass(PersistentClass model, HbmSubclass subClassMapping, I new FiltersBinder(model, Mappings).Bind(subClassMapping.filter); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cfg/XmlHbmBinding/ValuePropertyBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/ValuePropertyBinder.cs index 1d838b6b32e..ea62db092ec 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/ValuePropertyBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/ValuePropertyBinder.cs @@ -190,7 +190,6 @@ public void BindSimpleValue(HbmKeyProperty mapKeyManyToManyMapping, string prope name = mappings.NamingStrategy.PropertyToColumnName(propertyPath), length = mapKeyManyToManyMapping.length, }); - } public void BindSimpleValue(HbmKeyManyToOne mapKeyManyToManyMapping, string propertyPath, bool isNullable) diff --git a/src/NHibernate/Collection/AbstractPersistentCollection.cs b/src/NHibernate/Collection/AbstractPersistentCollection.cs index 9387b9f8ff0..7626b0e2509 100644 --- a/src/NHibernate/Collection/AbstractPersistentCollection.cs +++ b/src/NHibernate/Collection/AbstractPersistentCollection.cs @@ -2,11 +2,16 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Collection.Generic; +using NHibernate.Collection.Trackers; using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Loader; using NHibernate.Persister.Collection; +using NHibernate.Proxy; using NHibernate.Type; using NHibernate.Util; @@ -19,9 +24,15 @@ namespace NHibernate.Collection [Serializable] public abstract partial class AbstractPersistentCollection : IPersistentCollection, ILazyInitializedCollection { + // Since v5.3 + [Obsolete("This field has no more usages in NHibernate and will be removed in a future version.")] protected internal static readonly object Unknown = new object(); //place holder + // Since v5.3 + [Obsolete("This field has no more usages in NHibernate and will be removed in a future version.")] protected internal static readonly object NotFound = new object(); //place holder + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected interface IDelayedOperation { object AddedInstance { get; } @@ -29,6 +40,29 @@ protected interface IDelayedOperation void Operate(); } + class TypeComparer : IEqualityComparer + { + private readonly IType _type; + private readonly ISessionFactoryImplementor _sessionFactory; + + public TypeComparer(IType type, ISessionFactoryImplementor sessionFactory) + { + _type = type; + _sessionFactory = sessionFactory; + } + + public new bool Equals(object x, object y) + { + return _type.IsEqual(x, y, _sessionFactory); + } + + public int GetHashCode(object obj) + { + return _type.GetHashCode(obj, _sessionFactory); + } + } + + // 6.0 TODO: Remove private class AdditionEnumerable : IEnumerable { private readonly AbstractPersistentCollection enclosingInstance; @@ -85,9 +119,12 @@ public void Reset() [NonSerialized] private ISessionImplementor session; private bool initialized; - [NonSerialized] private List operationQueue; +#pragma warning disable 618 + [NonSerialized] private List operationQueue; // 6.0 TODO: Remove +#pragma warning restore 618 [NonSerialized] private bool directlyAccessible; [NonSerialized] private bool initializing; + [NonSerialized] private AbstractQueueOperationTracker _queueOperationTracker; private object owner; private int cachedSize = -1; @@ -276,26 +313,41 @@ protected virtual bool ReadSize() { return true; } - else + + ThrowLazyInitializationExceptionIfNotConnected(); + var entry = session.PersistenceContext.GetCollectionEntry(this); + var persister = entry.LoadedPersister; + if (persister.IsExtraLazy) { - ThrowLazyInitializationExceptionIfNotConnected(); - CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(this); - ICollectionPersister persister = entry.LoadedPersister; - if (persister.IsExtraLazy) + var queueOperationTracker = GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) { if (HasQueuedOperations) { session.Flush(); } + cachedSize = persister.GetSize(entry.LoadedKey, session); return true; } + + if (!queueOperationTracker.Cleared && !queueOperationTracker.DatabaseCollectionSize.HasValue) + { + queueOperationTracker.DatabaseCollectionSize = persister.GetSize(entry.LoadedKey, session); + } + + cachedSize = queueOperationTracker.Cleared + ? queueOperationTracker.GetQueueSize() + : queueOperationTracker.GetCollectionSize(); + return true; } + Read(); } - Read(); return false; } + // Since v5.3 + [Obsolete("This method has no more usages in NHibernate and will be removed in a future version.")] protected virtual bool? ReadIndexExistence(object index) { if (!initialized) @@ -316,6 +368,8 @@ protected virtual bool ReadSize() return null; } + // Since v5.3 + [Obsolete("This method has no more usages in NHibernate and will be removed in a future version.")] protected virtual bool? ReadElementExistence(object element) { if (!initialized) @@ -336,6 +390,8 @@ protected virtual bool ReadSize() return null; } + // Since v5.3 + [Obsolete("This method has no more usages in NHibernate and will be removed in a future version.")] protected virtual object ReadElementByIndex(object index) { if (!initialized) @@ -357,6 +413,228 @@ protected virtual object ReadElementByIndex(object index) return Unknown; } + protected virtual bool? ReadKeyExistence(TKey elementKey) + { + if (!initialized) + { + ThrowLazyInitializationExceptionIfNotConnected(); + CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(this); + ICollectionPersister persister = entry.LoadedPersister; + if (persister.IsExtraLazy) + { + var queueOperationTracker = (AbstractMapQueueOperationTracker) GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) + { + if (HasQueuedOperations) + { + session.Flush(); + } + + return persister.IndexExists(entry.LoadedKey, elementKey, session); + } + + if (queueOperationTracker.ContainsKey(elementKey)) + { + return true; + } + + if (queueOperationTracker.Cleared) + { + return false; + } + + if (queueOperationTracker.IsElementKeyQueuedForDelete(elementKey)) + { + return false; + } + + // As keys are unordered we don't have to calculate the current order of the key + return persister.IndexExists(entry.LoadedKey, elementKey, session); + } + Read(); + } + return null; + } + + protected virtual bool? ReadElementExistence(T element, out bool? existsInDb) + { + if (!initialized) + { + ThrowLazyInitializationExceptionIfNotConnected(); + CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(this); + ICollectionPersister persister = entry.LoadedPersister; + if (persister.IsExtraLazy) + { + var queueOperationTracker = (AbstractCollectionQueueOperationTracker) GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) + { + if (HasQueuedOperations) + { + session.Flush(); + } + + existsInDb = persister.ElementExists(entry.LoadedKey, element, session); + return existsInDb; + } + + if (queueOperationTracker.ContainsElement(element)) + { + existsInDb = null; + return true; + } + + if (queueOperationTracker.Cleared) + { + existsInDb = null; + return false; + } + + if (queueOperationTracker.IsElementQueuedForDelete(element)) + { + existsInDb = null; + return false; + } + + existsInDb = persister.ElementExists(entry.LoadedKey, element, session); + return existsInDb; + } + Read(); + } + + existsInDb = null; + return null; + } + + protected virtual bool? TryReadElementByKey(TKey elementKey, out TValue element, out bool? existsInDb) + { + if (!initialized) + { + ThrowLazyInitializationExceptionIfNotConnected(); + CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(this); + ICollectionPersister persister = entry.LoadedPersister; + if (persister.IsExtraLazy) + { + var queueOperationTracker = (AbstractMapQueueOperationTracker) GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) + { + if (HasQueuedOperations) + { + session.Flush(); + } + } + else + { + if (queueOperationTracker.TryGetElementByKey(elementKey, out element)) + { + existsInDb = null; + return true; + } + + if (queueOperationTracker.Cleared) + { + existsInDb = null; + return false; + } + + if (queueOperationTracker.IsElementKeyQueuedForDelete(elementKey)) + { + existsInDb = null; + return false; + } + } + + var elementByKey = persister.GetElementByIndex(entry.LoadedKey, elementKey, session, owner); + if (persister.NotFoundObject == elementByKey) + { + element = default(TValue); + existsInDb = false; + return false; + } + + element = (TValue) elementByKey; + existsInDb = true; + return true; + } + Read(); + } + + element = default(TValue); + existsInDb = null; + return null; + } + + protected virtual bool? TryReadElementAtIndex(int index, out T element) + { + if (!initialized) + { + ThrowLazyInitializationExceptionIfNotConnected(); + CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(this); + ICollectionPersister persister = entry.LoadedPersister; + if (persister.IsExtraLazy) + { + var queueOperationTracker = (AbstractCollectionQueueOperationTracker) GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) + { + if (HasQueuedOperations) + { + session.Flush(); + } + } + else if (HasQueuedOperations) + { + if (queueOperationTracker.RequiresFlushing(nameof(queueOperationTracker.TryGetElementAtIndex))) + { + session.Flush(); + } + else + { + if (!queueOperationTracker.DatabaseCollectionSize.HasValue && + queueOperationTracker.RequiresDatabaseCollectionSize(nameof(queueOperationTracker.TryGetElementAtIndex)) && + !ReadSize()) + { + element = default(T); + return null; // The collection was loaded + } + + if (queueOperationTracker.TryGetElementAtIndex(index, out element)) + { + return true; + } + + if (queueOperationTracker.Cleared) + { + return false; + } + + // We have to calculate the database index based on the changes in the queue + var dbIndex = queueOperationTracker.GetDatabaseElementIndex(index); + if (!dbIndex.HasValue) + { + element = default(T); + return false; + } + + index = dbIndex.Value; + } + } + + var elementByIndex = persister.GetElementByIndex(entry.LoadedKey, index, session, owner); + if (persister.NotFoundObject == elementByIndex) + { + element = default(T); + return false; + } + + element = (T) elementByIndex; + return true; + } + Read(); + } + + element = default(T); + return null; + } + /// /// Called by any writer method of the collection interface /// @@ -366,9 +644,30 @@ protected virtual void Write() Dirty(); } + internal virtual AbstractQueueOperationTracker CreateQueueOperationTracker() => null; + + internal AbstractQueueOperationTracker QueueOperationTracker + { + get => _queueOperationTracker; + set => _queueOperationTracker = value; + } + + internal AbstractQueueOperationTracker GetOrCreateQueueOperationTracker() + { + if (_queueOperationTracker != null) + { + return _queueOperationTracker; + } + + _queueOperationTracker = CreateQueueOperationTracker(); + return _queueOperationTracker; + } + /// /// Queue an addition, delete etc. if the persistent collection supports it /// + // Since v5.3 + [Obsolete("This method has no more usages in NHibernate and will be removed in a future version.")] protected virtual void QueueOperation(IDelayedOperation element) { if (operationQueue == null) @@ -379,6 +678,115 @@ protected virtual void QueueOperation(IDelayedOperation element) dirty = true; //needed so that we remove this collection from the second-level cache } + protected bool QueueAddElement(T element) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractCollectionQueueOperationTracker.AddElement)); + return queueOperationTracker.AddElement(element); + } + + protected void QueueRemoveExistingElement(T element, bool? existsInDb) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractCollectionQueueOperationTracker.RemoveExistingElement), out var wasFlushed); + queueOperationTracker.RemoveExistingElement(element, wasFlushed ? true : existsInDb); + } + + protected void QueueRemoveElementAtIndex(int index, T element) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractCollectionQueueOperationTracker.RemoveElementAtIndex)); + queueOperationTracker.RemoveElementAtIndex(index, element); + } + + protected void QueueAddElementAtIndex(int index, T element) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractCollectionQueueOperationTracker.AddElementAtIndex)); + queueOperationTracker.AddElementAtIndex(index, element); + } + + protected void QueueSetElementAtIndex(int index, T element, T oldElement) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractCollectionQueueOperationTracker.SetElementAtIndex)); + queueOperationTracker.SetElementAtIndex(index, element, oldElement); + } + + protected void QueueClearCollection() + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractQueueOperationTracker.ClearCollection), out _); + queueOperationTracker.ClearCollection(); + } + + protected void QueueAddElementByKey(TKey elementKey, TValue element) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractMapQueueOperationTracker.AddElementByKey)); + queueOperationTracker.AddElementByKey(elementKey, element); + } + + protected void QueueSetElementByKey(TKey elementKey, TValue element, TValue oldElement, bool? existsInDb) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractMapQueueOperationTracker.SetElementByKey)); + queueOperationTracker.SetElementByKey(elementKey, element, oldElement, existsInDb); + } + + protected bool QueueRemoveElementByKey(TKey elementKey, TValue oldElement, bool? existsInDb) + { + var queueOperationTracker = TryFlushAndGetQueueOperationTracker(nameof(AbstractMapQueueOperationTracker.RemoveElementByKey)); + return queueOperationTracker.RemoveElementByKey(elementKey, oldElement, existsInDb); + } + + private AbstractMapQueueOperationTracker TryFlushAndGetQueueOperationTracker(string operationName) + { + return (AbstractMapQueueOperationTracker) TryFlushAndGetQueueOperationTracker(operationName, out _); + } + + private AbstractCollectionQueueOperationTracker TryFlushAndGetQueueOperationTracker(string operationName) + { + return (AbstractCollectionQueueOperationTracker) TryFlushAndGetQueueOperationTracker(operationName, out _); + } + + private AbstractCollectionQueueOperationTracker TryFlushAndGetQueueOperationTracker(string operationName, out bool wasFlushed) + { + return (AbstractCollectionQueueOperationTracker) TryFlushAndGetQueueOperationTracker(operationName, out wasFlushed); + } + + private AbstractQueueOperationTracker TryFlushAndGetQueueOperationTracker(string operationName, out bool wasFlushed) + { + var queueOperationTracker = GetOrCreateQueueOperationTracker(); + if (queueOperationTracker == null) + { + throw new InvalidOperationException($"{nameof(CreateQueueOperationTracker)} must return a not null value."); + } + + if (queueOperationTracker.RequiresFlushing(operationName)) + { + session.Flush(); + wasFlushed = true; + } + else + { + wasFlushed = false; + } + + queueOperationTracker.BeforeOperation(operationName); + if (!queueOperationTracker.DatabaseCollectionSize.HasValue && queueOperationTracker.RequiresDatabaseCollectionSize(operationName) && !ReadSize()) + { + throw new InvalidOperationException($"The collection role {Role} must be mapped as extra lazy."); + } + + dirty = true; // Needed so that we remove this collection from the second-level cache + return queueOperationTracker; + } + + internal bool CanSkipElementExistenceCheck(object element) + { + var queryableCollection = (IQueryableCollection) Session.Factory.GetCollectionPersister(Role); + return + queryableCollection != null && + queryableCollection.ElementType.IsEntityType && + !queryableCollection.ElementPersister.EntityMetamodel.OverridesEquals && + !element.IsProxy() && + !Session.PersistenceContext.IsEntryFor(element) && + ForeignKeys.IsTransientFast(queryableCollection.ElementPersister.EntityName, element, Session) == true; + } + // Obsolete since v5.2 /// /// After reading all existing elements from the database, @@ -426,6 +834,7 @@ public void SetSnapshot(object key, string role, object snapshot) public virtual void PostAction() { operationQueue = null; + _queueOperationTracker?.AfterFlushing(); cachedSize = -1; ClearDirty(); } @@ -456,7 +865,7 @@ public virtual bool AfterInitialize(ICollectionPersister persister) { SetInitialized(); cachedSize = -1; - return operationQueue == null; + return operationQueue == null && _queueOperationTracker == null; } /// @@ -636,10 +1045,7 @@ public bool WasInitialized } /// Does this instance have any "queued" additions? - public bool HasQueuedOperations - { - get { return operationQueue != null && operationQueue.Count > 0; } - } + public bool HasQueuedOperations => (operationQueue != null && operationQueue.Count > 0) || _queueOperationTracker?.HasChanges() == true; /// public IEnumerable QueuedAdditionIterator @@ -648,6 +1054,13 @@ public IEnumerable QueuedAdditionIterator { if (HasQueuedOperations) { + // Use the queue operation tracker when available to get the added elements as AdditionEnumerable + // does not work correctly when the item is added and then removed + if (_queueOperationTracker != null) + { + return _queueOperationTracker.GetAddedElements(); + } + return new AdditionEnumerable(this); } else @@ -661,20 +1074,35 @@ public ICollection GetQueuedOrphans(string entityName) { if (HasQueuedOperations) { - List additions = new List(operationQueue.Count); - List removals = new List(operationQueue.Count); - for (int i = 0; i < operationQueue.Count; i++) + List additions; + List removals; + + // Use the queue operation tracker when available to get the orphans as the default logic + // does not work correctly when readding a transient entity. Removals list should + // contain only entities that are already in the database. + if (_queueOperationTracker != null) { - IDelayedOperation op = operationQueue[i]; - if (op.AddedInstance != null) - { - additions.Add(op.AddedInstance); - } - if (op.Orphan != null) + removals = new List(_queueOperationTracker.GetOrphans().Cast()); + additions = new List(_queueOperationTracker.GetAddedElements().Cast()); + } + else // 6.0 TODO: Remove whole else block + { + additions = new List(operationQueue.Count); + removals = new List(operationQueue.Count); + for (int i = 0; i < operationQueue.Count; i++) { - removals.Add(op.Orphan); + var op = operationQueue[i]; + if (op.AddedInstance != null) + { + additions.Add(op.AddedInstance); + } + if (op.Orphan != null) + { + removals.Add(op.Orphan); + } } } + return GetOrphans(removals, additions, entityName, session); } @@ -716,35 +1144,62 @@ protected virtual ICollection GetOrphans(ICollection oldElements, ICollection cu return oldElements; } - IType idType = session.Factory.GetEntityPersister(entityName).IdentifierType; + if (currentElements.Count == oldElements.Count && currentElements.Cast().SequenceEqual(oldElements.Cast(), ReferenceComparer.Instance)) + return Array.Empty(); - // create the collection holding the orphans - List res = new List(); + var persister = session.Factory.GetEntityPersister(entityName); + IType idType = persister.IdentifierType; - // collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access - var currentIds = new HashSet(); + var currentObjects = new HashSet(new TypeComparer(idType, session.Factory)); foreach (object current in currentElements) { - if (current != null && ForeignKeys.IsNotTransientSlow(entityName, current, session)) + if (current != null) { - object currentId = ForeignKeys.GetEntityIdentifierIfNotUnsaved(entityName, current, session); - currentIds.Add(new TypedValue(idType, currentId, false)); + var id = ForeignKeys.GetIdentifier(persister, current); + if (id != null) + currentObjects.Add(id); } } - // iterate over the *old* list + List res = new List(); + // oldElements may contain new elements (one case when session.Save is called on new object with collection filled with new objects) foreach (object old in oldElements) { - object oldId = ForeignKeys.GetEntityIdentifierIfNotUnsaved(entityName, old, session); - if (!currentIds.Contains(new TypedValue(idType, oldId, false))) + if (old != null) { - res.Add(old); + var id = ForeignKeys.GetIdentifier(persister, old); + if (id != null) + { + if (!currentObjects.Contains(id)) + { + res.Add(old); + } + } } } return res; } + // Since 5.3 + /// + /// Given a collection of entity instances that used to + /// belong to the collection, and a collection of instances + /// that currently belong, return a collection of orphans + /// + [Obsolete("This method has no more usages and will be removed in a future version")] + protected virtual Task GetOrphansAsync(ICollection oldElements, ICollection currentElements, string entityName, ISessionImplementor session, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return Task.FromResult(GetOrphans(oldElements, currentElements, entityName, session)); + } + + // Since 5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] public void IdentityRemove(IList list, object obj, string entityName, ISessionImplementor session) { if (obj != null && ForeignKeys.IsNotTransientSlow(entityName, obj, session)) @@ -866,6 +1321,29 @@ public abstract object ReadFrom(DbDataReader reader, ICollectionPersister role, /// The anticipated size of the collection after initialization is complete. public abstract void BeforeInitialize(ICollectionPersister persister, int anticipatedSize); + // Since 5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public Task GetQueuedOrphansAsync(string entityName, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + return Task.FromResult(GetQueuedOrphans(entityName)); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + // Since 5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken); + #region - Hibernate Collection Proxy Classes /* diff --git a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs index 67564a5de3e..98c916717c3 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -25,7 +26,7 @@ namespace NHibernate.Collection.Generic /// The underlying collection used is an [Serializable] [DebuggerTypeProxy(typeof (CollectionProxy<>))] - public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IList, IQueryable + public partial class PersistentGenericBag : AbstractPersistentCollection, IList, IReadOnlyList, IList, IQueryable { // TODO NH: find a way to writeonce (no duplicated code from PersistentBag) @@ -48,7 +49,7 @@ public partial class PersistentGenericBag : AbstractPersistentCollection, ILi * ! */ private IList _gbag; - private bool _isOneToMany; + private bool _isOneToMany; // 6.0 TODO: Remove public PersistentGenericBag() { @@ -67,6 +68,19 @@ public PersistentGenericBag(ISessionImplementor session, IEnumerable coll) IsDirectlyAccessible = true; } + internal override AbstractQueueOperationTracker CreateQueueOperationTracker() + { + var entry = Session.PersistenceContext.GetCollectionEntry(this); + return new BagQueueOperationTracker(entry.LoadedPersister); + } + + public override void ApplyQueuedOperations() + { + var queueOperation = (BagQueueOperationTracker) QueueOperationTracker; + queueOperation?.ApplyChanges(_gbag); + QueueOperationTracker = null; + } + protected IList InternalBag { get { return _gbag; } @@ -119,15 +133,16 @@ int IList.IndexOf(object value) int IList.Add(object value) { - Add((T) value); + if (!IsOperationQueueEnabled || !ReadSize()) + { + Write(); + return ((IList) _gbag).Add((T) value); + } - //TODO: take a look at this - I don't like it because it changes the - // meaning of Add - instead of returning the index it was added at - // returns a "fake" index - not consistent with IList interface... - var count = !IsOperationQueueEnabled - ? _gbag.Count - : 0; - return count - 1; + var val = (T) value; + QueueAddElement(val); + + return CachedSize; } void IList.Insert(int index, object value) @@ -181,7 +196,7 @@ public void Add(T item) } else { - QueueOperation(new SimpleAddDelayedOperation(this, item)); + QueueAddElement(item); } } @@ -189,7 +204,7 @@ public void Clear() { if (ClearQueueEnabled) { - QueueOperation(new ClearDelayedOperation(this)); + QueueClearCollection(); } else { @@ -204,7 +219,7 @@ public void Clear() public bool Contains(T item) { - return ReadElementExistence(item) ?? _gbag.Contains(item); + return ReadElementExistence(item, out _) ?? _gbag.Contains(item); } public void CopyTo(T[] array, int arrayIndex) @@ -295,12 +310,15 @@ public override bool EqualsSnapshot(ICollectionPersister persister) return false; } - foreach (var elt in _gbag) + for (var i = 0; i < _gbag.Count; i++) { - if (CountOccurrences(elt, _gbag, elementType) != CountOccurrences(elt, sn, elementType)) - { + if (elementType.IsSame(_gbag[i], sn[i])) + continue; + + var elt = _gbag[i]; + var countInSnapshot = CountOccurrences(elt, sn, elementType); + if (countInSnapshot == 0 || CountOccurrences(elt, _gbag, elementType) != countInSnapshot) return false; - } } return true; @@ -448,11 +466,9 @@ public override object ReadFrom(DbDataReader reader, ICollectionPersister role, // note that if we load this collection from a cartesian product // the multiplicity would be broken ... so use an idbag instead var element = role.ReadElement(reader, owner, descriptor.SuffixedElementAliases, Session); - // NH Different behavior : we don't check for null - // The NH-750 test show how checking for null we are ignoring the not-found tag and - // the DB may have some records ignored by NH. This issue may need some more deep consideration. - //if (element != null) - _gbag.Add((T) element); + + if (element != null) + _gbag.Add((T) element); return element; } @@ -500,78 +516,5 @@ private static int CountOccurrences(object element, IEnumerable list, IType elem return result; } - - private sealed class ClearDelayedOperation : IDelayedOperation - { - private readonly PersistentGenericBag _enclosingInstance; - - public ClearDelayedOperation(PersistentGenericBag enclosingInstance) - { - _enclosingInstance = enclosingInstance; - } - - public object AddedInstance - { - get { return null; } - } - - public object Orphan - { - get { throw new NotSupportedException("queued clear cannot be used with orphan delete"); } - } - - public void Operate() - { - _enclosingInstance._gbag.Clear(); - } - } - - private sealed class SimpleAddDelayedOperation : IDelayedOperation - { - private readonly PersistentGenericBag _enclosingInstance; - private readonly T _value; - - public SimpleAddDelayedOperation(PersistentGenericBag enclosingInstance, T value) - { - _enclosingInstance = enclosingInstance; - _value = value; - } - - public object AddedInstance - { - get { return _value; } - } - - public object Orphan - { - get { return null; } - } - - public void Operate() - { - // NH Different behavior for NH-739. A "bag" mapped as a bidirectional one-to-many of an entity with an - // id generator causing it to be inserted on flush must not replay the addition after initialization, - // if the entity was previously saved. In that case, the entity save has inserted it in database with - // its association to the bag, without causing a full flush. So for the bag, the operation is still - // pending, but in database it is already done. On initialization, the bag thus already receives the - // entity in its loaded list, and it should not be added again. - // Since a one-to-many bag is actually a set, we can simply check if the entity is already in the loaded - // state, and discard it if yes. (It also relies on the bag not having pending removes, which is the - // case, since it only handles delayed additions and clears.) - // Since this condition happens with transient instances added in the bag then saved, ReferenceEquals - // is enough to match them. - // This solution is a workaround, the root cause is not fixed. The root cause is the insertion on save - // done without caring for pending operations of one-to-many collections. This root cause could be fixed - // by triggering a full flush there before the insert (currently it just flushes pending inserts), or - // maybe by flushing just the dirty one-to-many non-initialized collections (but this can be tricky). - // (It is also likely one-to-many lists have a similar issue, but nothing is done yet for them. And - // their case is more complex due to having to care for the indexes and to handle many more delayed - // operation kinds.) - if (_enclosingInstance._isOneToMany && _enclosingInstance._gbag.Any(l => ReferenceEquals(l, _value))) - return; - - _enclosingInstance._gbag.Add(_value); - } - } } } diff --git a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs index e80eac9bd17..884d454b76e 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -12,6 +12,7 @@ using NHibernate.Loader; using NHibernate.Persister.Collection; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Collection.Generic { @@ -32,7 +33,7 @@ namespace NHibernate.Collection.Generic /// [Serializable] [DebuggerTypeProxy(typeof (CollectionProxy<>))] - public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IList, IQueryable + public partial class PersistentIdentifierBag : AbstractPersistentCollection, IList, IReadOnlyList, IList, IQueryable { /* NH considerations: * For various reason we know that the underlining type will be a List or a @@ -159,7 +160,7 @@ public override bool IsSnapshotEmpty(object snapshot) public override IEnumerable GetDeletes(ICollectionPersister persister, bool indexIsFormula) { var snap = (ISet)GetSnapshot(); - ArrayList deletes = new ArrayList(snap.Select(x => x.Id).ToArray()); + var deletes = snap.ToList(x => x.Id); for (int i = 0; i < _values.Count; i++) { if (_values[i] != null) @@ -245,7 +246,7 @@ public override object GetSnapshot(ICollectionPersister persister) public override ICollection GetOrphans(object snapshot, string entityName) { var sn = (ISet)GetSnapshot(); - return GetOrphans(sn.Select(x => x.Value).ToArray(), (ICollection) _values, entityName, Session); + return GetOrphans(sn.ToArray(x => x.Value), (ICollection) _values, entityName, Session); } public override void PreInsert(ICollectionPersister persister) diff --git a/src/NHibernate/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Collection/Generic/PersistentGenericList.cs index 8c676663f56..6255616dd0e 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericList.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -22,7 +23,7 @@ namespace NHibernate.Collection.Generic /// The underlying collection used is a [Serializable] [DebuggerTypeProxy(typeof (CollectionProxy<>))] - public partial class PersistentGenericList : AbstractPersistentCollection, IList, IList, IQueryable + public partial class PersistentGenericList : AbstractPersistentCollection, IList, IReadOnlyList, IList, IQueryable { protected IList WrappedList; @@ -53,6 +54,11 @@ public PersistentGenericList(ISessionImplementor session, IList list) : base( IsDirectlyAccessible = true; } + internal override AbstractQueueOperationTracker CreateQueueOperationTracker() + { + var entry = Session.PersistenceContext.GetCollectionEntry(this); + return new ListQueueOperationTracker(entry.LoadedPersister); + } public override object GetSnapshot(ICollectionPersister persister) { @@ -100,6 +106,13 @@ public override void BeforeInitialize(ICollectionPersister persister, int antici WrappedList = (IList) persister.CollectionType.Instantiate(anticipatedSize); } + public override void ApplyQueuedOperations() + { + var queueOperation = (ListQueueOperationTracker) QueueOperationTracker; + queueOperation?.ApplyChanges(WrappedList); + QueueOperationTracker = null; + } + public override bool IsWrapper(object collection) { return ReferenceEquals(WrappedList, collection); @@ -225,24 +238,6 @@ public override bool EntryExists(object entry, int i) return entry != null; } - public override bool Equals(object obj) - { - var that = obj as ICollection; - if (that == null) - { - return false; - } - Read(); - return CollectionHelper.SequenceEquals(WrappedList, that); - } - - public override int GetHashCode() - { - Read(); - return WrappedList.GetHashCode(); - } - - #region IList Members int IList.Add(object value) @@ -253,7 +248,9 @@ int IList.Add(object value) return ((IList)WrappedList).Add(value); } - QueueOperation(new SimpleAddDelayedOperation(this, (T) value)); + var val = (T) value; + QueueAddElement(val); + //TODO: take a look at this - I don't like it because it changes the // meaning of Add - instead of returning the index it was added at // returns a "fake" index - not consistent with IList interface... @@ -269,7 +266,7 @@ public void Clear() { if (ClearQueueEnabled) { - QueueOperation(new ClearDelayedOperation(this)); + QueueClearCollection(); } else { @@ -301,17 +298,20 @@ public void RemoveAt(int index) { if (index < 0) { - throw new IndexOutOfRangeException("negative index"); + throw new ArgumentOutOfRangeException( + nameof(index), + "Index was out of range. Must be non-negative and less than the size of the collection."); } - object old = PutQueueEnabled ? ReadElementByIndex(index) : Unknown; - if (old == Unknown) + + var found = TryReadElementAtIndex(index, out var element); + if (!found.HasValue) { Write(); WrappedList.RemoveAt(index); } else { - QueueOperation(new RemoveDelayedOperation(this, index, old == NotFound ? null : old)); + QueueRemoveElementAtIndex(index, element); } } @@ -333,7 +333,6 @@ bool IList.IsFixedSize #endregion - #region IList Members public int IndexOf(T item) @@ -346,7 +345,7 @@ public void Insert(int index, T item) { if (index < 0) { - throw new IndexOutOfRangeException("negative index"); + throw new ArgumentOutOfRangeException(nameof(index), "Index must be within the bounds of the List."); } if (!IsOperationQueueEnabled) { @@ -355,7 +354,7 @@ public void Insert(int index, T item) } else { - QueueOperation(new AddDelayedOperation(this, index, item)); + QueueAddElementAtIndex(index, item); } } @@ -365,46 +364,59 @@ public T this[int index] { if (index < 0) { - throw new IndexOutOfRangeException("negative index"); + throw new ArgumentOutOfRangeException( + nameof(index), + "Index was out of range. Must be non-negative and less than the size of the collection."); } - object result = ReadElementByIndex(index); - if (result == Unknown) + + var found = TryReadElementAtIndex(index, out var element); + if (!found.HasValue) { return WrappedList[index]; } - if (result == NotFound) + if (!found.Value) { // check if the index is valid if (index >= Count) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException( + nameof(index), + "Index was out of range. Must be non-negative and less than the size of the collection."); } return default(T); } - return (T) result; + return element; } set { if (index < 0) { - throw new IndexOutOfRangeException("negative index"); + throw new ArgumentOutOfRangeException( + nameof(index), + "Index was out of range. Must be non-negative and less than the size of the collection."); } - object old = PutQueueEnabled ? ReadElementByIndex(index) : Unknown; - if (old == Unknown) + + var old = default(T); + var found = PutQueueEnabled ? TryReadElementAtIndex(index, out old) : null; + if (!found.HasValue) { Write(); WrappedList[index] = value; } else { - QueueOperation(new SetDelayedOperation(this, index, value, old == NotFound ? null : old)); + if (EqualityComparer.Default.Equals(value, old)) + { + return; + } + + QueueSetElementAtIndex(index, value, old); } } } #endregion - #region ICollection Members void ICollection.CopyTo(Array array, int arrayIndex) @@ -438,7 +450,6 @@ bool ICollection.IsSynchronized #endregion - #region ICollection Members public void Add(T item) @@ -450,13 +461,13 @@ public void Add(T item) } else { - QueueOperation(new SimpleAddDelayedOperation(this, item)); + QueueAddElement(item); } } public bool Contains(T item) { - return ReadElementExistence(item) ?? WrappedList.Contains(item); + return ReadElementExistence(item, out _) ?? WrappedList.Contains(item); } public void CopyTo(T[] array, int arrayIndex) @@ -471,7 +482,8 @@ bool ICollection.IsReadOnly { public bool Remove(T item) { - bool? exists = PutQueueEnabled ? ReadElementExistence(item) : null; + bool? existsInDb = null; + bool? exists = PutQueueEnabled ? ReadElementExistence(item, out existsInDb) : null; if (!exists.HasValue) { Initialize(true); @@ -484,15 +496,16 @@ public bool Remove(T item) } else if (exists.Value) { - QueueOperation(new SimpleRemoveDelayedOperation(this, item)); + QueueRemoveExistingElement(item, existsInDb); + return true; } + return false; } #endregion - #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() @@ -530,6 +543,8 @@ IEnumerator IEnumerable.GetEnumerator() #region DelayedOperations + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class ClearDelayedOperation : IDelayedOperation { private readonly PersistentGenericList _enclosingInstance; @@ -555,6 +570,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class SimpleAddDelayedOperation : IDelayedOperation { private readonly PersistentGenericList _enclosingInstance; @@ -582,6 +599,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class AddDelayedOperation : IDelayedOperation { private readonly PersistentGenericList _enclosingInstance; @@ -611,6 +630,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class SetDelayedOperation : IDelayedOperation { private readonly PersistentGenericList _enclosingInstance; @@ -642,6 +663,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class RemoveDelayedOperation : IDelayedOperation { private readonly PersistentGenericList _enclosingInstance; @@ -671,6 +694,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class SimpleRemoveDelayedOperation : IDelayedOperation { private readonly PersistentGenericList _enclosingInstance; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index 994f71e0d44..5fab797d793 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -23,7 +24,7 @@ namespace NHibernate.Collection.Generic /// The type of the elements in the IDictionary. [Serializable] [DebuggerTypeProxy(typeof(DictionaryProxy<,>))] - public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, ICollection + public partial class PersistentGenericMap : AbstractPersistentCollection, IDictionary, IReadOnlyDictionary, ICollection { protected IDictionary WrappedMap; private readonly ICollection _wrappedValues; @@ -56,6 +57,12 @@ public PersistentGenericMap(ISessionImplementor session, IDictionary(entry.LoadedPersister); + } + public override object GetSnapshot(ICollectionPersister persister) { Dictionary clonedMap = new Dictionary(WrappedMap.Count); @@ -108,6 +115,13 @@ public override void BeforeInitialize(ICollectionPersister persister, int antici WrappedMap = (IDictionary)persister.CollectionType.Instantiate(anticipatedSize); } + public override void ApplyQueuedOperations() + { + var queueOperation = (AbstractMapQueueOperationTracker) QueueOperationTracker; + queueOperation?.ApplyChanges(WrappedMap); + QueueOperationTracker = null; + } + public override bool Empty { get { return (WrappedMap.Count == 0); } @@ -216,35 +230,16 @@ public override object GetSnapshotElement(object entry, int i) return sn[((KeyValuePair)entry).Key]; } - public override bool Equals(object other) - { - var that = other as IDictionary; - if (that == null) - { - return false; - } - Read(); - return CollectionHelper.DictionaryEquals(WrappedMap, that); - } - - public override int GetHashCode() - { - Read(); - return WrappedMap.GetHashCode(); - } - public override bool EntryExists(object entry, int i) { return WrappedMap.ContainsKey(((KeyValuePair)entry).Key); } - #region IDictionary Members public bool ContainsKey(TKey key) { - bool? exists = ReadIndexExistence(key); - return !exists.HasValue ? WrappedMap.ContainsKey(key) : exists.Value; + return ReadKeyExistence(key) ?? WrappedMap.ContainsKey(key); } public void Add(TKey key, TValue value) @@ -255,13 +250,20 @@ public void Add(TKey key, TValue value) } if (PutQueueEnabled) { - object old = ReadElementByIndex(key); - if (old != Unknown) + var found = TryReadElementByKey(key, out _, out _); + if (found.HasValue) { - QueueOperation(new PutDelayedOperation(this, key, value, old == NotFound ? null : old)); + if (found.Value) + { + throw new ArgumentException("An item with the same key has already been added."); // Mimic dictionary behavior + } + + QueueAddElementByKey(key, value); + return; } } + Initialize(true); WrappedMap.Add(key, value); Dirty(); @@ -269,8 +271,10 @@ public void Add(TKey key, TValue value) public bool Remove(TKey key) { - object old = PutQueueEnabled ? ReadElementByIndex(key) : Unknown; - if (old == Unknown) // queue is not enabled for 'puts', or element not found + var oldValue = default(TValue); + var existsInDb = default(bool?); + var found = PutQueueEnabled ? TryReadElementByKey(key, out oldValue, out existsInDb) : null; + if (!found.HasValue) // queue is not enabled for 'puts' or collection was initialized { Initialize(true); bool contained = WrappedMap.Remove(key); @@ -281,67 +285,71 @@ public bool Remove(TKey key) return contained; } - QueueOperation(new RemoveDelayedOperation(this, key, old == NotFound ? null : old)); - return true; + return QueueRemoveElementByKey(key, oldValue, existsInDb); } - public bool TryGetValue(TKey key, out TValue value) { - object result = ReadElementByIndex(key); - if (result == Unknown) + var found = TryReadElementByKey(key, out value, out _); + if (!found.HasValue) // collection was initialized { return WrappedMap.TryGetValue(key, out value); } - if(result == NotFound) + + if (found.Value) { - value = default(TValue); - return false; + return true; } - value = (TValue)result; - return true; + + value = default(TValue); + return false; } public TValue this[TKey key] { get { - object result = ReadElementByIndex(key); - if (result == Unknown) + var found = TryReadElementByKey(key, out var value, out _); + if (!found.HasValue) // collection was initialized { return WrappedMap[key]; } - if (result == NotFound) + + if (!found.Value) { throw new KeyNotFoundException(); } - return (TValue) result; + + return value; } set { // NH Note: the assignment in NET work like the put method in JAVA (mean assign or add) if (PutQueueEnabled) { - object old = ReadElementByIndex(key); - if (old != Unknown) + var found = TryReadElementByKey(key, out var oldValue, out var existsInDb); + if (found.HasValue) { - QueueOperation(new PutDelayedOperation(this, key, value, old == NotFound ? null : old)); + QueueSetElementByKey(key, value, oldValue, existsInDb); + return; } } + Initialize(true); - TValue tempObject; - WrappedMap.TryGetValue(key, out tempObject); - WrappedMap[key] = value; - TValue old2 = tempObject; - // would be better to use the element-type to determine - // whether the old and the new are equal here; the problem being - // we do not necessarily have access to the element type in all - // cases - if (!ReferenceEquals(value, old2)) + if (!WrappedMap.TryGetValue(key, out var old)) { + WrappedMap.Add(key, value); Dirty(); } + else + { + WrappedMap[key] = value; + if (!EqualityComparer.Default.Equals(value, old)) + { + Dirty(); + } + } } } @@ -362,6 +370,16 @@ public ICollection Values } } + IEnumerable IReadOnlyDictionary.Keys + { + get { return Keys; } + } + + IEnumerable IReadOnlyDictionary.Values + { + get { return Values; } + } + #endregion #region ICollection> Members @@ -375,7 +393,7 @@ public void Clear() { if (ClearQueueEnabled) { - QueueOperation(new ClearDelayedOperation(this)); + QueueClearCollection(); } else { @@ -390,7 +408,7 @@ public void Clear() public bool Contains(KeyValuePair item) { - bool? exists = ReadIndexExistence(item.Key); + bool? exists = ReadKeyExistence(item.Key); if (!exists.HasValue) { return WrappedMap.Contains(item); @@ -485,6 +503,8 @@ IEnumerator IEnumerable.GetEnumerator() #region DelayedOperations + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class ClearDelayedOperation : IDelayedOperation { private readonly PersistentGenericMap _enclosingInstance; @@ -510,6 +530,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class PutDelayedOperation : IDelayedOperation { private readonly PersistentGenericMap _enclosingInstance; @@ -541,6 +563,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class RemoveDelayedOperation : IDelayedOperation { private readonly PersistentGenericMap _enclosingInstance; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs index 812f7c26ce5..4e138bb22fa 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using NHibernate.Collection.Generic.SetHelpers; +using NHibernate.Collection.Trackers; using NHibernate.DebugHelpers; using NHibernate.Engine; using NHibernate.Linq; @@ -21,7 +22,7 @@ namespace NHibernate.Collection.Generic /// [Serializable] [DebuggerTypeProxy(typeof(CollectionProxy<>))] - public partial class PersistentGenericSet : AbstractPersistentCollection, ISet, IQueryable + public partial class PersistentGenericSet : AbstractPersistentCollection, ISet, IReadOnlyCollection, IQueryable { /// /// The that NHibernate is wrapping. @@ -38,14 +39,13 @@ public partial class PersistentGenericSet : AbstractPersistentCollection, ISe /// process. /// [NonSerialized] - private IList _tempList; + private List _tempList; // needed for serialization public PersistentGenericSet() { } - /// /// Constructor matching super. /// Instantiates a lazy set (the underlying set is un-initialized). @@ -74,6 +74,12 @@ public PersistentGenericSet(ISessionImplementor session, ISet original) IsDirectlyAccessible = true; } + internal override AbstractQueueOperationTracker CreateQueueOperationTracker() + { + var entry = Session.PersistenceContext.GetCollectionEntry(this); + return new SetQueueOperationTracker(entry.LoadedPersister); + } + public override bool RowUpdatePossible { get { return false; } @@ -104,13 +110,12 @@ public override ICollection GetOrphans(object snapshot, string entityName) public override bool EqualsSnapshot(ICollectionPersister persister) { var elementType = persister.ElementType; - var snapshot = (ISetSnapshot)GetSnapshot(); + var snapshot = (SetSnapShot)GetSnapshot(); if (((ICollection)snapshot).Count != WrappedSet.Count) { return false; } - foreach (T obj in WrappedSet) { T oldValue; @@ -131,6 +136,13 @@ public override void BeforeInitialize(ICollectionPersister persister, int antici WrappedSet = (ISet)persister.CollectionType.Instantiate(anticipatedSize); } + public override void ApplyQueuedOperations() + { + var queueOperation = (SetQueueOperationTracker) QueueOperationTracker; + queueOperation?.ApplyChanges(WrappedSet); + QueueOperationTracker = null; + } + /// /// Initializes this PersistentSet from the cached values. /// @@ -219,12 +231,11 @@ public override object Disassemble(ICollectionPersister persister) public override IEnumerable GetDeletes(ICollectionPersister persister, bool indexIsFormula) { IType elementType = persister.ElementType; - var sn = (ISetSnapshot)GetSnapshot(); + var sn = (SetSnapShot)GetSnapshot(); var deletes = new List(((ICollection)sn).Count); deletes.AddRange(sn.Where(obj => !WrappedSet.Contains(obj))); - foreach (var obj in WrappedSet) { T oldValue; @@ -237,7 +248,7 @@ public override IEnumerable GetDeletes(ICollectionPersister persister, bool inde public override bool NeedsInserting(object entry, int i, IType elemType) { - var sn = (ISetSnapshot)GetSnapshot(); + var sn = (SetSnapShot)GetSnapshot(); T oldKey; // note that it might be better to iterate the snapshot but this is safe, @@ -266,23 +277,6 @@ public override object GetSnapshotElement(object entry, int i) throw new NotSupportedException("Sets don't support updating by element"); } - public override bool Equals(object other) - { - var that = other as ISet; - if (that == null) - { - return false; - } - Read(); - return WrappedSet.SequenceEqual(that); - } - - public override int GetHashCode() - { - Read(); - return WrappedSet.GetHashCode(); - } - public override bool EntryExists(object entry, int i) { return true; @@ -295,34 +289,36 @@ public override bool IsWrapper(object collection) #region ISet Members - public bool Contains(T item) { - bool? exists = ReadElementExistence(item); - return exists == null ? WrappedSet.Contains(item) : exists.Value; + return ReadElementExistence(item, out _) ?? WrappedSet.Contains(item); } - public bool Add(T o) { - bool? exists = IsOperationQueueEnabled ? ReadElementExistence(o) : null; - if (!exists.HasValue) + // Skip checking the element existence in the database if we know that the element + // is transient, the mapped class does not override Equals method and the operation queue is enabled + if (WasInitialized || !IsOperationQueueEnabled || !CanSkipElementExistenceCheck(o)) { - Initialize(true); - if (WrappedSet.Add(o)) + var exists = IsOperationQueueEnabled ? ReadElementExistence(o, out _) : null; + if (!exists.HasValue) { - Dirty(); - return true; + Initialize(true); + if (WrappedSet.Add(o)) + { + Dirty(); + return true; + } + return false; } - return false; - } - if (exists.Value) - { - return false; + if (exists.Value) + { + return false; + } } - QueueOperation(new SimpleAddDelayedOperation(this, o)); - return true; + + return QueueAddElement(o); } public void UnionWith(IEnumerable other) @@ -425,7 +421,8 @@ public bool SetEquals(IEnumerable other) public bool Remove(T o) { - bool? exists = PutQueueEnabled ? ReadElementExistence(o) : null; + var existsInDb = default(bool?); + var exists = PutQueueEnabled ? ReadElementExistence(o, out existsInDb) : null; if (!exists.HasValue) { Initialize(true); @@ -439,9 +436,11 @@ public bool Remove(T o) if (exists.Value) { - QueueOperation(new SimpleRemoveDelayedOperation(this, o)); + QueueRemoveExistingElement(o, existsInDb); + return true; } + return false; } @@ -449,7 +448,7 @@ public void Clear() { if (ClearQueueEnabled) { - QueueOperation(new ClearDelayedOperation(this)); + QueueClearCollection(); } else { @@ -536,6 +535,8 @@ public IEnumerator GetEnumerator() #region DelayedOperations + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class ClearDelayedOperation : IDelayedOperation { private readonly PersistentGenericSet _enclosingInstance; @@ -561,6 +562,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class SimpleAddDelayedOperation : IDelayedOperation { private readonly PersistentGenericSet _enclosingInstance; @@ -588,6 +591,8 @@ public void Operate() } } + // Since v5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] protected sealed class SimpleRemoveDelayedOperation : IDelayedOperation { private readonly PersistentGenericSet _enclosingInstance; @@ -617,4 +622,4 @@ public void Operate() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Collection/Generic/SetHelpers/ISetSnapshot.cs b/src/NHibernate/Collection/Generic/SetHelpers/ISetSnapshot.cs deleted file mode 100644 index 3b86f42f41a..00000000000 --- a/src/NHibernate/Collection/Generic/SetHelpers/ISetSnapshot.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace NHibernate.Collection.Generic.SetHelpers -{ - internal interface ISetSnapshot : ICollection, ICollection - { - bool TryGetValue(T element, out T value); - } -} \ No newline at end of file diff --git a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs index 70492c45532..eb29c6b6fd5 100644 --- a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs +++ b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs @@ -1,104 +1,180 @@ using System; using System.Collections; using System.Collections.Generic; +#if NETCOREAPP2_0 +using System.Runtime.Serialization; +using System.Threading; +#endif namespace NHibernate.Collection.Generic.SetHelpers { +#if NETFX || NETSTANDARD2_0 + // TODO 6.0: Consider removing this class in case we upgrade to .NET 4.7.2 and NET Standard 2.1, + // which have HashSet.TryGetValue [Serializable] - internal class SetSnapShot : ISetSnapshot + internal class SetSnapShot : ICollection, IReadOnlyCollection, ICollection { - private readonly List _elements; - public SetSnapShot() - { - _elements = new List(); - } + private readonly Dictionary _values; + private bool _hasNull; public SetSnapShot(int capacity) { - _elements = new List(capacity); + _values = new Dictionary(capacity); } public SetSnapShot(IEnumerable collection) { - _elements = new List(collection); + _values = new Dictionary(); + foreach (var item in collection) + { + if (item == null) + { + _hasNull = true; + } + else + { + _values.Add(item, item); + } + } } - public IEnumerator GetEnumerator() + public bool TryGetValue(T equalValue, out T actualValue) { - return _elements.GetEnumerator(); + if (equalValue != null) + { + return _values.TryGetValue(equalValue, out actualValue); + } + + actualValue = default(T); + return _hasNull; } - IEnumerator IEnumerable.GetEnumerator() + public IEnumerator GetEnumerator() { - return GetEnumerator(); + if (_hasNull) + { + yield return default(T); + } + + foreach (var item in _values.Keys) + { + yield return item; + } } public void Add(T item) { - _elements.Add(item); + if (item == null) + { + _hasNull = true; + return; + } + + _values.Add(item, item); } public void Clear() { - throw new InvalidOperationException(); + _values.Clear(); + _hasNull = false; } public bool Contains(T item) { - return _elements.Contains(item); + return item == null ? _hasNull : _values.ContainsKey(item); } public void CopyTo(T[] array, int arrayIndex) { - _elements.CopyTo(array, arrayIndex); + if (_hasNull) + array[arrayIndex] = default(T); + _values.Keys.CopyTo(array, arrayIndex + (_hasNull ? 1 : 0)); } public bool Remove(T item) { - throw new InvalidOperationException(); + if (item != null) + { + return _values.Remove(item); + } + + if (!_hasNull) + { + return false; + } + + _hasNull = false; + return true; } - public void CopyTo(Array array, int index) + IEnumerator IEnumerable.GetEnumerator() { - ((ICollection)_elements).CopyTo(array, index); + return GetEnumerator(); } - int ICollection.Count + void ICollection.CopyTo(Array array, int index) { - get { return _elements.Count; } + if (!(array is T[] typedArray)) + { + throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array)); + } + + CopyTo(typedArray, index); } - public object SyncRoot + public int Count => _values.Count + (_hasNull ? 1 : 0); + + public bool IsReadOnly => ((ICollection>) _values).IsReadOnly; + + public object SyncRoot => ((ICollection) _values).SyncRoot; + + public bool IsSynchronized => ((ICollection) _values).IsSynchronized; + } +#endif + +#if NETCOREAPP2_0 + [Serializable] + internal class SetSnapShot : HashSet, ICollection + { + [NonSerialized] + private object _syncRoot; + + public SetSnapShot(int capacity) : base(capacity) { - get { return ((ICollection)_elements).SyncRoot; } } - public bool IsSynchronized + public SetSnapShot(IEnumerable collection) : base(collection) { - get { return ((ICollection)_elements).IsSynchronized; } } - int ICollection.Count + protected SetSnapShot(SerializationInfo info, StreamingContext context) : base(info, context) { - get { return _elements.Count; } } - public bool IsReadOnly + void ICollection.CopyTo(Array array, int index) { - get { return ((ICollection)_elements).IsReadOnly; } + if (!(array is T[] typedArray)) + { + throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array)); + } + + CopyTo(typedArray, index); } - public bool TryGetValue(T element, out T value) + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot { - var idx = _elements.IndexOf(element); - if (idx >= 0) + get { - value = _elements[idx]; - return true; - } + if (_syncRoot == null) + { + Interlocked.CompareExchange(ref _syncRoot, new object(), null); + } - value = default(T); - return false; + return _syncRoot; + } } } -} \ No newline at end of file +#endif +} diff --git a/src/NHibernate/Collection/ILazyInitializedCollection.cs b/src/NHibernate/Collection/ILazyInitializedCollection.cs index 768c0702c75..6423a5241fc 100644 --- a/src/NHibernate/Collection/ILazyInitializedCollection.cs +++ b/src/NHibernate/Collection/ILazyInitializedCollection.cs @@ -22,6 +22,5 @@ public partial interface ILazyInitializedCollection /// Force immediate initialization. /// void ForceInitialization(); - } } diff --git a/src/NHibernate/Collection/IPersistentCollection.cs b/src/NHibernate/Collection/IPersistentCollection.cs index 2021eceff12..cae02391957 100644 --- a/src/NHibernate/Collection/IPersistentCollection.cs +++ b/src/NHibernate/Collection/IPersistentCollection.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Collection.Generic; using NHibernate.Engine; using NHibernate.Loader; @@ -304,6 +306,11 @@ public partial interface IPersistentCollection /// Get the "queued" orphans ICollection GetQueuedOrphans(string entityName); + // Since 5.3 + /// Get the "queued" orphans + [Obsolete("This method has no more usages and will be removed in a future version")] + Task GetQueuedOrphansAsync(string entityName, CancellationToken cancellationToken); + /// /// Clear the dirty flag, after flushing changes /// to the database. @@ -337,6 +344,21 @@ public partial interface IPersistentCollection /// that have been orphaned. /// ICollection GetOrphans(object snapshot, string entityName); + + //Since 5.3 + /// + /// Get all "orphaned" elements + /// + /// The snapshot of the collection. + /// The persistent class whose objects + /// the collection is expected to contain. + /// A cancellation token that can be used to cancel the work + /// + /// An that contains all of the elements + /// that have been orphaned. + /// + [Obsolete("This method has no more usages and will be removed in a future version")] + Task GetOrphansAsync(object snapshot, string entityName, CancellationToken cancellationToken); } // 6.0 TODO: merge into IPersistentCollection diff --git a/src/NHibernate/Collection/PersistentArrayHolder.cs b/src/NHibernate/Collection/PersistentArrayHolder.cs index 60739da6e96..242d7dac51a 100644 --- a/src/NHibernate/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Collection/PersistentArrayHolder.cs @@ -94,14 +94,7 @@ public override bool IsSnapshotEmpty(object snapshot) public override ICollection GetOrphans(object snapshot, string entityName) { - object[] sn = (object[]) snapshot; - object[] arr = (object[]) array; - List result = new List(sn); - for (int i = 0; i < sn.Length; i++) - { - IdentityRemove(result, arr[i], entityName, Session); - } - return result; + return GetOrphans((object[]) snapshot, (object[]) array, entityName, Session); } public override bool IsWrapper(object collection) @@ -318,4 +311,4 @@ IEnumerator IEnumerable.GetEnumerator() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Collection/Trackers/AbstractCollectionQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/AbstractCollectionQueueOperationTracker.cs new file mode 100644 index 00000000000..f4ad28b8bf0 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/AbstractCollectionQueueOperationTracker.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; + +namespace NHibernate.Collection.Trackers +{ + /// + internal abstract class AbstractCollectionQueueOperationTracker : AbstractQueueOperationTracker + { + /// + /// A method that is called when an element is added to the collection. + /// + /// The element to add. + /// True whether the element was successfully added to the queue, false otherwise + public abstract bool AddElement(T element); + + /// + /// A method that is called when an existing element is removed from the collection. + /// + /// The element to remove. + /// Whether the element exists in the database. + public abstract void RemoveExistingElement(T element, bool? existsInDb); + + /// + /// Checks whether the element exists in the queue. + /// + /// The element to check. + /// True whether the element exists in the queue, false otherwise. + public abstract bool ContainsElement(T element); + + /// + /// Checks whether the element is queued for removal. + /// + /// The element to check. + /// True whether the element is queued for removal, false otherwise. + public abstract bool IsElementQueuedForDelete(T element); + + #region Indexed operations + + /// + /// A method that is called when an element is removed by its index from the collection. + /// + /// The index of the element. + /// The element to remove. + public abstract void RemoveElementAtIndex(int index, T element); + + /// + /// A method that is called when an element is added at a specific index of the collection. + /// + /// The index to put the element. + /// The element to add. + public abstract void AddElementAtIndex(int index, T element); + + /// + /// A method that is called when an element is set at a specific index of the collection. + /// + /// The index to set the new element. + /// The element to set. + /// The element that currently occupies the . + public abstract void SetElementAtIndex(int index, T element, T oldElement); + + /// + /// Tries to retrieve the element by a specific index of the collection. + /// + /// The index to put the element. + /// The output variable for the element. + /// True whether the element was found, false otherwise. + public abstract bool TryGetElementAtIndex(int index, out T element); + + /// + /// Gets the element index where it currently lies in the database by taking into the consideration the queued operations. + /// + /// The effective index that will be when all operations would be flushed. + /// The element index in the database or -1 if the index represents a transient element. + public abstract int? GetDatabaseElementIndex(int index); + + #endregion + } + + internal abstract class AbstractCollectionQueueOperationTracker : AbstractCollectionQueueOperationTracker + where TCollection : ICollection + { + /// + /// Applies all the queued changes to the loaded collection. + /// + /// The loaded collection. + public abstract void ApplyChanges(TCollection loadedCollection); + } +} diff --git a/src/NHibernate/Collection/Trackers/AbstractMapQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/AbstractMapQueueOperationTracker.cs new file mode 100644 index 00000000000..70e9701bfaa --- /dev/null +++ b/src/NHibernate/Collection/Trackers/AbstractMapQueueOperationTracker.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; + +namespace NHibernate.Collection.Trackers +{ + /// + internal abstract class AbstractMapQueueOperationTracker : AbstractQueueOperationTracker + { + /// + /// Tries to retrieve a queued element by its key. + /// + /// The element key. + /// The output variable for the element. + /// True whether the element was found, false otherwise. + public abstract bool TryGetElementByKey(TKey elementKey, out TValue element); + + /// + /// Checks whether the key exist in the queue. + /// + /// The key to check. + /// True whether it exists, false otherwise. + public abstract bool ContainsKey(TKey key); + + /// + /// A method that is called when the map method is called. + /// + /// The key to add. + /// The element to add + public abstract void AddElementByKey(TKey elementKey, TValue element); + + /// + /// A method that is called when the map is set. + /// + /// The key to set. + /// The element to set. + /// The element that currently occupies the . + /// Whether the element exists in the database. + public abstract void SetElementByKey(TKey elementKey, TValue element, TValue oldElement, bool? existsInDb); + + /// + /// A method that is called when the map is called. + /// + /// The key to remove. + /// The element that currently occupies the . + /// Whether the element exists in the database. + /// True whether the key was successfully removed from the queue. + public abstract bool RemoveElementByKey(TKey elementKey, TValue oldElement, bool? existsInDb); + + /// + /// Checks whether the element key is queued for removal. + /// + /// The element key to check. + /// True whether the element key is queued for removal, false otherwise. + public abstract bool IsElementKeyQueuedForDelete(TKey elementKey); + + /// + /// Applies all the queued changes to the loaded map. + /// + /// The loaded map. + public abstract void ApplyChanges(IDictionary loadedMap); + } +} diff --git a/src/NHibernate/Collection/Trackers/AbstractQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/AbstractQueueOperationTracker.cs new file mode 100644 index 00000000000..cb503503422 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/AbstractQueueOperationTracker.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Collection.Trackers +{ + /// + /// A tracker that is able to track changes that are done to an uninitialized collection. + /// + internal abstract class AbstractQueueOperationTracker + { + internal static HashSet IndexOperations = new HashSet + { + nameof(AbstractCollectionQueueOperationTracker.RemoveElementAtIndex), + nameof(AbstractCollectionQueueOperationTracker.AddElementAtIndex), + nameof(AbstractCollectionQueueOperationTracker.SetElementAtIndex), + nameof(AbstractCollectionQueueOperationTracker.TryGetElementAtIndex) + }; + + /// + /// The number of elements that the collection have in the database. + /// + public virtual int? DatabaseCollectionSize { get; protected internal set; } + + /// + /// Whether the Clear operation was performed on the uninitialized collection. + /// + public virtual bool Cleared { get; protected set; } + + /// + /// Returns the current size of the queue that can be negative when there are more removed than added elements. + /// + /// The queue size. + public abstract int GetQueueSize(); + + /// + /// Returns the current size of the collection by taking into the consideration the queued operations. + /// + /// The current collection size. + public int GetCollectionSize() + { + if (Cleared) + { + return GetQueueSize(); + } + + if (!DatabaseCollectionSize.HasValue) + { + throw new InvalidOperationException($"{nameof(DatabaseCollectionSize)} is not set"); + } + + return DatabaseCollectionSize.Value + GetQueueSize(); + } + + /// + /// Checks whether the database collection size is required for the given operation. + /// + /// The operation name to check. + /// True whether the database collection size is required, false otherwise. + public virtual bool RequiresDatabaseCollectionSize(string operationName) => false; + + /// + /// Checks whether flushing is required for the given operation. + /// + /// The operation name to check. + /// True whether flushing is required, false otherwise. + public virtual bool RequiresFlushing(string operationName) => false; + + /// + /// A method that will be called once the flushing is done. + /// + public abstract void AfterFlushing(); + + /// + /// A method that will be called before an operation. + /// + /// The operation that will be executed. + public virtual void BeforeOperation(string operationName) { } + + /// + /// A method that will be called when a Clear operation is performed on the collection. + /// + public virtual void ClearCollection() + { + Cleared = true; + } + + /// + /// Returns an of elements that were added into the collection. + /// + /// An of added elements. + public abstract IEnumerable GetAddedElements(); + + /// + /// Returns an of orphan elements of the collection. + /// + /// An of orphan elements. + public abstract IEnumerable GetOrphans(); + + /// + /// Checks whether a write operation was performed. + /// + /// True whether a write operation was performed, false otherwise. + public abstract bool HasChanges(); + } +} diff --git a/src/NHibernate/Collection/Trackers/BagQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/BagQueueOperationTracker.cs new file mode 100644 index 00000000000..5b9db6ac08c --- /dev/null +++ b/src/NHibernate/Collection/Trackers/BagQueueOperationTracker.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + /// + internal class BagQueueOperationTracker : CollectionQueueOperationTracker> + { + public BagQueueOperationTracker(ICollectionPersister collectionPersister) : base(collectionPersister) + { + } + + public override void AfterFlushing() + { + // We have to reset the current database collection size in case an element + // was added multiple times + DatabaseCollectionSize = null; + base.AfterFlushing(); + } + + /// + public override void ApplyChanges(IList loadedCollection) + { + if (Cleared) + { + loadedCollection.Clear(); + } + else if (RemovalQueue != null) + { + foreach (var toRemove in RemovalQueue) + { + loadedCollection.Remove(toRemove); + } + } + + if (Queue != null) + { + foreach (var toAdd in Queue) + { + // NH Different behavior for NH-739. A "bag" mapped as a bidirectional one-to-many of an entity with an + // id generator causing it to be inserted on flush must not replay the addition after initialization, + // if the entity was previously saved. In that case, the entity save has inserted it in database with + // its association to the bag, without causing a full flush. So for the bag, the operation is still + // pending, but in database it is already done. On initialization, the bag thus already receives the + // entity in its loaded list, and it should not be added again. + // Since a one-to-many bag is actually a set, we can simply check if the entity is already in the loaded + // state, and discard it if yes. (It also relies on the bag not having pending removes, which is the + // case, since it only handles delayed additions and clears.) + // Since this condition happens with transient instances added in the bag then saved, ReferenceEquals + // is enough to match them. + // This solution is a workaround, the root cause is not fixed. The root cause is the insertion on save + // done without caring for pending operations of one-to-many collections. This root cause could be fixed + // by triggering a full flush there before the insert (currently it just flushes pending inserts), or + // maybe by flushing just the dirty one-to-many non-initialized collections (but this can be tricky). + // (It is also likely one-to-many lists have a similar issue, but nothing is done yet for them. And + // their case is more complex due to having to care for the indexes and to handle many more delayed + // operation kinds.) + if (CollectionPersister.IsOneToMany && loadedCollection.Any(l => ReferenceEquals(l, toAdd))) + { + continue; + } + + loadedCollection.Add(toAdd); + } + } + } + } +} diff --git a/src/NHibernate/Collection/Trackers/ClearedListQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/ClearedListQueueOperationTracker.cs new file mode 100644 index 00000000000..d6549eb5a28 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/ClearedListQueueOperationTracker.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + /// + internal class ClearedListQueueOperationTracker : AbstractCollectionQueueOperationTracker> + { + protected IList Collection; + private readonly ICollectionPersister _collectionPersister; + + public ClearedListQueueOperationTracker(ICollectionPersister collectionPersister) + { + _collectionPersister = collectionPersister; + } + + /// + public override bool AddElement(T element) + { + GetOrCreateQueue().Add(element); + return true; + } + + /// + public override void RemoveExistingElement(T element, bool? existsInDb) + { + Collection?.Remove(element); + } + + /// + public override bool Cleared + { + get => true; + protected set => throw new NotSupportedException(); + } + + public override void AfterFlushing() + { + throw new NotSupportedException(); + } + + /// + public override void ClearCollection() + { + Collection?.Clear(); + } + + /// + public override bool ContainsElement(T element) + { + return Collection?.Contains(element) ?? false; + } + + /// + public override int GetQueueSize() + { + return Collection?.Count ?? 0; + } + + /// + public override bool IsElementQueuedForDelete(T element) + { + return false; + } + + /// + public override bool HasChanges() + { + return true; + } + + /// + public override void ApplyChanges(IList loadedCollection) + { + loadedCollection.Clear(); + if (Collection != null) + { + foreach (var toAdd in Collection) + { + loadedCollection.Add(toAdd); + } + } + } + + /// + public override int? GetDatabaseElementIndex(int index) + { + return null; + } + + /// + public override bool TryGetElementAtIndex(int index, out T element) + { + if (Collection == null || index < 0 || index >= Collection.Count) + { + element = default(T); + return false; + } + + element = Collection[index]; + return true; + } + + /// + public override void RemoveElementAtIndex(int index, T element) + { + Collection?.RemoveAt(index); + } + + /// + public override void AddElementAtIndex(int index, T element) + { + GetOrCreateQueue().Insert(index, element); + } + + /// + public override void SetElementAtIndex(int index, T element, T oldElement) + { + GetOrCreateQueue()[index] = element; + } + + /// + public override IEnumerable GetAddedElements() + { + return Collection ?? (IEnumerable) Enumerable.Empty(); + } + + /// + public override IEnumerable GetOrphans() + { + return Enumerable.Empty(); + } + + private IList GetOrCreateQueue() + { + return Collection ?? (Collection = (IList) _collectionPersister.CollectionType.Instantiate(-1)); + } + } +} diff --git a/src/NHibernate/Collection/Trackers/CollectionQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/CollectionQueueOperationTracker.cs new file mode 100644 index 00000000000..a342e01abc4 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/CollectionQueueOperationTracker.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + internal abstract class CollectionQueueOperationTracker : AbstractCollectionQueueOperationTracker + where TCollection : ICollection + { + protected TCollection Queue; + protected ISet RemovalQueue; + protected int QueueSize; + protected ISet Orphans; + protected readonly ICollectionPersister CollectionPersister; + + protected CollectionQueueOperationTracker(ICollectionPersister collectionPersister) + { + CollectionPersister = collectionPersister; + } + + /// + public override bool AddElement(T element) + { + InitializeQueue(); + RemovalQueue?.Remove(element); // We have to remove the element from the removal list when the element is readded + return Add(element); + } + + protected virtual bool Add(T element) + { + Queue.Add(element); + if (!Cleared) + { + QueueSize++; + } + + return true; + } + + /// + public override void AfterFlushing() + { + Queue?.Clear(); + QueueSize = 0; + Orphans?.Clear(); + RemovalQueue?.Clear(); + } + + /// + public override bool HasChanges() + { + return Cleared || RemovalQueue?.Count > 0 || Queue?.Count > 0; + } + + /// + public override void ApplyChanges(TCollection loadedCollection) + { + if (Cleared) + { + loadedCollection.Clear(); + } + else if (RemovalQueue != null) + { + foreach (var toRemove in RemovalQueue) + { + loadedCollection.Remove(toRemove); + } + } + + if (Queue != null) + { + foreach (var toAdd in Queue) + { + loadedCollection.Add(toAdd); + } + } + } + + /// + public override bool RequiresFlushing(string operationName) + { + return IndexOperations.Contains(operationName); + } + + /// + public override void RemoveExistingElement(T element, bool? existsInDb) + { + if (!Cleared) + { + // As this method is called only when the element exists in the queue or in database, we can safely reduce the queue size + QueueSize--; + } + + if (existsInDb == true) + { + GetOrCreateOrphansSet().Add(element); + } + + GetOrCreateRemovalQueue().Add(element); + Queue?.Remove(element); + } + + /// + public override void ClearCollection() + { + AfterFlushing(); + Cleared = true; + } + + /// + public override bool ContainsElement(T element) + { + return Queue?.Contains(element) ?? false; + } + + /// + public override int GetQueueSize() + { + return Cleared ? (Queue?.Count ?? 0) : QueueSize; + } + + /// + public override bool IsElementQueuedForDelete(T element) + { + return RemovalQueue?.Contains(element) ?? false; + } + + /// + public override IEnumerable GetAddedElements() + { + return (IEnumerable) Queue ?? Enumerable.Empty(); + } + + /// + public override IEnumerable GetOrphans() + { + return (IEnumerable) Orphans ?? Enumerable.Empty(); + } + + /// + public override void RemoveElementAtIndex(int index, T element) + { + throw new NotSupportedException(); + } + + /// + public override void AddElementAtIndex(int index, T element) + { + throw new NotSupportedException(); + } + + /// + public override void SetElementAtIndex(int index, T element, T oldElement) + { + throw new NotSupportedException(); + } + + /// + public override bool TryGetElementAtIndex(int index, out T element) + { + throw new NotSupportedException(); + } + + /// + public override int? GetDatabaseElementIndex(int index) + { + throw new NotSupportedException(); + } + + protected ISet GetOrCreateRemovalQueue() + { + return RemovalQueue ?? (RemovalQueue = new HashSet()); + } + + protected ISet GetOrCreateOrphansSet() + { + return Orphans ?? (Orphans = new HashSet()); + } + + private void InitializeQueue() + { + if (Queue == null) + { + Queue = (TCollection) CollectionPersister.CollectionType.Instantiate(-1); + } + } + } +} diff --git a/src/NHibernate/Collection/Trackers/IndexedListQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/IndexedListQueueOperationTracker.cs new file mode 100644 index 00000000000..32c21f1a303 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/IndexedListQueueOperationTracker.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace NHibernate.Collection.Trackers +{ + /// + internal class IndexedListQueueOperationTracker : AbstractCollectionQueueOperationTracker> + { + private static readonly KeyValuePairComparer Comparer = new KeyValuePairComparer(); + private class KeyValuePairComparer : IComparer> + { + public int Compare(KeyValuePair x, KeyValuePair y) + { + if (x.Key > y.Key) + { + return 1; + } + + if (x.Key < y.Key) + { + return -1; + } + + return 0; + } + } + + private List> _queue; // Sorted queue by index + private int _queueSize; + private List> _removedDbIndexes; // Sorted removed db indexes + private ISet _removalQueue; + + /// + public override bool RequiresFlushing(string operationName) + { + // For remove operation we don't know the index of the removal item + return nameof(RemoveExistingElement) == operationName; + } + + /// + public override bool RequiresDatabaseCollectionSize(string operationName) + { + return IndexOperations.Contains(operationName) || nameof(AddElement) == operationName; + } + + /// + public override bool ContainsElement(T element) + { + if (_queue == null) + { + return false; + } + + foreach (var pair in _queue) + { + if (EqualityComparer.Default.Equals(pair.Value, element)) + { + return true; + } + } + + return false; + } + + /// + public override bool IsElementQueuedForDelete(T element) + { + return _removalQueue?.Contains(element) ?? false; + } + + /// + public override bool HasChanges() + { + return _removedDbIndexes?.Count > 0 || _queue?.Count > 0; + } + + /// + public override void ApplyChanges(IList loadedCollection) + { + if (_removedDbIndexes != null) + { + for (var i = _removedDbIndexes.Count - 1; i >= 0; i--) + { + loadedCollection.RemoveAt(_removedDbIndexes[i].Key); + } + } + + if (_queue != null) + { + for (var i = 0; i < _queue.Count; i++) + { + var pair = _queue[i]; + loadedCollection.Insert(pair.Key, pair.Value); + } + } + } + + /// + public override bool AddElement(T element) + { + AddElementAtIndex(GetCollectionSize(), element); + return true; + } + + /// + public override void RemoveExistingElement(T element, bool? existsInDb) + { + throw new NotSupportedException(); + } + + /// + public override void ClearCollection() + { + throw new NotSupportedException(); + } + + /// + public override void AfterFlushing() + { + throw new NotSupportedException(); + } + + /// + public override IEnumerable GetAddedElements() + { + return _queue?.Select(o => o.Value) ?? (IEnumerable) Enumerable.Empty(); + } + + /// + public override IEnumerable GetOrphans() + { + return _removedDbIndexes?.Select(o => o.Value) ?? (IEnumerable) Enumerable.Empty(); + } + + /// + public override bool Cleared + { + get => false; + protected set => throw new NotSupportedException(); + } + + /// + public override int GetQueueSize() + { + return _queueSize; + } + + /// + public override int? GetDatabaseElementIndex(int index) + { + if (!DatabaseCollectionSize.HasValue) + { + throw new InvalidOperationException($"{nameof(DatabaseCollectionSize)} is not set"); + } + + var dbIndex = index; + + if (_queue != null) + { + foreach (var pair in _queue) + { + if (pair.Key == index) + { + return null; // The given index represents a queued value + } + + if (pair.Key > index) + { + break; + } + + dbIndex--; + } + } + + if (_removedDbIndexes != null) + { + var i = GetQueueIndex(ref _removedDbIndexes, new KeyValuePair(dbIndex, default(T)), true); + if (i < 0) + { + dbIndex += (Math.Abs(i) - 1); + } + else + { + i++; + dbIndex += i; + + // Increase until we find a non removed db index + for (; i < _removedDbIndexes.Count; i++) + { + if (_removedDbIndexes[i].Key != dbIndex) + { + break; + } + + dbIndex++; + } + } + } + + return dbIndex >= DatabaseCollectionSize + ? (int?) null + : dbIndex; + } + + /// + public override bool TryGetElementAtIndex(int index, out T element) + { + var pair = new KeyValuePair(index, default(T)); + var keyIndex = GetQueueIndex(ref _queue, pair, true); + if (keyIndex < 0) + { + element = default(T); + return false; + } + + element = _queue[keyIndex].Value; + return true; + } + + /// + public override void RemoveElementAtIndex(int index, T element) + { + if (index >= GetCollectionSize()) + { + // Mimic list behavior + throw new ArgumentOutOfRangeException( + nameof(index), + "Index was out of range. Must be non-negative and less than the size of the collection."); + } + + var pair = new KeyValuePair(index, default(T)); + var i = GetQueueIndex(ref _queue, pair, true); + if (i < 0) + { + i = Math.Abs(i) - 1; + var dbIndex = GetDatabaseElementIndex(index); + if (!dbIndex.HasValue) + { + throw new InvalidOperationException($"Invalid {nameof(index)} parameter."); + } + + var removedPair = new KeyValuePair(dbIndex.Value, element); + var j = GetQueueIndex(ref _removedDbIndexes, removedPair, true); + if (j < 0) + { + _removedDbIndexes.Insert(Math.Abs(j) - 1, removedPair); + } + } + else + { + _queue.RemoveAt(i); + } + + _queueSize--; + GetOrCreateRemovalQueue().Add(element); + + // We have to decrement all higher indexes by 1 + for (; i < _queue.Count; i++) + { + var currentPair = _queue[i]; + _queue[i] = new KeyValuePair(currentPair.Key - 1, currentPair.Value); + } + } + + /// + public override void AddElementAtIndex(int index, T element) + { + if (index > GetCollectionSize()) + { + // Mimic list behavior + throw new ArgumentOutOfRangeException(nameof(index), "Index must be within the bounds of the List."); + } + + var pair = new KeyValuePair(index, element); + var i = GetQueueIndex(ref _queue, pair, false); + _queue.Insert(i, pair); + i++; + _queueSize++; + _removalQueue?.Remove(element); + + // We have to increment all higher indexes by 1 + for (; i < _queue.Count; i++) + { + var currentPair = _queue[i]; + _queue[i] = new KeyValuePair(currentPair.Key + 1, currentPair.Value); + } + } + + /// + public override void SetElementAtIndex(int index, T element, T oldElement) + { + if (index >= GetCollectionSize()) + { + // Mimic list behavior + throw new ArgumentOutOfRangeException( + nameof(index), + "Index was out of range. Must be non-negative and less than the size of the collection."); + } + + var pair = new KeyValuePair(index, element); + + // NOTE: If we would need to have only items that are removed in the removal queue we would need + // to check the _queue if the item already exists as the item can be re-added to a different index. + // As this scenario should rarely happen and we also know that ContainsElement will be called before + // IsElementQueuedForDelete, we skip the check here to have a better performance. + GetOrCreateRemovalQueue().Add(oldElement); + _removalQueue.Remove(element); + + var i = GetQueueIndex(ref _queue, pair, true); + if (i < 0) + { + var dbIndex = GetDatabaseElementIndex(index); + if (!dbIndex.HasValue) + { + throw new InvalidOperationException($"Invalid {nameof(index)} parameter."); + } + + var removedPair = new KeyValuePair(dbIndex.Value, oldElement); + var j = GetQueueIndex(ref _removedDbIndexes, removedPair, true); + if (j < 0) + { + _removedDbIndexes.Insert(Math.Abs(j) - 1, removedPair); + } + + _queue.Insert(Math.Abs(i) - 1, pair); + } + else + { + _queue[i] = pair; + } + } + + private static int GetQueueIndex(ref List> queue, KeyValuePair pair, bool rawResult) + { + return GetQueueIndex(ref queue, pair, rawResult, Comparer); + } + + private static int GetQueueIndex(ref List queue, TType item, bool rawResult, IComparer comparer) + { + if (queue == null) + { + queue = new List(); + } + + var i = queue.BinarySearch(item, comparer); + if (i < 0) + { + return rawResult ? i : Math.Abs(i) - 1; + } + + return i; + } + + private ISet GetOrCreateRemovalQueue() + { + return _removalQueue ?? (_removalQueue = new HashSet()); + } + } +} diff --git a/src/NHibernate/Collection/Trackers/ListQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/ListQueueOperationTracker.cs new file mode 100644 index 00000000000..caa09999712 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/ListQueueOperationTracker.cs @@ -0,0 +1,184 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + /// + internal class ListQueueOperationTracker : AbstractCollectionQueueOperationTracker> + { + private readonly ICollectionPersister _collectionPersister; + private AbstractCollectionQueueOperationTracker> _tracker; + + public ListQueueOperationTracker(ICollectionPersister collectionPersister) + { + _collectionPersister = collectionPersister; + } + + /// + public override int? DatabaseCollectionSize + { + get => _tracker?.DatabaseCollectionSize ?? base.DatabaseCollectionSize; + protected internal set + { + base.DatabaseCollectionSize = value; + if (_tracker != null) + { + _tracker.DatabaseCollectionSize = value; + } + } + } + + /// + public override bool RequiresDatabaseCollectionSize(string operationName) + { + return _tracker?.RequiresDatabaseCollectionSize(operationName) ?? IndexOperations.Contains(operationName); + } + + /// + public override int GetQueueSize() + { + return _tracker?.GetQueueSize() ?? 0; + } + + /// + public override void ClearCollection() + { + _tracker = new ClearedListQueueOperationTracker(_collectionPersister); + Cleared = true; + } + + /// + public override IEnumerable GetAddedElements() + { + return _tracker?.GetAddedElements() ?? Enumerable.Empty(); + } + + /// + public override IEnumerable GetOrphans() + { + return _tracker?.GetOrphans() ?? Enumerable.Empty(); + } + + /// + public override bool RequiresFlushing(string operationName) + { + return _tracker?.RequiresFlushing(operationName) == true; + } + + /// + public override void AfterFlushing() + { + // We have to reset the current database collection size in case an element + // was added multiple times + DatabaseCollectionSize = null; + _tracker = null; + } + + public override void BeforeOperation(string operationName) + { + if (_tracker != null) + { + return; + } + + _tracker = IndexOperations.Contains(operationName) + ? (AbstractCollectionQueueOperationTracker>) new IndexedListQueueOperationTracker + { + DatabaseCollectionSize = DatabaseCollectionSize + } + : new NonIndexedListQueueOperationTracker(_collectionPersister) + { + DatabaseCollectionSize = DatabaseCollectionSize + }; + } + + /// + public override bool AddElement(T element) + { + return GetOrCreateStrategy().AddElement(element); + } + + /// + public override void RemoveExistingElement(T element, bool? existsInDb) + { + GetOrCreateStrategy().RemoveExistingElement(element, existsInDb); + } + + /// + public override bool ContainsElement(T element) + { + return _tracker?.ContainsElement(element) == true; + } + + /// + public override bool IsElementQueuedForDelete(T element) + { + return _tracker?.IsElementQueuedForDelete(element) == true; + } + + /// + public override void RemoveElementAtIndex(int index, T element) + { + GetOrCreateIndexedStrategy().RemoveElementAtIndex(index, element); + } + + /// + public override void AddElementAtIndex(int index, T element) + { + GetOrCreateIndexedStrategy().AddElementAtIndex(index, element); + } + + /// + public override void SetElementAtIndex(int index, T element, T oldElement) + { + GetOrCreateIndexedStrategy().SetElementAtIndex(index, element, oldElement); + } + + /// + public override bool TryGetElementAtIndex(int index, out T element) + { + if (_tracker != null) + { + return _tracker.TryGetElementAtIndex(index, out element); + } + + element = default(T); + return false; + } + + /// + public override int? GetDatabaseElementIndex(int index) + { + return _tracker?.GetDatabaseElementIndex(index) ?? index; + } + + /// + public override void ApplyChanges(IList loadedCollection) + { + _tracker?.ApplyChanges(loadedCollection); + } + + private AbstractCollectionQueueOperationTracker> GetOrCreateStrategy() + { + return _tracker ?? (_tracker = new NonIndexedListQueueOperationTracker(_collectionPersister) + { + DatabaseCollectionSize = DatabaseCollectionSize + }); + } + + private AbstractCollectionQueueOperationTracker> GetOrCreateIndexedStrategy() + { + return _tracker ?? (_tracker = new IndexedListQueueOperationTracker + { + DatabaseCollectionSize = DatabaseCollectionSize + }); + } + + public override bool HasChanges() + { + return _tracker?.HasChanges() ?? false; + } + } +} diff --git a/src/NHibernate/Collection/Trackers/MapQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/MapQueueOperationTracker.cs new file mode 100644 index 00000000000..effe2c5fa86 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/MapQueueOperationTracker.cs @@ -0,0 +1,200 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + /// + internal class MapQueueOperationTracker : AbstractMapQueueOperationTracker + { + private readonly ICollectionPersister _collectionPersister; + private IDictionary _queue; + private IDictionary _orphanMap; + private ISet _removalQueue; + private int _queueSize; + + public MapQueueOperationTracker(ICollectionPersister collectionPersister) + { + _collectionPersister = collectionPersister; + } + + public override void AfterFlushing() + { + _queue?.Clear(); + _removalQueue?.Clear(); + _queueSize = 0; + _orphanMap = null; + } + + /// + public override void ClearCollection() + { + AfterFlushing(); + Cleared = true; + } + + /// + public override IEnumerable GetAddedElements() + { + return _queue?.Values ?? (IEnumerable) Enumerable.Empty(); + } + + /// + public override IEnumerable GetOrphans() + { + return _orphanMap?.Values ?? (IEnumerable) Enumerable.Empty(); + } + + /// + public override void AddElementByKey(TKey elementKey, TValue element) + { + _removalQueue?.Remove(elementKey); // We have to remove the key from the removal list when the element is re-added + GetOrCreateQueue().Add(elementKey, element); + if (!Cleared) + { + _queueSize++; + } + } + + /// + public override bool ContainsKey(TKey key) + { + return _queue?.ContainsKey(key) == true; + } + + /// + public override int GetQueueSize() + { + return Cleared ? (_queue?.Count ?? 0) : _queueSize; + } + + /// + public override bool IsElementKeyQueuedForDelete(TKey elementKey) + { + return _removalQueue?.Contains(elementKey) ?? false; + } + + /// + public override bool RemoveElementByKey(TKey elementKey, TValue oldElement, bool? existsInDb) + { + if (Cleared) + { + return _queue?.Remove(elementKey) ?? false; + } + + // We can have the following scenarios: + // 1. remove a key that exists in db and is not in the queue and removal queue (decrease queue size) + // 2. remove a key that exists in db and is in the queue (decrease queue size) + // 3. remove a key that does not exist in db and is not in the queue (don't decrease queue size) + // 4. remove a key that does not exist in db and is in the queue (decrease queue size) + // 5. remove a key that exists in db and is in the removal queue (don't decrease queue size) + + // If the key is not present in the database and in the queue, do nothing + if (existsInDb == false && _queue?.ContainsKey(elementKey) != true) + { + return false; + } + + if (existsInDb == true) + { + GetOrCreateOrphanMap()[elementKey] = oldElement; + } + + // We don't want to have non database keys in the removal queue + if (_queue?.Remove(elementKey) == true | + (_orphanMap?.ContainsKey(elementKey) == true && GetOrCreateRemovalQueue().Add(elementKey))) + { + _queueSize--; + return true; + } + + return false; + } + + /// + public override void SetElementByKey(TKey elementKey, TValue element, TValue oldElement, bool? existsInDb) + { + if (Cleared) + { + GetOrCreateQueue()[elementKey] = element; + return; + } + + // We can have the following scenarios: + // 1. set a key that exists in db and is not in the queue (don't increase queue size) + // 2. set a key that exists in db and is in the queue (don't increase queue size) + // 3. set a key that does not exist in db and is not in the queue (increase queue size) + // 4. set a key that does not exist in db and is in the queue (don't increase queue size) + // 5. set a key that exists in db and is in the removal queue (increase queue size) + if ((existsInDb == false && _queue?.ContainsKey(elementKey) != true) || _removalQueue?.Remove(elementKey) == true) + { + _queueSize++; + } + + if (existsInDb == true) + { + GetOrCreateOrphanMap()[elementKey] = oldElement; + } + + GetOrCreateQueue()[elementKey] = element; + } + + /// + public override bool TryGetElementByKey(TKey elementKey, out TValue element) + { + if (_queue == null) + { + element = default(TValue); + return false; + } + + return _queue.TryGetValue(elementKey, out element); + } + + /// + public override bool HasChanges() + { + return Cleared || _removalQueue?.Count > 0 || _queue?.Count > 0; + } + + /// + public override void ApplyChanges(IDictionary loadedMap) + { + if (Cleared) + { + loadedMap.Clear(); + } + else if (_removalQueue != null) + { + foreach (var toRemove in _removalQueue) + { + loadedMap.Remove(toRemove); + } + } + + if (_queue != null) + { + foreach (var toAdd in _queue) + { + loadedMap[toAdd.Key] = toAdd.Value; + } + } + } + + private IDictionary GetOrCreateQueue() + { + return _queue ?? (_queue = (IDictionary) _collectionPersister.CollectionType.Instantiate(-1)); + } + + private IDictionary GetOrCreateOrphanMap() + { + return _orphanMap ?? (_orphanMap = (IDictionary) _collectionPersister.CollectionType.Instantiate(-1)); + } + + private ISet GetOrCreateRemovalQueue() + { + return _removalQueue ?? (_removalQueue = new HashSet()); + } + } +} diff --git a/src/NHibernate/Collection/Trackers/NonIndexedListQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/NonIndexedListQueueOperationTracker.cs new file mode 100644 index 00000000000..f8a61fe5234 --- /dev/null +++ b/src/NHibernate/Collection/Trackers/NonIndexedListQueueOperationTracker.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + /// + internal class NonIndexedListQueueOperationTracker : CollectionQueueOperationTracker> + { + public NonIndexedListQueueOperationTracker(ICollectionPersister collectionPersister) : base(collectionPersister) + { + } + } +} diff --git a/src/NHibernate/Collection/Trackers/SetQueueOperationTracker.cs b/src/NHibernate/Collection/Trackers/SetQueueOperationTracker.cs new file mode 100644 index 00000000000..0753068520a --- /dev/null +++ b/src/NHibernate/Collection/Trackers/SetQueueOperationTracker.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using NHibernate.Persister.Collection; + +namespace NHibernate.Collection.Trackers +{ + /// + /// A tracker that is able to track changes that are done to an uninitialized set. + /// + internal class SetQueueOperationTracker : CollectionQueueOperationTracker> + { + public SetQueueOperationTracker(ICollectionPersister collectionPersister) : base(collectionPersister) + { + } + + protected override bool Add(T element) + { + if (!Queue.Add(element)) + { + return false; + } + + if (!Cleared) + { + QueueSize++; + } + + return true; + } + } +} diff --git a/src/NHibernate/Connection/ConnectionProvider.cs b/src/NHibernate/Connection/ConnectionProvider.cs index 0d908f1f672..19293b5ca0e 100644 --- a/src/NHibernate/Connection/ConnectionProvider.cs +++ b/src/NHibernate/Connection/ConnectionProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Configuration; using System.Data.Common; using NHibernate.Driver; @@ -25,6 +24,9 @@ public abstract partial class ConnectionProvider : IConnectionProvider /// The to clean up. public virtual void CloseConnection(DbConnection conn) { + if (conn == null) + throw new ArgumentNullException(nameof(conn)); + log.Debug("Closing connection"); try { @@ -65,22 +67,15 @@ public virtual void Configure(IDictionary settings) } /// - /// Get the .NET 2.0 named connection string + /// Get a named connection string, if configured. /// /// /// Thrown when a was found - /// in the settings parameter but could not be found in the app.config + /// in the settings parameter but could not be found in the app.config. /// protected virtual string GetNamedConnectionString(IDictionary settings) { - string connStringName; - if(!settings.TryGetValue(Environment.ConnectionStringName, out connStringName)) - return null; - - ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[connStringName]; - if (connectionStringSettings == null) - throw new HibernateException(string.Format("Could not find named connection string {0}", connStringName)); - return connectionStringSettings.ConnectionString; + return Environment.GetNamedConnectionString(settings); } /// @@ -123,7 +118,8 @@ protected virtual void ConfigureDriver(IDictionary settings) /// The for the /// to connect to the database. /// - protected virtual string ConnectionString + //TODO 6.0: Make public + protected internal virtual string ConnectionString { get { return connString; } } @@ -143,7 +139,20 @@ public IDriver Driver /// Get an open . /// /// An open . - public abstract DbConnection GetConnection(); + public virtual DbConnection GetConnection() + { + return GetConnection(ConnectionString); + } + + //TODO 6.0: Make abstract + /// + /// Gets an open for given connectionString + /// + /// An open . + public virtual DbConnection GetConnection(string connectionString) + { + throw new NotImplementedException("This method must be overriden."); + } #region IDisposable Members @@ -198,13 +207,13 @@ protected virtual void Dispose(bool isDisposing) if (isDisposing) { log.Debug("Disposing of ConnectionProvider."); + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } #endregion diff --git a/src/NHibernate/Connection/DriverConnectionProvider.cs b/src/NHibernate/Connection/DriverConnectionProvider.cs index e3c43f4c0d0..94503d0ca75 100644 --- a/src/NHibernate/Connection/DriverConnectionProvider.cs +++ b/src/NHibernate/Connection/DriverConnectionProvider.cs @@ -30,13 +30,13 @@ public override void CloseConnection(DbConnection conn) /// /// If there is any problem creating or opening the . /// - public override DbConnection GetConnection() + public override DbConnection GetConnection(string connectionString) { log.Debug("Obtaining DbConnection from Driver"); var conn = Driver.CreateConnection(); try { - conn.ConnectionString = ConnectionString; + conn.ConnectionString = connectionString; conn.Open(); } catch (Exception) @@ -48,4 +48,4 @@ public override DbConnection GetConnection() return conn; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Connection/IConnectionAccess.cs b/src/NHibernate/Connection/IConnectionAccess.cs new file mode 100644 index 00000000000..d76f6f5194f --- /dev/null +++ b/src/NHibernate/Connection/IConnectionAccess.cs @@ -0,0 +1,32 @@ +using System.Data.Common; + +namespace NHibernate.Connection +{ + //JdbcConnectionAccess.java in hibernate + /// + /// Provides centralized access to connections. Centralized to hide the complexity of accounting for contextual + /// (multi-tenant) versus non-contextual access. + /// Implementation must be serializable + /// + public partial interface IConnectionAccess + { + /// + /// The connection string of the database connection. + /// + string ConnectionString { get; } + + //ObtainConnection in hibernate + /// + /// Gets the database connection. + /// + /// The database connection. + DbConnection GetConnection(); + + //ReleaseConnection in hibernate + /// + /// Closes the given database connection. + /// + /// The connection to close. + void CloseConnection(DbConnection connection); + } +} diff --git a/src/NHibernate/Connection/IConnectionProvider.cs b/src/NHibernate/Connection/IConnectionProvider.cs index 2657c4090a7..d40943915ea 100644 --- a/src/NHibernate/Connection/IConnectionProvider.cs +++ b/src/NHibernate/Connection/IConnectionProvider.cs @@ -2,9 +2,25 @@ using System.Collections.Generic; using System.Data.Common; using NHibernate.Driver; +using NHibernate.Util; namespace NHibernate.Connection { + //6.0 TODO: Merge into IConnectionProvider + public static partial class ConnectionProviderExtensions + { + internal static DbConnection GetConnection(this IConnectionProvider connectionProvider, string connectionString) + { + return ReflectHelper.CastOrThrow(connectionProvider, "open connection by connectionString").GetConnection(connectionString); + } + + //6.0 TODO: Expose as ConnectionString property + public static string GetConnectionString(this IConnectionProvider connectionProvider) + { + return ReflectHelper.CastOrThrow(connectionProvider, "retrieve connectionString").ConnectionString; + } + } + /// /// A strategy for obtaining ADO.NET . /// @@ -42,4 +58,4 @@ public partial interface IConnectionProvider : IDisposable /// An open . DbConnection GetConnection(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs b/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs index 076b3b0b3ad..ff15a643252 100644 --- a/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs +++ b/src/NHibernate/Connection/UserSuppliedConnectionProvider.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data.Common; - namespace NHibernate.Connection { /// @@ -41,7 +40,7 @@ public override void CloseConnection(DbConnection conn) /// Thrown when this method is called. User is responsible for creating /// s. /// - public override DbConnection GetConnection() + public override DbConnection GetConnection(string connectionString) { throw new InvalidOperationException("The user must provide an ADO.NET connection - NHibernate is not creating it."); } @@ -61,4 +60,4 @@ public override void Configure(IDictionary settings) ConfigureDriver(settings); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/ConnectionReleaseMode.cs b/src/NHibernate/ConnectionReleaseMode.cs index a7d828fe9e6..3f9967ab807 100644 --- a/src/NHibernate/ConnectionReleaseMode.cs +++ b/src/NHibernate/ConnectionReleaseMode.cs @@ -33,7 +33,7 @@ public static string ToString(ConnectionReleaseMode value) case ConnectionReleaseMode.AfterStatement: return "after_statement"; case ConnectionReleaseMode.AfterTransaction: - return "after_transaction" ; + return "after_transaction"; case ConnectionReleaseMode.OnClose: return "on_close"; default: @@ -41,4 +41,4 @@ public static string ToString(ConnectionReleaseMode value) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Context/ThreadLocalSessionContext.cs b/src/NHibernate/Context/ThreadLocalSessionContext.cs index 52735306949..296b9931088 100644 --- a/src/NHibernate/Context/ThreadLocalSessionContext.cs +++ b/src/NHibernate/Context/ThreadLocalSessionContext.cs @@ -40,7 +40,6 @@ public partial class ThreadLocalSessionContext : ICurrentSessionContext protected readonly ISessionFactoryImplementor factory; - public ThreadLocalSessionContext(ISessionFactoryImplementor factory) { this.factory = factory; @@ -78,16 +77,15 @@ private static void CleanupAnyOrphanedSession(ISessionFactory factory) try { - if (orphan.Transaction != null && orphan.Transaction.IsActive) + try + { + var transaction = orphan.GetCurrentTransaction(); + if (transaction?.IsActive == true) + transaction.Rollback(); + } + catch (Exception ex) { - try - { - orphan.Transaction.Rollback(); - } - catch (Exception ex) - { - log.Debug(ex, "Unable to rollback transaction for orphaned session"); - } + log.Debug(ex, "Unable to rollback transaction for orphaned session"); } orphan.Close(); } diff --git a/src/NHibernate/Criterion/AbstractCriterion.cs b/src/NHibernate/Criterion/AbstractCriterion.cs index 6ce1d41247c..98ffe003802 100644 --- a/src/NHibernate/Criterion/AbstractCriterion.cs +++ b/src/NHibernate/Criterion/AbstractCriterion.cs @@ -41,7 +41,6 @@ public abstract class AbstractCriterion : ICriterion /// /// An array of IProjection used by the Expression. public abstract IProjection[] GetProjections(); - #endregion @@ -57,7 +56,6 @@ public abstract class AbstractCriterion : ICriterion return new OrExpression(lhs, rhs); } - public static AbstractCriterion operator &(AbstractCriterion lhs, AbstractEmptinessExpression rhs) { return new AndExpression(lhs, rhs); @@ -68,7 +66,6 @@ public abstract class AbstractCriterion : ICriterion return new OrExpression(lhs, rhs); } - public static AbstractCriterion operator !(AbstractCriterion crit) { return new NotExpression(crit); diff --git a/src/NHibernate/Criterion/AbstractEmptinessExpression.cs b/src/NHibernate/Criterion/AbstractEmptinessExpression.cs index bd0f5ed45fd..88631c595b9 100644 --- a/src/NHibernate/Criterion/AbstractEmptinessExpression.cs +++ b/src/NHibernate/Criterion/AbstractEmptinessExpression.cs @@ -16,7 +16,6 @@ public abstract class AbstractEmptinessExpression : AbstractCriterion protected abstract bool ExcludeEmpty { get; } - protected AbstractEmptinessExpression(string propertyName) { this.propertyName = propertyName; @@ -62,7 +61,6 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri return new SqlString(new object[] {ExcludeEmpty ? "exists" : "not exists", innerSelect.ToString()}); } - protected IQueryableCollection GetQueryableCollection(string entityName, string actualPropertyName, ISessionFactoryImplementor factory) { diff --git a/src/NHibernate/Criterion/AggregateProjection.cs b/src/NHibernate/Criterion/AggregateProjection.cs index ce2a9f9804e..81b15d18c87 100644 --- a/src/NHibernate/Criterion/AggregateProjection.cs +++ b/src/NHibernate/Criterion/AggregateProjection.cs @@ -2,7 +2,6 @@ using NHibernate.SqlCommand; using NHibernate.Engine; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Criterion { @@ -49,19 +48,12 @@ public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuer public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery) { - if (projection == null) - { - return new SqlString(aggregate, "(", criteriaQuery.GetColumn(criteria, propertyName), ") as y", loc.ToString(), "_"); - } + var column = CriterionUtil.GetColumnNameAsSqlStringPart(propertyName, projection, criteriaQuery, criteria); return new SqlString( aggregate, "(", - SqlStringHelper.RemoveAsAliasesFromSql( - projection.ToSqlString( - criteria, - loc, - criteriaQuery)), + column, ") as y", loc.ToString(), "_"); diff --git a/src/NHibernate/Criterion/AvgProjection.cs b/src/NHibernate/Criterion/AvgProjection.cs index 9420547ae33..98afd60e52a 100644 --- a/src/NHibernate/Criterion/AvgProjection.cs +++ b/src/NHibernate/Criterion/AvgProjection.cs @@ -23,14 +23,14 @@ public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuer sql.Add("cast("); if (projection != null) { - sql.Add(SqlStringHelper.RemoveAsAliasesFromSql(projection.ToSqlString(criteria, loc, criteriaQuery))); + sql.AddObject(CriterionUtil.GetColumnNameAsSqlStringPart(projection, criteriaQuery, criteria)); } else { sql.Add(criteriaQuery.GetColumn(criteria, propertyName)); } sql.Add(" as ").Add(sqlType).Add(")"); - sql.Add(") as ").Add(GetColumnAliases(loc, criteria, criteriaQuery)[0]); + sql.Add(") as ").Add(GetColumnAlias(loc)); return sql.ToSqlString(); } diff --git a/src/NHibernate/Criterion/BetweenExpression.cs b/src/NHibernate/Criterion/BetweenExpression.cs index c07114d1f96..eb0ec058279 100644 --- a/src/NHibernate/Criterion/BetweenExpression.cs +++ b/src/NHibernate/Criterion/BetweenExpression.cs @@ -53,13 +53,13 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri var parametersTypes = GetTypedValues(criteria, criteriaQuery).ToArray(); var lowType = parametersTypes[0]; var highType = parametersTypes[1]; - SqlString[] columnNames = - CriterionUtil.GetColumnNames(_propertyName, _projection, criteriaQuery, criteria); + var columnNames = + CriterionUtil.GetColumnNamesAsSqlStringParts(_propertyName, _projection, criteriaQuery, criteria); if (columnNames.Length == 1) { sqlBuilder - .Add(columnNames[0]) + .AddObject(columnNames[0]) .Add(" between ") .Add(criteriaQuery.NewQueryParameter(lowType).Single()) .Add(" and ") @@ -78,7 +78,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri } andNeeded = true; - sqlBuilder.Add(columnNames[i]) + sqlBuilder.AddObject(columnNames[i]) .Add(" >= ") .Add(lowParameters[i]); } @@ -87,7 +87,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri for (int i = 0; i < columnNames.Length; i++) { sqlBuilder.Add(" AND ") - .Add(columnNames[i]) + .AddObject(columnNames[i]) .Add(" <= ") .Add(highParameters[i]); } diff --git a/src/NHibernate/Criterion/CastProjection.cs b/src/NHibernate/Criterion/CastProjection.cs index 3019ddcbca5..9b1a2d945f1 100644 --- a/src/NHibernate/Criterion/CastProjection.cs +++ b/src/NHibernate/Criterion/CastProjection.cs @@ -37,11 +37,9 @@ public override SqlString ToSqlString(ICriteria criteria, int position, ICriteri throw new QueryException("invalid Hibernate type for CastProjection"); } string sqlType = factory.Dialect.GetCastTypeName(sqlTypeCodes[0]); - int loc = position*GetHashCode(); - SqlString val = projection.ToSqlString(criteria, loc, criteriaQuery); - val = SqlStringHelper.RemoveAsAliasesFromSql(val); + var val = CriterionUtil.GetColumnNameAsSqlStringPart(projection, criteriaQuery, criteria); - return new SqlString("cast( ", val, " as ", sqlType, ") as ", GetColumnAliases(position, criteria, criteriaQuery)[0]); + return new SqlString("cast( ", val, " as ", sqlType, ") as ", GetColumnAlias(position)); } public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Criterion/ConditionalProjection.cs b/src/NHibernate/Criterion/ConditionalProjection.cs index c9f948ad046..1ff3a95a185 100644 --- a/src/NHibernate/Criterion/ConditionalProjection.cs +++ b/src/NHibernate/Criterion/ConditionalProjection.cs @@ -5,7 +5,6 @@ namespace NHibernate.Criterion using Engine; using SqlCommand; using Type; - using Util; [Serializable] public class ConditionalProjection : SimpleProjection @@ -45,12 +44,10 @@ public override bool IsAggregate public override SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { SqlString condition = criterion.ToSqlString(criteria, criteriaQuery); - SqlString ifTrue = whenTrue.ToSqlString(criteria, position + GetHashCode() + 1, criteriaQuery); - ifTrue = SqlStringHelper.RemoveAsAliasesFromSql(ifTrue); - SqlString ifFalse = whenFalse.ToSqlString(criteria, position + GetHashCode() + 2, criteriaQuery); - ifFalse = SqlStringHelper.RemoveAsAliasesFromSql(ifFalse); + var ifTrue = CriterionUtil.GetColumnNameAsSqlStringPart(whenTrue, criteriaQuery, criteria); + var ifFalse = CriterionUtil.GetColumnNameAsSqlStringPart(whenFalse, criteriaQuery, criteria); return new SqlString("(case when ", condition, " then ", ifTrue, " else ", ifFalse, " end) as ", - GetColumnAliases(position, criteria, criteriaQuery)[0]); + GetColumnAlias(position)); } public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) @@ -59,11 +56,11 @@ public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuer IType[] falseTypes = whenFalse.GetTypes(criteria, criteriaQuery); bool areEqual = trueTypes.Length == falseTypes.Length; - if (trueTypes.Length == falseTypes.Length) + if (areEqual) { for (int i = 0; i < trueTypes.Length; i++) { - if(trueTypes[i].Equals(falseTypes[i]) == false) + if(trueTypes[i].ReturnedClass != falseTypes[i].ReturnedClass) { areEqual = false; break; diff --git a/src/NHibernate/Criterion/ConstantProjection.cs b/src/NHibernate/Criterion/ConstantProjection.cs index 3504d633ab6..b012bdf4c67 100644 --- a/src/NHibernate/Criterion/ConstantProjection.cs +++ b/src/NHibernate/Criterion/ConstantProjection.cs @@ -13,7 +13,7 @@ namespace NHibernate.Criterion public class ConstantProjection : SimpleProjection { private readonly object value; - private readonly TypedValue typedValue; + public TypedValue TypedValue { get; } public ConstantProjection(object value) : this(value, NHibernateUtil.GuessType(value.GetType())) { @@ -22,7 +22,7 @@ public ConstantProjection(object value) : this(value, NHibernateUtil.GuessType(v public ConstantProjection(object value, IType type) { this.value = value; - typedValue = new TypedValue(type, this.value); + TypedValue = new TypedValue(type, this.value); } public override bool IsAggregate @@ -43,19 +43,19 @@ public override bool IsGrouped public override SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { return new SqlString( - criteriaQuery.NewQueryParameter(typedValue).Single(), + criteriaQuery.NewQueryParameter(TypedValue).Single(), " as ", - GetColumnAliases(position, criteria, criteriaQuery)[0]); + GetColumnAlias(position)); } public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new IType[] { typedValue.Type }; + return new IType[] { TypedValue.Type }; } public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new TypedValue[] { typedValue }; + return new TypedValue[] { TypedValue }; } } } diff --git a/src/NHibernate/Criterion/CountProjection.cs b/src/NHibernate/Criterion/CountProjection.cs index 6c955d0791b..5d6e5cb4d1d 100644 --- a/src/NHibernate/Criterion/CountProjection.cs +++ b/src/NHibernate/Criterion/CountProjection.cs @@ -35,7 +35,7 @@ public override SqlString ToSqlString(ICriteria criteria, int position, ICriteri } if (projection != null) { - buf.Add(SqlStringHelper.RemoveAsAliasesFromSql(projection.ToSqlString(criteria, position, criteriaQuery))); + buf.AddObject(CriterionUtil.GetColumnNameAsSqlStringPart(projection, criteriaQuery, criteria)); } else { diff --git a/src/NHibernate/Criterion/CriterionUtil.cs b/src/NHibernate/Criterion/CriterionUtil.cs index 38ed711a66f..76385384783 100644 --- a/src/NHibernate/Criterion/CriterionUtil.cs +++ b/src/NHibernate/Criterion/CriterionUtil.cs @@ -5,7 +5,6 @@ namespace NHibernate.Criterion using Engine; using SqlCommand; using Type; - using Util; public static class CriterionUtil { @@ -46,13 +45,58 @@ public static SqlString[] GetColumnNamesForSimpleExpression( internal static SqlString[] GetColumnNamesUsingProjection(IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria) { - SqlString sqlString = projection.ToSqlString(criteria, - criteriaQuery.GetIndexForAlias(), - criteriaQuery); - return new SqlString[] - { - SqlStringHelper.RemoveAsAliasesFromSql(sqlString) - }; + if (projection is IPropertyProjection propertyProjection) + { + return GetColumnNamesUsingPropertyName(criteriaQuery, criteria, propertyProjection.PropertyName); + } + + return GetProjectionColumns(projection, criteriaQuery, criteria); + } + + internal static object[] GetColumnNamesAsSqlStringParts(string propertyName, IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria) + { + if (propertyName != null) + return criteriaQuery.GetColumnsUsingProjection(criteria, propertyName); + + return GetColumnNamesAsSqlStringParts(projection, criteriaQuery, criteria); + } + + internal static object[] GetColumnNamesAsSqlStringParts(IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria) + { + if (projection is IPropertyProjection propertyProjection) + { + return criteriaQuery.GetColumnsUsingProjection(criteria, propertyProjection.PropertyName); + } + + return GetProjectionColumns(projection, criteriaQuery, criteria); + } + + internal static object GetColumnNameAsSqlStringPart(string propertyName, IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria) + { + var columnNames = GetColumnNamesAsSqlStringParts(propertyName, projection, criteriaQuery, criteria); + if (columnNames.Length != 1) + { + throw new QueryException("property or projection does not map to a single column: " + (propertyName ?? projection.ToString())); + } + + return columnNames[0]; + } + + internal static object GetColumnNameAsSqlStringPart(IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria) + { + var columnNames = GetColumnNamesAsSqlStringParts(projection, criteriaQuery, criteria); + if (columnNames.Length != 1) + { + throw new QueryException("property or projection does not map to a single column: " + (projection.ToString())); + } + + return columnNames[0]; + } + + private static SqlString[] GetProjectionColumns(IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria) + { + var sqlString = projection.ToSqlString(criteria, criteriaQuery.GetIndexForAlias(), criteriaQuery); + return new[] {SqlStringHelper.RemoveAsAliasesFromSql(sqlString)}; } private static SqlString[] GetColumnNamesUsingPropertyName(ICriteriaQuery criteriaQuery, ICriteria criteria, string propertyName) @@ -115,7 +159,19 @@ public static TypedValue[] GetTypedValues(ICriteriaQuery criteriaQuery, ICriteri } } return types.ToArray(); + } + + public static TypedValue GetTypedValue( + ICriteriaQuery criteriaQuery, + ICriteria criteria, + IProjection projection, + string propertyName, + object value) + { + if (propertyName != null || (propertyName = (projection as IPropertyProjection)?.PropertyName) != null) + return criteriaQuery.GetTypedValue(criteria, propertyName, value); + return new TypedValue(NHibernateUtil.GuessType(value), value); } } } diff --git a/src/NHibernate/Criterion/Distinct.cs b/src/NHibernate/Criterion/Distinct.cs index 993f403167a..60adf13f4af 100644 --- a/src/NHibernate/Criterion/Distinct.cs +++ b/src/NHibernate/Criterion/Distinct.cs @@ -17,8 +17,7 @@ public Distinct(IProjection proj) public virtual SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { - return new SqlString("distinct ") - .Append(projection.ToSqlString(criteria, position, criteriaQuery)); + return new SqlString("distinct ", projection.ToSqlString(criteria, position, criteriaQuery)); } public virtual SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) @@ -77,4 +76,4 @@ public override string ToString() return "distinct " + projection.ToString(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 814e1a0decd..37750e90597 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using NHibernate.Engine; using NHibernate.Loader; using NHibernate.Loader.Criteria; +using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; using IQueryable = NHibernate.Persister.Entity.IQueryable; @@ -38,10 +40,16 @@ public EntityProjection(System.Type entityType, string entityAlias) } /// - /// Fetch lazy properties + /// Fetch all lazy properties /// public bool FetchLazyProperties { get; set; } + /// + /// Fetch individual lazy properties or property groups + /// Note: To fetch single property it must be mapped with unique fetch group (lazy-group) + /// + public ICollection FetchLazyPropertyGroups { get; set; } + /// /// Lazy load entity /// @@ -63,7 +71,7 @@ public EntityProjection SetLazy(bool lazy = true) } /// - /// Fetch lazy properties + /// Fetch all lazy properties /// public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) { @@ -71,6 +79,17 @@ public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true) return this; } + /// + /// Fetch individual lazy properties or property groups + /// Provide lazy property name and it will be fetched along with properties that belong to the same fetch group (lazy-group) + /// Note: To fetch single property it must be mapped with unique fetch group (lazy-group) + /// + public EntityProjection SetFetchLazyPropertyGroups(params string[] lazyPropertyGroups) + { + FetchLazyPropertyGroups = lazyPropertyGroups; + return this; + } + #endregion Configuration methods #region IProjection implementation @@ -115,7 +134,14 @@ SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQue ? identifierSelectFragment : string.Concat( identifierSelectFragment, - Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties))); + GetPropertySelectFragment())); + } + + private string GetPropertySelectFragment() + { + return FetchLazyProperties + ? Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties) + : Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyPropertyGroups); } SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Criterion/Example.cs b/src/NHibernate/Criterion/Example.cs index c6002b01ea4..c3dd2b40142 100644 --- a/src/NHibernate/Criterion/Example.cs +++ b/src/NHibernate/Criterion/Example.cs @@ -345,7 +345,6 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri return builder.ToSqlString(); } - //note: now that Criterion are adding typed values via ICriteriaQuery.AddUsedTypedValues this function is never called. public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) { diff --git a/src/NHibernate/Criterion/GroupedProjection.cs b/src/NHibernate/Criterion/GroupedProjection.cs index f037160f32e..4bef3f4e716 100644 --- a/src/NHibernate/Criterion/GroupedProjection.cs +++ b/src/NHibernate/Criterion/GroupedProjection.cs @@ -23,6 +23,9 @@ public virtual SqlString ToSqlString(ICriteria criteria, int position, ICriteria public virtual SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { + if (projection is IPropertyProjection propertyProjection) + return new SqlString(string.Join(", ", criteriaQuery.GetColumns(criteria, propertyProjection.PropertyName))); + //This is kind of a hack. The hack is based on the fact that ToGroupSqlString always called after ToSqlString. return SqlStringHelper.RemoveAsAliasesFromSql(renderedProjection); } diff --git a/src/NHibernate/Criterion/ICriteriaQuery.cs b/src/NHibernate/Criterion/ICriteriaQuery.cs index 7113e6676b4..a5714c1623c 100644 --- a/src/NHibernate/Criterion/ICriteriaQuery.cs +++ b/src/NHibernate/Criterion/ICriteriaQuery.cs @@ -1,11 +1,29 @@ using System.Collections.Generic; using NHibernate.Engine; +using NHibernate.Loader.Criteria; using NHibernate.Param; using NHibernate.SqlCommand; using NHibernate.Type; namespace NHibernate.Criterion { + //TODO 6.0: Add to ICriteriaQuery interface + internal static class CriteriaQueryExtensions + { + /// + /// Substitute the SQL aliases in template. + /// + public static SqlString RenderSQLAliases(this ICriteriaQuery criteriaQuery, SqlString sqlTemplate) + { + if (criteriaQuery is CriteriaQueryTranslator translator) + { + return translator.RenderSQLAliases(sqlTemplate); + } + + return sqlTemplate; + } + } + /// /// An instance of is passed to criterion, /// order and projection instances when actually compiling and @@ -80,4 +98,4 @@ public interface ICriteriaQuery Parameter CreateSkipParameter(int value); Parameter CreateTakeParameter(int value); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/IdentifierEqExpression.cs b/src/NHibernate/Criterion/IdentifierEqExpression.cs index 6654aba0ff9..f75d400f12d 100644 --- a/src/NHibernate/Criterion/IdentifierEqExpression.cs +++ b/src/NHibernate/Criterion/IdentifierEqExpression.cs @@ -69,8 +69,7 @@ private void AddValueOrProjection(Parameter[] parameters, int paramIndex, ICrite } else { - SqlString sql = _projection.ToSqlString(criteria, GetHashCode(), criteriaQuery); - result.Add(SqlStringHelper.RemoveAsAliasesFromSql(sql)); + result.AddObject(CriterionUtil.GetColumnNameAsSqlStringPart(_projection, criteriaQuery, criteria)); } } diff --git a/src/NHibernate/Criterion/IdentifierProjection.cs b/src/NHibernate/Criterion/IdentifierProjection.cs index 52ae042ddc6..0a0c857e975 100644 --- a/src/NHibernate/Criterion/IdentifierProjection.cs +++ b/src/NHibernate/Criterion/IdentifierProjection.cs @@ -1,12 +1,12 @@ using System; +using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Criterion { [Serializable] - public class IdentifierProjection : SimpleProjection + public class IdentifierProjection : SimpleProjection, IPropertyProjection { private bool grouped; @@ -21,7 +21,7 @@ protected internal IdentifierProjection() : this(false) public override string ToString() { - return "id"; + return PropertyName; } public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) @@ -66,5 +66,7 @@ public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery cr } return new SqlString(string.Join(",", criteriaQuery.GetIdentifierColumns(criteria))); } + + public string PropertyName => EntityPersister.EntityID; } } diff --git a/src/NHibernate/Criterion/InExpression.cs b/src/NHibernate/Criterion/InExpression.cs index 72d97994d71..54409ba9f31 100644 --- a/src/NHibernate/Criterion/InExpression.cs +++ b/src/NHibernate/Criterion/InExpression.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using NHibernate.Engine; @@ -13,9 +12,6 @@ namespace NHibernate.Criterion /// An that constrains the property /// to a specified list of values. /// - /// - /// InExpression - should only be used with a Single Value column - no multicolumn properties... - /// [Serializable] public class InExpression : AbstractCriterion { @@ -62,41 +58,52 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri return new SqlString("1=0"); } - //TODO: add default capacity - SqlStringBuilder result = new SqlStringBuilder(); - SqlString[] columnNames = - CriterionUtil.GetColumnNames(_propertyName, _projection, criteriaQuery, criteria); - - // Generate SqlString of the form: - // columnName1 in (values) and columnName2 in (values) and ... - Parameter[] parameters = GetParameterTypedValues(criteria, criteriaQuery).SelectMany(t => criteriaQuery.NewQueryParameter(t)).ToArray(); + SqlString[] columns = CriterionUtil.GetColumnNames(_propertyName, _projection, criteriaQuery, criteria); - for (int columnIndex = 0; columnIndex < columnNames.Length; columnIndex++) + var list = new List(columns.Length * Values.Length); + foreach (var typedValue in GetParameterTypedValues(criteria, criteriaQuery)) { - SqlString columnName = columnNames[columnIndex]; - - if (columnIndex > 0) - { - result.Add(" and "); - } + //Must be executed after CriterionUtil.GetColumnNames (as it might add _projection parameters to criteria) + list.AddRange(criteriaQuery.NewQueryParameter(typedValue)); + } - result - .Add(columnName) - .Add(" in ("); + var bogusParam = Parameter.Placeholder; - for (int i = 0; i < _values.Length; i++) - { - if (i > 0) - { - result.Add(StringHelper.CommaSpace); - } - result.Add(parameters[i]); - } + var sqlString = GetSqlString(criteriaQuery, columns, bogusParam); + sqlString.SubstituteBogusParameters(list, bogusParam); + return sqlString; + } - result.Add(")"); + private SqlString GetSqlString(ICriteriaQuery criteriaQuery, SqlString[] columns, Parameter bogusParam) + { + if (columns.Length <= 1 || criteriaQuery.Factory.Dialect.SupportsRowValueConstructorSyntaxInInList) + { + var wrapInParens = columns.Length > 1; + const string comaSeparator = ", "; + var singleValueParam = SqlStringHelper.Repeat(new SqlString(bogusParam), columns.Length, comaSeparator, wrapInParens); + + var parameters = SqlStringHelper.Repeat(singleValueParam, Values.Length, comaSeparator, wrapInParens: false); + + //single column: col1 in (?, ?) + //multi column: (col1, col2) in ((?, ?), (?, ?)) + return new SqlString( + wrapInParens ? StringHelper.OpenParen : string.Empty, + SqlStringHelper.Join(comaSeparator, columns), + wrapInParens ? StringHelper.ClosedParen : string.Empty, + " in (", + parameters, + ")"); } - return result.ToSqlString(); + //((col1 = ? and col2 = ?) or (col1 = ? and col2 = ?)) + var cols = new SqlString( + " ( ", + SqlStringHelper.Join(new SqlString(" = ", bogusParam, " and "), columns), + "= ", + bogusParam, + " ) "); + cols = SqlStringHelper.Repeat(cols, Values.Length, " or ", wrapInParens: Values.Length > 1); + return cols; } private void AssertPropertyIsNotCollection(ICriteriaQuery criteriaQuery, ICriteria criteria) @@ -122,29 +129,24 @@ private List GetParameterTypedValues(ICriteria criteria, ICriteriaQu { IType type = GetElementType(criteria, criteriaQuery); - if (type.IsComponentType) + if (!type.IsComponentType) { - List list = new List(); - IAbstractComponentType actype = (IAbstractComponentType) type; - IType[] types = actype.Subtypes; + return _values.ToList(v => new TypedValue(type, v, false)); + } - for (int i = 0; i < types.Length; i++) + List list = new List(); + IAbstractComponentType actype = (IAbstractComponentType) type; + var types = actype.Subtypes; + foreach (var value in _values) + { + var propertyValues = value != null ? actype.GetPropertyValues(value) : null; + for (int ti = 0; ti < types.Length; ti++) { - for (int j = 0; j < _values.Length; j++) - { - object subval = _values[j] == null - ? null - : actype.GetPropertyValues(_values[j])[i]; - list.Add(new TypedValue(types[i], subval, false)); - } + list.Add(new TypedValue(types[ti], propertyValues?[ti], false)); } - - return list; - } - else - { - return _values.Select(v => new TypedValue(type, v, false)).ToList(); } + + return list; } /// diff --git a/src/NHibernate/Criterion/InsensitiveLikeExpression.cs b/src/NHibernate/Criterion/InsensitiveLikeExpression.cs index 7a66e3f92e2..e2eb2dab0c7 100644 --- a/src/NHibernate/Criterion/InsensitiveLikeExpression.cs +++ b/src/NHibernate/Criterion/InsensitiveLikeExpression.cs @@ -63,8 +63,8 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri { //TODO: add default capacity SqlStringBuilder sqlBuilder = new SqlStringBuilder(); - SqlString[] columnNames = - CriterionUtil.GetColumnNames(propertyName, projection, criteriaQuery, criteria); + var columnNames = + CriterionUtil.GetColumnNamesAsSqlStringParts(propertyName, projection, criteriaQuery, criteria); if (columnNames.Length != 1) { @@ -73,14 +73,14 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri if (criteriaQuery.Factory.Dialect is PostgreSQLDialect) { - sqlBuilder.Add(columnNames[0]); + sqlBuilder.AddObject(columnNames[0]); sqlBuilder.Add(" ilike "); } else { sqlBuilder.Add(criteriaQuery.Factory.Dialect.LowercaseFunction) .Add("(") - .Add(columnNames[0]) + .AddObject(columnNames[0]) .Add(")") .Add(" like "); } @@ -106,11 +106,7 @@ public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery c public TypedValue GetParameterTypedValue(ICriteria criteria, ICriteriaQuery criteriaQuery) { var matchValue = value.ToString().ToLower(); - if (projection != null) - { - return CriterionUtil.GetTypedValues(criteriaQuery, criteria, projection, null, matchValue).Single(); - } - return criteriaQuery.GetTypedValue(criteria, propertyName, matchValue); + return CriterionUtil.GetTypedValue(criteriaQuery, criteria, projection, propertyName, matchValue); } public override IProjection[] GetProjections() @@ -127,4 +123,4 @@ public override string ToString() return (projection ?? (object)propertyName) + " ilike " + value; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/Junction.cs b/src/NHibernate/Criterion/Junction.cs index 55949a571d0..d7becef1005 100644 --- a/src/NHibernate/Criterion/Junction.cs +++ b/src/NHibernate/Criterion/Junction.cs @@ -16,7 +16,7 @@ namespace NHibernate.Criterion [Serializable] public abstract class Junction : AbstractCriterion { - private readonly IList criteria = new List(); + private readonly List criteria = new List(); /// /// Adds an to the list of s @@ -98,7 +98,6 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri sqlBuilder.Add(this.criteria[this.criteria.Count - 1].ToSqlString(criteria, criteriaQuery)); - sqlBuilder.Add(")"); return sqlBuilder.ToSqlString(); diff --git a/src/NHibernate/Criterion/Lambda/LambdaNaturalIdentifierBuilder.cs b/src/NHibernate/Criterion/Lambda/LambdaNaturalIdentifierBuilder.cs index 781d11b8979..58fccb6d8e1 100644 --- a/src/NHibernate/Criterion/Lambda/LambdaNaturalIdentifierBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/LambdaNaturalIdentifierBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections; using System.Collections.Generic; @@ -25,7 +24,5 @@ public NaturalIdentifier Is(object value) { return naturalIdentifier.Set(propertyName, value); } - } - } diff --git a/src/NHibernate/Criterion/Lambda/LambdaRestrictionBuilder.cs b/src/NHibernate/Criterion/Lambda/LambdaRestrictionBuilder.cs index f1bb38f93df..841ab5892ad 100644 --- a/src/NHibernate/Criterion/Lambda/LambdaRestrictionBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/LambdaRestrictionBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections; using System.Collections.Generic; @@ -9,7 +8,6 @@ namespace NHibernate.Criterion.Lambda { - public class LambdaRestrictionBuilder { public class LambdaBetweenBuilder @@ -167,7 +165,5 @@ public AbstractCriterion IsLike(string value, MatchMode matchMode, char? escapeC { return Process(Restrictions.Like(projection.AsProperty(), value, matchMode, escapeChar)); } - } - } diff --git a/src/NHibernate/Criterion/Lambda/LambdaSubqueryBuilder.cs b/src/NHibernate/Criterion/Lambda/LambdaSubqueryBuilder.cs index 9206e406718..a40c43911c7 100644 --- a/src/NHibernate/Criterion/Lambda/LambdaSubqueryBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/LambdaSubqueryBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,7 +7,6 @@ namespace NHibernate.Criterion.Lambda { - public class LambdaSubqueryBuilder { private string propertyName; @@ -189,7 +187,5 @@ public AbstractCriterion NotIn(QueryOver detachedCriteria) { return CreatePropertyCriterion(Subqueries.PropertyNotIn, Subqueries.NotIn, detachedCriteria); } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverFetchBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverFetchBuilder.cs index c2991047c6e..54dd84bdae4 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverFetchBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverFetchBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -12,27 +11,22 @@ namespace NHibernate.Criterion.Lambda [Obsolete] public class QueryOverFetchBuilder : QueryOverFetchBuilderBase, TRoot, TSubType> { - public QueryOverFetchBuilder(QueryOver root, Expression> path) : base(root, path) { } - } //Since 5.2 [Obsolete] public class IQueryOverFetchBuilder : QueryOverFetchBuilderBase, TRoot, TSubType> { - public IQueryOverFetchBuilder(IQueryOver root, Expression> path) : base(root, path) { } - } //Since 5.2 [Obsolete] public class QueryOverFetchBuilderBase where TReturn : IQueryOver { - protected TReturn root; protected string path; @@ -68,7 +62,5 @@ public TReturn Default return this.root; } } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverJoinBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverJoinBuilder.cs index 3061b46c505..b1cfe32fef7 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverJoinBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverJoinBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,7 +7,6 @@ namespace NHibernate.Criterion.Lambda { - public class QueryOverJoinBuilder : QueryOverJoinBuilderBase, TRoot, TSubType> { public QueryOverJoinBuilder(QueryOver root, JoinType joinType) : base(root, joinType) { } @@ -112,7 +110,6 @@ public QueryOver JoinQueryOver(Expression>> path { return root.JoinQueryOver(path, alias, joinType, withClause); } - } public class IQueryOverJoinBuilder : QueryOverJoinBuilderBase, TRoot, TSubType> @@ -218,12 +215,10 @@ public IQueryOver JoinQueryOver(Expression>> pat { return root.JoinQueryOver(path, alias, joinType, withClause); } - } public class QueryOverJoinBuilderBase where TReturn : IQueryOver { - protected TReturn root; protected JoinType joinType; @@ -302,7 +297,5 @@ public TReturn JoinAlias(Expression>> path, Expression : QueryOverLockBuilderBase, TRoot, TSubType> { - public QueryOverLockBuilder(QueryOver root, Expression> alias) : base(root, alias) { } - } public class IQueryOverLockBuilder : QueryOverLockBuilderBase, TRoot, TSubType> { - public IQueryOverLockBuilder(IQueryOver root, Expression> alias) : base(root, alias) { } - } public class QueryOverLockBuilderBase where TReturn : IQueryOver { - protected TReturn root; protected string alias; @@ -100,7 +93,5 @@ public TReturn Write return this.root; } } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverOrderBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverOrderBuilder.cs index 40a4bc5d5d8..1bc7044a61f 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverOrderBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverOrderBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,10 +7,8 @@ namespace NHibernate.Criterion.Lambda { - public class QueryOverOrderBuilder : QueryOverOrderBuilderBase, TRoot, TSubType> { - public QueryOverOrderBuilder(QueryOver root, Expression> path) : base(root, path) {} @@ -20,12 +17,10 @@ public QueryOverOrderBuilder(QueryOver root, Expression root, ExpressionProcessor.ProjectionInfo projection) : base(root, projection) {} - } public class IQueryOverOrderBuilder : QueryOverOrderBuilderBase, TRoot, TSubType> { - public IQueryOverOrderBuilder(IQueryOver root, Expression> path) : base(root, path) {} @@ -34,12 +29,10 @@ public IQueryOverOrderBuilder(IQueryOver root, Expression root, ExpressionProcessor.ProjectionInfo projection) : base(root, projection) {} - } public class QueryOverOrderBuilderBase where TReturn : IQueryOver { - protected TReturn root; protected LambdaExpression path; protected bool isAlias; @@ -93,7 +86,5 @@ public TReturn Desc return this.root; } } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverProjectionBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverProjectionBuilder.cs index c480ba4f209..cc07e709743 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverProjectionBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverProjectionBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Linq.Expressions; @@ -6,10 +5,8 @@ namespace NHibernate.Criterion.Lambda { - public class QueryOverProjectionBuilder { - private ProjectionList projectionList; private IProjection lastProjection = null; @@ -180,7 +177,7 @@ public QueryOverProjectionBuilder SelectMin(Expression> expressi /// public QueryOverProjectionBuilder Select(Expression> expression) { - PushProjection(ExpressionProcessor.FindMemberProjection(expression.Body).AsProjection()); + PushProjection(Projections.Select(expression)); return this; } @@ -189,7 +186,7 @@ public QueryOverProjectionBuilder Select(Expression> expressi /// public QueryOverProjectionBuilder Select(Expression> expression) { - PushProjection(ExpressionProcessor.FindMemberProjection(expression.Body).AsProjection()); + PushProjection(Projections.Select(expression)); return this; } @@ -216,7 +213,5 @@ public QueryOverProjectionBuilder SelectSum(Expression> expressi PushProjection(Projections.Sum(expression)); return this; } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverRestrictionBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverRestrictionBuilder.cs index a7f71c68c42..79d165bfb59 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverRestrictionBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverRestrictionBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections; using System.Collections.Generic; @@ -9,10 +8,8 @@ namespace NHibernate.Criterion.Lambda { - public class QueryOverRestrictionBuilder : QueryOverRestrictionBuilderBase, TRoot, TSubType> { - public QueryOverRestrictionBuilder(QueryOver root, ExpressionProcessor.ProjectionInfo projection) : base(root, projection) { } @@ -24,12 +21,10 @@ public QueryOverRestrictionBuilder Not return this; } } - } public class IQueryOverRestrictionBuilder : QueryOverRestrictionBuilderBase, TRoot, TSubType> { - public IQueryOverRestrictionBuilder(IQueryOver root, ExpressionProcessor.ProjectionInfo projection) : base(root, projection) { } @@ -41,7 +36,6 @@ public IQueryOverRestrictionBuilder Not return this; } } - } public class QueryOverRestrictionBuilderBase @@ -200,7 +194,5 @@ public TReturn IsLike(string value, MatchMode matchMode, char? escapeChar) { return Add(Restrictions.Like(projection.AsProperty(), value, matchMode, escapeChar)); } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverSubqueryBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverSubqueryBuilder.cs index 978e37db5a9..752eadc3f97 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverSubqueryBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverSubqueryBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,28 +7,22 @@ namespace NHibernate.Criterion.Lambda { - public class QueryOverSubqueryBuilder : QueryOverSubqueryBuilderBase, TRoot, TSubType, QueryOverSubqueryPropertyBuilder> { - public QueryOverSubqueryBuilder(QueryOver root) : base(root) { } - } public class IQueryOverSubqueryBuilder : QueryOverSubqueryBuilderBase, TRoot, TSubType, IQueryOverSubqueryPropertyBuilder> { - public IQueryOverSubqueryBuilder(IQueryOver root) : base(root) { } - } public class QueryOverSubqueryBuilderBase where TReturn : IQueryOver where TBuilderType : QueryOverSubqueryPropertyBuilderBase, new() { - protected TReturn root; protected QueryOverSubqueryBuilderBase(TReturn root) @@ -137,7 +130,5 @@ public TBuilderType WhereValue(object value) { return (TBuilderType)new TBuilderType().Set(root, null, value); } - } - } diff --git a/src/NHibernate/Criterion/Lambda/QueryOverSubqueryPropertyBuilder.cs b/src/NHibernate/Criterion/Lambda/QueryOverSubqueryPropertyBuilder.cs index abcef2cf8f5..ad065bf4e9f 100644 --- a/src/NHibernate/Criterion/Lambda/QueryOverSubqueryPropertyBuilder.cs +++ b/src/NHibernate/Criterion/Lambda/QueryOverSubqueryPropertyBuilder.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,21 +7,16 @@ namespace NHibernate.Criterion.Lambda { - public class QueryOverSubqueryPropertyBuilder : QueryOverSubqueryPropertyBuilderBase, TRoot, TSubType> { - public QueryOverSubqueryPropertyBuilder() : base() { } - } public class IQueryOverSubqueryPropertyBuilder : QueryOverSubqueryPropertyBuilderBase, TRoot, TSubType> { - public IQueryOverSubqueryPropertyBuilder() : base() { } - } public abstract class QueryOverSubqueryPropertyBuilderBase @@ -35,7 +29,6 @@ protected QueryOverSubqueryPropertyBuilderBase() { } public class QueryOverSubqueryPropertyBuilderBase : QueryOverSubqueryPropertyBuilderBase where TReturn : IQueryOver { - protected TReturn root; protected string path; protected object value; @@ -236,7 +229,5 @@ public TReturn NotIn(QueryOver detachedCriteria) AddSubquery(Subqueries.PropertyNotIn, Subqueries.NotIn, detachedCriteria); return root; } - } - } diff --git a/src/NHibernate/Criterion/LikeExpression.cs b/src/NHibernate/Criterion/LikeExpression.cs index f18a59ee4f1..8b2eb65efe5 100644 --- a/src/NHibernate/Criterion/LikeExpression.cs +++ b/src/NHibernate/Criterion/LikeExpression.cs @@ -40,7 +40,6 @@ public LikeExpression(IProjection projection, string value, MatchMode matchMode) typedValue = new TypedValue(NHibernateUtil.String, this.value, false); } - public LikeExpression(string propertyName, string value) : this(propertyName, value, null, false) { @@ -60,7 +59,7 @@ public LikeExpression(string propertyName, string value, MatchMode matchMode, ch public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - SqlString[] columns = CriterionUtil.GetColumnNamesUsingProjection(projection, criteriaQuery, criteria); + var columns = CriterionUtil.GetColumnNamesAsSqlStringParts(projection, criteriaQuery, criteria); if (columns.Length != 1) throw new HibernateException("Like may only be used with single-column properties / projections."); @@ -71,11 +70,11 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri Dialect.Dialect dialect = criteriaQuery.Factory.Dialect; lhs.Add(dialect.LowercaseFunction) .Add(StringHelper.OpenParen) - .Add(columns[0]) + .AddObject(columns[0]) .Add(StringHelper.ClosedParen); } else - lhs.Add(columns[0]); + lhs.AddObject(columns[0]); if (ignoreCase) { diff --git a/src/NHibernate/Criterion/LogicalExpression.cs b/src/NHibernate/Criterion/LogicalExpression.cs index 83ee3759dbc..3b683f0163d 100644 --- a/src/NHibernate/Criterion/LogicalExpression.cs +++ b/src/NHibernate/Criterion/LogicalExpression.cs @@ -82,7 +82,6 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri false // not wrapping because the prefix and postfix params already take care of that ); - return sqlBuilder.ToSqlString(); } diff --git a/src/NHibernate/Criterion/NotNullExpression.cs b/src/NHibernate/Criterion/NotNullExpression.cs index f08fc7bdd30..0d7df9dc039 100644 --- a/src/NHibernate/Criterion/NotNullExpression.cs +++ b/src/NHibernate/Criterion/NotNullExpression.cs @@ -39,8 +39,8 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri //TODO: add default capacity SqlStringBuilder sqlBuilder = new SqlStringBuilder(); - SqlString[] columnNames = - CriterionUtil.GetColumnNames(_propertyName, _projection, criteriaQuery, criteria); + var columnNames = + CriterionUtil.GetColumnNamesAsSqlStringParts(_propertyName, _projection, criteriaQuery, criteria); bool opNeeded = false; @@ -52,7 +52,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri } opNeeded = true; - sqlBuilder.Add(columnNames[i]) + sqlBuilder.AddObject(columnNames[i]) .Add(" is not null"); } diff --git a/src/NHibernate/Criterion/NullExpression.cs b/src/NHibernate/Criterion/NullExpression.cs index cbd0a955575..b3d46150691 100644 --- a/src/NHibernate/Criterion/NullExpression.cs +++ b/src/NHibernate/Criterion/NullExpression.cs @@ -38,8 +38,8 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri //TODO: add default capacity SqlStringBuilder sqlBuilder = new SqlStringBuilder(); - SqlString[] columnNames = - CriterionUtil.GetColumnNames(_propertyName, _projection, criteriaQuery, criteria); + var columnNames = + CriterionUtil.GetColumnNamesAsSqlStringParts(_propertyName, _projection, criteriaQuery, criteria); for (int i = 0; i < columnNames.Length; i++) { @@ -48,7 +48,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri sqlBuilder.Add(" and "); } - sqlBuilder.Add(columnNames[i]) + sqlBuilder.AddObject(columnNames[i]) .Add(" is null"); } diff --git a/src/NHibernate/Criterion/Order.cs b/src/NHibernate/Criterion/Order.cs index 6f1e466a0e7..8b366ad5fe9 100644 --- a/src/NHibernate/Criterion/Order.cs +++ b/src/NHibernate/Criterion/Order.cs @@ -1,8 +1,7 @@ using System; -using System.Text; -using NHibernate.Criterion; using NHibernate.Engine; using NHibernate.SqlCommand; +using NHibernate.SqlTypes; namespace NHibernate.Criterion { @@ -38,46 +37,54 @@ public Order(string propertyName, bool ascending) /// public virtual SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - if (projection != null) - { - SqlString sb = SqlString.Empty; - SqlString produced = this.projection.ToSqlString(criteria, 0, criteriaQuery); - SqlString truncated = SqlStringHelper.RemoveAsAliasesFromSql(produced); - sb = sb.Append(truncated); - sb = sb.Append(ascending ? " asc" : " desc"); - return sb; - } - - string[] columns = criteriaQuery.GetColumnAliasesUsingProjection(criteria, propertyName); - Type.IType type = criteriaQuery.GetTypeUsingProjection(criteria, propertyName); + var columnsOrAliases = GetColumnsOrAliases(criteria, criteriaQuery); + var sqlTypes = ignoreCase ? SqlTypes(criteria, criteriaQuery) : null; - StringBuilder fragment = new StringBuilder(); - ISessionFactoryImplementor factory = criteriaQuery.Factory; - for (int i = 0; i < columns.Length; i++) + var fragment = new SqlStringBuilder(); + var factory = criteriaQuery.Factory; + for (var i = 0; i < columnsOrAliases.Length; i++) { - bool lower = ignoreCase && IsStringType(type.SqlTypes(factory)[i]); - + var lower = sqlTypes != null && IsStringType(sqlTypes[i]); if (lower) { - fragment.Append(factory.Dialect.LowercaseFunction) - .Append("("); + fragment + .Add(factory.Dialect.LowercaseFunction) + .Add("("); } - fragment.Append(columns[i]); + + fragment.AddObject(columnsOrAliases[i]); if (lower) { - fragment.Append(")"); + fragment.Add(")"); } - fragment.Append(ascending ? " asc" : " desc"); + fragment.Add(ascending ? " asc" : " desc"); - if (i < columns.Length - 1) + if (i < columnsOrAliases.Length - 1) { - fragment.Append(", "); + fragment.Add(", "); } } - return new SqlString(fragment.ToString()); + return fragment.ToSqlString(); + } + + private object[] GetColumnsOrAliases(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + var propName = propertyName ?? (projection as IPropertyProjection)?.PropertyName; + return propName != null + ? criteriaQuery.GetColumnAliasesUsingProjection(criteria, propName) + : CriterionUtil.GetColumnNamesAsSqlStringParts(projection, criteriaQuery, criteria); + } + + private SqlType[] SqlTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + var type = projection == null + ? criteriaQuery.GetTypeUsingProjection(criteria, propertyName) + : projection.GetTypes(criteria, criteriaQuery)[0]; + + return type.SqlTypes(criteriaQuery.Factory); } public override string ToString() diff --git a/src/NHibernate/Criterion/ProjectionList.cs b/src/NHibernate/Criterion/ProjectionList.cs index 565d833de34..3777fb56b97 100644 --- a/src/NHibernate/Criterion/ProjectionList.cs +++ b/src/NHibernate/Criterion/ProjectionList.cs @@ -12,7 +12,7 @@ namespace NHibernate.Criterion [Serializable] public class ProjectionList : IProjection { - private IList elements = new List(); + private List elements = new List(); protected internal ProjectionList() { @@ -41,7 +41,7 @@ public ProjectionList Add(IProjection projection, Expression> alias) public IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) { - IList types = new List(Length); + var types = new List(Length); for (int i = 0; i < Length; i++) { diff --git a/src/NHibernate/Criterion/Projections.cs b/src/NHibernate/Criterion/Projections.cs index 5142c94c281..f152ef9459b 100644 --- a/src/NHibernate/Criterion/Projections.cs +++ b/src/NHibernate/Criterion/Projections.cs @@ -153,7 +153,6 @@ public static AggregateProjection Max(IProjection projection) return new AggregateProjection("max", projection); } - /// /// A property minimum value /// @@ -300,7 +299,6 @@ public static IProjection Cast(IType type, IProjection projection) return new CastProjection(type, projection); } - /// /// Return a constant value /// @@ -322,7 +320,6 @@ public static IProjection Constant(object obj, IType type) return new ConstantProjection(obj,type); } - /// /// Calls the named /// @@ -523,6 +520,22 @@ public static string Concat(params string[] strings) throw QueryOver.GetDirectUsageException(); } + /// + /// Projects given lambda expression + /// + public static IProjection Select(Expression> expression) + { + return ExpressionProcessor.FindMemberProjection(expression.Body).AsProjection(); + } + + /// + /// Projects given lambda expression + /// + public static IProjection Select(Expression> expression) + { + return ExpressionProcessor.FindMemberProjection(expression.Body).AsProjection(); + } + internal static IProjection ProcessConcat(MethodCallExpression methodCallExpression) { NewArrayExpression args = (NewArrayExpression)methodCallExpression.Arguments[0]; diff --git a/src/NHibernate/Criterion/ProjectionsExtensions.cs b/src/NHibernate/Criterion/ProjectionsExtensions.cs index 8278a00fbb5..c3fccc224f0 100644 --- a/src/NHibernate/Criterion/ProjectionsExtensions.cs +++ b/src/NHibernate/Criterion/ProjectionsExtensions.cs @@ -186,7 +186,6 @@ internal static IProjection ProcessInt64Abs(MethodCallExpression methodCallExpre return Projections.SqlFunction("abs", NHibernateUtil.Int64, property); } - internal static IProjection ProcessRound(MethodCallExpression methodCallExpression) { IProjection innerProjection = @@ -199,7 +198,6 @@ internal static IProjection ProcessRound(MethodCallExpression methodCallExpressi return Projections.SqlFunction("round", NHibernateUtil.Double, innerProjection, digitsProjection); } - /// /// Project SQL function abs() /// Note: throws an exception outside of a QueryOver expression @@ -272,9 +270,9 @@ public static string Substr(this string stringProperty, int startIndex, int leng internal static IProjection ProcessSubstr(MethodCallExpression methodCallExpression) { IProjection property = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection(); - object startIndex = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]); - object length = ExpressionProcessor.FindValue(methodCallExpression.Arguments[2]); - return Projections.SqlFunction("substring", NHibernateUtil.String, property, Projections.Constant(startIndex), Projections.Constant(length)); + var startIndex = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[1]); + var length = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[2]); + return Projections.SqlFunction("substring", NHibernateUtil.String, property, startIndex.AsProjection(), length.AsProjection()); } /// @@ -289,9 +287,9 @@ public static int CharIndex(this string stringProperty, string theChar, int star internal static IProjection ProcessCharIndex(MethodCallExpression methodCallExpression) { IProjection property = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection(); - object theChar = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]); - object startLocation = ExpressionProcessor.FindValue(methodCallExpression.Arguments[2]); - return Projections.SqlFunction("locate", NHibernateUtil.String, Projections.Constant(theChar), property, Projections.Constant(startLocation)); + var theChar = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[1]); + var startLocation = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[2]); + return Projections.SqlFunction("locate", NHibernateUtil.String, theChar.AsProjection(), property, startLocation.AsProjection()); } /// @@ -315,8 +313,8 @@ public static T Coalesce(this T objectProperty, T replaceValueIfIsNull) internal static IProjection ProcessCoalesce(MethodCallExpression methodCallExpression) { IProjection property = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection(); - object replaceValueIfIsNull = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]); - return Projections.SqlFunction("coalesce", NHibernateUtil.Object, property, Projections.Constant(replaceValueIfIsNull)); + var replaceValueIfIsNull = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[1]); + return new SqlFunctionProjection("coalesce", returnTypeProjection: property, property, replaceValueIfIsNull.AsProjection()); } /// @@ -331,8 +329,8 @@ public static int Mod(this int numericProperty, int divisor) internal static IProjection ProcessMod(MethodCallExpression methodCallExpression) { IProjection property = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection(); - object divisor = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]); - return Projections.SqlFunction("mod", NHibernateUtil.Int32, property, Projections.Constant(divisor)); + var divisor = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[1]); + return Projections.SqlFunction("mod", NHibernateUtil.Int32, property, divisor.AsProjection()); } /// diff --git a/src/NHibernate/Criterion/Property.cs b/src/NHibernate/Criterion/Property.cs index 0903544bb1e..c08325824ad 100644 --- a/src/NHibernate/Criterion/Property.cs +++ b/src/NHibernate/Criterion/Property.cs @@ -191,7 +191,6 @@ public Property GetProperty(String propertyName) return ForName(PropertyName + '.' + propertyName); } - public AbstractCriterion Eq(DetachedCriteria subselect) { return Subqueries.PropertyEq(PropertyName, subselect); @@ -277,4 +276,4 @@ public AbstractCriterion GeSome(DetachedCriteria subselect) return Subqueries.PropertyGeSome(PropertyName, subselect); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/PropertyExpression.cs b/src/NHibernate/Criterion/PropertyExpression.cs index 5453b982c75..b27bd3184d3 100644 --- a/src/NHibernate/Criterion/PropertyExpression.cs +++ b/src/NHibernate/Criterion/PropertyExpression.cs @@ -70,31 +70,28 @@ protected PropertyExpression(string lhsPropertyName, IProjection rhsProjection) public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - SqlString[] columnNames = - CriterionUtil.GetColumnNames(_lhsPropertyName, _lhsProjection, criteriaQuery, criteria); - SqlString[] otherColumnNames = - CriterionUtil.GetColumnNames(_rhsPropertyName, _rhsProjection, criteriaQuery, criteria); + var columnNames = + CriterionUtil.GetColumnNamesAsSqlStringParts(_lhsPropertyName, _lhsProjection, criteriaQuery, criteria); + var otherColumnNames = + CriterionUtil.GetColumnNamesAsSqlStringParts(_rhsPropertyName, _rhsProjection, criteriaQuery, criteria); - SqlStringBuilder sb = new SqlStringBuilder(); - if (columnNames.Length > 1) + switch (columnNames.Length) { - sb.Add(StringHelper.OpenParen); - } - bool first = true; - foreach (SqlString sqlString in SqlStringHelper.Add(columnNames, Op, otherColumnNames)) - { - if (first == false) - { - sb.Add(" and "); - } - first = false; - sb.Add(sqlString); + case 1: + return new SqlString(columnNames[0], Op, otherColumnNames[0]); + case 0: + return SqlString.Empty; } - if (columnNames.Length > 1) + var sb = new SqlStringBuilder(); + sb.Add(StringHelper.OpenParen); + sb.AddObject(columnNames[0]).Add(Op).AddObject(otherColumnNames[0]); + for (var i = 1; i < columnNames.Length; i++) { - sb.Add(StringHelper.ClosedParen); + sb.Add(" and "); + sb.AddObject(columnNames[i]).Add(Op).AddObject(otherColumnNames[i]); } + sb.Add(StringHelper.ClosedParen); return sb.ToSqlString(); } diff --git a/src/NHibernate/Criterion/PropertySubqueryExpression.cs b/src/NHibernate/Criterion/PropertySubqueryExpression.cs index cf0f0d505e7..a44093d0a3c 100644 --- a/src/NHibernate/Criterion/PropertySubqueryExpression.cs +++ b/src/NHibernate/Criterion/PropertySubqueryExpression.cs @@ -20,7 +20,10 @@ internal PropertySubqueryExpression(String propertyName, String op, String quant protected override SqlString ToLeftSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new SqlString(criteriaQuery.GetColumn(criteria, propertyName)); + var columns = criteriaQuery.GetColumns(criteria, propertyName); + if (columns.Length <= 1) + return new SqlString(columns); + return new SqlString("(", string.Join(", ", columns), ")"); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/QueryOver.cs b/src/NHibernate/Criterion/QueryOver.cs index a5c4ed1ea6a..81620e646d6 100644 --- a/src/NHibernate/Criterion/QueryOver.cs +++ b/src/NHibernate/Criterion/QueryOver.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq; @@ -10,14 +9,13 @@ using NHibernate.Loader; using NHibernate.SqlCommand; using NHibernate.Transform; +using NHibernate.Util; namespace NHibernate.Criterion { - [Serializable] public abstract class QueryOver { - protected ICriteria criteria; protected CriteriaImpl impl; @@ -219,7 +217,6 @@ public S As() throw new HibernateException("Incorrect syntax; .As method is for use in Lambda expressions only."); } - IList IQueryOver.List() { return List(); } @@ -279,7 +276,6 @@ IQueryOver IQueryOver.CacheRegion(string cacheRegion) IQueryOver IQueryOver.ReadOnly() { return ReadOnly(); } - } /// @@ -289,7 +285,6 @@ IQueryOver IQueryOver.ReadOnly() public class QueryOver : QueryOver, IQueryOver, ISupportEntityJoinQueryOver, ISupportSelectModeQueryOver { - protected internal QueryOver() { impl = new CriteriaImpl(typeof(TRoot), null); @@ -410,12 +405,7 @@ public QueryOverRestrictionBuilder WhereRestrictionOn(Expression public QueryOver Select(params Expression>[] projections) { - List projectionList = new List(); - - foreach (var projection in projections) - projectionList.Add(ExpressionProcessor.FindMemberProjection(projection.Body).AsProjection()); - - criteria.SetProjection(projectionList.ToArray()); + criteria.SetProjection(projections.ToArray(x => Projections.Select(x))); return this; } @@ -1012,11 +1002,15 @@ IQueryOverJoinBuilder IQueryOver.Right IQueryOverJoinBuilder IQueryOver.Full { get { return new IQueryOverJoinBuilder(this, JoinType.FullJoin); } } - public IQueryOver Fetch(SelectMode mode, Expression> path) + IQueryOver ISupportSelectModeQueryOver.Fetch(SelectMode mode, Expression> path) + { + return Fetch(mode, path); + } + + public QueryOver Fetch(SelectMode mode, Expression> path) { UnderlyingCriteria.Fetch(mode, ExpressionProcessor.FindMemberExpression(path.Body), null); return this; } } - } diff --git a/src/NHibernate/Criterion/QueryOverBuilderExtensions.cs b/src/NHibernate/Criterion/QueryOverBuilderExtensions.cs index f6004e9d739..d2f1da269e6 100644 --- a/src/NHibernate/Criterion/QueryOverBuilderExtensions.cs +++ b/src/NHibernate/Criterion/QueryOverBuilderExtensions.cs @@ -4,7 +4,6 @@ namespace NHibernate.Criterion { public static class QueryOverBuilderExtensions { - // Fetch builder // Since v5.2 [Obsolete("Use Fetch(SelectMode.Default, Expression> path) instead")] @@ -109,7 +108,6 @@ public static IQueryOver Write(this Lambda.IQu return builder.Write; } - // Order builder public static QueryOver Asc(this Lambda.QueryOverOrderBuilder builder) { @@ -131,7 +129,6 @@ public static IQueryOver Desc(this Lambda.IQue return builder.Desc; } - // Restriction builder public static QueryOver IsEmpty(this Lambda.QueryOverRestrictionBuilder builder) { @@ -172,6 +169,5 @@ public static IQueryOver IsNull(this Lambda.IQ { return builder.IsNull; } - } } diff --git a/src/NHibernate/Criterion/Restrictions.cs b/src/NHibernate/Criterion/Restrictions.cs index 68c535c15e5..2ca3e11d374 100644 --- a/src/NHibernate/Criterion/Restrictions.cs +++ b/src/NHibernate/Criterion/Restrictions.cs @@ -171,7 +171,6 @@ public static SimpleExpression Lt(string propertyName, object value) return new SimpleExpression(propertyName, value, " < "); } - /// /// Apply a "less than" constraint to the projection /// @@ -222,7 +221,6 @@ public static SimpleExpression Ge(IProjection projection, object value) return new SimpleExpression(projection, value, " >= "); } - /// /// Apply a "between" constraint to the named property /// @@ -378,7 +376,6 @@ public static AbstractCriterion EqProperty(IProjection projection, string otherP return new EqPropertyExpression(projection, otherPropertyName); } - /// /// Apply an "equal" constraint to lshProjection and rshProjection /// @@ -390,7 +387,6 @@ public static AbstractCriterion EqProperty(IProjection lshProjection, IProjectio return new EqPropertyExpression(lshProjection, rshProjection); } - /// /// Apply an "equal" constraint to the property and rshProjection /// @@ -523,7 +519,6 @@ public static AbstractCriterion GeProperty(IProjection projection, string otherP return new GePropertyExpression(projection, otherPropertyName); } - /// /// Apply a "greater than or equal" constraint to two properties /// @@ -590,7 +585,6 @@ public static AbstractCriterion LeProperty(string propertyName, string otherProp return new LePropertyExpression(propertyName, otherPropertyName); } - /// /// Apply a "less than or equal" constraint to two properties /// @@ -602,7 +596,6 @@ public static AbstractCriterion LeProperty(IProjection projection, string otherP return new LePropertyExpression(projection, otherPropertyName); } - /// /// Apply a "less than or equal" constraint to two properties /// @@ -614,7 +607,6 @@ public static AbstractCriterion LeProperty(string propertyName, IProjection proj return new LePropertyExpression(propertyName, projection); } - /// /// Apply a "less than or equal" constraint to two properties /// @@ -626,7 +618,6 @@ public static AbstractCriterion LeProperty(IProjection lhsProjection, IProjectio return new LePropertyExpression(lhsProjection, rhsProjection); } - /// /// Apply an "is not null" constraint to the named property /// @@ -658,7 +649,7 @@ public static AbstractEmptinessExpression IsNotEmpty(string propertyName) } /// - /// Apply an "is not empty" constraint to the named property + /// Apply an "is empty" constraint to the named property /// /// The name of the Property in the class. /// A . @@ -804,6 +795,5 @@ public static LambdaRestrictionBuilder On(Expression> expression) ExpressionProcessor.ProjectionInfo projection = ExpressionProcessor.FindMemberProjection(expression.Body); return new LambdaRestrictionBuilder(projection); } - } } diff --git a/src/NHibernate/Criterion/RowCountProjection.cs b/src/NHibernate/Criterion/RowCountProjection.cs index ff6ad411159..6b5bce40196 100644 --- a/src/NHibernate/Criterion/RowCountProjection.cs +++ b/src/NHibernate/Criterion/RowCountProjection.cs @@ -38,7 +38,6 @@ public override bool IsGrouped public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - throw new InvalidOperationException("not a grouping projection"); } } diff --git a/src/NHibernate/Criterion/SQLCriterion.cs b/src/NHibernate/Criterion/SQLCriterion.cs index 06df9db44a3..093a165cfe4 100644 --- a/src/NHibernate/Criterion/SQLCriterion.cs +++ b/src/NHibernate/Criterion/SQLCriterion.cs @@ -9,6 +9,7 @@ namespace NHibernate.Criterion /// /// An that creates a SQLExpression. /// The string {alias} will be replaced by the alias of the root entity. + /// Criteria aliases can also be used: "{a}.Value + {bc}.Value". /// /// /// This allows for database specific Expressions at the cost of needing to @@ -42,7 +43,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri parameters[paramPos++].BackTrack = parameter.BackTrack; } } - return _sql.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)); + return criteriaQuery.RenderSQLAliases(_sql).Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)); } public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Criterion/SQLProjection.cs b/src/NHibernate/Criterion/SQLProjection.cs index ce1d9c39448..ca538a21b61 100644 --- a/src/NHibernate/Criterion/SQLProjection.cs +++ b/src/NHibernate/Criterion/SQLProjection.cs @@ -1,7 +1,6 @@ using System; using NHibernate.SqlCommand; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Criterion { @@ -9,6 +8,7 @@ namespace NHibernate.Criterion /// /// A SQL fragment. The string {alias} will be replaced by the alias of the root entity. + /// Criteria aliases can also be used: "{a}.Value + {bc}.Value". /// [Serializable] public sealed class SQLProjection : IProjection @@ -37,15 +37,17 @@ internal SQLProjection(string sql, string groupBy, string[] columnAliases, IType public SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery) { - //SqlString result = new SqlString(criteriaQuery.GetSQLAlias(criteria)); - //result.Replace(sql, "{alias}"); - //return result; - return new SqlString(sql?.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria))); + return GetSqlString(criteria, criteriaQuery, sql); } public SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new SqlString(groupBy?.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria))); + return GetSqlString(criteria, criteriaQuery, groupBy); + } + + private SqlString GetSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, string sqlTemplate) + { + return criteriaQuery.RenderSQLAliases(new SqlString(sqlTemplate)).Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)); } public override string ToString() diff --git a/src/NHibernate/Criterion/SimpleExpression.cs b/src/NHibernate/Criterion/SimpleExpression.cs index be1266eda70..8b19ec50126 100644 --- a/src/NHibernate/Criterion/SimpleExpression.cs +++ b/src/NHibernate/Criterion/SimpleExpression.cs @@ -156,11 +156,7 @@ public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery c public TypedValue GetParameterTypedValue(ICriteria criteria, ICriteriaQuery criteriaQuery) { object icvalue = ignoreCase ? value.ToString().ToLower() : value; - if (_projection != null) - { - return CriterionUtil.GetTypedValues(criteriaQuery, criteria, _projection, null, icvalue).Single(); - } - return criteriaQuery.GetTypedValue(criteria, propertyName, icvalue); + return CriterionUtil.GetTypedValue(criteriaQuery, criteria, _projection, propertyName, icvalue); } public override IProjection[] GetProjections() diff --git a/src/NHibernate/Criterion/SimpleProjection.cs b/src/NHibernate/Criterion/SimpleProjection.cs index 9304c4c4222..96ffd602252 100644 --- a/src/NHibernate/Criterion/SimpleProjection.cs +++ b/src/NHibernate/Criterion/SimpleProjection.cs @@ -26,11 +26,18 @@ public virtual IType[] GetTypes(string alias, ICriteria criteria, ICriteriaQuery return null; } + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public virtual string[] GetColumnAliases(int loc) { - return new string[] {"y" + loc + "_"}; + return new[] {GetColumnAlias(loc)}; } - + + protected string GetColumnAlias(int position) + { + return "y" + position + "_"; + } + public string[] GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery) { return GetColumnAliases(alias, position); @@ -40,14 +47,13 @@ public String[] GetColumnAliases(int position, ICriteria criteria, ICriteriaQuer { int numColumns = this.GetColumnCount(criteria, criteriaQuery); string[] aliases = new string[numColumns]; - for (int i = 0; i < numColumns; i++) + for (int i = 0; i < numColumns; i++) { - aliases[i] = "y" + position + "_"; - position++; + aliases[i] = GetColumnAlias(position + i); } return aliases; } - + public virtual string[] Aliases { get { return new String[1]; } diff --git a/src/NHibernate/Criterion/SimpleSubqueryExpression.cs b/src/NHibernate/Criterion/SimpleSubqueryExpression.cs index d66b8504209..1c3ff13b18e 100644 --- a/src/NHibernate/Criterion/SimpleSubqueryExpression.cs +++ b/src/NHibernate/Criterion/SimpleSubqueryExpression.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; +using NHibernate.Type; namespace NHibernate.Criterion { @@ -23,20 +23,38 @@ internal SimpleSubqueryExpression(Object value, String op, String quantifier, De public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) { TypedValue[] superTv = base.GetTypedValues(criteria, criteriaQuery); - TypedValue[] result = new TypedValue[superTv.Length + 1]; - superTv.CopyTo(result, 1); - result[0] = FirstTypedValue(); + var typedValues = GetTypedValues(GetTypes()[0], value); + TypedValue[] result = new TypedValue[superTv.Length + typedValues.Length]; + superTv.CopyTo(result, 0); + typedValues.CopyTo(result, superTv.Length); + return result; } - private TypedValue FirstTypedValue() + protected override SqlString ToLeftSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new TypedValue(GetTypes()[0], value); + var typedValues = GetTypedValues(GetTypes()[0], value); + var parameters = typedValues.Select(tv => criteriaQuery.NewQueryParameter(tv)).SelectMany(x => x).ToList(); + return SqlStringHelper.ParametersList(parameters); } - protected override SqlString ToLeftSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) + private static TypedValue[] GetTypedValues(IType type, object value) { - return new SqlString(criteriaQuery.NewQueryParameter(FirstTypedValue()).First()); + if (!type.IsComponentType) + return new[] {new TypedValue(type, value)}; + + IAbstractComponentType actype = (IAbstractComponentType) type; + IType[] types = actype.Subtypes; + var list = new TypedValue[types.Length]; + var propertyValues = value == null + ? null + : actype.GetPropertyValues(value); + for (int ti = 0; ti < types.Length; ti++) + { + list[ti] = new TypedValue(types[ti], propertyValues?[ti], false); + } + + return list; } } } diff --git a/src/NHibernate/Criterion/SqlFunctionProjection.cs b/src/NHibernate/Criterion/SqlFunctionProjection.cs index c958ce055f9..9825c275e3e 100644 --- a/src/NHibernate/Criterion/SqlFunctionProjection.cs +++ b/src/NHibernate/Criterion/SqlFunctionProjection.cs @@ -1,11 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Dialect.Function; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Criterion { @@ -16,6 +15,7 @@ public class SqlFunctionProjection : SimpleProjection private readonly ISQLFunction function; private readonly string functionName; private readonly IType returnType; + private readonly IProjection returnTypeProjection; public SqlFunctionProjection(string functionName, IType returnType, params IProjection[] args) { @@ -31,6 +31,13 @@ public SqlFunctionProjection(ISQLFunction function, IType returnType, params IPr this.args = args; } + public SqlFunctionProjection(string functionName, IProjection returnTypeProjection, params IProjection[] args) + { + this.functionName = functionName; + this.returnTypeProjection = returnTypeProjection; + this.args = args; + } + public override bool IsAggregate { get { return false; } @@ -75,14 +82,14 @@ public override SqlString ToSqlString(ICriteria criteria, int position, ICriteri var arguments = new List(); for (int i = 0; i < args.Length; i++) { - SqlString projectArg = GetProjectionArgument(criteriaQuery, criteria, args[i], 0); // The loc parameter is unused. - arguments.Add(projectArg); + var projectArg = GetProjectionArguments(criteriaQuery, criteria, args[i]); + arguments.AddRange(projectArg); } return new SqlString( sqlFunction.Render(arguments, criteriaQuery.Factory), " as ", - GetColumnAliases(position, criteria, criteriaQuery)[0]); + GetColumnAlias(position)); } private ISQLFunction GetFunction(ICriteriaQuery criteriaQuery) @@ -100,17 +107,24 @@ private ISQLFunction GetFunction(ICriteriaQuery criteriaQuery) return dialectFunction; } - private static SqlString GetProjectionArgument(ICriteriaQuery criteriaQuery, ICriteria criteria, IProjection projection, int loc) + private static object[] GetProjectionArguments(ICriteriaQuery criteriaQuery, ICriteria criteria, IProjection projection) { - SqlString sql = projection.ToSqlString(criteria, loc, criteriaQuery); - return SqlStringHelper.RemoveAsAliasesFromSql(sql); + return CriterionUtil.GetColumnNamesAsSqlStringParts(projection, criteriaQuery, criteria); } public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + var type = GetReturnType(criteria, criteriaQuery); + return type != null ? new[] {type} : Array.Empty(); + } + + private IType GetReturnType(ICriteria criteria, ICriteriaQuery criteriaQuery) { ISQLFunction sqlFunction = GetFunction(criteriaQuery); - IType type = sqlFunction.ReturnType(returnType, criteriaQuery.Factory); - return new IType[] {type}; + + var resultType = returnType ?? returnTypeProjection?.GetTypes(criteria, criteriaQuery).FirstOrDefault(); + + return sqlFunction.GetReturnType(new[] {resultType}, criteriaQuery.Factory, true); } public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Criterion/SubqueryExpression.cs b/src/NHibernate/Criterion/SubqueryExpression.cs index 6fa12f37936..6d2d76a76f4 100644 --- a/src/NHibernate/Criterion/SubqueryExpression.cs +++ b/src/NHibernate/Criterion/SubqueryExpression.cs @@ -98,7 +98,6 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri return buf.ToSqlString(); } - public override string ToString() { if (prefixOp) @@ -135,4 +134,4 @@ static ISessionImplementor DeriveRootSession(ICriteria criteria) return null; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/SubqueryProjection.cs b/src/NHibernate/Criterion/SubqueryProjection.cs index c7a68afdbfc..763af33fc50 100644 --- a/src/NHibernate/Criterion/SubqueryProjection.cs +++ b/src/NHibernate/Criterion/SubqueryProjection.cs @@ -42,7 +42,7 @@ public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuer public override SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery) { return _subQuery.ToSqlString(criteria, criteriaQuery) - .Append(new SqlString(" as y", loc.ToString(), "_")); + .Append(" as y", loc.ToString(), "_"); } public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Dialect/DB2Dialect.cs b/src/NHibernate/Dialect/DB2Dialect.cs index 9a830de2c4e..a558e6d313d 100644 --- a/src/NHibernate/Dialect/DB2Dialect.cs +++ b/src/NHibernate/Dialect/DB2Dialect.cs @@ -80,6 +80,7 @@ public DB2Dialect() RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double)); RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double)); RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); + RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double)); RegisterFunction("sin", new StandardSQLFunction("sin", NHibernateUtil.Double)); RegisterFunction("soundex", new StandardSQLFunction("soundex", NHibernateUtil.String)); RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double)); @@ -87,6 +88,8 @@ public DB2Dialect() RegisterFunction("tan", new StandardSQLFunction("tan", NHibernateUtil.Double)); RegisterFunction("variance", new StandardSQLFunction("variance", NHibernateUtil.Double)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("current_timestamp", NHibernateUtil.LocalDateTime, false)); + RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.LocalDate, false)); RegisterFunction("julian_day", new StandardSQLFunction("julian_day", NHibernateUtil.Int32)); RegisterFunction("microsecond", new StandardSQLFunction("microsecond", NHibernateUtil.Int32)); RegisterFunction("midnight_seconds", new StandardSQLFunction("midnight_seconds", NHibernateUtil.Int32)); @@ -128,7 +131,7 @@ public DB2Dialect() RegisterFunction("length", new StandardSQLFunction("length", NHibernateUtil.Int32)); RegisterFunction("ltrim", new StandardSQLFunction("ltrim")); - RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); + RegisterFunction("mod", new ModulusFunction(true, false)); RegisterFunction("substring", new StandardSQLFunction("substr", NHibernateUtil.String)); @@ -138,8 +141,6 @@ public DB2Dialect() RegisterFunction("bxor", new Function.BitwiseFunctionOperation("bitxor")); RegisterFunction("bnot", new Function.BitwiseFunctionOperation("bitnot")); - RegisterFunction("current_timestamp", new NoArgSQLFunction("current_timestamp", NHibernateUtil.DateTime, false)); - DefaultProperties[Environment.ConnectionDriver] = "NHibernate.Driver.DB2Driver"; } @@ -179,7 +180,6 @@ public override string IdentityInsertString get { return "default"; } } - public override string GetSelectSequenceNextValString(string sequenceName) { return "nextval for " + sequenceName; @@ -232,28 +232,29 @@ public override bool SupportsVariableLimit get { return false; } } - public override SqlString GetLimitString(SqlString querySqlString, SqlString offset, SqlString limit) + public override SqlString GetLimitString(SqlString sql, SqlString offset, SqlString limit) { if (offset == null) { - return new SqlString(querySqlString, - " fetch first ", - limit, - " rows only"); + return new SqlString(sql, " fetch first ", limit, " rows only"); } + ExtractColumnOrAliasNames(sql, out var selectColumns, out _, out _); + /* * "select * from (select row_number() over(orderby_clause) as rownum, " * querySqlString_without select * " ) as tempresult where rownum between ? and ?" */ - string rownumClause = GetRowNumber(querySqlString); + string rownumClause = GetRowNumber(sql); SqlStringBuilder pagingBuilder = new SqlStringBuilder(); pagingBuilder - .Add("select * from (select ") + .Add("select " ) + .Add(string.Join(",", selectColumns)) + .Add(" from (select ") .Add(rownumClause) - .Add(querySqlString.Substring(7)) + .Add(sql.Substring(7)) .Add(") as tempresult where rownum "); if (limit != null) @@ -297,10 +298,15 @@ public override string ForUpdateString #region Overridden informational metadata + public override bool SupportsNullInUnique => false; + public override bool SupportsEmptyInList => false; public override bool SupportsResultSetPositionQueryMethodsOnForwardOnlyCursor => false; + /// + public override bool SupportsCrossJoin => false; // DB2 v9.1 doesn't support 'cross join' syntax + public override bool SupportsLobValueChangePropogation => false; public override bool SupportsExistsInSelect => false; diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index 03afd28afb6..10b12bc05bc 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -55,6 +55,7 @@ public abstract partial class Dialect static Dialect() { StandardAggregateFunctions["count"] = new CountQueryFunctionInfo(); + StandardAggregateFunctions["count_big"] = new CountQueryFunctionInfo(); StandardAggregateFunctions["avg"] = new AvgQueryFunctionInfo(); StandardAggregateFunctions["max"] = new ClassicAggregateFunction("max", false); StandardAggregateFunctions["min"] = new ClassicAggregateFunction("min", false); @@ -93,19 +94,19 @@ protected Dialect() RegisterFunction("coalesce", new StandardSQLFunction("coalesce")); RegisterFunction("nullif", new StandardSQLFunction("nullif")); RegisterFunction("abs", new StandardSQLFunction("abs")); - RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); + RegisterFunction("mod", new ModulusFunction(false, false)); RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double)); RegisterFunction("upper", new StandardSQLFunction("upper")); RegisterFunction("lower", new StandardSQLFunction("lower")); RegisterFunction("cast", new CastFunction()); RegisterFunction("transparentcast", new TransparentCastFunction()); RegisterFunction("extract", new AnsiExtractFunction()); - RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(", "||", ")")); + RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(", " || ", ")")); // the syntax of current_timestamp is extracted from H3.2 tests // - test\hql\ASTParserLoadingTest.java // - test\org\hibernate\test\hql\HQLTest.java - RegisterFunction("current_timestamp", new NoArgSQLFunction("current_timestamp", NHibernateUtil.DateTime, true)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("current_timestamp", NHibernateUtil.LocalDateTime, true)); RegisterFunction("sysdate", new NoArgSQLFunction("sysdate", NHibernateUtil.DateTime, false)); //map second/minute/hour/day/month/year to ANSI extract(), override on subclasses @@ -266,6 +267,18 @@ public virtual string GetLongestTypeName(DbType dbType) public virtual string GetCastTypeName(SqlType sqlType) => GetCastTypeName(sqlType, _typeNames); + /// + /// Get the name of the database type appropriate for casting operations + /// (via the CAST() SQL function) for the given typecode. + /// + /// The typecode. + /// The database type name that will be set in case it was found. + /// Whether the type name was found. + public virtual bool TryGetCastTypeName(SqlType sqlType, out string typeName) + { + return TryGetCastTypeName(sqlType, _typeNames, out typeName); + } + /// /// Get the name of the database type appropriate for casting operations /// (via the CAST() SQL function) for the given typecode. @@ -274,9 +287,27 @@ public virtual string GetCastTypeName(SqlType sqlType) => /// The source for type names. /// The database type name. protected virtual string GetCastTypeName(SqlType sqlType, TypeNames castTypeNames) + { + if (!TryGetCastTypeName(sqlType, castTypeNames, out var result)) + { + throw new ArgumentException("Dialect does not support DbType." + sqlType.DbType, nameof(sqlType)); + } + + return result; + } + + /// + /// Get the name of the database type appropriate for casting operations + /// (via the CAST() SQL function) for the given typecode. + /// + /// The typecode. + /// The source for type names. + /// The database type name that will be set in case it was found. + /// Whether the type name was found. + protected virtual bool TryGetCastTypeName(SqlType sqlType, TypeNames castTypeNames, out string typeName) { if (sqlType.LengthDefined || sqlType.PrecisionDefined || sqlType.ScaleDefined) - return castTypeNames.Get(sqlType.DbType, sqlType.Length, sqlType.Precision, sqlType.Scale); + return castTypeNames.TryGet(sqlType.DbType, sqlType.Length, sqlType.Precision, sqlType.Scale, out typeName); switch (sqlType.DbType) { case DbType.Decimal: @@ -284,18 +315,18 @@ protected virtual string GetCastTypeName(SqlType sqlType, TypeNames castTypeName case DbType.Double: // We cannot know if the user needs its digit after or before the dot, so use a configurable // default. - return castTypeNames.Get(sqlType.DbType, 0, DefaultCastPrecision, DefaultCastScale); + return castTypeNames.TryGet(sqlType.DbType, 0, DefaultCastPrecision, DefaultCastScale, out typeName); case DbType.DateTime: case DbType.DateTime2: case DbType.DateTimeOffset: case DbType.Time: case DbType.Currency: // Use default for these, dialects are supposed to map them to max capacity - return castTypeNames.Get(sqlType.DbType); + return castTypeNames.TryGet(sqlType.DbType, out typeName); default: // Other types are either length bound or not length/precision/scale bound. Otherwise they need to be // handled previously. - return castTypeNames.Get(sqlType.DbType, DefaultCastLength, 0, 0); + return castTypeNames.TryGet(sqlType.DbType, DefaultCastLength, 0, 0, out typeName); } } @@ -795,7 +826,6 @@ public virtual string GetDropForeignKeyConstraintString(string constraintName) return " drop constraint " + constraintName; } - /// /// The syntax that is used to check if a constraint does not exists before creating it /// @@ -1308,6 +1338,11 @@ public virtual JoinFragment CreateOuterJoinFragment() return new ANSIJoinFragment(); } + /// + /// Does this dialect support CROSS JOIN? + /// + public virtual bool SupportsCrossJoin => true; + /// /// Create a strategy responsible /// for handling this dialect's variations in how CASE statements are diff --git a/src/NHibernate/Dialect/FirebirdDialect.cs b/src/NHibernate/Dialect/FirebirdDialect.cs index af32c3e09d5..8134ef31324 100644 --- a/src/NHibernate/Dialect/FirebirdDialect.cs +++ b/src/NHibernate/Dialect/FirebirdDialect.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Data; using System.Data.Common; using NHibernate.Dialect.Function; @@ -154,7 +155,7 @@ public override SqlString Render(IList args, ISessionFactoryImplementor factory) [Serializable] private class CurrentTimeStamp : NoArgSQLFunction { - public CurrentTimeStamp() : base("current_timestamp", NHibernateUtil.DateTime, true) + public CurrentTimeStamp() : base("current_timestamp", NHibernateUtil.LocalDateTime, true) { } @@ -205,7 +206,7 @@ public override string SelectGUIDString } [Serializable] - private class PositionFunction : ISQLFunction + private class PositionFunction : ISQLFunction, ISQLFunctionExtended { // The cast is needed, at least in the case that ?3 is a named integer parameter, otherwise firebird will generate an error. // We have a unit test to cover this potential firebird bug. @@ -214,11 +215,28 @@ private class PositionFunction : ISQLFunction private static readonly ISQLFunction LocateWith3Params = new SQLFunctionTemplate(NHibernateUtil.Int32, "position(?1, ?2, cast(?3 as int))"); + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int32; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return NHibernateUtil.Int32; + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "position"; + public bool HasArguments { get { return true; } @@ -413,15 +431,18 @@ protected virtual void RegisterFunctions() private void OverrideStandardHQLFunctions() { RegisterFunction("current_timestamp", new CurrentTimeStamp()); + RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.LocalDate, false)); RegisterFunction("length", new StandardSafeSQLFunction("char_length", NHibernateUtil.Int64, 1)); RegisterFunction("nullif", new StandardSafeSQLFunction("nullif", 2)); RegisterFunction("lower", new StandardSafeSQLFunction("lower", NHibernateUtil.String, 1)); RegisterFunction("upper", new StandardSafeSQLFunction("upper", NHibernateUtil.String, 1)); - RegisterFunction("mod", new StandardSafeSQLFunction("mod", NHibernateUtil.Double, 2)); + // Modulo does not throw for decimal parameters but they are casted to int by Firebird, which produces unexpected results + RegisterFunction("mod", new ModulusFunction(false, false)); RegisterFunction("str", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as VARCHAR(255))")); RegisterFunction("strguid", new StandardSQLFunction("uuid_to_char", NHibernateUtil.String)); RegisterFunction("sysdate", new CastedFunction("today", NHibernateUtil.Date)); RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.Date, "cast(?1 as date)")); + RegisterFunction("new_uuid", new NoArgSQLFunction("gen_uuid", NHibernateUtil.Guid)); // Bitwise operations RegisterFunction("band", new Function.BitwiseFunctionOperation("bin_and")); RegisterFunction("bor", new Function.BitwiseFunctionOperation("bin_or")); @@ -435,7 +456,7 @@ private void RegisterFirebirdServerEmbeddedFunctions() RegisterFunction("yesterday", new CastedFunction("yesterday", NHibernateUtil.Date)); RegisterFunction("tomorrow", new CastedFunction("tomorrow", NHibernateUtil.Date)); RegisterFunction("now", new CastedFunction("now", NHibernateUtil.DateTime)); - RegisterFunction("iif", new StandardSafeSQLFunction("iif", 3)); + RegisterFunction("iif", new IifSafeSQLFunction()); // New embedded functions in FB 2.0 (http://www.firebirdsql.org/rlsnotes20/rnfbtwo-str.html#str-string-func) RegisterFunction("char_length", new StandardSafeSQLFunction("char_length", NHibernateUtil.Int64, 1)); RegisterFunction("bit_length", new StandardSafeSQLFunction("bit_length", NHibernateUtil.Int64, 1)); @@ -463,6 +484,7 @@ private void RegisterMathematicalFunctions() RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double)); RegisterFunction("pi", new NoArgSQLFunction("pi", NHibernateUtil.Double)); RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); + RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double)); RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32)); RegisterFunction("sqtr", new StandardSQLFunction("sqtr", NHibernateUtil.Double)); RegisterFunction("trunc", new StandardSQLFunction("trunc")); @@ -503,6 +525,7 @@ private void RegisterStringAndCharFunctions() RegisterFunction("locate", new PositionFunction()); RegisterFunction("replace", new StandardSafeSQLFunction("replace", NHibernateUtil.String, 3)); RegisterFunction("left", new StandardSQLFunction("left")); + RegisterFunction("right", new StandardSQLFunction("right")); } private void RegisterBlobFunctions() diff --git a/src/NHibernate/Dialect/Function/AnsiSubstringFunction.cs b/src/NHibernate/Dialect/Function/AnsiSubstringFunction.cs index b12e4e7e041..0ab50b074e9 100644 --- a/src/NHibernate/Dialect/Function/AnsiSubstringFunction.cs +++ b/src/NHibernate/Dialect/Function/AnsiSubstringFunction.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Text; using NHibernate.Engine; using NHibernate.SqlCommand; @@ -22,15 +24,34 @@ namespace NHibernate.Dialect.Function ///]]> /// [Serializable] - public class AnsiSubstringFunction : ISQLFunction + public class AnsiSubstringFunction : ISQLFunction, ISQLFunctionExtended { #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.String; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "substring"; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Function/AnsiTrimEmulationFunction.cs b/src/NHibernate/Dialect/Function/AnsiTrimEmulationFunction.cs index e9cde7d70f2..d007ba8513d 100644 --- a/src/NHibernate/Dialect/Function/AnsiTrimEmulationFunction.cs +++ b/src/NHibernate/Dialect/Function/AnsiTrimEmulationFunction.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; - +using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.Type; @@ -17,7 +17,7 @@ namespace NHibernate.Dialect.Function /// functionality. /// [Serializable] - public class AnsiTrimEmulationFunction : ISQLFunction, IFunctionGrammar + public class AnsiTrimEmulationFunction : ISQLFunction, IFunctionGrammar, ISQLFunctionExtended { private static readonly ISQLFunction LeadingSpaceTrim = new SQLFunctionTemplate(NHibernateUtil.String, "ltrim( ?1 )"); private static readonly ISQLFunction TrailingSpaceTrim = new SQLFunctionTemplate(NHibernateUtil.String, "rtrim( ?1 )"); @@ -76,11 +76,30 @@ public AnsiTrimEmulationFunction(string replaceFunction) #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.String; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => null; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Function/AnsiTrimFunction.cs b/src/NHibernate/Dialect/Function/AnsiTrimFunction.cs index b7160f6afc7..741a4be79dc 100644 --- a/src/NHibernate/Dialect/Function/AnsiTrimFunction.cs +++ b/src/NHibernate/Dialect/Function/AnsiTrimFunction.cs @@ -11,7 +11,6 @@ public AnsiTrimFunction() { } - #region IFunctionGrammar Members bool IFunctionGrammar.IsSeparator(string token) diff --git a/src/NHibernate/Dialect/Function/AvgQueryFunctionInfo.cs b/src/NHibernate/Dialect/Function/AvgQueryFunctionInfo.cs index 47f8903cb48..c780479747f 100644 --- a/src/NHibernate/Dialect/Function/AvgQueryFunctionInfo.cs +++ b/src/NHibernate/Dialect/Function/AvgQueryFunctionInfo.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using NHibernate.Engine; -using NHibernate.SqlTypes; using NHibernate.Type; namespace NHibernate.Dialect.Function @@ -10,27 +10,22 @@ class AvgQueryFunctionInfo : ClassicAggregateFunction { public AvgQueryFunctionInfo() : base("avg", false) { } + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public override IType ReturnType(IType columnType, IMapping mapping) { - if (columnType == null) - { - throw new ArgumentNullException("columnType"); - } - SqlType[] sqlTypes; - try - { - sqlTypes = columnType.SqlTypes(mapping); - } - catch (MappingException me) - { - throw new QueryException(me); - } + return GetReturnType(new[] {columnType}, mapping, true); + } - if (sqlTypes.Length != 1) + /// + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + if (!TryGetArgumentType(argumentTypes, mapping, throwOnError, out _, out _)) { - throw new QueryException("multi-column type can not be in avg()"); + return null; } + return NHibernateUtil.Double; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Function/BitwiseFunctionOperation.cs b/src/NHibernate/Dialect/Function/BitwiseFunctionOperation.cs index 5ebfbbe4c59..f6ae8af1345 100644 --- a/src/NHibernate/Dialect/Function/BitwiseFunctionOperation.cs +++ b/src/NHibernate/Dialect/Function/BitwiseFunctionOperation.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.Type; @@ -27,8 +29,9 @@ namespace NHibernate.Dialect.Function /// Treats bitwise operations as SQL function calls. /// [Serializable] - public class BitwiseFunctionOperation : ISQLFunction + public class BitwiseFunctionOperation : ISQLFunction, ISQLFunctionExtended { + // TODO 6.0: convert FunctionName to read-only auto-property private readonly string _functionName; /// @@ -45,11 +48,30 @@ public BitwiseFunctionOperation(string functionName) #region ISQLFunction Members /// + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int64; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => _functionName; + /// public bool HasArguments => true; diff --git a/src/NHibernate/Dialect/Function/BitwiseNativeOperation.cs b/src/NHibernate/Dialect/Function/BitwiseNativeOperation.cs index 01e9e462d97..cd7ad761b3c 100644 --- a/src/NHibernate/Dialect/Function/BitwiseNativeOperation.cs +++ b/src/NHibernate/Dialect/Function/BitwiseNativeOperation.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.Type; @@ -32,7 +34,7 @@ namespace NHibernate.Dialect.Function /// Treats bitwise operations as native operations. /// [Serializable] - public class BitwiseNativeOperation : ISQLFunction + public class BitwiseNativeOperation : ISQLFunction, ISQLFunctionExtended { private readonly string _sqlOpToken; private readonly bool _isUnary; @@ -65,11 +67,30 @@ public BitwiseNativeOperation(string sqlOpToken, bool isUnary) #region ISQLFunction Members /// + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int64; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => null; + /// public bool HasArguments => true; diff --git a/src/NHibernate/Dialect/Function/CastFunction.cs b/src/NHibernate/Dialect/Function/CastFunction.cs index 747dd90440a..7e1e38db9bc 100644 --- a/src/NHibernate/Dialect/Function/CastFunction.cs +++ b/src/NHibernate/Dialect/Function/CastFunction.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Xml; using NHibernate.Engine; using NHibernate.SqlCommand; @@ -12,10 +14,12 @@ namespace NHibernate.Dialect.Function /// ANSI-SQL style cast(foo as type) where the type is a NHibernate type /// [Serializable] - public class CastFunction : ISQLFunction, IFunctionGrammar + public class CastFunction : ISQLFunction, IFunctionGrammar, ISQLFunctionExtended { #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { //note there is a weird implementation in the client side @@ -23,6 +27,23 @@ public IType ReturnType(IType columnType, IMapping mapping) return columnType; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "cast"; + public bool HasArguments { get { return true; } @@ -33,7 +54,7 @@ public bool HasParenthesesIfNoArguments get { return true; } } - public SqlString Render(IList args, ISessionFactoryImplementor factory) + public virtual SqlString Render(IList args, ISessionFactoryImplementor factory) { if (args.Count != 2) { @@ -50,13 +71,8 @@ public SqlString Render(IList args, ISessionFactoryImplementor factory) { throw new QueryException("invalid NHibernate type for cast(), was:" + typeName); } + sqlType = factory.Dialect.GetCastTypeName(sqlTypeCodes[0]); - if (sqlType == null) - { - //TODO: never reached, since GetTypeName() actually throws an exception! - sqlType = typeName; - } - //else //{ // //trim off the length/precision/scale // int loc = sqlType.IndexOf('('); @@ -71,23 +87,36 @@ public SqlString Render(IList args, ISessionFactoryImplementor factory) throw new QueryException(string.Format("invalid Hibernate type for cast(): type {0} not found", typeName)); } - if (CastingIsRequired(sqlType)) - { - return new SqlString("cast(", args[0], " as ", sqlType, ")"); - } - else - { + // TODO 6.0: Remove pragma block with its content +#pragma warning disable 618 + if (!CastingIsRequired(sqlType)) return new SqlString("(", args[0], ")"); - } +#pragma warning restore 618 + + return Render(args[0], sqlType, factory); } #endregion + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] protected virtual bool CastingIsRequired(string sqlType) { return true; } + /// + /// Renders the SQL fragment representing the SQL cast. + /// + /// The cast argument. + /// The SQL type to cast to. + /// The session factory. + /// A SQL fragment. + protected virtual SqlString Render(object expression, string sqlType, ISessionFactoryImplementor factory) + { + return new SqlString("cast(", expression, " as ", sqlType, ")"); + } + #region IFunctionGrammar Members bool IFunctionGrammar.IsSeparator(string token) diff --git a/src/NHibernate/Dialect/Function/CharIndexFunction.cs b/src/NHibernate/Dialect/Function/CharIndexFunction.cs index 1cdfafbff8c..617f3a34ddf 100644 --- a/src/NHibernate/Dialect/Function/CharIndexFunction.cs +++ b/src/NHibernate/Dialect/Function/CharIndexFunction.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Text; using NHibernate.Engine; using NHibernate.SqlCommand; @@ -11,7 +13,7 @@ namespace NHibernate.Dialect.Function /// Emulation of locate() on Sybase /// [Serializable] - public class CharIndexFunction : ISQLFunction + public class CharIndexFunction : ISQLFunction, ISQLFunctionExtended { public CharIndexFunction() { @@ -19,11 +21,30 @@ public CharIndexFunction() #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int32; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "charindex"; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Function/ClassicAggregateFunction.cs b/src/NHibernate/Dialect/Function/ClassicAggregateFunction.cs index 4c080d62d59..fbbf803658b 100644 --- a/src/NHibernate/Dialect/Function/ClassicAggregateFunction.cs +++ b/src/NHibernate/Dialect/Function/ClassicAggregateFunction.cs @@ -1,15 +1,16 @@ using System; using System.Collections; -using System.Text; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; +using NHibernate.SqlTypes; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Dialect.Function { [Serializable] - public class ClassicAggregateFunction : ISQLFunction, IFunctionGrammar + public class ClassicAggregateFunction : ISQLFunction, IFunctionGrammar, ISQLFunctionExtended { private IType returnType = null; private readonly string name; @@ -40,11 +41,30 @@ public ClassicAggregateFunction(string name, bool acceptAsterisk, IType typeValu #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public virtual IType ReturnType(IType columnType, IMapping mapping) { return returnType ?? columnType; } + /// + public virtual IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => name; + public bool HasArguments { get { return true; } @@ -91,12 +111,68 @@ public SqlString Render(IList args, ISessionFactoryImplementor factory) #endregion + protected bool TryGetArgumentType( + IEnumerable argumentTypes, + IMapping mapping, + bool throwOnError, + out IType argumentType, + out SqlType sqlType) + { + sqlType = null; + argumentType = null; + if (argumentTypes.Count() != 1) + { + if (throwOnError) + { + throw new QueryException($"Invalid number of arguments for {name}()"); + } + + return false; + } + + argumentType = argumentTypes.First(); + if (argumentType == null) + { + // The argument is a parameter (e.g. select avg(:p1) from OrderLine). In that case, if the datatype is needed + // a QueryException will be thrown in SelectClause class, otherwise the query will be executed + // (e.g. select case when avg(:p1) > 0 then 1 else 0 end from OrderLine). + return false; + } + + SqlType[] sqlTypes; + try + { + sqlTypes = argumentType.SqlTypes(mapping); + } + catch (MappingException me) + { + if (throwOnError) + { + throw new QueryException(me); + } + + return false; + } + + if (sqlTypes.Length != 1) + { + if (throwOnError) + { + throw new QueryException($"Multi-column type can not be in {name}()"); + } + + return false; + } + + sqlType = sqlTypes[0]; + return true; + } + public override string ToString() { return name; } - #region IFunctionGrammar Members bool IFunctionGrammar.IsSeparator(string token) diff --git a/src/NHibernate/Dialect/Function/ClassicAvgFunction.cs b/src/NHibernate/Dialect/Function/ClassicAvgFunction.cs index ec802a85096..737ed7f2ae4 100644 --- a/src/NHibernate/Dialect/Function/ClassicAvgFunction.cs +++ b/src/NHibernate/Dialect/Function/ClassicAvgFunction.cs @@ -1,7 +1,7 @@ using System; +using System.Collections.Generic; using System.Data; using NHibernate.Engine; -using NHibernate.SqlTypes; using NHibernate.Type; namespace NHibernate.Dialect.Function @@ -16,36 +16,28 @@ public ClassicAvgFunction() : base("avg", false) { } + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public override IType ReturnType(IType columnType, IMapping mapping) { - if (columnType == null) - { - throw new ArgumentNullException("columnType"); - } - SqlType[] sqlTypes; - try - { - sqlTypes = columnType.SqlTypes(mapping); - } - catch (MappingException me) - { - throw new QueryException(me); - } + return GetReturnType(new[] {columnType}, mapping, true); + } - if (sqlTypes.Length != 1) + /// + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + if (!TryGetArgumentType(argumentTypes, mapping, throwOnError, out var argumentType, out var sqlType)) { - throw new QueryException("multi-column type can not be in avg()"); + return null; } - SqlType sqlType = sqlTypes[0]; - if (sqlType.DbType == DbType.Int16 || sqlType.DbType == DbType.Int32 || sqlType.DbType == DbType.Int64) { return NHibernateUtil.Single; } else { - return columnType; + return argumentType; } } } diff --git a/src/NHibernate/Dialect/Function/ClassicCountFunction.cs b/src/NHibernate/Dialect/Function/ClassicCountFunction.cs index 821146519e9..6cf138866df 100644 --- a/src/NHibernate/Dialect/Function/ClassicCountFunction.cs +++ b/src/NHibernate/Dialect/Function/ClassicCountFunction.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.Type; @@ -14,9 +16,19 @@ public ClassicCountFunction() : base("count", true) { } + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public override IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int32; } + + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + // 6.0 TODO: return NHibernateUtil.Int32; +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Function/CountQueryFunctionInfo.cs b/src/NHibernate/Dialect/Function/CountQueryFunctionInfo.cs index b201a7522ea..6f2d873be28 100644 --- a/src/NHibernate/Dialect/Function/CountQueryFunctionInfo.cs +++ b/src/NHibernate/Dialect/Function/CountQueryFunctionInfo.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.Type; @@ -9,9 +11,19 @@ class CountQueryFunctionInfo : ClassicAggregateFunction { public CountQueryFunctionInfo() : base("count", true) { } + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public override IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int64; } + + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + // 6.0 TODO: return NHibernateUtil.Int64; +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Function/EmulatedLengthSubstringFunction.cs b/src/NHibernate/Dialect/Function/EmulatedLengthSubstringFunction.cs index 3758e21c67a..288cad0840d 100644 --- a/src/NHibernate/Dialect/Function/EmulatedLengthSubstringFunction.cs +++ b/src/NHibernate/Dialect/Function/EmulatedLengthSubstringFunction.cs @@ -24,7 +24,6 @@ public EmulatedLengthSubstringFunction() { } - #region ISQLFunction Members public override SqlString Render(IList args, ISessionFactoryImplementor factory) @@ -48,6 +47,5 @@ public override SqlString Render(IList args, ISessionFactoryImplementor factory) } #endregion - } } diff --git a/src/NHibernate/Dialect/Function/ISQLFunction.cs b/src/NHibernate/Dialect/Function/ISQLFunction.cs index 4433e5dc63c..8ced743a4b3 100644 --- a/src/NHibernate/Dialect/Function/ISQLFunction.cs +++ b/src/NHibernate/Dialect/Function/ISQLFunction.cs @@ -1,4 +1,7 @@ +using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.Type; @@ -21,6 +24,8 @@ public interface ISQLFunction /// The type of the first argument /// /// + // Since v5.3 + [Obsolete("Use GetReturnType extension method instead.")] IType ReturnType(IType columnType, IMapping mapping); /// @@ -41,4 +46,84 @@ public interface ISQLFunction /// SQL fragment for the function. SqlString Render(IList args, ISessionFactoryImplementor factory); } + + // 6.0 TODO: Remove + public static class SQLFunctionExtensions + { + /// + /// Get the type that will be effectively returned by the underlying database. + /// + /// The sql function. + /// The types of arguments. + /// The mapping for retrieving the argument sql types. + /// Whether to throw when the number of arguments is invalid or they are not supported. + /// The type returned by the underlying database or when the number of arguments + /// is invalid or they are not supported. + /// When is set to and the + /// number of arguments is invalid or they are not supported. + public static IType GetEffectiveReturnType( + this ISQLFunction sqlFunction, + IEnumerable argumentTypes, + IMapping mapping, + bool throwOnError) + { + if (!(sqlFunction is ISQLFunctionExtended extendedSqlFunction)) + { + try + { +#pragma warning disable 618 + return sqlFunction.ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + catch (QueryException) + { + if (throwOnError) + { + throw; + } + + return null; + } + } + + return extendedSqlFunction.GetEffectiveReturnType(argumentTypes, mapping, throwOnError); + } + + /// + /// Get the function general return type, ignoring underlying database specifics. + /// + /// The sql function. + /// The types of arguments. + /// The mapping for retrieving the argument sql types. + /// Whether to throw when the number of arguments is invalid or they are not supported. + /// The type returned by the underlying database or when the number of arguments + /// is invalid or they are not supported. + public static IType GetReturnType( + this ISQLFunction sqlFunction, + IEnumerable argumentTypes, + IMapping mapping, + bool throwOnError) + { + if (!(sqlFunction is ISQLFunctionExtended extendedSqlFunction)) + { + try + { +#pragma warning disable 618 + return sqlFunction.ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + catch (QueryException) + { + if (throwOnError) + { + throw; + } + + return null; + } + } + + return extendedSqlFunction.GetReturnType(argumentTypes, mapping, throwOnError); + } + } } diff --git a/src/NHibernate/Dialect/Function/ISQLFunctionExtended.cs b/src/NHibernate/Dialect/Function/ISQLFunctionExtended.cs new file mode 100644 index 00000000000..eab9bdfcd32 --- /dev/null +++ b/src/NHibernate/Dialect/Function/ISQLFunctionExtended.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using NHibernate.Engine; +using NHibernate.Type; + +namespace NHibernate.Dialect.Function +{ + // 6.0 TODO: Merge into ISQLFunction + internal interface ISQLFunctionExtended : ISQLFunction + { + /// + /// The function name or when multiple functions/operators/statements are used. + /// + string Name { get; } + + /// + /// Get the function general return type, ignoring underlying database specifics. + /// + /// The types of arguments. + /// The mapping for retrieving the argument sql types. + /// Whether to throw when the number of arguments is invalid or they are not supported. + /// The type returned by the underlying database or when the number of arguments + /// is invalid or they are not supported. + IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError); + + /// + /// Get the type that will be effectively returned by the underlying database. + /// + /// The types of arguments. + /// The mapping for retrieving the argument sql types. + /// Whether to throw when the number of arguments is invalid or they are not supported. + /// The type returned by the underlying database or when the number of arguments + /// is invalid or they are not supported. + /// When is set to and the + /// number of arguments is invalid or they are not supported. + IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError); + } +} diff --git a/src/NHibernate/Dialect/Function/IifSQLFunction.cs b/src/NHibernate/Dialect/Function/IifSQLFunction.cs new file mode 100644 index 00000000000..3aee491dc3f --- /dev/null +++ b/src/NHibernate/Dialect/Function/IifSQLFunction.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Engine; +using NHibernate.Type; + +namespace NHibernate.Dialect.Function +{ + [Serializable] + internal class IifSQLFunction : SQLFunctionTemplate + { + public IifSQLFunction() : base(null, "case when ?1 then ?2 else ?3 end") + { + } + + /// + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + var args = argumentTypes.ToList(); + if (args.Count != 3) + { + if (throwOnError) + { + throw new QueryException($"Invalid number of arguments for iif()"); + } + + return null; + } + + return args[1] ?? args[2]; + } + } +} diff --git a/src/NHibernate/Dialect/Function/IifSafeSQLFunction.cs b/src/NHibernate/Dialect/Function/IifSafeSQLFunction.cs new file mode 100644 index 00000000000..7828d68b9aa --- /dev/null +++ b/src/NHibernate/Dialect/Function/IifSafeSQLFunction.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Engine; +using NHibernate.Type; + +namespace NHibernate.Dialect.Function +{ + [Serializable] + internal class IifSafeSQLFunction : StandardSafeSQLFunction + { + public IifSafeSQLFunction() : base("iif", 3) + { + } + + /// + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + var args = argumentTypes.ToList(); + if (args.Count != 3) + { + return null; // Not enough information + } + + return args[1] ?? args[2]; + } + } +} diff --git a/src/NHibernate/Dialect/Function/ModulusFunction.cs b/src/NHibernate/Dialect/Function/ModulusFunction.cs new file mode 100644 index 00000000000..46b287b4ab4 --- /dev/null +++ b/src/NHibernate/Dialect/Function/ModulusFunction.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using NHibernate.Engine; +using NHibernate.Type; + +namespace NHibernate.Dialect.Function +{ + [Serializable] + internal class ModulusFunction : StandardSafeSQLFunction + { + private readonly ModulusFunctionTypeDetector _modulusFunctionTypeDetector; + + public ModulusFunction(bool supportDecimals, bool supportFloatingNumbers) + : this(new ModulusFunctionTypeDetector(supportDecimals, supportFloatingNumbers)) + { + } + + public ModulusFunction(ModulusFunctionTypeDetector modulusFunction) : base("mod", NHibernateUtil.Int32, 2) + { + _modulusFunctionTypeDetector = modulusFunction; + } + + /// + public override IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return _modulusFunctionTypeDetector.GetReturnType(argumentTypes, mapping, throwOnError); + } + } +} diff --git a/src/NHibernate/Dialect/Function/ModulusFunctionTemplate.cs b/src/NHibernate/Dialect/Function/ModulusFunctionTemplate.cs new file mode 100644 index 00000000000..881cc7816be --- /dev/null +++ b/src/NHibernate/Dialect/Function/ModulusFunctionTemplate.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using NHibernate.Engine; +using NHibernate.Type; + +namespace NHibernate.Dialect.Function +{ + [Serializable] + internal class ModulusFunctionTemplate : SQLFunctionTemplate + { + private readonly ModulusFunctionTypeDetector _modulusFunctionTypeDetector; + + public ModulusFunctionTemplate(bool supportDecimals) : this(new ModulusFunctionTypeDetector(supportDecimals)) + { + } + + public ModulusFunctionTemplate(ModulusFunctionTypeDetector modulusFunction) : base(NHibernateUtil.Int32, "((?1) % (?2))") + { + _modulusFunctionTypeDetector = modulusFunction; + } + + /// + public override IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return _modulusFunctionTypeDetector.GetReturnType(argumentTypes, mapping, throwOnError); + } + } +} diff --git a/src/NHibernate/Dialect/Function/ModulusFunctionTypeDetector.cs b/src/NHibernate/Dialect/Function/ModulusFunctionTypeDetector.cs new file mode 100644 index 00000000000..eab42090b12 --- /dev/null +++ b/src/NHibernate/Dialect/Function/ModulusFunctionTypeDetector.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Data; +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.Type; + +namespace NHibernate.Dialect.Function +{ + [Serializable] + internal class ModulusFunctionTypeDetector + { + // The supported DbTypes with their priorities in order to detect which is the + // returned type when mixing them + private readonly Lazy>> _supportedDbTypesLazy; + private readonly bool _supportDecimals; + private readonly bool _supportFloatingNumbers; + + public ModulusFunctionTypeDetector(bool supportDecimals, bool supportFloatingNumbers) + { + _supportDecimals = supportDecimals; + _supportFloatingNumbers = supportFloatingNumbers; + _supportedDbTypesLazy = new Lazy>>(GetSupportedTypes); + } + + public ModulusFunctionTypeDetector(bool supportDecimals) : this(supportDecimals, false) + { + } + + protected virtual Dictionary> GetSupportedTypes() + { + var types = new Dictionary>() + { + {DbType.Int16, new KeyValuePair(1, NHibernateUtil.Int16)}, + {DbType.Int32, new KeyValuePair(2, NHibernateUtil.Int32)}, + {DbType.Int64, new KeyValuePair(3, NHibernateUtil.Int64)}, + }; + + if (_supportDecimals) + { + types.Add(DbType.Currency, new KeyValuePair(4, NHibernateUtil.Decimal)); + types.Add(DbType.Decimal, new KeyValuePair(4, NHibernateUtil.Decimal)); + } + + if (_supportFloatingNumbers) + { + types.Add(DbType.Single, new KeyValuePair(5, NHibernateUtil.Single)); + types.Add(DbType.Double, new KeyValuePair(6, NHibernateUtil.Double)); + } + + return types; + } + + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + KeyValuePair currentReturnType = default; + int totalArguments = 0; + foreach (var argumentType in argumentTypes) + { + if (argumentType == null) + { + return null; + } + + SqlType[] sqlTypes; + try + { + sqlTypes = argumentType.SqlTypes(mapping); + } + catch (MappingException me) + { + if (throwOnError) + { + throw new QueryException(me); + } + + return null; + } + + if (sqlTypes.Length != 1) + { + return ThrowOrReturnDefault("Multi-column type can not be in mod()", throwOnError); + } + + if (!_supportedDbTypesLazy.Value.TryGetValue(sqlTypes[0].DbType, out var returnType)) + { + return ThrowOrReturnDefault($"DbType {sqlTypes[0].DbType} is not supported for mod()", throwOnError); + } + + if (returnType.Key > currentReturnType.Key) + { + currentReturnType = returnType; + } + + totalArguments++; + } + + return totalArguments == 2 + ? currentReturnType.Value + : ThrowOrReturnDefault("Invalid number of arguments for mod()", throwOnError); + } + + private IType ThrowOrReturnDefault(string error, bool throwOnError) + { + if (throwOnError) + { + throw new QueryException(error); + } + + return null; + } + } +} diff --git a/src/NHibernate/Dialect/Function/NoArgSQLFunction.cs b/src/NHibernate/Dialect/Function/NoArgSQLFunction.cs index e8e700a9773..c3b5092f6a6 100644 --- a/src/NHibernate/Dialect/Function/NoArgSQLFunction.cs +++ b/src/NHibernate/Dialect/Function/NoArgSQLFunction.cs @@ -3,6 +3,8 @@ using NHibernate.SqlCommand; using NHibernate.Type; using System; +using System.Collections.Generic; +using System.Linq; namespace NHibernate.Dialect.Function { @@ -10,7 +12,7 @@ namespace NHibernate.Dialect.Function /// Summary description for NoArgSQLFunction. /// [Serializable] - public class NoArgSQLFunction : ISQLFunction + public class NoArgSQLFunction : ISQLFunction, ISQLFunctionExtended { public NoArgSQLFunction(string name, IType returnType) : this(name, returnType, true) @@ -26,15 +28,32 @@ public NoArgSQLFunction(string name, IType returnType, bool hasParenthesesIfNoAr public IType FunctionReturnType { get; protected set; } - public string Name { get; protected set; } - #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return FunctionReturnType; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name { get; protected set; } + public bool HasArguments { get { return false; } diff --git a/src/NHibernate/Dialect/Function/NvlFunction.cs b/src/NHibernate/Dialect/Function/NvlFunction.cs index aa17cf0fbd1..68f881f4eac 100644 --- a/src/NHibernate/Dialect/Function/NvlFunction.cs +++ b/src/NHibernate/Dialect/Function/NvlFunction.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.Type; @@ -10,7 +12,7 @@ namespace NHibernate.Dialect.Function /// Emulation of coalesce() on Oracle, using multiple nvl() calls /// [Serializable] - public class NvlFunction : ISQLFunction + public class NvlFunction : ISQLFunction, ISQLFunctionExtended { public NvlFunction() { @@ -18,11 +20,30 @@ public NvlFunction() #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return columnType; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "nvl"; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Function/PositionSubstringFunction.cs b/src/NHibernate/Dialect/Function/PositionSubstringFunction.cs index 9a89bda39c4..ca99935f2a4 100644 --- a/src/NHibernate/Dialect/Function/PositionSubstringFunction.cs +++ b/src/NHibernate/Dialect/Function/PositionSubstringFunction.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Text; using Antlr.Runtime; using NHibernate.Engine; @@ -12,7 +14,7 @@ namespace NHibernate.Dialect.Function /// Emulation of locate() on PostgreSQL /// [Serializable] - public class PositionSubstringFunction : ISQLFunction + public class PositionSubstringFunction : ISQLFunction, ISQLFunctionExtended { public PositionSubstringFunction() { @@ -20,11 +22,30 @@ public PositionSubstringFunction() #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int32; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "position"; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Function/SQLFunctionTemplate.cs b/src/NHibernate/Dialect/Function/SQLFunctionTemplate.cs index 976a5f0e127..a3c9704df77 100644 --- a/src/NHibernate/Dialect/Function/SQLFunctionTemplate.cs +++ b/src/NHibernate/Dialect/Function/SQLFunctionTemplate.cs @@ -6,6 +6,8 @@ using NHibernate.SqlCommand; using NHibernate.Type; using System; +using System.Collections.Generic; +using System.Linq; namespace NHibernate.Dialect.Function { @@ -18,7 +20,7 @@ namespace NHibernate.Dialect.Function /// parameters with '?' followed by parameter's index (first index is 1). /// [Serializable] - public class SQLFunctionTemplate : ISQLFunction + public class SQLFunctionTemplate : ISQLFunction, ISQLFunctionExtended { private const int InvalidArgumentIndex = -1; private static readonly Regex SplitRegex = new Regex("(\\?[0-9]+)"); @@ -80,11 +82,30 @@ private void InitFromTemplate() #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return (returnType == null) ? columnType : returnType; } + /// + public virtual IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public virtual string Name => null; + public bool HasArguments { get { return hasArguments; } diff --git a/src/NHibernate/Dialect/Function/StandardSQLFunction.cs b/src/NHibernate/Dialect/Function/StandardSQLFunction.cs index df1adc53f09..a450506daa8 100644 --- a/src/NHibernate/Dialect/Function/StandardSQLFunction.cs +++ b/src/NHibernate/Dialect/Function/StandardSQLFunction.cs @@ -4,6 +4,8 @@ using NHibernate.SqlCommand; using NHibernate.Type; using System; +using System.Collections.Generic; +using System.Linq; namespace NHibernate.Dialect.Function { @@ -16,7 +18,7 @@ namespace NHibernate.Dialect.Function /// for processing of the associated function. /// [Serializable] - public class StandardSQLFunction : ISQLFunction + public class StandardSQLFunction : ISQLFunction, ISQLFunctionExtended { private IType returnType = null; protected readonly string name; @@ -43,11 +45,30 @@ public StandardSQLFunction(string name, IType typeValue) #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public virtual IType ReturnType(IType columnType, IMapping mapping) { return returnType ?? columnType; } + /// + public virtual IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => name; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Function/SumQueryFunctionInfo.cs b/src/NHibernate/Dialect/Function/SumQueryFunctionInfo.cs index 3ee6c7d6f2e..6c0af160841 100644 --- a/src/NHibernate/Dialect/Function/SumQueryFunctionInfo.cs +++ b/src/NHibernate/Dialect/Function/SumQueryFunctionInfo.cs @@ -1,7 +1,7 @@ using System; +using System.Collections.Generic; using System.Data; using NHibernate.Engine; -using NHibernate.SqlTypes; using NHibernate.Type; namespace NHibernate.Dialect.Function @@ -12,29 +12,21 @@ class SumQueryFunctionInfo : ClassicAggregateFunction public SumQueryFunctionInfo() : base("sum", false) { } //H3.2 behavior + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public override IType ReturnType(IType columnType, IMapping mapping) { - if (columnType == null) - { - throw new ArgumentNullException("columnType"); - } - SqlType[] sqlTypes; - try - { - sqlTypes = columnType.SqlTypes(mapping); - } - catch (MappingException me) - { - throw new QueryException(me); - } + return GetReturnType(new[] { columnType }, mapping, true); + } - if (sqlTypes.Length != 1) + /// + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + if (!TryGetArgumentType(argumentTypes, mapping, throwOnError, out var argumentType, out var sqlType)) { - throw new QueryException("multi-column type can not be in sum()"); + return null; } - SqlType sqlType = sqlTypes[0]; - // TODO: (H3.2 for nullable types) First allow the actual type to control the return value. (the actual underlying sqltype could actually be different) // finally use the sqltype if == on Hibernate types did not find a match. @@ -57,8 +49,8 @@ public override IType ReturnType(IType columnType, IMapping mapping) return NHibernateUtil.UInt64; default: - return columnType; + return argumentType; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Function/TransparentCastFunction.cs b/src/NHibernate/Dialect/Function/TransparentCastFunction.cs index 3572fc422ce..dcba86da2e9 100644 --- a/src/NHibernate/Dialect/Function/TransparentCastFunction.cs +++ b/src/NHibernate/Dialect/Function/TransparentCastFunction.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using NHibernate.Engine; +using NHibernate.SqlCommand; namespace NHibernate.Dialect.Function { @@ -8,9 +11,23 @@ namespace NHibernate.Dialect.Function [Serializable] public class TransparentCastFunction : CastFunction { + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] protected override bool CastingIsRequired(string sqlType) { return false; } + + /// + /// Renders the SQL fragment representing the casted expression without actually casting it. + /// + /// The cast argument. + /// The SQL type to cast to, ignored for rendering. + /// The session factory. + /// A SQL fragment. + protected override SqlString Render(object expression, string sqlType, ISessionFactoryImplementor factory) + { + return new SqlString("(", expression, ")"); + } } } diff --git a/src/NHibernate/Dialect/Function/VarArgsSQLFunction.cs b/src/NHibernate/Dialect/Function/VarArgsSQLFunction.cs index 457a1b86d8e..edd434f0927 100644 --- a/src/NHibernate/Dialect/Function/VarArgsSQLFunction.cs +++ b/src/NHibernate/Dialect/Function/VarArgsSQLFunction.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Text; using NHibernate.Engine; using NHibernate.SqlCommand; @@ -12,7 +14,7 @@ namespace NHibernate.Dialect.Function /// with an unlimited number of arguments. /// [Serializable] - public class VarArgsSQLFunction : ISQLFunction + public class VarArgsSQLFunction : ISQLFunction, ISQLFunctionExtended { private readonly string begin; private readonly string sep; @@ -34,11 +36,30 @@ public VarArgsSQLFunction(IType type, string begin, string sep, string end) #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public virtual IType ReturnType(IType columnType, IMapping mapping) { return (returnType == null) ? columnType : returnType; } + /// + public virtual IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public virtual IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public virtual string Name => null; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/GenericDialect.cs b/src/NHibernate/Dialect/GenericDialect.cs index a55c638d55e..2545630d291 100644 --- a/src/NHibernate/Dialect/GenericDialect.cs +++ b/src/NHibernate/Dialect/GenericDialect.cs @@ -1,4 +1,3 @@ - using System.Data; namespace NHibernate.Dialect diff --git a/src/NHibernate/Dialect/HanaDialectBase.cs b/src/NHibernate/Dialect/HanaDialectBase.cs index e76226eb449..650965a2c06 100644 --- a/src/NHibernate/Dialect/HanaDialectBase.cs +++ b/src/NHibernate/Dialect/HanaDialectBase.cs @@ -1,7 +1,9 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Linq; using NHibernate.Dialect.Function; using NHibernate.Dialect.Schema; using NHibernate.Engine; @@ -17,7 +19,7 @@ namespace NHibernate.Dialect public abstract class HanaDialectBase : Dialect { [Serializable] - private class TypeConvertingVarArgsSQLFunction : ISQLFunction + private class TypeConvertingVarArgsSQLFunction : ISQLFunction, ISQLFunctionExtended { private readonly string _begin; private readonly string _sep; @@ -33,12 +35,31 @@ public TypeConvertingVarArgsSQLFunction(string begin, string sep, string end) #region ISQLFunction Members + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { _type = columnType.SqlTypes(mapping)[0]; return columnType; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public virtual string Name => "cast"; + public bool HasArguments => true; public bool HasParenthesesIfNoArguments => true; @@ -392,9 +413,11 @@ protected virtual void RegisterNHibernateFunctions() RegisterFunction("ceiling", new StandardSQLFunction("ceil")); RegisterFunction("chr", new StandardSQLFunction("char", NHibernateUtil.AnsiChar)); RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.Date, "to_date(?1)")); - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("iif", new IifSQLFunction()); RegisterFunction("sysdate", new NoArgSQLFunction("current_timestamp", NHibernateUtil.DateTime, false)); RegisterFunction("truncate", new SQLFunctionTemplateWithRequiredParameters(null, "floor(?1 * power(10, ?2)) / power(10, ?2)", new object[] { null, "0" })); + RegisterFunction("new_uuid", new NoArgSQLFunction("sysuuid", NHibernateUtil.Guid, false)); + RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double)); } protected virtual void RegisterHANAFunctions() @@ -439,20 +462,20 @@ protected virtual void RegisterHANAFunctions() RegisterFunction("cosh", new StandardSQLFunction("cosh", NHibernateUtil.Double)); RegisterFunction("cot", new StandardSQLFunction("cot", NHibernateUtil.Double)); RegisterFunction("current_connection", new NoArgSQLFunction("current_connection", NHibernateUtil.Int32)); - RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.DateTime, false)); + RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.LocalDate, false)); RegisterFunction("current_identity_value", new NoArgSQLFunction("current_identity_value", NHibernateUtil.Int64)); RegisterFunction("current_mvcc_snapshot_timestamp", new NoArgSQLFunction("current_mvcc_snapshot_timestamp", NHibernateUtil.Int32)); RegisterFunction("current_object_schema", new NoArgSQLFunction("current_object_schema", NHibernateUtil.String)); RegisterFunction("current_schema", new NoArgSQLFunction("current_schema", NHibernateUtil.String, false)); RegisterFunction("current_time", new NoArgSQLFunction("current_time", NHibernateUtil.DateTime, false)); - RegisterFunction("current_timestamp", new NoArgSQLFunction("current_timestamp", NHibernateUtil.DateTime, false)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("current_timestamp", NHibernateUtil.LocalDateTime, false)); RegisterFunction("current_transaction_isolation_level", new NoArgSQLFunction("current_transaction_isolation_level", NHibernateUtil.String, false)); RegisterFunction("current_update_statement_sequence", new NoArgSQLFunction("current_update_statement_sequence", NHibernateUtil.Int64)); RegisterFunction("current_update_transaction", new NoArgSQLFunction("current_update_transaction", NHibernateUtil.Int64)); RegisterFunction("current_user", new NoArgSQLFunction("current_user", NHibernateUtil.String, false)); RegisterFunction("current_utcdate", new NoArgSQLFunction("current_utcdate", NHibernateUtil.DateTime, false)); RegisterFunction("current_utctime", new NoArgSQLFunction("current_utctime", NHibernateUtil.DateTime, false)); - RegisterFunction("current_utctimestamp", new NoArgSQLFunction("current_utctimestamp", NHibernateUtil.DateTime, false)); + RegisterFunction("current_utctimestamp", new NoArgSQLFunction("current_utctimestamp", NHibernateUtil.UtcDateTime, false)); RegisterFunction("dayname", new StandardSQLFunction("dayname", NHibernateUtil.String)); RegisterFunction("dayofmonth", new StandardSQLFunction("dayofmonth", NHibernateUtil.Int32)); RegisterFunction("dayofyear", new StandardSQLFunction("dayofyear", NHibernateUtil.Int32)); @@ -496,7 +519,7 @@ protected virtual void RegisterHANAFunctions() RegisterFunction("map", new VarArgsSQLFunction("map(", ",", ")")); RegisterFunction("mimetype", new StandardSQLFunction("mimetype", NHibernateUtil.String)); RegisterFunction("minute", new StandardSQLFunction("minute", NHibernateUtil.Int32)); - RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); + RegisterFunction("mod", new ModulusFunction(false, false)); RegisterFunction("month", new StandardSQLFunction("month", NHibernateUtil.Int32)); RegisterFunction("monthname", new StandardSQLFunction("monthname", NHibernateUtil.String)); RegisterFunction("months_between", new StandardSQLFunction("months_between", NHibernateUtil.Int32)); @@ -583,7 +606,6 @@ protected virtual void RegisterHANAFunctions() RegisterFunction("xmltable", new StandardSQLFunction("xmltable")); RegisterFunction("year", new StandardSQLFunction("year", NHibernateUtil.Int32)); RegisterFunction("years_between", new StandardSQLFunction("years_between", NHibernateUtil.Int32)); - } #region DDL support diff --git a/src/NHibernate/Dialect/InformixDialect.cs b/src/NHibernate/Dialect/InformixDialect.cs index 5c67f32c01b..2f942815a57 100644 --- a/src/NHibernate/Dialect/InformixDialect.cs +++ b/src/NHibernate/Dialect/InformixDialect.cs @@ -68,7 +68,7 @@ public InformixDialect() RegisterFunction("substr", new StandardSQLFunction("substr")); // RegisterFunction("trim", new AnsiTrimFunction()); // defined in base class - // RegisterFunction("length", new StandardSQLFunction("length", NHibernateUtil.Int32)); // defined in base class + // RegisterFunction("length", new StandardSQLFunction("length", NHibernateUtil.Int32)); // defined in base class RegisterFunction("coalesce", new NvlFunction()); // base class override // RegisterFunction("abs", new StandardSQLFunction("abs")); // RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); @@ -78,7 +78,8 @@ public InformixDialect() // RegisterFunction("cast", new CastFunction()); // RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(", "||", ")")); - RegisterFunction("current_timestamp", new NoArgSQLFunction("current", NHibernateUtil.DateTime, false)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("current", NHibernateUtil.LocalDateTime, false)); + RegisterFunction("current_date", new NoArgSQLFunction("today", NHibernateUtil.LocalDate, false)); RegisterFunction("sysdate", new NoArgSQLFunction("today", NHibernateUtil.DateTime, false)); RegisterFunction("current", new NoArgSQLFunction("current", NHibernateUtil.DateTime, false)); RegisterFunction("today", new NoArgSQLFunction("today", NHibernateUtil.DateTime, false)); diff --git a/src/NHibernate/Dialect/InformixDialect0940.cs b/src/NHibernate/Dialect/InformixDialect0940.cs index 59ef5b7a6e6..bdfcae4f27f 100644 --- a/src/NHibernate/Dialect/InformixDialect0940.cs +++ b/src/NHibernate/Dialect/InformixDialect0940.cs @@ -8,7 +8,6 @@ //using NHibernate.Dialect.Schema; using Environment = NHibernate.Cfg.Environment; - namespace NHibernate.Dialect { /// @@ -42,7 +41,6 @@ public InformixDialect0940() RegisterColumnType(DbType.String, 2147483647, "CLOB"); } - /// Get the select command used retrieve the names of all sequences. /// The select command; or null if sequences are not supported. public override string QuerySequencesString @@ -106,8 +104,7 @@ public override string GetCreateSequenceString(string sequenceName) return "create sequence " + sequenceName; // + //" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE CACHE 20 NOORDER"; - - } + } // in .NET overloaded version cannot be overriden (in Java allowed) //protected override string GetCreateSequenceString(string sequenceName, int initialValue, int incrementSize) @@ -129,7 +126,10 @@ public override JoinFragment CreateOuterJoinFragment() return new ANSIJoinFragment(); } - /// + /// + public override bool SupportsCrossJoin => false; + + /// /// Does this Dialect have some kind of LIMIT syntax? /// /// False, unless overridden. diff --git a/src/NHibernate/Dialect/InformixDialect1000.cs b/src/NHibernate/Dialect/InformixDialect1000.cs index db4c9781591..cc231b26263 100644 --- a/src/NHibernate/Dialect/InformixDialect1000.cs +++ b/src/NHibernate/Dialect/InformixDialect1000.cs @@ -7,7 +7,6 @@ //using NHibernate.Dialect.Schema; using Environment = NHibernate.Cfg.Environment; - namespace NHibernate.Dialect { /// @@ -54,4 +53,4 @@ public override bool SupportsLimitOffset get { return true; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/MsSql2000Dialect.cs b/src/NHibernate/Dialect/MsSql2000Dialect.cs index 5cada797985..1aec544ed2d 100644 --- a/src/NHibernate/Dialect/MsSql2000Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2000Dialect.cs @@ -286,7 +286,8 @@ protected virtual void RegisterKeywords() protected virtual void RegisterFunctions() { - RegisterFunction("count", new CountBigQueryFunction()); + RegisterFunction("count", new CountQueryFunction()); + RegisterFunction("count_big", new CountBigQueryFunction()); RegisterFunction("abs", new StandardSQLFunction("abs")); RegisterFunction("absval", new StandardSQLFunction("absval")); @@ -312,9 +313,11 @@ protected virtual void RegisterFunctions() RegisterFunction("ln", new StandardSQLFunction("ln", NHibernateUtil.Double)); RegisterFunction("log", new StandardSQLFunction("log", NHibernateUtil.Double)); RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double)); - RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))")); + RegisterFunction("mod", new ModulusFunctionTemplate(true)); RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double)); RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); + // SQL Server rand returns the same value for each row, unless hacking it with a random seed per row + RegisterFunction("random", new SQLFunctionTemplate(NHibernateUtil.Double, "rand(checksum(newid()))")); RegisterFunction("sin", new StandardSQLFunction("sin", NHibernateUtil.Double)); RegisterFunction("soundex", new StandardSQLFunction("soundex", NHibernateUtil.String)); RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double)); @@ -326,7 +329,9 @@ protected virtual void RegisterFunctions() RegisterFunction("right", new SQLFunctionTemplate(NHibernateUtil.String, "right(?1, ?2)")); RegisterFunction("locate", new StandardSQLFunction("charindex", NHibernateUtil.Int32)); - RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.DateTime, true)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.LocalDateTime, true)); + RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.LocalDate, "dateadd(dd, 0, datediff(dd, 0, getdate()))")); + RegisterFunction("current_utctimestamp", new NoArgSQLFunction("getutcdate", NHibernateUtil.UtcDateTime, true)); RegisterFunction("second", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(second, ?1)")); RegisterFunction("minute", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(minute, ?1)")); RegisterFunction("hour", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(hour, ?1)")); @@ -346,7 +351,7 @@ protected virtual void RegisterFunctions() RegisterFunction("ltrim", new StandardSQLFunction("ltrim")); RegisterFunction("trim", new AnsiTrimEmulationFunction()); - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("iif", new IifSQLFunction()); RegisterFunction("replace", new StandardSafeSQLFunction("replace", NHibernateUtil.String, 3)); // Casting to CHAR (without specified length) truncates to 30 characters. @@ -358,6 +363,8 @@ protected virtual void RegisterFunctions() RegisterFunction("bit_length", new SQLFunctionTemplate(NHibernateUtil.Int32, "datalength(?1) * 8")); RegisterFunction("extract", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(?1, ?3)")); + + RegisterFunction("new_uuid", new NoArgSQLFunction("newid", NHibernateUtil.Guid)); } protected virtual void RegisterGuidTypeMapping() @@ -698,11 +705,15 @@ protected virtual string GetSelectExistingObject(string catalog, string schema, [Serializable] protected class CountBigQueryFunction : ClassicAggregateFunction { - public CountBigQueryFunction() : base("count_big", true) { } + public CountBigQueryFunction() : base("count_big", true, NHibernateUtil.Int64) { } + } - public override IType ReturnType(IType columnType, IMapping mapping) + [Serializable] + private class CountQueryFunction : CountQueryFunctionInfo + { + public override IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) { - return NHibernateUtil.Int64; + return NHibernateUtil.Int32; } } diff --git a/src/NHibernate/Dialect/MsSql2008Dialect.cs b/src/NHibernate/Dialect/MsSql2008Dialect.cs index 7c40549a700..d0ef5580389 100644 --- a/src/NHibernate/Dialect/MsSql2008Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2008Dialect.cs @@ -51,11 +51,20 @@ protected override void RegisterFunctions() { RegisterFunction( "current_timestamp", - new NoArgSQLFunction("sysdatetime", NHibernateUtil.DateTime, true)); + new NoArgSQLFunction("sysdatetime", NHibernateUtil.LocalDateTime, true)); + RegisterFunction( + "current_utctimestamp", + new NoArgSQLFunction("sysutcdatetime", NHibernateUtil.UtcDateTime, true)); } + + RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.LocalDate, "cast(getdate() as date)")); RegisterFunction( "current_timestamp_offset", new NoArgSQLFunction("sysdatetimeoffset", NHibernateUtil.DateTimeOffset, true)); + RegisterFunction( + "current_utctimestamp_offset", + new SQLFunctionTemplate(NHibernateUtil.DateTimeOffset, "todatetimeoffset(sysutcdatetime(), 0)")); + RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.Date, "cast(?1 as date)")); } protected override void RegisterKeywords() diff --git a/src/NHibernate/Dialect/MsSql2012Dialect.cs b/src/NHibernate/Dialect/MsSql2012Dialect.cs index 8649d6861db..7ec84ef66f9 100644 --- a/src/NHibernate/Dialect/MsSql2012Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2012Dialect.cs @@ -53,7 +53,7 @@ public override string QuerySequencesString protected override void RegisterFunctions() { base.RegisterFunctions(); - RegisterFunction("iif", new StandardSafeSQLFunction("iif", 3)); + RegisterFunction("iif", new IifSafeSQLFunction()); } public override SqlString GetLimitString(SqlString querySqlString, SqlString offset, SqlString limit) diff --git a/src/NHibernate/Dialect/MsSqlCeDialect.cs b/src/NHibernate/Dialect/MsSqlCeDialect.cs index 90da41cf9bb..86edd426e6e 100644 --- a/src/NHibernate/Dialect/MsSqlCeDialect.cs +++ b/src/NHibernate/Dialect/MsSqlCeDialect.cs @@ -172,7 +172,8 @@ protected virtual void RegisterFunctions() RegisterFunction("str", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as nvarchar)")); RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as nvarchar)")); - RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.DateTime, true)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.LocalDateTime, true)); + RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.LocalDate, "dateadd(dd, 0, datediff(dd, 0, getdate()))")); RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.DateTime, "dateadd(dd, 0, datediff(dd, 0, ?1))")); RegisterFunction("second", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(second, ?1)")); RegisterFunction("minute", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(minute, ?1)")); @@ -190,16 +191,19 @@ protected virtual void RegisterFunctions() RegisterFunction("lower", new StandardSQLFunction("lower")); RegisterFunction("trim", new AnsiTrimEmulationFunction()); - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("iif", new IifSQLFunction()); RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(", "+", ")")); - RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))")); + // Modulo is not supported on real, float, money, and numeric data types + RegisterFunction("mod", new ModulusFunctionTemplate(false)); RegisterFunction("round", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0"})); RegisterFunction("truncate", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0", "1"})); RegisterFunction("bit_length", new SQLFunctionTemplate(NHibernateUtil.Int32, "datalength(?1) * 8")); RegisterFunction("extract", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(?1, ?3)")); + + RegisterFunction("new_uuid", new NoArgSQLFunction("newid", NHibernateUtil.Guid)); } protected virtual void RegisterDefaultProperties() diff --git a/src/NHibernate/Dialect/MySQL55Dialect.cs b/src/NHibernate/Dialect/MySQL55Dialect.cs index 9c47c111221..26dd7de2709 100644 --- a/src/NHibernate/Dialect/MySQL55Dialect.cs +++ b/src/NHibernate/Dialect/MySQL55Dialect.cs @@ -1,4 +1,3 @@ - using System.Data; using NHibernate.Dialect.Function; @@ -11,5 +10,12 @@ public MySQL55Dialect() RegisterColumnType(DbType.Guid, "CHAR(36)"); RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "?1")); } + + protected override void RegisterFunctions() + { + base.RegisterFunctions(); + + RegisterFunction("current_utctimestamp", new NoArgSQLFunction("UTC_TIMESTAMP", NHibernateUtil.UtcDateTime, true)); + } } } diff --git a/src/NHibernate/Dialect/MySQL57Dialect.cs b/src/NHibernate/Dialect/MySQL57Dialect.cs index 8c06d81ee7f..1d397d39511 100644 --- a/src/NHibernate/Dialect/MySQL57Dialect.cs +++ b/src/NHibernate/Dialect/MySQL57Dialect.cs @@ -16,5 +16,8 @@ public MySQL57Dialect() /// public override bool SupportsDateTimeScale => true; + + /// + public override bool SupportsRowValueConstructorSyntaxInInList => true; } } diff --git a/src/NHibernate/Dialect/MySQL5Dialect.cs b/src/NHibernate/Dialect/MySQL5Dialect.cs index 1dfac2f6f46..0797206b02a 100644 --- a/src/NHibernate/Dialect/MySQL5Dialect.cs +++ b/src/NHibernate/Dialect/MySQL5Dialect.cs @@ -12,8 +12,14 @@ public MySQL5Dialect() // My SQL supports precision up to 65, but .Net is limited to 28-29. RegisterColumnType(DbType.Decimal, 29, "DECIMAL($p, $s)"); RegisterColumnType(DbType.Guid, "BINARY(16)"); + } + + protected override void RegisterFunctions() + { + base.RegisterFunctions(); RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "concat(hex(reverse(substr(?1, 1, 4))), '-', hex(reverse(substring(?1, 5, 2))), '-', hex(reverse(substr(?1, 7, 2))), '-', hex(substr(?1, 9, 2)), '-', hex(substr(?1, 11)))")); + RegisterFunction("new_uuid", new NoArgSQLFunction("uuid", NHibernateUtil.Guid)); } protected override void RegisterCastTypes() diff --git a/src/NHibernate/Dialect/MySQLDialect.cs b/src/NHibernate/Dialect/MySQLDialect.cs index 59539395226..5ecbf270402 100644 --- a/src/NHibernate/Dialect/MySQLDialect.cs +++ b/src/NHibernate/Dialect/MySQLDialect.cs @@ -1,11 +1,15 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Linq; using System.Text; using NHibernate.Dialect.Function; using NHibernate.Dialect.Schema; +using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.SqlTypes; +using NHibernate.Type; using NHibernate.Util; using Environment=NHibernate.Cfg.Environment; @@ -43,7 +47,6 @@ public MySQLDialect() //default: //http://dev.mysql.com/doc/refman/5.0/en/data-type-defaults.html - //string type RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); @@ -152,6 +155,7 @@ public MySQLDialect() "float8", "force", "fulltext", + "goto", "high_priority", "hour_microsecond", "hour_minute", @@ -167,6 +171,7 @@ public MySQLDialect() "key", "keys", "kill", + "label", "limit", "linear", "lines", @@ -176,6 +181,7 @@ public MySQLDialect() "longblob", "longtext", "low_priority", + "master_ssl_verify_server_cert", "mediumblob", "mediumint", "mediumtext", @@ -204,6 +210,7 @@ public MySQLDialect() "second_microsecond", "separator", "show", + "shutdown", "spatial", "sql_big_result", "sql_calc_found_rows", @@ -243,7 +250,8 @@ protected virtual void RegisterKeywords() protected virtual void RegisterFunctions() { - RegisterFunction("iif", new StandardSQLFunction("if")); + RegisterFunction("iif", new IfSQLFunction()); + RegisterFunction("mod", new ModulusFunction(true, true)); RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32)); @@ -266,7 +274,8 @@ protected virtual void RegisterFunctions() RegisterFunction("truncate", new StandardSQLFunctionWithRequiredParameters("truncate", new object[] {null, "0"})); RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); - + RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double)); + RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); RegisterFunction("stddev", new StandardSQLFunction("stddev", NHibernateUtil.Double)); @@ -295,7 +304,7 @@ protected virtual void RegisterFunctions() RegisterFunction("hex", new StandardSQLFunction("hex", NHibernateUtil.String)); RegisterFunction("soundex", new StandardSQLFunction("soundex", NHibernateUtil.String)); - RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.Date, false)); + RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.LocalDate, false)); RegisterFunction("current_time", new NoArgSQLFunction("current_time", NHibernateUtil.Time, false)); RegisterFunction("second", new StandardSQLFunction("second", NHibernateUtil.Int32)); @@ -498,6 +507,10 @@ protected void RegisterCastType(DbType code, int capacity, string name) public override string GetCastTypeName(SqlType sqlType) => GetCastTypeName(sqlType, castTypeNames); + /// + public override bool TryGetCastTypeName(SqlType sqlType, out string typeName) => + TryGetCastTypeName(sqlType, castTypeNames, out typeName); + public override long TimestampResolutionInTicks { get @@ -545,5 +558,30 @@ public override long TimestampResolutionInTicks public override bool SupportsDistributedTransactions => false; #endregion + + [Serializable] + internal class IfSQLFunction : StandardSQLFunction + { + public IfSQLFunction() : base("if") + { + } + + /// + public override IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + var args = argumentTypes.ToList(); + if (args.Count != 3) + { + if (throwOnError) + { + throw new QueryException($"Invalid number of arguments for iif()"); + } + + return null; + } + + return args[1] ?? args[2]; + } + } } } diff --git a/src/NHibernate/Dialect/Oracle10gDialect.cs b/src/NHibernate/Dialect/Oracle10gDialect.cs index 7219390c0e9..6344f42dcc9 100644 --- a/src/NHibernate/Dialect/Oracle10gDialect.cs +++ b/src/NHibernate/Dialect/Oracle10gDialect.cs @@ -1,4 +1,10 @@ +using System.Collections.Generic; +using System.Data; +using NHibernate.Cfg; +using NHibernate.Dialect.Function; using NHibernate.SqlCommand; +using NHibernate.SqlTypes; +using NHibernate.Util; namespace NHibernate.Dialect { @@ -11,9 +17,54 @@ namespace NHibernate.Dialect /// public class Oracle10gDialect : Oracle9iDialect { + private bool _useBinaryFloatingPointTypes; + public override JoinFragment CreateOuterJoinFragment() { return new ANSIJoinFragment(); } + + public override void Configure(IDictionary settings) + { + _useBinaryFloatingPointTypes = PropertiesHelper.GetBoolean( + Environment.OracleUseBinaryFloatingPointTypes, + settings, + false); + + base.Configure(settings); + } + + // Avoid registering weighted double type when using binary floating point types + protected override void RegisterFloatingPointTypeMappings() + { + if (_useBinaryFloatingPointTypes) + { + // Use binary_float (available since 10g) instead of float. With Oracle, float is a decimal but + // with a precision expressed in number of bytes instead of digits. + RegisterColumnType(DbType.Single, "binary_float"); + // Using binary_double (available since 10g) instead of double precision. With Oracle, double + // precision is a float(126), which is a decimal with a 126 bytes precision. + RegisterColumnType(DbType.Double, "binary_double"); + } + else + { + base.RegisterFloatingPointTypeMappings(); + } + } + + protected override void RegisterFunctions() + { + base.RegisterFunctions(); + + // DBMS_RANDOM package was available in previous versions, but it was requiring initialization and + // was not having the value function. + // It yields a decimal between 0 included and 1 excluded, with 38 significant digits. It sometimes + // causes an overflow when read by the Oracle provider as a .Net Decimal, so better explicitly cast + // it to double. + RegisterFunction("random", new SQLFunctionTemplate(NHibernateUtil.Double, "cast(DBMS_RANDOM.VALUE() as binary_double)")); + } + + /// + public override bool SupportsCrossJoin => true; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs index f3a943a3b8e..9bbe3e7dfb9 100644 --- a/src/NHibernate/Dialect/Oracle8iDialect.cs +++ b/src/NHibernate/Dialect/Oracle8iDialect.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Linq; using NHibernate.Dialect.Function; using NHibernate.Dialect.Schema; using NHibernate.Engine; @@ -102,6 +103,7 @@ public override void Configure(IDictionary settings) // If changing the default value, keep it in sync with OracleDataClientDriverBase.Configure. UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, settings, false); RegisterCharacterTypeMappings(); + RegisterFloatingPointTypeMappings(); } #region private static readonly string[] DialectKeywords = { ... } @@ -184,14 +186,18 @@ protected virtual void RegisterNumericTypeMappings() // 6.0 TODO: bring down to 18,4 for consistency with other dialects. RegisterColumnType(DbType.Currency, "NUMBER(22,4)"); - RegisterColumnType(DbType.Single, "FLOAT(24)"); - RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); - RegisterColumnType(DbType.Double, 40, "NUMBER($p,$s)"); RegisterColumnType(DbType.Decimal, "NUMBER(19,5)"); // Oracle max precision is 39-40, but .Net is limited to 28-29. RegisterColumnType(DbType.Decimal, 29, "NUMBER($p,$s)"); } + protected virtual void RegisterFloatingPointTypeMappings() + { + RegisterColumnType(DbType.Single, "FLOAT(24)"); + RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); + RegisterColumnType(DbType.Double, 40, "NUMBER($p,$s)"); + } + protected virtual void RegisterDateTimeTypeMappings() { RegisterColumnType(DbType.Date, "DATE"); @@ -252,7 +258,7 @@ protected virtual void RegisterFunctions() // In Oracle, date includes a time, just with fractional seconds dropped. For actually only having // the date, it must be truncated. Otherwise comparisons may yield unexpected results. - RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.Date, "trunc(current_date)")); + RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.LocalDate, "trunc(current_date)")); RegisterFunction("current_time", new NoArgSQLFunction("current_timestamp", NHibernateUtil.Time, false)); RegisterFunction("current_timestamp", new CurrentTimeStamp()); @@ -291,7 +297,7 @@ protected virtual void RegisterFunctions() // Multi-param numeric dialect functions... RegisterFunction("atan2", new StandardSQLFunction("atan2", NHibernateUtil.Double)); RegisterFunction("log", new StandardSQLFunction("log", NHibernateUtil.Int32)); - RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); + RegisterFunction("mod", new ModulusFunction(true, true)); RegisterFunction("nvl", new StandardSQLFunction("nvl")); RegisterFunction("nvl2", new StandardSQLFunction("nvl2")); RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); @@ -304,12 +310,14 @@ protected virtual void RegisterFunctions() RegisterFunction("str", new StandardSQLFunction("to_char", NHibernateUtil.String)); RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(rawtohex(?1), 7, 2) || substr(rawtohex(?1), 5, 2) || substr(rawtohex(?1), 3, 2) || substr(rawtohex(?1), 1, 2) || '-' || substr(rawtohex(?1), 11, 2) || substr(rawtohex(?1), 9, 2) || '-' || substr(rawtohex(?1), 15, 2) || substr(rawtohex(?1), 13, 2) || '-' || substr(rawtohex(?1), 17, 4) || '-' || substr(rawtohex(?1), 21) ")); - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("iif", new IifSQLFunction()); RegisterFunction("band", new Function.BitwiseFunctionOperation("bitand")); RegisterFunction("bor", new SQLFunctionTemplate(null, "?1 + ?2 - BITAND(?1, ?2)")); RegisterFunction("bxor", new SQLFunctionTemplate(null, "?1 + ?2 - BITAND(?1, ?2) * 2")); RegisterFunction("bnot", new SQLFunctionTemplate(null, "(-1 - ?1)")); + + RegisterFunction("new_uuid", new NoArgSQLFunction("sys_guid", NHibernateUtil.Guid)); } protected internal virtual void RegisterDefaultProperties() @@ -328,6 +336,9 @@ public override JoinFragment CreateOuterJoinFragment() return new OracleJoinFragment(); } + /// + public override bool SupportsCrossJoin => false; + /// /// Map case support to the Oracle DECODE function. Oracle did not /// add support for CASE until 9i. @@ -568,7 +579,7 @@ public override bool SupportsExistsInSelect [Serializable] private class CurrentTimeStamp : NoArgSQLFunction { - public CurrentTimeStamp() : base("current_timestamp", NHibernateUtil.DateTime, true) {} + public CurrentTimeStamp() : base("current_timestamp", NHibernateUtil.LocalDateTime, true) {} public override SqlString Render(IList args, ISessionFactoryImplementor factory) { @@ -576,7 +587,7 @@ public override SqlString Render(IList args, ISessionFactoryImplementor factory) } } [Serializable] - private class LocateFunction : ISQLFunction + private class LocateFunction : ISQLFunction, ISQLFunctionExtended { private static readonly ISQLFunction LocateWith2Params = new SQLFunctionTemplate(NHibernateUtil.Int32, "instr(?2, ?1)"); @@ -586,11 +597,30 @@ private class LocateFunction : ISQLFunction #region Implementation of ISQLFunction + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int32; } + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "instr"; + public bool HasArguments { get { return true; } diff --git a/src/NHibernate/Dialect/Oracle9iDialect.cs b/src/NHibernate/Dialect/Oracle9iDialect.cs index 85245862c95..b36b1a34b37 100644 --- a/src/NHibernate/Dialect/Oracle9iDialect.cs +++ b/src/NHibernate/Dialect/Oracle9iDialect.cs @@ -1,4 +1,5 @@ using System.Data; +using NHibernate.Dialect.Function; using NHibernate.SqlCommand; using NHibernate.SqlTypes; @@ -41,6 +42,15 @@ protected override void RegisterDateTimeTypeMappings() RegisterColumnType(DbType.Xml, "XMLTYPE"); } + protected override void RegisterFunctions() + { + base.RegisterFunctions(); + + RegisterFunction( + "current_utctimestamp", + new SQLFunctionTemplate(NHibernateUtil.UtcDateTime, "SYS_EXTRACT_UTC(current_timestamp)")); + } + public override long TimestampResolutionInTicks => 1; public override string GetSelectClauseNullString(SqlType sqlType) @@ -56,5 +66,8 @@ public override CaseFragment CreateCaseFragment() /// public override bool SupportsDateTimeScale => true; + + /// + public override bool SupportsRowValueConstructorSyntaxInInList => true; } } diff --git a/src/NHibernate/Dialect/OracleLiteDialect.cs b/src/NHibernate/Dialect/OracleLiteDialect.cs index cf5e33914a5..eb5af510652 100644 --- a/src/NHibernate/Dialect/OracleLiteDialect.cs +++ b/src/NHibernate/Dialect/OracleLiteDialect.cs @@ -101,7 +101,7 @@ public OracleLiteDialect() RegisterFunction("translate", new StandardSQLFunction("translate", NHibernateUtil.String)); // Multi-param numeric dialect functions... - RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); + RegisterFunction("mod", new ModulusFunction(true, false)); RegisterFunction("nvl", new StandardSQLFunction("nvl")); // Multi-param date dialect functions... diff --git a/src/NHibernate/Dialect/PostgreSQL81Dialect.cs b/src/NHibernate/Dialect/PostgreSQL81Dialect.cs index 81b062bafdd..b59ca08388c 100644 --- a/src/NHibernate/Dialect/PostgreSQL81Dialect.cs +++ b/src/NHibernate/Dialect/PostgreSQL81Dialect.cs @@ -1,4 +1,5 @@ using System.Data; +using NHibernate.Dialect.Function; using NHibernate.SqlCommand; namespace NHibernate.Dialect @@ -40,6 +41,11 @@ protected override void RegisterDateTimeTypeMappings() RegisterColumnType(DbType.Time, 6, "time($s)"); // Not overriding default scale: Posgres doc writes it means "no explicit limit", so max of what it can support, // which suits our needs. + + // timezone seems not available prior to version 8.0 + RegisterFunction( + "current_utctimestamp", + new SQLFunctionTemplate(NHibernateUtil.UtcDateTime, "timezone('UTC', current_timestamp)")); } public override string ForUpdateNowaitString @@ -111,7 +117,7 @@ public override SqlString AppendIdentitySelectToInsert(SqlString insertSql) public override SqlString AppendIdentitySelectToInsert(SqlString insertString, string identifierColumnName) { - return insertString.Append(" returning ").Append(identifierColumnName); + return insertString.Append(" returning ", identifierColumnName); } public override bool SupportsInsertSelectIdentity diff --git a/src/NHibernate/Dialect/PostgreSQL82Dialect.cs b/src/NHibernate/Dialect/PostgreSQL82Dialect.cs index af33c2b2aa4..5f08073ca0b 100644 --- a/src/NHibernate/Dialect/PostgreSQL82Dialect.cs +++ b/src/NHibernate/Dialect/PostgreSQL82Dialect.cs @@ -27,5 +27,8 @@ public override string GetDropSequenceString(string sequenceName) { return string.Concat("drop sequence if exists ", sequenceName); } + + /// + public override bool SupportsRowValueConstructorSyntaxInInList => true; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/PostgreSQLDialect.cs b/src/NHibernate/Dialect/PostgreSQLDialect.cs index d1353bb037b..725d530a22a 100644 --- a/src/NHibernate/Dialect/PostgreSQLDialect.cs +++ b/src/NHibernate/Dialect/PostgreSQLDialect.cs @@ -1,7 +1,9 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Linq; using NHibernate.Dialect.Function; using NHibernate.Dialect.Schema; using NHibernate.Engine; @@ -60,13 +62,14 @@ public PostgreSQLDialect() RegisterColumnType(DbType.String, 1073741823, "text"); // Override standard HQL function - RegisterFunction("current_timestamp", new NoArgSQLFunction("now", NHibernateUtil.DateTime, true)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("now", NHibernateUtil.LocalDateTime, true)); RegisterFunction("str", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as varchar)")); RegisterFunction("locate", new PositionSubstringFunction()); - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("iif", new IifSQLFunction()); RegisterFunction("replace", new StandardSQLFunction("replace", NHibernateUtil.String)); RegisterFunction("left", new SQLFunctionTemplate(NHibernateUtil.String, "substr(?1,1,?2)")); - RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))")); + RegisterFunction("right", new SQLFunctionTemplate(NHibernateUtil.String, "substr(?1, length(?1) + 1 -?2)")); + RegisterFunction("mod", new ModulusFunctionTemplate(true)); RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32)); RegisterFunction("round", new RoundFunction(false)); @@ -94,9 +97,14 @@ public PostgreSQLDialect() // Register the date function, since when used in LINQ select clauses, NH must know the data type. RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.Date, "cast(?1 as date)")); + RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.LocalDate, false)); RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "?1::TEXT")); + // The uuid_generate_v4 is not native and must be installed, but SelectGUIDString property already uses it, + // and NHibernate.TestDatabaseSetup does install it. + RegisterFunction("new_uuid", new NoArgSQLFunction("uuid_generate_v4", NHibernateUtil.Guid)); + RegisterKeywords(); } @@ -177,7 +185,7 @@ public override string GetDropSequenceString(string sequenceName) public override SqlString AddIdentifierOutParameterToInsert(SqlString insertString, string identifierColumnName, string parameterName) { - return insertString.Append(" returning ").Append(identifierColumnName); + return insertString.Append(" returning ", identifierColumnName); } public override InsertGeneratedIdentifierRetrievalMethod InsertGeneratedIdentifierRetrievalMethod @@ -342,7 +350,7 @@ public override string CurrentTimestampSelectString #endregion [Serializable] - private class RoundFunction : ISQLFunction + private class RoundFunction : ISQLFunction, ISQLFunctionExtended { private static readonly ISQLFunction Round = new StandardSQLFunction("round"); private static readonly ISQLFunction Truncate = new StandardSQLFunction("trunc"); @@ -356,7 +364,7 @@ private class RoundFunction : ISQLFunction private readonly ISQLFunction _singleParamFunction; private readonly ISQLFunction _twoParamFunction; - private readonly string _name; + private readonly string _name; // TODO 6.0: convert FunctionName to read-only auto property public RoundFunction(bool truncate) { @@ -374,8 +382,27 @@ public RoundFunction(bool truncate) } } + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] public IType ReturnType(IType columnType, IMapping mapping) => columnType; + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => _name; + public bool HasArguments => true; public bool HasParenthesesIfNoArguments => true; diff --git a/src/NHibernate/Dialect/SQLiteDialect.cs b/src/NHibernate/Dialect/SQLiteDialect.cs index dd72bdf5d8a..3efc67b471f 100644 --- a/src/NHibernate/Dialect/SQLiteDialect.cs +++ b/src/NHibernate/Dialect/SQLiteDialect.cs @@ -1,9 +1,14 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Linq; using System.Text; using NHibernate.Dialect.Function; +using NHibernate.Engine; using NHibernate.SqlCommand; +using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Dialect @@ -18,6 +23,12 @@ namespace NHibernate.Dialect /// public class SQLiteDialect : Dialect { + /// + /// The effective value of the BinaryGuid connection string parameter. + /// The default value in SQLite is true. + /// + private bool _binaryGuid = true; + /// /// /// @@ -31,30 +42,48 @@ public SQLiteDialect() protected virtual void RegisterColumnTypes() { + // SQLite really has only five types, and a very lax typing system, see https://www.sqlite.org/datatype3.html + // Please do not map (again) fancy types that do not actually exist in SQLite, as this is kind of supported by + // SQLite but creates bugs in convert operations. RegisterColumnType(DbType.Binary, "BLOB"); - RegisterColumnType(DbType.Byte, "TINYINT"); - RegisterColumnType(DbType.Int16, "SMALLINT"); - RegisterColumnType(DbType.Int32, "INT"); - RegisterColumnType(DbType.Int64, "BIGINT"); + RegisterColumnType(DbType.Byte, "INTEGER"); + RegisterColumnType(DbType.Int16, "INTEGER"); + RegisterColumnType(DbType.Int32, "INTEGER"); + RegisterColumnType(DbType.Int64, "INTEGER"); RegisterColumnType(DbType.SByte, "INTEGER"); RegisterColumnType(DbType.UInt16, "INTEGER"); RegisterColumnType(DbType.UInt32, "INTEGER"); RegisterColumnType(DbType.UInt64, "INTEGER"); - RegisterColumnType(DbType.Currency, "NUMERIC"); - RegisterColumnType(DbType.Decimal, "NUMERIC"); - RegisterColumnType(DbType.Double, "DOUBLE"); - RegisterColumnType(DbType.Single, "DOUBLE"); - RegisterColumnType(DbType.VarNumeric, "NUMERIC"); + + // NUMERIC and REAL are almost the same, they are binary floating point numbers. There is only a slight difference + // for values without a floating part. They will be represented as integers with numeric, but still as floating + // values with real. The side-effect of this is numeric being able of storing exactly bigger integers than real. + // But it also creates bugs in division, when dividing two numeric happening to be integers, the result is then + // never fractional. So we use "REAL" for all. + RegisterColumnType(DbType.Currency, "REAL"); + RegisterColumnType(DbType.Decimal, "REAL"); + RegisterColumnType(DbType.Double, "REAL"); + RegisterColumnType(DbType.Single, "REAL"); + RegisterColumnType(DbType.VarNumeric, "REAL"); + RegisterColumnType(DbType.AnsiString, "TEXT"); RegisterColumnType(DbType.String, "TEXT"); RegisterColumnType(DbType.AnsiStringFixedLength, "TEXT"); RegisterColumnType(DbType.StringFixedLength, "TEXT"); - RegisterColumnType(DbType.Date, "DATE"); - RegisterColumnType(DbType.DateTime, "DATETIME"); - RegisterColumnType(DbType.Time, "TIME"); - RegisterColumnType(DbType.Boolean, "BOOL"); - RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); + // https://www.sqlite.org/datatype3.html#boolean_datatype + RegisterColumnType(DbType.Boolean, "INTEGER"); + + // See https://www.sqlite.org/datatype3.html#date_and_time_datatype, we have three choices for date and time + // The one causing the less issues in case of an explicit cast is text. Beware, System.Data.SQLite has an + // internal use only "DATETIME" type. Using it causes it to directly convert the text stored into SQLite to + // a .Net DateTime, but also causes columns in SQLite to have numeric affinity and convert to destroy the + // value. As said in their chm documentation, this "DATETIME" type is for System.Data.SQLite internal use only. + RegisterColumnType(DbType.Date, "TEXT"); + RegisterColumnType(DbType.DateTime, "TEXT"); + RegisterColumnType(DbType.Time, "TEXT"); + + RegisterColumnType(DbType.Guid, _binaryGuid ? "BLOB" : "TEXT"); } protected virtual void RegisterFunctions() @@ -67,24 +96,29 @@ protected virtual void RegisterFunctions() RegisterFunction("month", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(strftime('%m', ?1) as int)")); RegisterFunction("year", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(strftime('%Y', ?1) as int)")); // Uses local time like MSSQL and PostgreSQL. - RegisterFunction("current_timestamp", new SQLFunctionTemplate(NHibernateUtil.DateTime, "datetime(current_timestamp, 'localtime')")); + RegisterFunction("current_timestamp", new SQLFunctionTemplate(NHibernateUtil.LocalDateTime, "datetime(current_timestamp, 'localtime')")); + RegisterFunction("current_utctimestamp", new SQLFunctionTemplate(NHibernateUtil.UtcDateTime, "datetime(current_timestamp)")); // The System.Data.SQLite driver stores both Date and DateTime as 'YYYY-MM-DD HH:MM:SS' // The SQLite date() function returns YYYY-MM-DD, which unfortunately SQLite does not consider // as equal to 'YYYY-MM-DD 00:00:00'. Because of this, it is best to return the // 'YYYY-MM-DD 00:00:00' format for the date function. RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.Date, "datetime(date(?1))")); + // SQLite has current_date, but as current_timestamp, it is in UTC. So converting the timestamp to + // localtime then to date then, like the above date function, go back to datetime format for comparisons + // sake. + RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.LocalDate, "datetime(date(current_timestamp, 'localtime'))")); RegisterFunction("substring", new StandardSQLFunction("substr", NHibernateUtil.String)); RegisterFunction("left", new SQLFunctionTemplate(NHibernateUtil.String, "substr(?1,1,?2)")); + RegisterFunction("right", new SQLFunctionTemplate(NHibernateUtil.String, "substr(?1,-?2)")); RegisterFunction("trim", new AnsiTrimEmulationFunction()); RegisterFunction("replace", new StandardSafeSQLFunction("replace", NHibernateUtil.String, 3)); RegisterFunction("chr", new StandardSQLFunction("char", NHibernateUtil.Character)); + RegisterFunction("locate", new LocateFunction()); - RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))")); - - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("mod", new ModulusFunctionTemplate(false)); - RegisterFunction("cast", new SQLiteCastFunction()); + RegisterFunction("iif", new IifSQLFunction()); RegisterFunction("round", new StandardSQLFunction("round")); @@ -94,8 +128,60 @@ protected virtual void RegisterFunctions() // NH-3787: SQLite requires the cast in SQL too for not defaulting to string. RegisterFunction("transparentcast", new CastFunction()); - - RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) ")); + + if (_binaryGuid) + RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) ")); + else + RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as text)")); + + // SQLite random function yields a long, ranging form MinValue to MaxValue. (-9223372036854775808 to + // 9223372036854775807). HQL random requires a float from 0 inclusive to 1 exclusive, so we divide by + // 9223372036854775808 then 2 for having a value between -0.5 included to 0.5 excluded, and finally + // add 0.5. The division is written as "/ 4611686018427387904 / 4" for avoiding overflowing long. + RegisterFunction( + "random", + new SQLFunctionTemplate( + NHibernateUtil.Double, + "(cast(random() as real) / 4611686018427387904 / 4 + 0.5)")); + } + + public override void Configure(IDictionary settings) + { + base.Configure(settings); + + ConfigureBinaryGuid(settings); + + // Re-register functions and types depending on settings. + RegisterColumnTypes(); + RegisterFunctions(); + } + + private void ConfigureBinaryGuid(IDictionary settings) + { + // We can use a SQLite specific setting to force it, but in common cases it + // should be detected automatically from the connection string below. + settings.TryGetValue(Cfg.Environment.SqliteBinaryGuid, out var strBinaryGuid); + + if (string.IsNullOrWhiteSpace(strBinaryGuid)) + { + string connectionString = Cfg.Environment.GetConfiguredConnectionString(settings); + if (!string.IsNullOrWhiteSpace(connectionString)) + { + var builder = new DbConnectionStringBuilder {ConnectionString = connectionString}; + + strBinaryGuid = GetConnectionStringProperty(builder, "BinaryGuid"); + } + } + + // Note that "BinaryGuid=false" is supported by System.Data.SQLite but not Microsoft.Data.Sqlite. + + _binaryGuid = string.IsNullOrWhiteSpace(strBinaryGuid) || bool.Parse(strBinaryGuid); + } + + private string GetConnectionStringProperty(DbConnectionStringBuilder builder, string propertyName) + { + builder.TryGetValue(propertyName, out object propertyValue); + return (string) propertyValue; } #region private static readonly string[] DialectKeywords = { ... } @@ -326,7 +412,6 @@ public override string Qualify(string catalog, string schema, string table) if (quoted) return OpenQuote + name + CloseQuote; return name; - } public override string NoColumnsInsertString @@ -421,16 +506,87 @@ public override bool SupportsForeignKeyConstraintInAlterTable /// public override int MaxAliasLength => 128; + // Since v5.3 + [Obsolete("This class has no usage in NHibernate anymore and will be removed in a future version. Use or extend CastFunction instead.")] [Serializable] protected class SQLiteCastFunction : CastFunction { + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] protected override bool CastingIsRequired(string sqlType) { - // SQLite doesn't support casting to datetime types. It assumes you want an integer and destroys the date string. - if (StringHelper.ContainsCaseInsensitive(sqlType, "date") || StringHelper.ContainsCaseInsensitive(sqlType, "time")) + if (StringHelper.ContainsCaseInsensitive(sqlType, "date") || + StringHelper.ContainsCaseInsensitive(sqlType, "time")) return false; return true; } } + + [Serializable] + private class LocateFunction : ISQLFunction, ISQLFunctionExtended + { + // Since v5.3 + [Obsolete("Use GetReturnType method instead.")] + public IType ReturnType(IType columnType, IMapping mapping) + { + return NHibernateUtil.Int32; + } + + /// + public IType GetReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { +#pragma warning disable 618 + return ReturnType(argumentTypes.FirstOrDefault(), mapping); +#pragma warning restore 618 + } + + /// + public IType GetEffectiveReturnType(IEnumerable argumentTypes, IMapping mapping, bool throwOnError) + { + return GetReturnType(argumentTypes, mapping, throwOnError); + } + + /// + public string Name => "instr"; + + public bool HasArguments => true; + + public bool HasParenthesesIfNoArguments => true; + + public SqlString Render(IList args, ISessionFactoryImplementor factory) + { + if (args.Count != 2 && args.Count != 3) + { + throw new QueryException("'locate' function takes 2 or 3 arguments. Provided count: " + args.Count); + } + + if (args.Count == 2) + { + return new SqlString("instr(", args[1], ", ", args[0], ")"); + } + + var text = args[1]; + var value = args[0]; + var startIndex = args[2]; + //ifnull( + // nullif( + // instr(substr(text, startIndex), value) + // , 0) + // + startIndex -1 + //, 0) + return + new SqlString( + "ifnull(nullif(instr(substr(", + text, + ", ", + startIndex, + "), ", + value, + "), 0) + ", + startIndex, + " -1, 0)" + ); + } + } } } diff --git a/src/NHibernate/Dialect/Schema/FirebirdMetaData.cs b/src/NHibernate/Dialect/Schema/FirebirdMetaData.cs index 8a9b42021c4..02920c2f7d2 100644 --- a/src/NHibernate/Dialect/Schema/FirebirdMetaData.cs +++ b/src/NHibernate/Dialect/Schema/FirebirdMetaData.cs @@ -95,5 +95,4 @@ public FirebirdForeignKeyMetadata(DataRow rs) Name = Convert.ToString(rs["CONSTRAINT_NAME"]); } } - } diff --git a/src/NHibernate/Dialect/Schema/IDataBaseSchema.cs b/src/NHibernate/Dialect/Schema/IDataBaseSchema.cs index d7e29207163..913db3f5a65 100644 --- a/src/NHibernate/Dialect/Schema/IDataBaseSchema.cs +++ b/src/NHibernate/Dialect/Schema/IDataBaseSchema.cs @@ -56,7 +56,7 @@ public interface IDataBaseSchema /// The name of the column that represent the TABLE_NAME in the /// returned by . /// - string ColumnNameForTableName { get;} + string ColumnNameForTableName { get; } /// /// Get the Table MetaData. diff --git a/src/NHibernate/Dialect/Schema/PostgreSQLMetadata.cs b/src/NHibernate/Dialect/Schema/PostgreSQLMetadata.cs index b4977075e99..74796bc8dbb 100644 --- a/src/NHibernate/Dialect/Schema/PostgreSQLMetadata.cs +++ b/src/NHibernate/Dialect/Schema/PostgreSQLMetadata.cs @@ -75,7 +75,6 @@ public override DataTable GetForeignKeys(string catalog, string schema, string t foreignKeys.Locale = CultureInfo.InvariantCulture; return foreignKeys; } - } public class PostgreSQLTableMetadata : AbstractTableMetadata @@ -160,4 +159,4 @@ public PostgreSQLForeignKeyMetadata(DataRow rs) Name = Convert.ToString(rs["CONSTRAINT_NAME"]); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Schema/SapSQLAnywhere17MetaData.cs b/src/NHibernate/Dialect/Schema/SapSQLAnywhere17MetaData.cs index 9ff41672b3e..3922b4ecb2c 100644 --- a/src/NHibernate/Dialect/Schema/SapSQLAnywhere17MetaData.cs +++ b/src/NHibernate/Dialect/Schema/SapSQLAnywhere17MetaData.cs @@ -3,7 +3,6 @@ using System.Data; using System.Data.Common; - namespace NHibernate.Dialect.Schema { // Metadata for connections using the Sap.Data.SQLAnywhere.v4.5 ADO.NET provider diff --git a/src/NHibernate/Dialect/Schema/SchemaHelper.cs b/src/NHibernate/Dialect/Schema/SchemaHelper.cs index c35e13c34b8..6366f31d56f 100644 --- a/src/NHibernate/Dialect/Schema/SchemaHelper.cs +++ b/src/NHibernate/Dialect/Schema/SchemaHelper.cs @@ -5,7 +5,6 @@ namespace NHibernate.Dialect.Schema { public static class SchemaHelper { - /// /// Get a value from the DataRow. Multiple alternative column names can be given. /// The names are tried in order, and the value from the first present column @@ -28,7 +27,6 @@ public static object GetValue(DataRow row, params string[] alternativeColumnName throw new InvalidOperationException("None of the alternative column names found in the DataTable."); } - /// /// Get a string value from the DataRow. Multiple alternative column names can be given. /// The names are tried in order, and the value from the first present column diff --git a/src/NHibernate/Dialect/Schema/SybaseAnywhereMetaData.cs b/src/NHibernate/Dialect/Schema/SybaseAnywhereMetaData.cs index 61b82c1386f..5eae685adc0 100644 --- a/src/NHibernate/Dialect/Schema/SybaseAnywhereMetaData.cs +++ b/src/NHibernate/Dialect/Schema/SybaseAnywhereMetaData.cs @@ -3,7 +3,6 @@ using System.Data; using System.Data.Common; - namespace NHibernate.Dialect.Schema { // Metadata for connections using the iAnywhere.Data.SQLAnywhere ADO.NET provider diff --git a/src/NHibernate/Dialect/SybaseASA9Dialect.cs b/src/NHibernate/Dialect/SybaseASA9Dialect.cs index 12ad1e3c088..dfae7baa471 100644 --- a/src/NHibernate/Dialect/SybaseASA9Dialect.cs +++ b/src/NHibernate/Dialect/SybaseASA9Dialect.cs @@ -72,7 +72,7 @@ public SybaseASA9Dialect() //RegisterColumnType(DbType.Xml, "TEXT"); // Override standard HQL function - RegisterFunction("current_timestamp", new StandardSQLFunction("current_timestamp")); + RegisterFunction("current_timestamp", new StandardSQLFunction("current_timestamp", NHibernateUtil.LocalDateTime)); RegisterFunction("length", new StandardSafeSQLFunction("length", NHibernateUtil.String, 1)); RegisterFunction("nullif", new StandardSafeSQLFunction("nullif", 2)); RegisterFunction("lower", new StandardSafeSQLFunction("lower", NHibernateUtil.String, 1)); @@ -97,6 +97,9 @@ public override bool OffsetStartsAtOne get { return true; } } + /// + public override bool SupportsCrossJoin => false; + public override SqlString GetLimitString(SqlString queryString, SqlString offset, SqlString limit) { int intSelectInsertPoint = GetAfterSelectInsertPoint(queryString); diff --git a/src/NHibernate/Dialect/SybaseASE15Dialect.cs b/src/NHibernate/Dialect/SybaseASE15Dialect.cs index 1b33b672cdf..e45b1f7700a 100644 --- a/src/NHibernate/Dialect/SybaseASE15Dialect.cs +++ b/src/NHibernate/Dialect/SybaseASE15Dialect.cs @@ -56,6 +56,9 @@ public SybaseASE15Dialect() RegisterColumnType(DbType.Date, "date"); RegisterColumnType(DbType.Binary, 8000, "varbinary($l)"); RegisterColumnType(DbType.Binary, "varbinary"); + // newid default is to generate a 32 bytes character uuid (no-dashes), but it has an option for + // including dashes, then raising it to 36 bytes. + RegisterColumnType(DbType.Guid, "varchar(36)"); RegisterFunction("abs", new StandardSQLFunction("abs")); RegisterFunction("acos", new StandardSQLFunction("acos", NHibernateUtil.Double)); @@ -68,9 +71,10 @@ public SybaseASE15Dialect() RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(","+",")")); RegisterFunction("cos", new StandardSQLFunction("cos", NHibernateUtil.Double)); RegisterFunction("cot", new StandardSQLFunction("cot", NHibernateUtil.Double)); - RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.Date)); + RegisterFunction("current_date", new NoArgSQLFunction("current_date", NHibernateUtil.LocalDate)); RegisterFunction("current_time", new NoArgSQLFunction("current_time", NHibernateUtil.Time)); - RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.DateTime)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.LocalDateTime)); + RegisterFunction("current_utctimestamp", new NoArgSQLFunction("getutcdate", NHibernateUtil.UtcDateTime)); RegisterFunction("datename", new StandardSQLFunction("datename", NHibernateUtil.String)); RegisterFunction("day", new StandardSQLFunction("day", NHibernateUtil.Int32)); RegisterFunction("degrees", new StandardSQLFunction("degrees", NHibernateUtil.Double)); @@ -89,11 +93,13 @@ public SybaseASE15Dialect() RegisterFunction("lower", new StandardSQLFunction("lower")); RegisterFunction("ltrim", new StandardSQLFunction("ltrim")); RegisterFunction("minute", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(minute, ?1)")); - RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "?1 % ?2")); + RegisterFunction("mod", new ModulusFunctionTemplate(false)); RegisterFunction("month", new StandardSQLFunction("month", NHibernateUtil.Int32)); RegisterFunction("pi", new NoArgSQLFunction("pi", NHibernateUtil.Double)); RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double)); RegisterFunction("rand", new StandardSQLFunction("rand", NHibernateUtil.Double)); + // rand returns the same value for each row, rand2 returns a new one for each row. + RegisterFunction("random", new StandardSQLFunction("rand2", NHibernateUtil.Double)); RegisterFunction("reverse", new StandardSQLFunction("reverse")); RegisterFunction("round", new StandardSQLFunction("round")); RegisterFunction("rtrim", new StandardSQLFunction("rtrim")); @@ -112,6 +118,8 @@ public SybaseASE15Dialect() RegisterFunction("year", new StandardSQLFunction("year", NHibernateUtil.Int32)); RegisterFunction("substring", new EmulatedLengthSubstringFunction()); + + RegisterFunction("new_uuid", new NoArgSQLFunction("newid", NHibernateUtil.Guid)); } public override string AddColumnString @@ -247,7 +255,10 @@ public override bool SupportsExpectedLobUsagePattern { get { return false; } } - + + /// + public override bool SupportsCrossJoin => false; + public override char OpenQuote { get { return '['; } diff --git a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs index 388947b45c4..290e626671f 100644 --- a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs +++ b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs @@ -137,11 +137,12 @@ protected virtual void RegisterMathFunctions() RegisterFunction("floor", new StandardSQLFunction("floor", NHibernateUtil.Double)); RegisterFunction("log", new StandardSQLFunction("log", NHibernateUtil.Double)); RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double)); - RegisterFunction("mod", new StandardSQLFunction("mod")); + RegisterFunction("mod", new ModulusFunction(false, false)); RegisterFunction("pi", new NoArgSQLFunction("pi", NHibernateUtil.Double, true)); RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double)); RegisterFunction("rand", new StandardSQLFunction("rand", NHibernateUtil.Double)); + RegisterFunction("random", new StandardSQLFunction("rand", NHibernateUtil.Double)); RegisterFunction("remainder", new StandardSQLFunction("remainder")); RegisterFunction("round", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0"})); RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32)); @@ -238,9 +239,9 @@ protected virtual void RegisterDateFunctions() RegisterFunction("ymd", new StandardSQLFunction("ymd", NHibernateUtil.Date)); // compatibility functions - RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.DateTime, true)); + RegisterFunction("current_timestamp", new NoArgSQLFunction("getdate", NHibernateUtil.LocalDateTime, true)); RegisterFunction("current_time", new NoArgSQLFunction("getdate", NHibernateUtil.Time, true)); - RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.Date, "date(getdate())")); + RegisterFunction("current_date", new SQLFunctionTemplate(NHibernateUtil.LocalDate, "date(getdate())")); } protected virtual void RegisterStringFunctions() @@ -338,6 +339,7 @@ protected virtual void RegisterMiscellaneousFunctions() RegisterFunction("isnull", new VarArgsSQLFunction("isnull(", ",", ")")); RegisterFunction("lesser", new StandardSQLFunction("lesser")); RegisterFunction("newid", new NoArgSQLFunction("newid", NHibernateUtil.String, true)); + RegisterFunction("new_uuid", new NoArgSQLFunction("newid", NHibernateUtil.Guid)); RegisterFunction("nullif", new StandardSQLFunction("nullif")); RegisterFunction("number", new NoArgSQLFunction("number", NHibernateUtil.Int32)); RegisterFunction("plan", new VarArgsSQLFunction(NHibernateUtil.String, "plan(", ",", ")")); @@ -353,7 +355,7 @@ protected virtual void RegisterMiscellaneousFunctions() RegisterFunction("transactsql", new StandardSQLFunction("transactsql", NHibernateUtil.String)); RegisterFunction("varexists", new StandardSQLFunction("varexists", NHibernateUtil.Int32)); RegisterFunction("watcomsql", new StandardSQLFunction("watcomsql", NHibernateUtil.String)); - RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end")); + RegisterFunction("iif", new IifSQLFunction()); } #region private static readonly string[] DialectKeywords = { ... } diff --git a/src/NHibernate/Dialect/SybaseSQLAnywhere12Dialect.cs b/src/NHibernate/Dialect/SybaseSQLAnywhere12Dialect.cs index a0b0125e8b7..88a846ba1de 100644 --- a/src/NHibernate/Dialect/SybaseSQLAnywhere12Dialect.cs +++ b/src/NHibernate/Dialect/SybaseSQLAnywhere12Dialect.cs @@ -77,9 +77,16 @@ protected override void RegisterDateTimeTypeMappings() protected override void RegisterDateFunctions() { base.RegisterDateFunctions(); + + RegisterFunction( + "current_utctimestamp", + new SQLFunctionTemplate(NHibernateUtil.UtcDateTime, "cast(current UTC timestamp as timestamp)")); RegisterFunction( "current_timestamp_offset", new NoArgSQLFunction("sysdatetimeoffset", NHibernateUtil.DateTimeOffset, true)); + RegisterFunction( + "current_utctimestamp_offset", + new SQLFunctionTemplate(NHibernateUtil.DateTimeOffset, "(current UTC timestamp)")); } /// diff --git a/src/NHibernate/Dialect/TypeNames.cs b/src/NHibernate/Dialect/TypeNames.cs index 7c25a211461..b2fbd4529fc 100644 --- a/src/NHibernate/Dialect/TypeNames.cs +++ b/src/NHibernate/Dialect/TypeNames.cs @@ -57,13 +57,24 @@ public class TypeNames /// the default type name associated with the specified key public string Get(DbType typecode) { - if (!defaults.TryGetValue(typecode, out var result)) + if (!TryGet(typecode, out var result)) { throw new ArgumentException("Dialect does not support DbType." + typecode, nameof(typecode)); } return result; } + /// + /// Get default type name for specified type. + /// + /// The type key. + /// The default type name that will be set in case it was found. + /// Whether the default type name was found. + public bool TryGet(DbType typecode, out string typeName) + { + return defaults.TryGetValue(typecode, out typeName); + } + /// /// Get the type name specified type and size /// @@ -76,6 +87,28 @@ public string Get(DbType typecode) /// if available, otherwise the default type name. /// public string Get(DbType typecode, int size, int precision, int scale) + { + if (!TryGet(typecode, size, precision, scale, out var result)) + { + throw new ArgumentException("Dialect does not support DbType." + typecode, nameof(typecode)); + } + + return result; + } + + /// + /// Get the type name specified type and size. + /// + /// The type key. + /// The SQL length. + /// The SQL scale. + /// The SQL precision. + /// + /// The associated name with smallest capacity >= size (or precision for decimal, or scale for date time types) + /// if available, otherwise the default type name. + /// + /// Whether the type name was found. + public bool TryGet(DbType typecode, int size, int precision, int scale, out string typeName) { weighted.TryGetValue(typecode, out var map); if (map != null && map.Count > 0) @@ -88,7 +121,8 @@ public string Get(DbType typecode, int size, int precision, int scale) { if (requiredCapacity <= entry.Key) { - return Replace(entry.Value, size, precision, scale); + typeName = Replace(entry.Value, size, precision, scale); + return true; } } if (isPrecisionType && precision != 0) @@ -102,11 +136,12 @@ public string Get(DbType typecode, int size, int precision, int scale) // But if the type is used for storing amounts, this may cause losing the ability to store cents... // So better just reduce as few as possible. var adjustedScale = Math.Min(scale, adjustedPrecision); - return Replace(maxEntry.Value, size, adjustedPrecision, adjustedScale); + typeName = Replace(maxEntry.Value, size, adjustedPrecision, adjustedScale); + return true; } } //Could not find a specific type for the capacity, using the default - return Get(typecode); + return TryGet(typecode, out typeName); } /// diff --git a/src/NHibernate/Driver/BasicResultSetsCommand.cs b/src/NHibernate/Driver/BasicResultSetsCommand.cs index 36e6a8a8485..a45779e7eae 100644 --- a/src/NHibernate/Driver/BasicResultSetsCommand.cs +++ b/src/NHibernate/Driver/BasicResultSetsCommand.cs @@ -15,14 +15,12 @@ namespace NHibernate.Driver public partial class BasicResultSetsCommand: IResultSetsCommand { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(BasicResultSetsCommand)); - private SqlString sqlString = SqlString.Empty; - private readonly string _statementTerminator; + private SqlString _sqlString = SqlString.Empty; public BasicResultSetsCommand(ISessionImplementor session) { Commands = new List(); Session = session; - _statementTerminator = session.Factory.Dialect.StatementTerminator.ToString(); } protected List Commands { get; private set; } @@ -32,7 +30,7 @@ public BasicResultSetsCommand(ISessionImplementor session) public virtual void Append(ISqlCommand command) { Commands.Add(command); - sqlString = sqlString.Append(command.Query).Append(_statementTerminator).Append(Environment.NewLine); + _sqlString = null; } public bool HasQueries @@ -40,9 +38,26 @@ public bool HasQueries get { return Commands.Count > 0; } } - public virtual SqlString Sql + public virtual SqlString Sql => _sqlString ?? (_sqlString = GetSqlString()); + + private SqlString GetSqlString() { - get { return sqlString; } + switch (Commands.Count) + { + case 0: + return SqlString.Empty; + case 1: + return Commands[0].Query; + } + + var statementTerminator = Session.Factory.Dialect.StatementTerminator.ToString() + Environment.NewLine; + var builder = new SqlStringBuilder(Commands.Sum(c => c.Query.Count) + Commands.Count); + foreach (var command in Commands) + { + builder.Add(command.Query).Add(statementTerminator); + } + + return builder.ToSqlString(); } public virtual DbDataReader GetReader(int? commandTimeout) @@ -50,7 +65,7 @@ public virtual DbDataReader GetReader(int? commandTimeout) var batcher = Session.Batcher; SqlType[] sqlTypes = Commands.SelectMany(c => c.ParameterTypes).ToArray(); ForEachSqlCommand((sqlLoaderCommand, offset) => sqlLoaderCommand.ResetParametersIndexesForTheCommand(offset)); - var command = batcher.PrepareQueryCommand(CommandType.Text, sqlString, sqlTypes); + var command = batcher.PrepareQueryCommand(CommandType.Text, Sql, sqlTypes); if (commandTimeout.HasValue) { command.CommandTimeout = commandTimeout.Value; diff --git a/src/NHibernate/Driver/DB2CoreDriver.cs b/src/NHibernate/Driver/DB2CoreDriver.cs index 85c25a76641..1838ee13373 100644 --- a/src/NHibernate/Driver/DB2CoreDriver.cs +++ b/src/NHibernate/Driver/DB2CoreDriver.cs @@ -1,3 +1,6 @@ +using System.Data.Common; +using NHibernate.SqlTypes; + namespace NHibernate.Driver { /// @@ -8,5 +11,17 @@ public class DB2CoreDriver : DB2DriverBase public DB2CoreDriver() : base("IBM.Data.DB2.Core") { } + + public override bool UseNamedPrefixInSql => true; + + public override bool UseNamedPrefixInParameter => true; + + public override string NamedPrefix => "@"; + + protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType) + { + dbParam.ParameterName = FormatNameForParameter(name); + base.InitializeParameter(dbParam, name, sqlType); + } } } diff --git a/src/NHibernate/Driver/DriverBase.cs b/src/NHibernate/Driver/DriverBase.cs index 1d63bc64dd8..b5f04cacd42 100644 --- a/src/NHibernate/Driver/DriverBase.cs +++ b/src/NHibernate/Driver/DriverBase.cs @@ -249,7 +249,7 @@ public void RemoveUnusedCommandParameters(DbCommand cmd, SqlString sqlString) public virtual void ExpandQueryParameters(DbCommand cmd, SqlString sqlString, SqlType[] parameterTypes) { if (UseNamedPrefixInSql) - return; // named parameters are ok + return; // named parameters are ok var expandedParameters = new List(); foreach (object part in sqlString) diff --git a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs new file mode 100644 index 00000000000..1c4ad3d9fc0 --- /dev/null +++ b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using NHibernate.AdoNet; +using NHibernate.Dialect; +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.Util; + +namespace NHibernate.Driver +{ + /// + /// A NHibernate Driver for using the SqlClient DataProvider + /// + public class MicrosoftDataSqlClientDriver : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider, IParameterAdjuster + { + const byte MaxTime = 5; + + private static readonly Action SetSqlDbType = DelegateHelper.BuildPropertySetter(System.Type.GetType("Microsoft.Data.SqlClient.SqlParameter, Microsoft.Data.SqlClient", true), "SqlDbType"); + + private Dialect.Dialect _dialect; + + public MicrosoftDataSqlClientDriver() + : base( + "Microsoft.Data.SqlClient", + "Microsoft.Data.SqlClient.SqlConnection", + "Microsoft.Data.SqlClient.SqlCommand") + { + } + + /// + /// MsSql requires the use of a Named Prefix in the SQL statement. + /// + /// + /// because MsSql uses "@". + /// + public override bool UseNamedPrefixInSql => true; + + /// + /// MsSql requires the use of a Named Prefix in the Parameter. + /// + /// + /// because MsSql uses "@". + /// + public override bool UseNamedPrefixInParameter => true; + + /// + /// The Named Prefix for parameters. + /// + /// + /// Sql Server uses "@". + /// + public override string NamedPrefix => "@"; + + /// + public override bool SupportsMultipleOpenReaders => false; + + /// + public override bool SupportsMultipleQueries => true; + + /// + /// With read committed snapshot or lower, SQL Server may have not actually already committed the transaction + /// right after the scope disposal. + /// + public override bool HasDelayedDistributedTransactionCompletion => true; + + public override bool RequiresTimeSpanForTime => true; + + /// + public override DateTime MinDate => DateTime.MinValue; + + System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(GenericBatchingBatcherFactory); + + /// + public virtual void AdjustParameterForValue(DbParameter parameter, SqlType sqlType, object value) + { + if (value is string stringVal) + switch (parameter.DbType) + { + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + parameter.Size = IsAnsiText(parameter, sqlType) + ? MsSql2000Dialect.MaxSizeForAnsiClob + : Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size); + break; + case DbType.String: + case DbType.StringFixedLength: + parameter.Size = IsText(parameter, sqlType) + ? MsSql2000Dialect.MaxSizeForClob + : Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size); + break; + } + } + + public override void Configure(IDictionary settings) + { + base.Configure(settings); + + _dialect = Dialect.Dialect.GetDialect(settings); + } + + /// + protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType) + { + base.InitializeParameter(dbParam, name, sqlType); + + // Defaults size/precision/scale + switch (dbParam.DbType) + { + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + dbParam.Size = IsAnsiText(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForAnsiClob + : MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString; + break; + case DbType.Binary: + dbParam.Size = IsBlob(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForBlob + : MsSql2000Dialect.MaxSizeForLengthLimitedBinary; + break; + case DbType.Decimal: + if (_dialect == null) + throw new InvalidOperationException( + "Dialect not available, is this driver used without having been configured?"); + dbParam.Precision = _dialect.DefaultCastPrecision; + dbParam.Scale = _dialect.DefaultCastScale; + break; + case DbType.String: + case DbType.StringFixedLength: + dbParam.Size = IsText(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForClob + : MsSql2000Dialect.MaxSizeForLengthLimitedString; + break; + case DbType.DateTime2: + dbParam.Size = MsSql2000Dialect.MaxDateTime2; + break; + case DbType.DateTimeOffset: + dbParam.Size = MsSql2000Dialect.MaxDateTimeOffset; + break; + case DbType.Xml: + dbParam.Size = MsSql2005Dialect.MaxSizeForXml; + break; + } + switch (sqlType.DbType) + { + case DbType.Time: + SetSqlDbType(dbParam, SqlDbType.Time); + dbParam.Size = MaxTime; + break; + case DbType.Date: + SetSqlDbType(dbParam, SqlDbType.Date); + break; + } + + // Do not override the default length for string using data from SqlType, since LIKE expressions needs + // larger columns. https://nhibernate.jira.com/browse/NH-3036 + + if (sqlType.PrecisionDefined) + { + dbParam.Precision = sqlType.Precision; + dbParam.Scale = sqlType.Scale; + } + } + + /// + /// Interprets if a parameter is a Clob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Clob, otherwise False + protected static bool IsAnsiText(DbParameter dbParam, SqlType sqlType) + { + return (DbType.AnsiString == dbParam.DbType || DbType.AnsiStringFixedLength == dbParam.DbType) && + sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString; + } + + /// + /// Interprets if a parameter is a Clob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Clob, otherwise False + protected static bool IsText(DbParameter dbParam, SqlType sqlType) + { + return sqlType is StringClobSqlType || + (DbType.String == dbParam.DbType || DbType.StringFixedLength == dbParam.DbType) && + sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedString; + } + + /// + /// Interprets if a parameter is a Blob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Blob, otherwise False + protected static bool IsBlob(DbParameter dbParam, SqlType sqlType) + { + return sqlType is BinaryBlobSqlType || DbType.Binary == dbParam.DbType && sqlType.LengthDefined && + sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedBinary; + } + + /// + public override IResultSetsCommand GetResultSetsCommand(ISessionImplementor session) + { + return new BasicResultSetsCommand(session); + } + } +} diff --git a/src/NHibernate/Driver/NDataReader.cs b/src/NHibernate/Driver/NDataReader.cs index 1c1727e397c..515777eab1b 100644 --- a/src/NHibernate/Driver/NDataReader.cs +++ b/src/NHibernate/Driver/NDataReader.cs @@ -492,10 +492,10 @@ private partial class NResult // key = field name // index = field index - private readonly IDictionary fieldNameToIndex = new Dictionary(); - private readonly IList fieldIndexToName = new List(); - private readonly IList fieldTypes = new List(); - private readonly IList fieldDataTypeNames = new List(); + private readonly Dictionary fieldNameToIndex = new Dictionary(); + private readonly List fieldIndexToName = new List(); + private readonly List fieldTypes = new List(); + private readonly List fieldDataTypeNames = new List(); private NResult() { } @@ -552,7 +552,6 @@ internal static NResult Create(DbDataReader reader, bool isMidstream) return result; } - private static DataTable SafeGetSchemaTable(IDataReader reader, out NotSupportedException exception) { exception = null; diff --git a/src/NHibernate/Driver/OdbcDriver.cs b/src/NHibernate/Driver/OdbcDriver.cs index 80e6358617e..5ac80336849 100644 --- a/src/NHibernate/Driver/OdbcDriver.cs +++ b/src/NHibernate/Driver/OdbcDriver.cs @@ -25,7 +25,6 @@ public class OdbcDriver private byte? _dbDateTimeScale; - public override void Configure(IDictionary settings) { base.Configure(settings); @@ -79,10 +78,18 @@ private void SetVariableLengthParameterSize(DbParameter dbParam, SqlType sqlType { switch (dbParam.DbType) { - case DbType.AnsiString: + case DbType.StringFixedLength: case DbType.AnsiStringFixedLength: + // For types that are using one character (CharType, AnsiCharType, TrueFalseType, YesNoType and EnumCharType), + // we have to specify the length otherwise sql function like charindex won't work as expected. + if (sqlType.Length == 1) + { + dbParam.Size = sqlType.Length; + } + + break; case DbType.String: - case DbType.StringFixedLength: + case DbType.AnsiString: // NH-4083: do not limit to column length if above 2000. Setting size may trigger conversion from // nvarchar to ntext when size is superior or equal to 2000, causing some queries to fail: // https://stackoverflow.com/q/8569844/1178314 diff --git a/src/NHibernate/Driver/OleDbDriver.cs b/src/NHibernate/Driver/OleDbDriver.cs index 504411995e0..0401673a907 100644 --- a/src/NHibernate/Driver/OleDbDriver.cs +++ b/src/NHibernate/Driver/OleDbDriver.cs @@ -16,7 +16,6 @@ public class OleDbDriver : ReflectionBasedDriver #endif { - #if !NETFX public OleDbDriver() : base ("System.Data.OleDb", "System.Data.OleDb.OleDbConnection", "System.Data.OleDb.OleDbCommand") diff --git a/src/NHibernate/Driver/OracleClientDriver.cs b/src/NHibernate/Driver/OracleClientDriver.cs index 334a1f09f67..f3cf9d28dd3 100644 --- a/src/NHibernate/Driver/OracleClientDriver.cs +++ b/src/NHibernate/Driver/OracleClientDriver.cs @@ -1,3 +1,4 @@ +using System; using System.Data; using System.Data.Common; using NHibernate.Engine.Query; @@ -8,6 +9,9 @@ namespace NHibernate.Driver /// /// A NHibernate Driver for using the Oracle DataProvider. /// + // Since v5.3 + // Deprecated by Microsoft: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/oracle-and-adonet + [Obsolete("Use OracleManagedDataClientDriver or OracleDataClientDriver driver instead.")] public class OracleClientDriver : ReflectionBasedDriver { private static readonly SqlType GuidSqlType = new SqlType(DbType.Binary, 16); diff --git a/src/NHibernate/Driver/OracleDataClientDriverBase.cs b/src/NHibernate/Driver/OracleDataClientDriverBase.cs index f5e8620af6d..85f8cbdbd88 100644 --- a/src/NHibernate/Driver/OracleDataClientDriverBase.cs +++ b/src/NHibernate/Driver/OracleDataClientDriverBase.cs @@ -30,6 +30,8 @@ public abstract class OracleDataClientDriverBase : ReflectionBasedDriver, IEmbed private readonly object _oracleDbTypeBlob; private readonly object _oracleDbTypeNVarchar2; private readonly object _oracleDbTypeNChar; + private readonly object _oracleDbTypeBinaryDouble; + private readonly object _oracleDbTypeBinaryFloat; /// /// Default constructor. @@ -58,6 +60,8 @@ private OracleDataClientDriverBase(string driverAssemblyName, string clientNames _oracleDbTypeBlob = Enum.Parse(oracleDbTypeEnum, "Blob"); _oracleDbTypeNVarchar2 = Enum.Parse(oracleDbTypeEnum, "NVarchar2"); _oracleDbTypeNChar = Enum.Parse(oracleDbTypeEnum, "NChar"); + _oracleDbTypeBinaryDouble = Enum.Parse(oracleDbTypeEnum, "BinaryDouble"); + _oracleDbTypeBinaryFloat = Enum.Parse(oracleDbTypeEnum, "BinaryFloat"); } /// @@ -67,6 +71,7 @@ public override void Configure(IDictionary settings) // If changing the default value, keep it in sync with Oracle8iDialect.Configure. UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Cfg.Environment.OracleUseNPrefixedTypesForUnicode, settings, false); + UseBinaryFloatingPointTypes = PropertiesHelper.GetBoolean(Cfg.Environment.OracleUseBinaryFloatingPointTypes, settings, false); } /// @@ -84,6 +89,11 @@ public override void Configure(IDictionary settings) /// public bool UseNPrefixedTypesForUnicode { get; private set; } + /// + /// Whether binary_double and binary_float are used for and types. + /// + public bool UseBinaryFloatingPointTypes { get; private set; } + /// public override bool UseNamedPrefixInSql => true; @@ -131,6 +141,18 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq case DbType.Currency: base.InitializeParameter(dbParam, name, SqlTypeFactory.Decimal); break; + case DbType.Double: + if (UseBinaryFloatingPointTypes) + InitializeParameter(dbParam, name, _oracleDbTypeBinaryDouble); + else + base.InitializeParameter(dbParam, name, sqlType); + break; + case DbType.Single: + if (UseBinaryFloatingPointTypes) + InitializeParameter(dbParam, name, _oracleDbTypeBinaryFloat); + else + base.InitializeParameter(dbParam, name, sqlType); + break; default: base.InitializeParameter(dbParam, name, sqlType); break; diff --git a/src/NHibernate/Driver/SapSQLAnywhere17Driver.cs b/src/NHibernate/Driver/SapSQLAnywhere17Driver.cs index 2edf3e071d8..cac8b8bed14 100644 --- a/src/NHibernate/Driver/SapSQLAnywhere17Driver.cs +++ b/src/NHibernate/Driver/SapSQLAnywhere17Driver.cs @@ -5,7 +5,6 @@ public class SapSQLAnywhere17Driver : ReflectionBasedDriver public SapSQLAnywhere17Driver() : base("Sap.Data.SQLAnywhere", "Sap.Data.SQLAnywhere.v4.5", "Sap.Data.SQLAnywhere.SAConnection", "Sap.Data.SQLAnywhere.SACommand") { - } public override bool UseNamedPrefixInSql => true; diff --git a/src/NHibernate/Driver/SqlClientDriver.cs b/src/NHibernate/Driver/SqlClientDriver.cs index 81d46097a50..1002005395d 100644 --- a/src/NHibernate/Driver/SqlClientDriver.cs +++ b/src/NHibernate/Driver/SqlClientDriver.cs @@ -104,7 +104,6 @@ System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass } #endif - /// /// MsSql requires the use of a Named Prefix in the SQL statement. /// @@ -162,7 +161,9 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq { case DbType.AnsiString: case DbType.AnsiStringFixedLength: - dbParam.Size = IsAnsiText(dbParam, sqlType) ? MsSql2000Dialect.MaxSizeForAnsiClob : MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString; + dbParam.Size = IsAnsiText(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForAnsiClob + : IsChar(dbParam, sqlType) ? sqlType.Length : MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString; break; case DbType.Binary: dbParam.Size = IsBlob(dbParam, sqlType) ? MsSql2000Dialect.MaxSizeForBlob : MsSql2000Dialect.MaxSizeForLengthLimitedBinary; @@ -175,7 +176,9 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq break; case DbType.String: case DbType.StringFixedLength: - dbParam.Size = IsText(dbParam, sqlType) ? MsSql2000Dialect.MaxSizeForClob : MsSql2000Dialect.MaxSizeForLengthLimitedString; + dbParam.Size = IsText(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForClob + : IsChar(dbParam, sqlType) ? sqlType.Length : MsSql2000Dialect.MaxSizeForLengthLimitedString; break; case DbType.DateTime2: dbParam.Size = MsSql2000Dialect.MaxDateTime2; @@ -284,6 +287,17 @@ protected static bool IsBlob(DbParameter dbParam, SqlType sqlType) return (sqlType is BinaryBlobSqlType) || ((DbType.Binary == dbParam.DbType) && sqlType.LengthDefined && (sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedBinary)); } + /// + /// Interprets if a parameter is a character (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a character, otherwise False + protected static bool IsChar(DbParameter dbParam, SqlType sqlType) + { + return (DbType.StringFixedLength == dbParam.DbType || DbType.AnsiStringFixedLength == dbParam.DbType) && + sqlType.LengthDefined && sqlType.Length == 1; + } public override IResultSetsCommand GetResultSetsCommand(ISessionImplementor session) { diff --git a/src/NHibernate/Driver/SqlServerCeDriver.cs b/src/NHibernate/Driver/SqlServerCeDriver.cs index 300e15c06cc..0b6a4ad93bc 100644 --- a/src/NHibernate/Driver/SqlServerCeDriver.cs +++ b/src/NHibernate/Driver/SqlServerCeDriver.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Reflection; using NHibernate.SqlTypes; +using NHibernate.Util; namespace NHibernate.Driver { @@ -12,6 +11,11 @@ namespace NHibernate.Driver /// public class SqlServerCeDriver : ReflectionBasedDriver { + private static readonly Action SetSqlDbType = + DelegateHelper.BuildPropertySetter( + ReflectHelper.TypeFromAssembly("System.Data.SqlServerCe.SqlCeParameter", "System.Data.SqlServerCe", true), + "SqlDbType"); + /// /// Initializes a new instance of the class. /// @@ -23,19 +27,6 @@ public SqlServerCeDriver() { } - private PropertyInfo dbParamSqlDbTypeProperty; - - public override void Configure(IDictionary settings) - { - base.Configure(settings); - - using (var cmd = CreateCommand()) - { - var dbParam = cmd.CreateParameter(); - dbParamSqlDbTypeProperty = dbParam.GetType().GetProperty("SqlDbType"); - } - } - /// /// MsSql requires the use of a Named Prefix in the SQL statement. /// @@ -84,6 +75,12 @@ public override IResultSetsCommand GetResultSetsCommand(Engine.ISessionImplement protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType) { base.InitializeParameter(dbParam, name, AdjustSqlType(sqlType)); + // For types that are using one character (CharType, AnsiCharType, TrueFalseType, YesNoType and EnumCharType), + // we have to specify the length otherwise sql function like charindex won't work as expected. + if (sqlType.LengthDefined && sqlType.Length == 1) + { + dbParam.Size = sqlType.Length; + } AdjustDbParamTypeForLargeObjects(dbParam, sqlType); } @@ -109,11 +106,11 @@ private void AdjustDbParamTypeForLargeObjects(DbParameter dbParam, SqlType sqlTy { if (sqlType is BinaryBlobSqlType) { - dbParamSqlDbTypeProperty.SetValue(dbParam, SqlDbType.Image, null); + SetSqlDbType(dbParam, SqlDbType.Image); } else if (sqlType is StringClobSqlType) { - dbParamSqlDbTypeProperty.SetValue(dbParam, SqlDbType.NText, null); + SetSqlDbType(dbParam, SqlDbType.NText); } } diff --git a/src/NHibernate/Driver/SqlStringFormatter.cs b/src/NHibernate/Driver/SqlStringFormatter.cs index c16889b22d0..04a3112b6d5 100644 --- a/src/NHibernate/Driver/SqlStringFormatter.cs +++ b/src/NHibernate/Driver/SqlStringFormatter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using NHibernate.SqlCommand; using NHibernate.Engine.Query; @@ -15,7 +14,7 @@ public class SqlStringFormatter : ISqlStringVisitor private readonly string multipleQueriesSeparator; private bool hasReturnParameter; private bool foundReturnParameter = false; - private IList assignedParameterNames = new List(); + private readonly List assignedParameterNames = new List(); public SqlStringFormatter(ISqlParameterFormatter formatter, string multipleQueriesSeparator) { diff --git a/src/NHibernate/DuplicateMappingException.cs b/src/NHibernate/DuplicateMappingException.cs index b510b23b5e6..3b9d43ce626 100644 --- a/src/NHibernate/DuplicateMappingException.cs +++ b/src/NHibernate/DuplicateMappingException.cs @@ -7,7 +7,6 @@ namespace NHibernate [Serializable] public class DuplicateMappingException : MappingException { - /// /// Initializes a new instance of the class. /// diff --git a/src/NHibernate/Engine/ActionQueue.cs b/src/NHibernate/Engine/ActionQueue.cs index 5c33537779c..36a76591edd 100644 --- a/src/NHibernate/Engine/ActionQueue.cs +++ b/src/NHibernate/Engine/ActionQueue.cs @@ -1,13 +1,13 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using NHibernate.Action; using NHibernate.Cache; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Engine { @@ -156,14 +156,13 @@ public void RegisterProcess(AfterTransactionCompletionProcessDelegate process) RegisterProcess(new AfterTransactionCompletionDelegatedProcess(process)); } - private void ExecuteActions(IList list) + private void ExecuteActions(List list) where T: IExecutable { // Actions may raise events to which user code can react and cause changes to action list. // It will then fail here due to list being modified. (Some previous code was dodging the // trouble with a for loop which was not failing provided the list was not getting smaller. // But then it was clearing it without having executed added actions (if any), ...) - - foreach (IExecutable executable in list) + foreach (var executable in list) { InnerExecute(executable); } @@ -173,7 +172,7 @@ private void ExecuteActions(IList list) private void PreInvalidateCaches() { - if (session.Factory.Settings.IsQueryCacheEnabled) + if (session.Factory.Settings.IsQueryCacheEnabled && executedSpaces.Count > 0) { session.Factory.UpdateTimestampsCache.PreInvalidate(executedSpaces); } @@ -258,9 +257,9 @@ public void ExecuteActions() } } - private static void PrepareActions(IList queue) + private static void PrepareActions(List queue) where T: IExecutable { - foreach (IExecutable executable in queue) + foreach (var executable in queue) executable.BeforeExecutions(); } @@ -295,7 +294,7 @@ public void AfterTransactionCompletion(bool success) private void InvalidateCaches() { - if (session.Factory.Settings.IsQueryCacheEnabled) + if (session.Factory.Settings.IsQueryCacheEnabled && executedSpaces.Count > 0) { session.Factory.UpdateTimestampsCache.Invalidate(executedSpaces); } @@ -329,9 +328,9 @@ public bool AreInsertionsOrDeletionsQueued get { return (insertions.Count > 0 || deletions.Count > 0); } } - private static bool AreTablesToUpdated(IList executables, ICollection tablespaces) + private static bool AreTablesToUpdated(List executables, ISet tablespaces) where T: IExecutable { - foreach (IExecutable exec in executables) + foreach (var exec in executables) { var spaces = exec.PropertySpaces; foreach (string o in spaces) @@ -605,7 +604,7 @@ private class InsertActionSorter // The map of entities to their batch. private readonly Dictionary _entityBatchNumber; // The map of entities to the latest batch (of another entities) they depend on. - private readonly Dictionary _entityBatchDependency = new Dictionary(); + private readonly Dictionary _entityBatchDependency = new Dictionary(ReferenceComparer.Instance); // the map of batch numbers to EntityInsertAction lists private readonly Dictionary> _actionBatches = new Dictionary>(); @@ -619,7 +618,7 @@ public InsertActionSorter(ActionQueue actionQueue) _actionQueue = actionQueue; //optimize the hash size to eliminate a rehash. - _entityBatchNumber = new Dictionary(actionQueue.insertions.Count + 1); + _entityBatchNumber = new Dictionary(actionQueue.insertions.Count + 1, ReferenceComparer.Instance); } // This sorting does not actually optimize some features like mapped inheritance or joined-table, diff --git a/src/NHibernate/Engine/BatchFetchQueue.cs b/src/NHibernate/Engine/BatchFetchQueue.cs index f07c26e293e..d9bc7068108 100644 --- a/src/NHibernate/Engine/BatchFetchQueue.cs +++ b/src/NHibernate/Engine/BatchFetchQueue.cs @@ -22,16 +22,16 @@ public partial class BatchFetchQueue /// A Map structure is used to segment the keys by entity type since loading can only be done for a particular entity /// type at a time. /// - private readonly IDictionary> batchLoadableEntityKeys = new Dictionary>(8); + private readonly Dictionary> batchLoadableEntityKeys = new Dictionary>(8); /// /// A map of subselect-fetch descriptors /// keyed by the against which the descriptor is /// registered. /// - private readonly IDictionary subselectsByEntityKey = new Dictionary(8); + private readonly Dictionary subselectsByEntityKey = new Dictionary(8); - private readonly IDictionary> batchLoadableCollections = new Dictionary>(8); + private readonly Dictionary> batchLoadableCollections = new Dictionary>(8); /// /// The owning persistence context. /// diff --git a/src/NHibernate/Engine/Cascade.cs b/src/NHibernate/Engine/Cascade.cs index a9ba97a214d..2c2c2987a3b 100644 --- a/src/NHibernate/Engine/Cascade.cs +++ b/src/NHibernate/Engine/Cascade.cs @@ -86,7 +86,6 @@ public Cascade(CascadingAction action, CascadePoint point, IEventSource eventSou this.action = action; } - /// Cascade an action from the parent entity instance to all its children. /// The parent's entity persister /// The parent reference. @@ -169,7 +168,8 @@ private void CascadeProperty(object parent, object child, IType type, CascadeSty // value is orphaned if loaded state for this property shows not null // because it is currently null. EntityEntry entry = eventSource.PersistenceContext.GetEntry(parent); - if (entry != null && entry.Status != Status.Saving) + //LoadedState is null when detached entity is cascaded from session.Update context + if (entry?.LoadedState != null && entry.Status != Status.Saving) { object loadedValue; if (componentPathStack.Count == 0) diff --git a/src/NHibernate/Engine/CascadeStyle.cs b/src/NHibernate/Engine/CascadeStyle.cs index 9bb5fb97ec9..0267c35b459 100644 --- a/src/NHibernate/Engine/CascadeStyle.cs +++ b/src/NHibernate/Engine/CascadeStyle.cs @@ -129,7 +129,6 @@ Object IObjectReference.GetRealObject(StreamingContext context) #region The CascadeStyle implementations - [Serializable] private class AllDeleteOrphanCascadeStyle : CascadeStyle { diff --git a/src/NHibernate/Engine/CascadingAction.cs b/src/NHibernate/Engine/CascadingAction.cs index 14f8af2d173..68e3aa4ee6e 100644 --- a/src/NHibernate/Engine/CascadingAction.cs +++ b/src/NHibernate/Engine/CascadingAction.cs @@ -38,8 +38,7 @@ public abstract partial class CascadingAction /// Does this action potentially extrapolate to orphan deletes? /// True if this action can lead to deletions of orphans. - public abstract bool DeleteOrphans { get;} - + public abstract bool DeleteOrphans { get; } /// Does the specified cascading action require verification of no cascade validity? /// True if this action requires no-cascade verification; false otherwise. diff --git a/src/NHibernate/Engine/CollectionEntry.cs b/src/NHibernate/Engine/CollectionEntry.cs index 038f07cbe76..a3c591bce32 100644 --- a/src/NHibernate/Engine/CollectionEntry.cs +++ b/src/NHibernate/Engine/CollectionEntry.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Action; using NHibernate.Collection; using NHibernate.Impl; @@ -389,6 +391,23 @@ public ICollection GetOrphans(string entityName, IPersistentCollection collectio return collection.GetOrphans(snapshot, entityName); } + //Since 5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public Task GetOrphansAsync(string entityName, IPersistentCollection collection, CancellationToken cancellationToken) + { + if (snapshot == null) + { + throw new AssertionFailure("no collection snapshot for orphan delete"); + } + + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return collection.GetOrphansAsync(snapshot, entityName, cancellationToken); + } + public bool IsSnapshotEmpty(IPersistentCollection collection) { return collection.WasInitialized && (LoadedPersister == null || LoadedPersister.IsMutable) && collection.IsSnapshotEmpty(Snapshot); diff --git a/src/NHibernate/Engine/Collections.cs b/src/NHibernate/Engine/Collections.cs index 69de56655c2..311903bde46 100644 --- a/src/NHibernate/Engine/Collections.cs +++ b/src/NHibernate/Engine/Collections.cs @@ -1,4 +1,3 @@ - using NHibernate.Collection; using NHibernate.Impl; using NHibernate.Persister.Collection; diff --git a/src/NHibernate/Engine/EntityUniqueKey.cs b/src/NHibernate/Engine/EntityUniqueKey.cs index aa728f37667..15edf429bbe 100644 --- a/src/NHibernate/Engine/EntityUniqueKey.cs +++ b/src/NHibernate/Engine/EntityUniqueKey.cs @@ -112,6 +112,5 @@ public override string ToString() { return string.Format("EntityUniqueKey{0}", MessageHelper.InfoString(entityName, uniqueKeyName, key)); } - } } diff --git a/src/NHibernate/Engine/FilterDefinition.cs b/src/NHibernate/Engine/FilterDefinition.cs index 3a77a3efc5a..abe229cc2e0 100644 --- a/src/NHibernate/Engine/FilterDefinition.cs +++ b/src/NHibernate/Engine/FilterDefinition.cs @@ -13,7 +13,7 @@ public class FilterDefinition { private readonly string filterName; private readonly string defaultFilterCondition; - private readonly IDictionary parameterTypes= new Dictionary(); + private readonly IDictionary parameterTypes; private readonly bool useInManyToOne; /// diff --git a/src/NHibernate/Engine/ForeignKeys.cs b/src/NHibernate/Engine/ForeignKeys.cs index c48dbefaca9..fe8dbb9cdea 100644 --- a/src/NHibernate/Engine/ForeignKeys.cs +++ b/src/NHibernate/Engine/ForeignKeys.cs @@ -1,4 +1,3 @@ - using NHibernate.Id; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -98,7 +97,6 @@ private object NullifyTransientReferences(object value, IType type) /// private bool IsNullifiable(string entityName, object obj) { - //if (obj == org.hibernate.intercept.LazyPropertyInitializer_Fields.UNFETCHED_PROPERTY) // return false; //this is kinda the best we can do... @@ -155,10 +153,6 @@ private bool IsNullifiable(string entityName, object obj) /// public static bool IsNotTransientSlow(string entityName, object entity, ISessionImplementor session) { - if (entity.IsProxy()) - return true; - if (session.PersistenceContext.IsEntryFor(entity)) - return true; return !IsTransientSlow(entityName, entity, session); } @@ -233,6 +227,11 @@ static bool HasDbSnapshot(string entityName, object entity, ISessionImplementor return snapshot == null; } + internal static object GetIdentifier(IEntityPersister persister, object entity) + { + return entity is INHibernateProxy proxy ? proxy.HibernateLazyInitializer.Identifier : persister.GetIdentifier(entity); + } + /// /// Return the identifier of the persistent or transient object, or throw /// an exception if the instance is "unsaved" @@ -265,21 +264,10 @@ public static object GetEntityIdentifierIfNotUnsaved(string entityName, object e if (IsTransientFast(entityName, entity, session).GetValueOrDefault()) { - /***********************************************/ - // TODO NH verify the behavior of NH607 test - // these lines are only to pass test NH607 during PersistenceContext porting - // i'm not secure that NH607 is a test for a right behavior - EntityEntry entry = session.PersistenceContext.GetEntry(entity); - if (entry != null) - return entry.Id; - // the check was put here to have les possible impact - /**********************************************/ - entityName = entityName ?? session.GuessEntityName(entity); string entityString = entity.ToString(); throw new TransientObjectException( string.Format("object references an unsaved transient instance - save the transient instance before flushing or set cascade action for the property to something that would make it autosave. Type: {0}, Entity: {1}", entityName, entityString)); - } id = session.GetEntityPersister(entityName, entity).GetIdentifier(entity); } diff --git a/src/NHibernate/Engine/IJoin.cs b/src/NHibernate/Engine/IJoin.cs new file mode 100644 index 00000000000..ec0ed3e2c26 --- /dev/null +++ b/src/NHibernate/Engine/IJoin.cs @@ -0,0 +1,15 @@ +using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; +using NHibernate.Type; + +namespace NHibernate.Engine +{ + internal interface IJoin + { + IJoinable Joinable { get; } + string[] LHSColumns { get; } + string Alias { get; } + IAssociationType AssociationType { get; } + JoinType JoinType { get; } + } +} diff --git a/src/NHibernate/Engine/IPersistenceContext.cs b/src/NHibernate/Engine/IPersistenceContext.cs index 3544bc2d3a9..f23f9731788 100644 --- a/src/NHibernate/Engine/IPersistenceContext.cs +++ b/src/NHibernate/Engine/IPersistenceContext.cs @@ -17,44 +17,44 @@ namespace NHibernate.Engine /// public partial interface IPersistenceContext { - bool IsStateless { get;} + bool IsStateless { get; } /// /// Get the session to which this persistence context is bound. /// - ISessionImplementor Session { get;} + ISessionImplementor Session { get; } /// /// Retrieve this persistence context's managed load context. /// - LoadContexts LoadContexts { get;} + LoadContexts LoadContexts { get; } /// /// Get the BatchFetchQueue, instantiating one if necessary. /// - BatchFetchQueue BatchFetchQueue { get;} + BatchFetchQueue BatchFetchQueue { get; } /// Retrieve the set of EntityKeys representing nullifiable references - ISet NullifiableEntityKeys { get;} + ISet NullifiableEntityKeys { get; } /// Get the mapping from key value to entity instance - IDictionary EntitiesByKey { get;} + IDictionary EntitiesByKey { get; } /// Get the mapping from entity instance to entity entry - IDictionary EntityEntries { get;} + IDictionary EntityEntries { get; } /// Get the mapping from collection instance to collection entry - IDictionary CollectionEntries { get;} + IDictionary CollectionEntries { get; } /// Get the mapping from collection key to collection instance - IDictionary CollectionsByKey { get;} + IDictionary CollectionsByKey { get; } /// How deep are we cascaded? - int CascadeLevel { get;} + int CascadeLevel { get; } /// Is a flush cycle currently in process? /// Called before and after the flushcycle - bool Flushing { get; set;} + bool Flushing { get; set; } /// /// The read-only status for entities (and proxies) loaded into this persistence context. @@ -87,7 +87,7 @@ public partial interface IPersistenceContext void Clear(); /// False if we know for certain that all the entities are read-only - bool HasNonReadOnlyEntities { get;} + bool HasNonReadOnlyEntities { get; } /// Set the status of an entry void SetEntryStatus(EntityEntry entry, Status status); diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 84b30da88ae..9a0cb58b77a 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -18,9 +18,24 @@ namespace NHibernate.Engine { - // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode - internal static partial class SessionImplementorExtensions + // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode, GetTenantIdentifier + public static partial class SessionImplementorExtensions { + //NOTE: Keep it as extension + /// + /// Obtain the tenant identifier associated with this session. + /// + /// The tenant identifier associated with this session or null + public static string GetTenantIdentifier(this ISessionImplementor session) + { + if (session is AbstractSessionImpl sessionImpl) + { + return sessionImpl.TenantConfiguration?.TenantIdentifier; + } + + return null; + } + /// /// Instantiate the entity class, initializing with the given identifier /// diff --git a/src/NHibernate/Engine/IdentifierValue.cs b/src/NHibernate/Engine/IdentifierValue.cs index 734616ccae6..8f44c7e434e 100644 --- a/src/NHibernate/Engine/IdentifierValue.cs +++ b/src/NHibernate/Engine/IdentifierValue.cs @@ -1,5 +1,3 @@ - - namespace NHibernate.Engine { /// diff --git a/src/NHibernate/Engine/JoinHelper.cs b/src/NHibernate/Engine/JoinHelper.cs index 95df9848c5e..0ca78b84a20 100644 --- a/src/NHibernate/Engine/JoinHelper.cs +++ b/src/NHibernate/Engine/JoinHelper.cs @@ -70,7 +70,6 @@ protected AbstractLhsAssociationTypeSqlInfo(string @alias, IOuterJoinLoadable pe #region Implementation of ILhsAssociationTypeSqlInfo - public string[] GetAliasedColumnNames(IAssociationType type, int begin) { if (type.UseLHSPrimaryKey) @@ -207,5 +206,4 @@ public override string GetTableName(IAssociationType type) #endregion } - } diff --git a/src/NHibernate/Engine/JoinSequence.cs b/src/NHibernate/Engine/JoinSequence.cs index 8d5a20b15ab..f1d239676cc 100644 --- a/src/NHibernate/Engine/JoinSequence.cs +++ b/src/NHibernate/Engine/JoinSequence.cs @@ -1,6 +1,7 @@ -using System.Collections; +using System; using System.Collections.Generic; using System.Text; +using NHibernate.Hql.Ast.ANTLR.Tree; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; @@ -39,7 +40,7 @@ public override string ToString() return buf.Append('}').ToString(); } - private sealed class Join + private sealed class Join : IJoin { private readonly IAssociationType associationType; private readonly IJoinable joinable; @@ -48,7 +49,7 @@ private sealed class Join private readonly string[] lhsColumns; public Join(ISessionFactoryImplementor factory, IAssociationType associationType, string alias, JoinType joinType, - string[] lhsColumns) + string[] lhsColumns) { this.associationType = associationType; this.joinable = associationType.GetAssociatedJoinable(factory); @@ -133,80 +134,61 @@ public JoinFragment ToJoinFragment() public JoinFragment ToJoinFragment(IDictionary enabledFilters, bool includeExtraJoins) { - return ToJoinFragment(enabledFilters, includeExtraJoins, null, null); + return ToJoinFragment(enabledFilters, includeExtraJoins, true, null); } + public JoinFragment ToJoinFragment(IDictionary enabledFilters, bool includeAllSubclassJoins, SqlString withClause) + { + return ToJoinFragment(enabledFilters, includeAllSubclassJoins, true, withClause); + } + + // Since 5.4 + [Obsolete("This method has no more usages and will be removed in a future version.")] public JoinFragment ToJoinFragment( IDictionary enabledFilters, bool includeExtraJoins, SqlString withClauseFragment, string withClauseJoinAlias) { - return ToJoinFragment(enabledFilters, includeExtraJoins, withClauseFragment, withClauseJoinAlias, rootAlias); + return ToJoinFragment(enabledFilters, includeExtraJoins, true, withClauseFragment); } - internal virtual JoinFragment ToJoinFragment( + internal JoinFragment ToJoinFragment( IDictionary enabledFilters, - bool includeExtraJoins, - SqlString withClauseFragment, - string withClauseJoinAlias, - string withRootAlias) + bool includeAllSubclassJoins, + bool renderSubclassJoins, + SqlString withClauseFragment) { QueryJoinFragment joinFragment = new QueryJoinFragment(factory.Dialect, useThetaStyle); if (rootJoinable != null) { - joinFragment.AddCrossJoin(rootJoinable.TableName, withRootAlias); - string filterCondition = rootJoinable.FilterFragment(withRootAlias, enabledFilters); + joinFragment.AddCrossJoin(rootJoinable.TableName, rootAlias); + string filterCondition = rootJoinable.FilterFragment(rootAlias, enabledFilters); // JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it // can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track // of that fact. joinFragment.HasFilterCondition = joinFragment.AddCondition(filterCondition); - if (includeExtraJoins) - { - //TODO: not quite sure about the full implications of this! - AddExtraJoins(joinFragment, withRootAlias, rootJoinable, true); - } + AddSubclassJoins(joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins); } + var withClauses = new SqlString[joins.Count]; IJoinable last = rootJoinable; - for (int i = 0; i < joins.Count; i++) { Join join = joins[i]; - string on = join.AssociationType.GetOnCondition(join.Alias, factory, enabledFilters); - SqlString condition = new SqlString(); - if (last != null && - IsManyToManyRoot(last) && - ((IQueryableCollection)last).ElementType == join.AssociationType) - { - // the current join represents the join between a many-to-many association table - // and its "target" table. Here we need to apply any additional filters - // defined specifically on the many-to-many - string manyToManyFilter = ((IQueryableCollection)last) - .GetManyToManyFilterFragment(join.Alias, enabledFilters); - condition = new SqlString("".Equals(manyToManyFilter) - ? on - : "".Equals(on) - ? manyToManyFilter - : on + " and " + manyToManyFilter); - } - else - { - // NH Different behavior : NH1179 and NH1293 - // Apply filters in Many-To-One association - var enabledForManyToOne = FilterHelper.GetEnabledForManyToOne(enabledFilters); - condition = new SqlString(string.IsNullOrEmpty(on) && enabledForManyToOne.Count > 0 - ? join.Joinable.FilterFragment(join.Alias, enabledForManyToOne) - : on); - } - if (withClauseFragment != null) - { - if (join.Alias.Equals(withClauseJoinAlias)) - { - condition = condition.Append(" and ").Append(withClauseFragment); - } - } + withClauses[i] = GetWithClause(enabledFilters, ref withClauseFragment, join, last); + last = join.Joinable; + } + + if (rootJoinable == null && TableGroupJoinHelper.ProcessAsTableGroupJoin(joins, withClauses, includeAllSubclassJoins, joinFragment, alias => IsIncluded(alias), factory)) + { + return joinFragment; + } + + for (int i = 0; i < joins.Count; i++) + { + Join join = joins[i]; // NH: the variable "condition" have to be a SqlString because it may contains Parameter instances with BackTrack joinFragment.AddJoin( @@ -215,18 +197,15 @@ internal virtual JoinFragment ToJoinFragment( join.LHSColumns, JoinHelper.GetRHSColumnNames(join.AssociationType, factory), join.JoinType, - condition - ); - if (includeExtraJoins) - { - //TODO: not quite sure about the full implications of this! - AddExtraJoins(joinFragment, join.Alias, join.Joinable, join.JoinType == JoinType.InnerJoin); - } - last = join.Joinable; + withClauses[i] + ); + + AddSubclassJoins(joinFragment, join.Alias, join.Joinable, join.JoinType == JoinType.InnerJoin, renderSubclassJoins); } + if (next != null) { - joinFragment.AddFragment(next.ToJoinFragment(enabledFilters, includeExtraJoins)); + joinFragment.AddFragment(next.ToJoinFragment(enabledFilters, includeAllSubclassJoins)); } joinFragment.AddCondition(conditions.ToSqlString()); if (isFromPart) @@ -234,6 +213,45 @@ internal virtual JoinFragment ToJoinFragment( return joinFragment; } + private SqlString GetWithClause(IDictionary enabledFilters, ref SqlString withClauseFragment, Join join, IJoinable last) + { + string on = join.AssociationType.GetOnCondition(join.Alias, factory, enabledFilters); + var withConditions = new List(); + + if (!string.IsNullOrEmpty(on)) + withConditions.Add(on); + + if (last != null && + IsManyToManyRoot(last) && + ((IQueryableCollection) last).ElementType == join.AssociationType) + { + // the current join represents the join between a many-to-many association table + // and its "target" table. Here we need to apply any additional filters + // defined specifically on the many-to-many + string manyToManyFilter = ((IQueryableCollection) last) + .GetManyToManyFilterFragment(join.Alias, enabledFilters); + + if (!string.IsNullOrEmpty(manyToManyFilter)) + withConditions.Add(manyToManyFilter); + } + else if (string.IsNullOrEmpty(on)) + { + // NH Different behavior : NH1179 and NH1293 + // Apply filters for entity joins and Many-To-One association + var enabledForManyToOne = FilterHelper.GetEnabledForManyToOne(enabledFilters); + if (ForceFilter || enabledForManyToOne.Count > 0) + withConditions.Add(join.Joinable.FilterFragment(join.Alias, enabledForManyToOne)); + } + + if (withClauseFragment != null && !IsManyToManyRoot(join.Joinable)) + { + withConditions.Add(withClauseFragment); + withClauseFragment = null; + } + + return SqlStringHelper.JoinParts(" and ", withConditions); + } + private bool IsManyToManyRoot(IJoinable joinable) { if (joinable != null && joinable.IsCollection) @@ -249,9 +267,9 @@ private bool IsIncluded(string alias) return selector != null && selector.IncludeSubclasses(alias); } - private void AddExtraJoins(JoinFragment joinFragment, string alias, IJoinable joinable, bool innerJoin) + private void AddSubclassJoins(JoinFragment joinFragment, String alias, IJoinable joinable, bool innerJoin, bool includeSubclassJoins) { - bool include = IsIncluded(alias); + bool include = includeSubclassJoins && IsIncluded(alias); joinFragment.AddJoins(joinable.FromJoinFragment(alias, innerJoin, include), joinable.WhereJoinFragment(alias, innerJoin, include)); } @@ -327,5 +345,13 @@ public interface ISelector internal string RootAlias => rootAlias; public ISessionFactoryImplementor Factory => factory; + + internal bool ForceFilter { get; set; } + + public JoinSequence AddJoin(FromElement fromElement) + { + joins.AddRange(fromElement.JoinSequence.joins); + return this; + } } } diff --git a/src/NHibernate/Engine/Loading/CollectionLoadContext.cs b/src/NHibernate/Engine/Loading/CollectionLoadContext.cs index 5aae44f1862..67afff74aea 100644 --- a/src/NHibernate/Engine/Loading/CollectionLoadContext.cs +++ b/src/NHibernate/Engine/Loading/CollectionLoadContext.cs @@ -25,7 +25,7 @@ public partial class CollectionLoadContext private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(CollectionLoadContext)); private readonly LoadContexts loadContexts; private readonly DbDataReader resultSet; - private readonly ISet localLoadingCollectionKeys = new HashSet(); + private readonly HashSet localLoadingCollectionKeys = new HashSet(); /// /// Creates a collection load context for the given result set. @@ -124,6 +124,8 @@ public IPersistentCollection GetLoadingCollection(ICollectionPersister persister } else { + if (loadingCollectionEntry.StopLoading) + return null; if (loadingCollectionEntry.ResultSet == resultSet) { log.Debug("found loading collection bound to current result set processing; reading row"); @@ -416,5 +418,17 @@ public override string ToString() { return base.ToString() + ""; } + + internal void StopLoadingCollections(ICollectionPersister[] collectionPersisters) + { + foreach (var collectionKey in localLoadingCollectionKeys) + { + var loadingCollectionEntry = LoadContext.LocateLoadingCollectionEntry(collectionKey); + if (loadingCollectionEntry != null && Array.IndexOf(collectionPersisters, loadingCollectionEntry.Persister) >= 0) + { + loadingCollectionEntry.StopLoading = true; + } + } + } } } diff --git a/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs b/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs index 992abf1ae5e..78b0260552a 100644 --- a/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs +++ b/src/NHibernate/Engine/Loading/LoadingCollectionEntry.cs @@ -44,6 +44,8 @@ public IPersistentCollection Collection get { return collection; } } + public bool StopLoading { get; set; } + public override string ToString() { return GetType().FullName + "@" + Convert.ToString(GetHashCode(), 16); diff --git a/src/NHibernate/Engine/Query/CallableParser.cs b/src/NHibernate/Engine/Query/CallableParser.cs index dbd6c126dcb..73a4a784305 100644 --- a/src/NHibernate/Engine/Query/CallableParser.cs +++ b/src/NHibernate/Engine/Query/CallableParser.cs @@ -7,7 +7,6 @@ namespace NHibernate.Engine.Query { public static class CallableParser { - public class Detail { public bool IsCallable; diff --git a/src/NHibernate/Engine/Query/HQLQueryPlan.cs b/src/NHibernate/Engine/Query/HQLQueryPlan.cs index ee1062a26e9..eb02c296cea 100644 --- a/src/NHibernate/Engine/Query/HQLQueryPlan.cs +++ b/src/NHibernate/Engine/Query/HQLQueryPlan.cs @@ -108,7 +108,7 @@ public void PerformList(QueryParameters queryParameters, ISessionImplementor ses } IList combinedResults = results ?? new List(); - IdentitySet distinction = new IdentitySet(); + var distinction = new HashSet(ReferenceComparer.Instance); int includedCount = -1; for (int i = 0; i < Translators.Length; i++) { @@ -176,7 +176,7 @@ public IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource public IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session) { - return new SafetyEnumerable(PerformIterate(queryParameters, session)); + return PerformIterate(queryParameters, session).CastOrDefault(); } public int PerformExecuteUpdate(QueryParameters queryParameters, ISessionImplementor session) diff --git a/src/NHibernate/Engine/Query/ParamLocationRecognizer.cs b/src/NHibernate/Engine/Query/ParamLocationRecognizer.cs index f9e33db651a..ff2ee80292f 100644 --- a/src/NHibernate/Engine/Query/ParamLocationRecognizer.cs +++ b/src/NHibernate/Engine/Query/ParamLocationRecognizer.cs @@ -117,6 +117,5 @@ public bool JpaStyle get { return jpaStyle; } } } - } } diff --git a/src/NHibernate/Engine/Query/ParameterMetadata.cs b/src/NHibernate/Engine/Query/ParameterMetadata.cs index b39b93c4b10..a465b52b437 100644 --- a/src/NHibernate/Engine/Query/ParameterMetadata.cs +++ b/src/NHibernate/Engine/Query/ParameterMetadata.cs @@ -15,7 +15,7 @@ public class ParameterMetadata public ParameterMetadata(IEnumerable ordinalDescriptors, IDictionary namedDescriptorMap) { - this.ordinalDescriptors = ordinalDescriptors == null ? Enumerable.Empty().ToArray() : ordinalDescriptors.ToArray(); + this.ordinalDescriptors = ordinalDescriptors == null ? Array.Empty() : ordinalDescriptors.ToArray(); this.namedDescriptorMap = namedDescriptorMap == null ? new Dictionary(1) : new Dictionary(namedDescriptorMap); } @@ -61,4 +61,4 @@ public IType GetNamedParameterExpectedType(string name) return GetNamedParameterDescriptor(name).ExpectedType; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Engine/Query/QueryPlanCache.cs b/src/NHibernate/Engine/Query/QueryPlanCache.cs index 6a0c0b6a7ee..4964b9014e7 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -62,7 +62,7 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo } plan = new QueryExpressionPlan(queryExpression, shallow, enabledFilters, factory); // 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface - if (!(queryExpression is NhLinqExpression linqExpression) || linqExpression.CanCachePlan) + if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan) planCache.Put(key, plan); else log.Debug("Query plan not cacheable"); @@ -117,7 +117,7 @@ public IQueryExpressionPlan GetFilterQueryPlan(IQueryExpression queryExpression, log.Debug("unable to locate collection-filter query plan in cache; generating ({0} : {1})", collectionRole, queryExpression.Key); plan = new FilterQueryPlan(queryExpression, collectionRole, shallow, enabledFilters, factory); // 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface - if (!(queryExpression is NhLinqExpression linqExpression) || linqExpression.CanCachePlan) + if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan) planCache.Put(key, plan); else log.Debug("Query plan not cacheable"); diff --git a/src/NHibernate/Engine/QueryCacheBatchQueue.cs b/src/NHibernate/Engine/QueryCacheBatchQueue.cs index 43d3f435f49..fb041e11f0e 100644 --- a/src/NHibernate/Engine/QueryCacheBatchQueue.cs +++ b/src/NHibernate/Engine/QueryCacheBatchQueue.cs @@ -16,35 +16,35 @@ internal class QueryCacheBatchQueue /// Used to hold information about the entities that are currently eligible for batch-fetching. Ultimately /// used by to build entity load batches. /// - private readonly IDictionary> _queryEntityKeys; + private readonly Dictionary> _queryEntityKeys; /// /// Used to hold information about entity keys that were checked in the cache. /// - private readonly IDictionary> _queryCheckedEntityKeys; + private readonly Dictionary> _queryCheckedEntityKeys; /// /// Used to hold information about collection entries that are currently eligible for batch-fetching. Ultimately /// used by to build collection load batches. /// - private readonly IDictionary> _queryCollectionKeys; + private readonly Dictionary> _queryCollectionKeys; /// /// Used to hold information about collection keys that were checked in the cache. /// - private readonly IDictionary> _queryCheckedCollectionKeys; + private readonly Dictionary> _queryCheckedCollectionKeys; /// /// Used to hold information about collection entries that were checked in the cache. /// - private readonly IDictionary> _queryCheckedCollectionEntries; + private readonly Dictionary> _queryCheckedCollectionEntries; internal QueryCacheBatchQueue(IPersistenceContext persistenceContext) { _persistenceContext = persistenceContext; _queryEntityKeys = new Dictionary>(); _queryCheckedEntityKeys = new Dictionary>(); - _queryCollectionKeys = new Dictionary>(); + _queryCollectionKeys = new Dictionary>(); _queryCheckedCollectionKeys = new Dictionary>(); _queryCheckedCollectionEntries = new Dictionary>(); } diff --git a/src/NHibernate/Engine/StatefulPersistenceContext.cs b/src/NHibernate/Engine/StatefulPersistenceContext.cs index 021bf96bd23..7ca2ce0338c 100644 --- a/src/NHibernate/Engine/StatefulPersistenceContext.cs +++ b/src/NHibernate/Engine/StatefulPersistenceContext.cs @@ -65,10 +65,10 @@ public partial class StatefulPersistenceContext : IPersistenceContext, ISerializ private readonly Dictionary collectionsByKey; // Set of EntityKeys of deleted objects - private readonly ISet nullifiableEntityKeys; + private readonly HashSet nullifiableEntityKeys; // properties that we have tried to load, and not found in the database - private ISet nullAssociations; + private HashSet nullAssociations; // A list of collection wrappers that were instantiating during result set // processing, that we will need to initialize at the end of the query @@ -238,13 +238,9 @@ public IPersistentCollection UseUnownedCollection(CollectionKey key) { return null; } - else - { - IPersistentCollection tempObject; - if (unownedCollections.TryGetValue(key, out tempObject)) - unownedCollections.Remove(key); - return tempObject; - } + + unownedCollections.Remove(key, out var tempObject); + return tempObject; } /// Clear the state of the persistence context @@ -447,17 +443,18 @@ public bool ContainsEntity(EntityKey key) /// public object RemoveEntity(EntityKey key) { - object tempObject = entitiesByKey[key]; - entitiesByKey.Remove(key); - object entity = tempObject; - List toRemove = new List(); - foreach (KeyValuePair pair in entitiesByUniqueKey) - { - if (pair.Value == entity) toRemove.Add(pair.Key); - } - foreach (EntityUniqueKey uniqueKey in toRemove) + if (entitiesByKey.Remove(key, out var entity)) { - entitiesByUniqueKey.Remove(uniqueKey); + List toRemove = new List(); + foreach (KeyValuePair pair in entitiesByUniqueKey) + { + if (pair.Value == entity) toRemove.Add(pair.Key); + } + + foreach (EntityUniqueKey uniqueKey in toRemove) + { + entitiesByUniqueKey.Remove(uniqueKey); + } } entitySnapshotsByKey.Remove(key); @@ -1099,9 +1096,7 @@ public object RemoveProxy(EntityKey key) batchFetchQueue.RemoveBatchLoadableEntityKey(key); batchFetchQueue.RemoveSubselect(key); } - INHibernateProxy tempObject; - if (proxiesByKey.TryGetValue(key, out tempObject)) - proxiesByKey.Remove(key); + proxiesByKey.Remove(key, out INHibernateProxy tempObject); return tempObject; } @@ -1384,8 +1379,9 @@ public bool IsReadOnly(object entityOrProxy) public void ReplaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, object generatedId) { - object tempObject = entitiesByKey[oldKey]; - entitiesByKey.Remove(oldKey); + if (!entitiesByKey.Remove(oldKey, out var tempObject)) + throw new KeyNotFoundException(oldKey.ToString()); + object entity = tempObject; object tempObject2 = entityEntries[entity]; entityEntries.Remove(entity); @@ -1477,7 +1473,6 @@ void IDeserializationCallback.OnDeserialization(object sender) { ce.AfterDeserialize(Session.Factory); } - } catch (HibernateException he) { diff --git a/src/NHibernate/Engine/TableGroupJoinHelper.cs b/src/NHibernate/Engine/TableGroupJoinHelper.cs new file mode 100644 index 00000000000..f5d1f8e39c4 --- /dev/null +++ b/src/NHibernate/Engine/TableGroupJoinHelper.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; + +namespace NHibernate.Engine +{ + //Generates table group join if neccessary. Example of generated query with table group join: + // SELECT * + // FROM Person person0_ + // INNER JOIN ( + // IndividualCustomer individual1_ + // INNER JOIN Customer individual1_1_ ON individual1_.IndividualCustomerID = individual1_1_.Id + // ) ON person0_.Id = individual1_.PersonID AND individual1_1_.Deleted = @p0 + internal class TableGroupJoinHelper + { + internal static bool ProcessAsTableGroupJoin(IReadOnlyList tableGroupJoinables, SqlString[] withClauseFragments, bool includeAllSubclassJoins, JoinFragment joinFragment, Func isSubclassIncluded, ISessionFactoryImplementor sessionFactoryImplementor) + { + if (!NeedsTableGroupJoin(tableGroupJoinables, withClauseFragments, includeAllSubclassJoins)) + return false; + + var first = tableGroupJoinables[0]; + string joinString = ANSIJoinFragment.GetJoinString(first.JoinType); + joinFragment.AddFromFragmentString( + new SqlString( + joinString, + " (", + first.Joinable.TableName, + " ", + first.Alias + )); + + foreach (var join in tableGroupJoinables) + { + if (join != first) + joinFragment.AddJoin( + join.Joinable.TableName, + join.Alias, + join.LHSColumns, + JoinHelper.GetRHSColumnNames(join.AssociationType, sessionFactoryImplementor), + join.JoinType, + SqlString.Empty); + + bool include = includeAllSubclassJoins && isSubclassIncluded(join.Alias); + // TODO (from hibernate): Think about if this could be made always true + // NH Specific: made always true (original check: join.JoinType == JoinType.InnerJoin) + const bool innerJoin = true; + joinFragment.AddJoins( + join.Joinable.FromJoinFragment(join.Alias, innerJoin, include), + join.Joinable.WhereJoinFragment(join.Alias, innerJoin, include)); + } + + var withClause = GetTableGroupJoinWithClause(withClauseFragments, first, sessionFactoryImplementor); + joinFragment.AddFromFragmentString(withClause); + return true; + } + + private static bool NeedsTableGroupJoin(IReadOnlyList joins, SqlString[] withClauseFragments, bool includeSubclasses) + { + // If we don't have a with clause, we don't need a table group join + if (withClauseFragments.All(x => SqlStringHelper.IsEmpty(x))) + { + return false; + } + + // If we only have one join, a table group join is only necessary if subclass columns are used in the with clause + if (joins.Count == 1) + { + return joins[0].Joinable is AbstractEntityPersister persister && persister.HasSubclassJoins(includeSubclasses); + //NH Specific: No alias processing + //return isSubclassAliasDereferenced( joins[ 0], withClauseFragment ); + } + + //NH Specific: No alias processing (see hibernate JoinSequence.NeedsTableGroupJoin) + return true; + } + + private static SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragments, IJoin first, ISessionFactoryImplementor factory) + { + SqlStringBuilder fromFragment = new SqlStringBuilder(); + fromFragment.Add(")").Add(" on "); + + string[] lhsColumns = first.LHSColumns; + var isAssociationJoin = lhsColumns.Length > 0; + if (isAssociationJoin) + { + string rhsAlias = first.Alias; + string[] rhsColumns = JoinHelper.GetRHSColumnNames(first.AssociationType, factory); + fromFragment.Add(lhsColumns[0]).Add("=").Add(rhsAlias).Add(".").Add(rhsColumns[0]); + for (int j = 1; j < lhsColumns.Length; j++) + { + fromFragment.Add(" and ").Add(lhsColumns[j]).Add("=").Add(rhsAlias).Add(".").Add(rhsColumns[j]); + } + } + + AppendWithClause(fromFragment, isAssociationJoin, withClauseFragments); + + return fromFragment.ToSqlString(); + } + + private static void AppendWithClause(SqlStringBuilder fromFragment, bool hasConditions, SqlString[] withClauseFragments) + { + for (var i = 0; i < withClauseFragments.Length; i++) + { + var withClause = withClauseFragments[i]; + if (SqlStringHelper.IsEmpty(withClause)) + continue; + + if (withClause.StartsWithCaseInsensitive(" and ")) + { + if (!hasConditions) + { + withClause = withClause.Substring(4); + } + } + else if (hasConditions) + { + fromFragment.Add(" and "); + } + + fromFragment.Add(withClause); + hasConditions = true; + } + } + } +} diff --git a/src/NHibernate/Engine/Versioning.cs b/src/NHibernate/Engine/Versioning.cs index c9c293d3204..a450d8022db 100644 --- a/src/NHibernate/Engine/Versioning.cs +++ b/src/NHibernate/Engine/Versioning.cs @@ -1,4 +1,3 @@ - using NHibernate.Persister.Entity; using NHibernate.Type; diff --git a/src/NHibernate/Event/Default/AbstractSaveEventListener.cs b/src/NHibernate/Event/Default/AbstractSaveEventListener.cs index f4fe501ab2e..802ed4eeb6b 100644 --- a/src/NHibernate/Event/Default/AbstractSaveEventListener.cs +++ b/src/NHibernate/Event/Default/AbstractSaveEventListener.cs @@ -35,7 +35,7 @@ protected virtual bool? AssumedUnsaved get { return null; } } - protected abstract CascadingAction CascadeAction { get;} + protected abstract CascadingAction CascadeAction { get; } /// /// After the save, will te version number be incremented diff --git a/src/NHibernate/Event/Default/DefaultAutoFlushEventListener.cs b/src/NHibernate/Event/Default/DefaultAutoFlushEventListener.cs index 70a8bd898da..6434c2a2203 100644 --- a/src/NHibernate/Event/Default/DefaultAutoFlushEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultAutoFlushEventListener.cs @@ -48,7 +48,6 @@ public virtual void OnAutoFlush(AutoFlushEvent @event) } else { - if (log.IsDebugEnabled()) log.Debug("Dont need to execute flush"); source.ActionQueue.ClearFromFlushNeededCheck(oldSize); diff --git a/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs b/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs index 13f9a2e20a1..96f6703563e 100644 --- a/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs @@ -26,7 +26,7 @@ public partial class DefaultDeleteEventListener : IDeleteEventListener /// The delete event to be handled. public virtual void OnDelete(DeleteEvent @event) { - OnDelete(@event, new IdentitySet()); + OnDelete(@event, new HashSet(ReferenceComparer.Instance)); } public virtual void OnDelete(DeleteEvent @event, ISet transientEntities) @@ -143,7 +143,7 @@ protected virtual void DeleteTransientEntity(IEventSource session, object entity // NH different impl : NH-1895 if(transientEntities == null) { - transientEntities = new HashSet(); + transientEntities = new HashSet(ReferenceComparer.Instance); } if (!transientEntities.Add(entity)) { diff --git a/src/NHibernate/Event/Default/DefaultDirtyCheckEventListener.cs b/src/NHibernate/Event/Default/DefaultDirtyCheckEventListener.cs index 482968352d3..e8692a1f0bd 100644 --- a/src/NHibernate/Event/Default/DefaultDirtyCheckEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultDirtyCheckEventListener.cs @@ -1,6 +1,5 @@ using System; - namespace NHibernate.Event.Default { /// diff --git a/src/NHibernate/Event/Default/DefaultEvictEventListener.cs b/src/NHibernate/Event/Default/DefaultEvictEventListener.cs index c19bc54fd90..a1b5612d9a9 100644 --- a/src/NHibernate/Event/Default/DefaultEvictEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultEvictEventListener.cs @@ -59,7 +59,6 @@ public virtual void OnEvict(EvictEvent @event) protected virtual void DoEvict(object obj, EntityKey key, IEntityPersister persister, IEventSource session) { - if (log.IsDebugEnabled()) { log.Debug("evicting {0}", MessageHelper.InfoString(persister)); diff --git a/src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs b/src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs index 28b4654ebbf..7848f25cdaa 100644 --- a/src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs @@ -389,7 +389,6 @@ protected bool IsUpdateNecessary(FlushEntityEvent @event) } else { - int[] dirtyProperties = @event.DirtyProperties; if (dirtyProperties != null && dirtyProperties.Length != 0) { @@ -496,7 +495,6 @@ protected virtual void DirtyCheck(FlushEntityEvent @event) @event.DirtyCheckPossible = !cannotDirtyCheck; } - private object[] GetDatabaseSnapshot(ISessionImplementor session, IEntityPersister persister, object id) { if (persister.IsSelectBeforeUpdateRequired) @@ -523,6 +521,5 @@ private object[] GetDatabaseSnapshot(ISessionImplementor session, IEntityPersist return session.PersistenceContext.GetCachedDatabaseSnapshot(entityKey); } } - } } diff --git a/src/NHibernate/Event/Default/DefaultLoadEventListener.cs b/src/NHibernate/Event/Default/DefaultLoadEventListener.cs index c3db8bf4f0f..644cf67d1d1 100644 --- a/src/NHibernate/Event/Default/DefaultLoadEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultLoadEventListener.cs @@ -43,7 +43,6 @@ public virtual void OnLoad(LoadEvent @event, LoadType loadType) if (persister == null) { - var message = new StringBuilder(512); message.AppendLine(string.Format("Unable to locate persister for the entity named '{0}'.", @event.EntityClassName)); message.AppendLine("The persister define the persistence strategy for an entity."); @@ -406,7 +405,6 @@ protected virtual object LoadFromSessionCache(LoadEvent @event, EntityKey keyToL return old; } - /// Attempts to load the entity from the second-level cache. /// The load event /// The persister for the entity being requested for load diff --git a/src/NHibernate/Event/Default/DefaultMergeEventListener.cs b/src/NHibernate/Event/Default/DefaultMergeEventListener.cs index 31781db0df4..02922db2ebf 100644 --- a/src/NHibernate/Event/Default/DefaultMergeEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultMergeEventListener.cs @@ -9,7 +9,6 @@ using NHibernate.Proxy; using NHibernate.Type; - namespace NHibernate.Event.Default { /// diff --git a/src/NHibernate/Event/Default/DefaultRefreshEventListener.cs b/src/NHibernate/Event/Default/DefaultRefreshEventListener.cs index 08c4f40da61..4c04c066859 100644 --- a/src/NHibernate/Event/Default/DefaultRefreshEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultRefreshEventListener.cs @@ -112,8 +112,7 @@ public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready) if (!persister.IsMutable) source.SetReadOnly(result, true); else - source.SetReadOnly(result, (e == null ? source.DefaultReadOnly : e.IsReadOnly)); - + source.SetReadOnly(result, e == null ? source.DefaultReadOnly : !e.IsModifiableEntity()); source.FetchProfile = previousFetchProfile; // NH Different behavior : we are ignoring transient entities without throw any kind of exception diff --git a/src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs b/src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs index 897793a8180..06eebb15f1b 100644 --- a/src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs @@ -72,7 +72,7 @@ protected virtual object PerformSaveOrUpdate(SaveOrUpdateEvent @event) case EntityState.Persistent: return EntityIsPersistent(@event); - default: //TRANSIENT or DELETED + default: //TRANSIENT or DELETED return EntityIsTransient(@event); } } diff --git a/src/NHibernate/Event/Default/DirtyCollectionSearchVisitor.cs b/src/NHibernate/Event/Default/DirtyCollectionSearchVisitor.cs index 6f1181873bf..5296aedee20 100644 --- a/src/NHibernate/Event/Default/DirtyCollectionSearchVisitor.cs +++ b/src/NHibernate/Event/Default/DirtyCollectionSearchVisitor.cs @@ -51,7 +51,6 @@ public bool WasDirtyCollectionFound internal override object ProcessCollection(object collection, CollectionType type) { - if (collection != null) { ISessionImplementor session = Session; diff --git a/src/NHibernate/Event/Default/EvictVisitor.cs b/src/NHibernate/Event/Default/EvictVisitor.cs index ce4a3c89a95..8ad1dca730a 100644 --- a/src/NHibernate/Event/Default/EvictVisitor.cs +++ b/src/NHibernate/Event/Default/EvictVisitor.cs @@ -1,4 +1,3 @@ - using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Impl; @@ -20,7 +19,6 @@ public EvictVisitor(IEventSource session) : base(session) { } internal override object ProcessCollection(object collection, CollectionType type) { - if (collection != null) EvictCollection(collection, type); diff --git a/src/NHibernate/Event/Default/ProxyVisitor.cs b/src/NHibernate/Event/Default/ProxyVisitor.cs index f725a4c5fe9..ddd7f8b0056 100644 --- a/src/NHibernate/Event/Default/ProxyVisitor.cs +++ b/src/NHibernate/Event/Default/ProxyVisitor.cs @@ -19,7 +19,6 @@ public ProxyVisitor(IEventSource session) : base(session) { } /// internal override object ProcessEntity(object value, EntityType entityType) { - if (value != null) { Session.PersistenceContext.ReassociateIfUninitializedProxy(value); diff --git a/src/NHibernate/Event/Default/ReattachVisitor.cs b/src/NHibernate/Event/Default/ReattachVisitor.cs index e2d14ac7d61..64584875a81 100644 --- a/src/NHibernate/Event/Default/ReattachVisitor.cs +++ b/src/NHibernate/Event/Default/ReattachVisitor.cs @@ -1,4 +1,3 @@ - using NHibernate.Action; using NHibernate.Engine; using NHibernate.Impl; diff --git a/src/NHibernate/Event/Default/WrapVisitor.cs b/src/NHibernate/Event/Default/WrapVisitor.cs index 309367aaea2..d5996d448d6 100644 --- a/src/NHibernate/Event/Default/WrapVisitor.cs +++ b/src/NHibernate/Event/Default/WrapVisitor.cs @@ -1,4 +1,3 @@ - using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Persister.Collection; diff --git a/src/NHibernate/Event/IDestructible.cs b/src/NHibernate/Event/IDestructible.cs index 4c0182a1eca..f91756c36ec 100644 --- a/src/NHibernate/Event/IDestructible.cs +++ b/src/NHibernate/Event/IDestructible.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Event { /// @@ -12,4 +11,4 @@ public interface IDestructible /// void Cleanup(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Event/IEventSource.cs b/src/NHibernate/Event/IEventSource.cs index 5f5c0d42090..e30d86ba441 100644 --- a/src/NHibernate/Event/IEventSource.cs +++ b/src/NHibernate/Event/IEventSource.cs @@ -9,7 +9,7 @@ namespace NHibernate.Event public partial interface IEventSource : ISessionImplementor, ISession { /// Get the ActionQueue for this session - ActionQueue ActionQueue { get;} + ActionQueue ActionQueue { get; } /// /// Is auto-flush suspended? diff --git a/src/NHibernate/Exceptions/GenericADOException.cs b/src/NHibernate/Exceptions/GenericADOException.cs index d45e298c3f3..b82dcfeb2b1 100644 --- a/src/NHibernate/Exceptions/GenericADOException.cs +++ b/src/NHibernate/Exceptions/GenericADOException.cs @@ -9,10 +9,9 @@ public class GenericADOException : ADOException { public GenericADOException() { - } public GenericADOException(SerializationInfo info, StreamingContext context) : base(info, context) { } public GenericADOException(string message, Exception innerException, string sql) : base(message, innerException, sql) { } public GenericADOException(string message, Exception innerException) : base(message, innerException) { } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Exceptions/ReflectionBasedSqlStateExtracter.cs b/src/NHibernate/Exceptions/ReflectionBasedSqlStateExtracter.cs index c89a0e87342..18838332370 100644 --- a/src/NHibernate/Exceptions/ReflectionBasedSqlStateExtracter.cs +++ b/src/NHibernate/Exceptions/ReflectionBasedSqlStateExtracter.cs @@ -6,7 +6,6 @@ namespace NHibernate.Exceptions { class ReflectionBasedSqlStateExtracter: SqlStateExtracter { - /* OdbcException, OleDbException, IfxException, Db2Exception, and possible others * have Errors collection which contains fields: NativeError and SQLState * These fields can be extracted using reflection @@ -61,4 +60,4 @@ public override string ExtractSingleSqlState(DbException sqle) return sqlState; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Exceptions/SqlParseException.cs b/src/NHibernate/Exceptions/SqlParseException.cs index 615d7dbed0b..758b3414afa 100644 --- a/src/NHibernate/Exceptions/SqlParseException.cs +++ b/src/NHibernate/Exceptions/SqlParseException.cs @@ -6,7 +6,6 @@ namespace NHibernate.Exceptions [Serializable] public class SqlParseException : Exception { - public SqlParseException(string message) : base(message) { } diff --git a/src/NHibernate/Exceptions/TemplatedViolatedConstraintNameExtracter.cs b/src/NHibernate/Exceptions/TemplatedViolatedConstraintNameExtracter.cs index b80095738ec..24702089849 100644 --- a/src/NHibernate/Exceptions/TemplatedViolatedConstraintNameExtracter.cs +++ b/src/NHibernate/Exceptions/TemplatedViolatedConstraintNameExtracter.cs @@ -8,7 +8,6 @@ namespace NHibernate.Exceptions /// public abstract class TemplatedViolatedConstraintNameExtracter : IViolatedConstraintNameExtracter { - /// /// Extracts the constraint name based on a template (i.e., templateStartconstraintNametemplateEnd). /// diff --git a/src/NHibernate/FKUnmatchingColumnsException.cs b/src/NHibernate/FKUnmatchingColumnsException.cs index 27d4f5e31ee..1a0c2996f5f 100644 --- a/src/NHibernate/FKUnmatchingColumnsException.cs +++ b/src/NHibernate/FKUnmatchingColumnsException.cs @@ -18,7 +18,6 @@ public FKUnmatchingColumnsException(string message) { } - /// /// Initializes a new instance of the class. /// @@ -49,5 +48,4 @@ protected FKUnmatchingColumnsException(SerializationInfo info, StreamingContext { } } - } diff --git a/src/NHibernate/Hql/Ast/ANTLR/ASTQueryTranslatorFactory.cs b/src/NHibernate/Hql/Ast/ANTLR/ASTQueryTranslatorFactory.cs index 59b03cd7597..e7b95eed2cb 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/ASTQueryTranslatorFactory.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/ASTQueryTranslatorFactory.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; -using System.Linq; using NHibernate.Engine; using NHibernate.Hql.Ast.ANTLR.Tree; +using NHibernate.Linq; +using NHibernate.Util; namespace NHibernate.Hql.Ast.ANTLR { @@ -16,16 +17,24 @@ public class ASTQueryTranslatorFactory : IQueryTranslatorFactory { public IQueryTranslator[] CreateQueryTranslators(IQueryExpression queryExpression, string collectionRole, bool shallow, IDictionary filters, ISessionFactoryImplementor factory) { - return CreateQueryTranslators(queryExpression.Translate(factory, collectionRole != null), queryExpression.Key, collectionRole, shallow, filters, factory); + return CreateQueryTranslators(queryExpression, queryExpression.Translate(factory, collectionRole != null), queryExpression.Key, collectionRole, shallow, filters, factory); } - static IQueryTranslator[] CreateQueryTranslators(IASTNode ast, string queryIdentifier, string collectionRole, bool shallow, IDictionary filters, ISessionFactoryImplementor factory) + static IQueryTranslator[] CreateQueryTranslators( + IQueryExpression queryExpression, + IASTNode ast, + string queryIdentifier, + string collectionRole, + bool shallow, + IDictionary filters, + ISessionFactoryImplementor factory) { var polymorphicParsers = AstPolymorphicProcessor.Process(ast, factory); var translators = polymorphicParsers - .Select(hql => new QueryTranslatorImpl(queryIdentifier, hql, filters, factory)) - .ToArray(); + .ToArray(hql => queryExpression is NhLinqExpression linqExpression + ? new QueryTranslatorImpl(queryIdentifier, hql, filters, factory, linqExpression.NamedParameters) + : new QueryTranslatorImpl(queryIdentifier, hql, filters, factory)); foreach (var translator in translators) { @@ -40,7 +49,6 @@ static IQueryTranslator[] CreateQueryTranslators(IASTNode ast, string queryIdent } return translators; - } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/AstPolymorphicProcessor.cs b/src/NHibernate/Hql/Ast/ANTLR/AstPolymorphicProcessor.cs index 9b92315de48..259e6d203d4 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/AstPolymorphicProcessor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/AstPolymorphicProcessor.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using System.Linq; using NHibernate.Engine; using NHibernate.Hql.Ast.ANTLR.Tree; +using NHibernate.Util; namespace NHibernate.Hql.Ast.ANTLR { @@ -9,7 +9,7 @@ public class AstPolymorphicProcessor { private readonly IASTNode _ast; private readonly ISessionFactoryImplementor _factory; - private IEnumerable> _nodeMapping; + private Dictionary _nodeMapping; private AstPolymorphicProcessor(IASTNode ast, ISessionFactoryImplementor factory) { @@ -29,28 +29,27 @@ private IASTNode[] Process() // Find all the polymorphic query sources _nodeMapping = new PolymorphicQuerySourceDetector(_factory).Process(_ast); - if (_nodeMapping.Count() > 0) - { - return DuplicateTree().ToArray(); - } - else + if (_nodeMapping.Count == 0) + return new[] {_ast}; + + var parsers = DuplicateTree(); + + if (parsers.Length == 0) { - return new[] { _ast }; + var entityNames = _nodeMapping.Keys.ToArray(x => PolymorphicQuerySourceDetector.GetClassName(x)); + throw new QuerySyntaxException( + entityNames.Length == 1 + ? entityNames[0] + " is not mapped" + : string.Join(", ", entityNames) + " are not mapped"); } + + return parsers; } - private IEnumerable DuplicateTree() + private IASTNode[] DuplicateTree() { var replacements = CrossJoinDictionaryArrays.PerformCrossJoin(_nodeMapping); - - var dups = new IASTNode[replacements.Count()]; - - for (var i = 0; i < replacements.Count(); i++) - { - dups[i] = DuplicateTree(_ast, replacements[i]); - } - - return dups; + return replacements.ToArray(x => DuplicateTree(_ast, x)); } private static IASTNode DuplicateTree(IASTNode ast, IDictionary nodeMapping) @@ -72,4 +71,4 @@ private static IASTNode DuplicateTree(IASTNode ast, IDictionary internal static class CollectionProperties { - public static readonly Dictionary HQL_COLLECTION_PROPERTIES = new Dictionary(StringComparer.OrdinalIgnoreCase); static CollectionProperties() diff --git a/src/NHibernate/Hql/Ast/ANTLR/ErrorCounter.cs b/src/NHibernate/Hql/Ast/ANTLR/ErrorCounter.cs index 0c57c7f33f9..1c1897dcb04 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/ErrorCounter.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/ErrorCounter.cs @@ -3,7 +3,6 @@ using System.Text; using Antlr.Runtime; - namespace NHibernate.Hql.Ast.ANTLR { /// @@ -55,8 +54,8 @@ private string GetErrorString() if (!first) buf.Append('\n'); first = false; - } + return buf.ToString(); } @@ -83,5 +82,4 @@ public void ThrowQueryException() } } } - } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs b/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs index 9a585f6b1e5..9c2f86a4fd0 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs @@ -42,7 +42,7 @@ protected AbstractStatementExecutor(IStatement statement, INHibernateLogger log) protected ISessionFactoryImplementor Factory { - get{return Walker.SessionFactoryHelper.Factory;} + get { return Walker.SessionFactoryHelper.Factory; } } protected virtual void CoordinateSharedCacheCleanup(ISessionImplementor session) @@ -188,9 +188,9 @@ protected virtual void DropTemporaryTableIfNecessary(IQueryable persister, ISess { IIsolatedWork work = new TmpIdTableDropIsolatedWork(persister, log, session); - if (ShouldIsolateTemporaryTableDDL()) + if (ShouldIsolateTemporaryTableDDL() && session.ConnectionManager.CurrentTransaction != null) { - session.ConnectionManager.Transaction.RegisterSynchronization( + session.ConnectionManager.CurrentTransaction.RegisterSynchronization( new IsolatedWorkAfterTransaction(work, session)); } else diff --git a/src/NHibernate/Hql/Ast/ANTLR/Hql.g b/src/NHibernate/Hql/Ast/ANTLR/Hql.g index 2783936a19a..f074f6e44d9 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Hql.g +++ b/src/NHibernate/Hql/Ast/ANTLR/Hql.g @@ -19,6 +19,7 @@ tokens BETWEEN='between'; CLASS='class'; COUNT='count'; + CROSS='cross'; DELETE='delete'; DESCENDING='desc'; DOT; @@ -255,6 +256,7 @@ fromClause fromJoin : ( ( ( LEFT | RIGHT ) (OUTER)? ) | FULL | INNER )? JOIN^ (FETCH)? path (asAlias)? (propertyFetch)? (withClause)? | ( ( ( LEFT | RIGHT ) (OUTER)? ) | FULL | INNER )? JOIN^ (FETCH)? ELEMENTS! OPEN! path CLOSE! (asAlias)? (propertyFetch)? (withClause)? + | CROSS JOIN^ { WeakKeywords(); } path (asAlias)? (propertyFetch)? ; withClause @@ -527,23 +529,31 @@ unaryExpression ; caseExpression - : CASE (whenClause)+ (elseClause)? END - -> ^(CASE whenClause+ elseClause?) - | CASE unaryExpression (altWhenClause)+ (elseClause)? END - -> ^(CASE2 unaryExpression altWhenClause+ elseClause?) + : simpleCaseStatement + | searchedCaseStatement ; - -whenClause - : (WHEN^ logicalExpression THEN! expression) + +simpleCaseStatement + : CASE expression (simpleCaseWhenClause)+ (elseClause)? END + -> ^(CASE2 expression simpleCaseWhenClause+ elseClause?) ; - -altWhenClause - : (WHEN^ unaryExpression THEN! expression) + +simpleCaseWhenClause + : (WHEN^ expression THEN! expression) ; elseClause : (ELSE^ expression) ; + +searchedCaseStatement + : CASE (searchedCaseWhenClause)+ (elseClause)? END + -> ^(CASE searchedCaseWhenClause+ elseClause?) + ; + +searchedCaseWhenClause + : (WHEN^ logicalExpression THEN! expression) + ; quantifiedExpression : ( SOME^ | EXISTS^ | ALL^ | ANY^ ) @@ -603,8 +613,8 @@ identPrimary //## aggregateFunction: //## COUNT | 'sum' | 'avg' | 'max' | 'min'; aggregate - : ( op=SUM | op=AVG | op=MAX | op=MIN ) OPEN additiveExpression CLOSE - -> ^(AGGREGATE[$op] additiveExpression) + : ( op=SUM | op=AVG | op=MAX | op=MIN ) OPEN aggregateArgument CLOSE + -> ^(AGGREGATE[$op] aggregateArgument) // Special case for count - It's 'parameters' can be keywords. | COUNT OPEN ( s=STAR | p=aggregateDistinctAll ) CLOSE -> {s == null}? ^(COUNT $p) @@ -612,10 +622,19 @@ aggregate | collectionExpr ; +aggregateArgument + : ( additiveExpression | selectStatement ) + ; + aggregateDistinctAll - : ( ( DISTINCT | ALL )? ( path | collectionExpr ) ) + : ( distinctAll aggregateArgument ) => (distinctAll aggregateArgument) + | aggregateArgument ; - + +distinctAll + : ( DISTINCT | ALL ) + ; + //## collection: ( OPEN query CLOSE ) | ( 'elements'|'indices' OPEN path CLOSE ); collectionExpr diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs index 55e8912babe..f33f8b13acb 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs @@ -102,7 +102,6 @@ public bool Filter set { filter = value; } } - public override void ReportError(RecognitionException e) { _parseErrorHandler.ReportError(e); @@ -132,6 +131,18 @@ public void WeakKeywords() } } break; + case LEFT: + case RIGHT: + // Support left and right functions + if (input.LA(2) == OPEN) + { + input.LT(1).Type = IDENT; + if (log.IsDebugEnabled()) + { + log.Debug("weakKeywords() : new LT(1) token - {0}", input.LT(1)); + } + } + break; default: // Case 2: The current token is after FROM and before '.'. if (t != IDENT && input.LA(-1) == FROM && ((input.LA(2) == DOT) || (input.LA(2) == IDENT) || (input.LA(2) == -1))) diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 9ab54dd02f4..2672dc9aec1 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -36,7 +36,7 @@ public partial class HqlSqlWalker private string _statementTypeName; private int _positionalParameterCount; private int _parameterCount; - private readonly NullableDictionary _namedParameters = new NullableDictionary(); + private readonly NullableDictionary _namedParameterLocations = new NullableDictionary(); private readonly List _parameters = new List(); private FromClause _currentFromClause; private SelectClause _selectClause; @@ -47,13 +47,14 @@ public partial class HqlSqlWalker //Maps each top-level result variable to its SelectExpression; //(excludes result variables defined in subqueries) // - private readonly IDictionary selectExpressionsByResultVariable = new Dictionary(); + private readonly Dictionary selectExpressionsByResultVariable = new Dictionary(); - private readonly ISet _querySpaces = new HashSet(); + private readonly HashSet _querySpaces = new HashSet(); private readonly LiteralProcessor _literalProcessor; private readonly IDictionary _tokenReplacements; + private readonly IDictionary _namedParameters; private JoinType _impliedJoinType; @@ -64,17 +65,30 @@ public partial class HqlSqlWalker private int numberOfParametersInSetClause; private Stack clauseStack=new Stack(); - public HqlSqlWalker(QueryTranslatorImpl qti, - ISessionFactoryImplementor sfi, - ITreeNodeStream input, - IDictionary tokenReplacements, - string collectionRole) + public HqlSqlWalker( + QueryTranslatorImpl qti, + ISessionFactoryImplementor sfi, + ITreeNodeStream input, + IDictionary tokenReplacements, + string collectionRole) + : this(qti, sfi, input, tokenReplacements, null, collectionRole) + { + } + + internal HqlSqlWalker( + QueryTranslatorImpl qti, + ISessionFactoryImplementor sfi, + ITreeNodeStream input, + IDictionary tokenReplacements, + IDictionary namedParameters, + string collectionRole) : this(input) { _sessionFactoryHelper = new SessionFactoryHelperExtensions(sfi); _qti = qti; _literalProcessor = new LiteralProcessor(this); _tokenReplacements = tokenReplacements; + _namedParameters = namedParameters; _collectionFilterRole = collectionRole; } @@ -122,7 +136,7 @@ public ISet QuerySpaces public IDictionary NamedParameters { - get { return _namedParameters; } + get { return _namedParameterLocations; } } internal SessionFactoryHelperExtensions SessionFactoryHelper @@ -413,6 +427,10 @@ void HandleClauseStart(int clauseType) { clauseStack.Push(_currentClauseType); _currentClauseType = clauseType; + if (_level == 1) + { + CurrentTopLevelClauseType = clauseType; + } } void HandleClauseEnd(int clauseType) @@ -736,6 +754,11 @@ void CreateFromJoinElement( HandleWithFragment(fromElement, with); } + + if (fromElement.Parent == null) + { + fromElement.FromClause.AddChild(fromElement); + } } if ( log.IsDebugEnabled() ) @@ -775,8 +798,8 @@ private IQueryable ResolveEntityJoinReferencedPersister(IASTNode path) if (path.Type == IDENT) { var pathIdentNode = (IdentNode) path; - string name = path.Text ?? pathIdentNode.OriginalText; - return SessionFactoryHelper.FindQueryableUsingImports(name); + // Since IDENT node is not expected for implicit join path, we can throw on not found persister + return (IQueryable) SessionFactoryHelper.RequireClassPersister(pathIdentNode.Path); } else if (path.Type == DOT) { @@ -929,7 +952,7 @@ static void ProcessFunction(IASTNode functionCall, bool inSelect) void ProcessBool(IASTNode constant) { - _literalProcessor.ProcessBoolean(constant); // Use the delegate. + _literalProcessor.ProcessBoolean(constant); // Use the delegate. } static void PrepareLogicOperator(IASTNode operatorNode) @@ -1028,13 +1051,20 @@ IASTNode GenerateNamedParameter(IASTNode delimiterNode, IASTNode nameNode) ); parameter.HqlParameterSpecification = paramSpec; + if (_namedParameters != null && _namedParameters.TryGetValue(name, out var namedParameter)) + { + // Add the parameter type information so that we are able to calculate functions return types + // when the parameter is used as an argument. + parameter.ExpectedType = namedParameter.Type; + } + _parameters.Add(paramSpec); return parameter; } IASTNode GeneratePositionalParameter(IASTNode inputNode) { - if (_namedParameters.Count > 0) + if (_namedParameterLocations.Count > 0) { // NH TODO: remove this limitation throw new SemanticException("cannot define positional parameter after any named parameters have been defined"); @@ -1070,6 +1100,12 @@ public int CurrentClauseType get { return _currentClauseType; } } + // Note: CurrentClauseType tracks the current clause within the current + // statement, regardless of level; CurrentTopLevelClauseType, on the other + // hand, tracks the current clause within the top (or primary) statement. + // Thus, CurrentTopLevelClauseType ignores the clauses from any subqueries. + public int CurrentTopLevelClauseType { get; private set; } + public IDictionary EnabledFilters { get { return _qti.EnabledFilters; } @@ -1135,7 +1171,6 @@ public FromClause GetFinalFromClause() return top; } - // Helper methods public bool IsFilter() @@ -1167,15 +1202,15 @@ public void AddQuerySpaces(string[] spaces) private void TrackNamedParameterPositions(string name) { int loc = _parameterCount++; - object o = _namedParameters[name]; + object o = _namedParameterLocations[name]; if ( o == null ) { - _namedParameters.Add(name, loc); + _namedParameterLocations.Add(name, loc); } else if (o is int) { List list = new List(4) {(int) o, loc}; - _namedParameters[name] = list; + _namedParameterLocations[name] = list; } else { @@ -1204,8 +1239,7 @@ private void HandleWithFragment(FromElement fromElement, IASTNode hqlWithNode) sql.whereExpr(); - var withClauseFragment = new SqlString("(", sql.GetSQL(), ")"); - fromElement.SetWithClauseFragment(visitor.GetJoinAlias(), withClauseFragment); + fromElement.WithClauseFragment = new SqlString("(", sql.GetSQL(), ")"); } catch (SemanticException) { @@ -1225,44 +1259,15 @@ private void HandleWithFragment(FromElement fromElement, IASTNode hqlWithNode) class WithClauseVisitor : IVisitationStrategy { private readonly FromElement _joinFragment; - private readonly bool _multiTable; public WithClauseVisitor(FromElement fromElement) { _joinFragment = fromElement; - _multiTable = (fromElement.EntityPersister as IQueryable)?.IsMultiTable == true; } public void Visit(IASTNode node) { - // todo : currently expects that the individual with expressions apply to the same sql table join. - // This may not be the case for joined-subclass where the property values - // might be coming from different tables in the joined hierarchy. At some - // point we should expand this to support that capability. However, that has - // some difficulties: - // 1) the biggest is how to handle ORs when the individual comparisons are - // linked to different sql joins. - // 2) here we would need to track each comparison individually, along with - // the join alias to which it applies and then pass that information - // back to the FromElement so it can pass it along to the JoinSequence - if (_multiTable && node is DotNode dotNode) - { - FromElement fromElement = dotNode.FromElement; - if (_joinFragment == fromElement) - { - var joinAlias = ExtractAppliedAlias(dotNode); - //See WithClauseFixture.InvalidWithSemantics to understand the logic behind this check - // todo : temporary - // needed because currently persister is the one that - // creates and renders the join fragments for inheritence - // hierarchies... - if (joinAlias != _joinFragment.TableAlias) - { - throw new InvalidWithClauseException("with clause can only reference columns in the driving table"); - } - } - } - else if (node is ParameterNode paramNode) + if (node is ParameterNode paramNode) { ApplyParameterSpecification(paramNode.HqlParameterSpecification); } @@ -1289,11 +1294,8 @@ private void ApplyParameterSpecification(IParameterSpecification paramSpec) _joinFragment.AddEmbeddedParameter(paramSpec); } - private static String ExtractAppliedAlias(IASTNode dotNode) - { - return dotNode.Text.Substring( 0, dotNode.Text.IndexOf( '.' ) ); - } - + // Since 5.4 + [Obsolete("This method has no more usages and will be removed in a future version.")] public String GetJoinAlias() { return _joinFragment.TableAlias; diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g index 78825998019..fba1010337c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g @@ -240,6 +240,7 @@ constructor aggregateExpr : expr //p:propertyRef { resolve(#p); } | collectionFunction + | selectStatement ; propertyFetch @@ -305,6 +306,9 @@ joinType returns [int j] | INNER { $j = INNER; } + | CROSS { + $j = CROSS; + } ; // Matches a path and returns the normalized string for the path (usually @@ -432,8 +436,28 @@ arithmeticExpr ; caseExpr - : ^(CASE { _inCase = true; } (^(WHEN logicalExpr expr))+ (^(ELSE expr))?) { _inCase = false; } - | ^(CASE2 { _inCase = true; } expr (^(WHEN expr expr))+ (^(ELSE expr))?) { _inCase = false; } + : simpleCaseExpression + | searchedCaseExpression + ; + +simpleCaseExpression + : ^(CASE2 {_inCase=true;} exprOrSubquery (simpleCaseWhenClause)+ (elseClause)?) {_inCase=false;} + ; + +simpleCaseWhenClause + : ^(WHEN exprOrSubquery exprOrSubquery) + ; + +elseClause + : ^(ELSE exprOrSubquery) + ; + +searchedCaseExpression + : ^(CASE {_inCase = true;} (searchedCaseWhenClause)+ (elseClause)?) {_inCase = false;} + ; + +searchedCaseWhenClause + : ^(WHEN logicalExpr exprOrSubquery) ; //TODO: I don't think we need this anymore .. how is it different to diff --git a/src/NHibernate/Hql/Ast/ANTLR/PolymorphicQuerySourceDetector.cs b/src/NHibernate/Hql/Ast/ANTLR/PolymorphicQuerySourceDetector.cs index 2ad6d708f1d..262f6062387 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/PolymorphicQuerySourceDetector.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/PolymorphicQuerySourceDetector.cs @@ -5,6 +5,7 @@ using NHibernate.Engine; using NHibernate.Hql.Ast.ANTLR.Tree; using NHibernate.Hql.Util; +using NHibernate.Util; namespace NHibernate.Hql.Ast.ANTLR { @@ -41,10 +42,10 @@ private void AddImplementorsToMap(IASTNode querySource, string className, string } _map.Add(querySource, - implementors.Select(implementor => MakeIdent(querySource, implementor)).ToArray()); + implementors.ToArray(implementor => MakeIdent(querySource, implementor))); } - private static string GetClassName(IASTNode querySource) + internal static string GetClassName(IASTNode querySource) { switch (querySource.Type) { @@ -56,7 +57,6 @@ private static string GetClassName(IASTNode querySource) // TODO throw new NotSupportedException($"{querySource.ToString()} {querySource.ToStringTree()}"); } - } private static IASTNode MakeIdent(IASTNode source, string text) @@ -88,6 +88,5 @@ private static void BuildPath(IASTNode node, StringBuilder sb) sb.Append(node.Text); } } - } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs b/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs index 04ec2db6769..bcf3dc14e11 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs @@ -29,7 +29,8 @@ public partial class QueryTranslatorImpl : IFilterTranslator private readonly string _queryIdentifier; private readonly IASTNode _stageOneAst; private readonly ISessionFactoryImplementor _factory; - + private readonly IDictionary _namedParameters; + private bool _shallowQuery; private bool _compiled; private IDictionary _enabledFilters; @@ -47,10 +48,28 @@ public partial class QueryTranslatorImpl : IFilterTranslator /// Currently enabled filters /// The session factory constructing this translator instance. public QueryTranslatorImpl( - string queryIdentifier, - IASTNode parsedQuery, - IDictionary enabledFilters, - ISessionFactoryImplementor factory) + string queryIdentifier, + IASTNode parsedQuery, + IDictionary enabledFilters, + ISessionFactoryImplementor factory) + : this(queryIdentifier, parsedQuery, enabledFilters, factory, null) + { + } + + /// + /// Creates a new AST-based query translator. + /// + /// The query-identifier (used in stats collection) + /// The hql query to translate + /// Currently enabled filters + /// The session factory constructing this translator instance. + /// The named parameters information. + internal QueryTranslatorImpl( + string queryIdentifier, + IASTNode parsedQuery, + IDictionary enabledFilters, + ISessionFactoryImplementor factory, + IDictionary namedParameters) { _queryIdentifier = queryIdentifier; _stageOneAst = parsedQuery; @@ -58,6 +77,7 @@ public QueryTranslatorImpl( _shallowQuery = false; _enabledFilters = enabledFilters; _factory = factory; + _namedParameters = namedParameters; } /// @@ -123,7 +143,7 @@ public IList List(ISessionImplementor session, QueryParameters queryParameters) int size = results.Count; var tmp = new List(); - var distinction = new IdentitySet(); + var distinction = new HashSet(ReferenceComparer.Instance); for ( int i = 0; i < size; i++ ) { @@ -434,7 +454,7 @@ private static IStatementExecutor BuildAppropriateStatementExecutor(IStatement s private HqlSqlTranslator Analyze(string collectionRole) { - var translator = new HqlSqlTranslator(_stageOneAst, this, _factory, _tokenReplacements, collectionRole); + var translator = new HqlSqlTranslator(_stageOneAst, this, _factory, _tokenReplacements, _namedParameters, collectionRole); translator.Translate(); @@ -548,15 +568,23 @@ internal class HqlSqlTranslator private readonly QueryTranslatorImpl _qti; private readonly ISessionFactoryImplementor _sfi; private readonly IDictionary _tokenReplacements; + private readonly IDictionary _namedParameters; private readonly string _collectionRole; private IStatement _resultAst; - public HqlSqlTranslator(IASTNode ast, QueryTranslatorImpl qti, ISessionFactoryImplementor sfi, IDictionary tokenReplacements, string collectionRole) + public HqlSqlTranslator( + IASTNode ast, + QueryTranslatorImpl qti, + ISessionFactoryImplementor sfi, + IDictionary tokenReplacements, + IDictionary namedParameters, + string collectionRole) { _inputAst = ast; _qti = qti; _sfi = sfi; _tokenReplacements = tokenReplacements; + _namedParameters = namedParameters; _collectionRole = collectionRole; } @@ -576,7 +604,7 @@ public IStatement Translate() var nodes = new BufferedTreeNodeStream(_inputAst); - var hqlSqlWalker = new HqlSqlWalker(_qti, _sfi, nodes, _tokenReplacements, _collectionRole); + var hqlSqlWalker = new HqlSqlWalker(_qti, _sfi, nodes, _tokenReplacements, _namedParameters, _collectionRole); hqlSqlWalker.TreeAdaptor = new HqlSqlWalkerTreeAdaptor(hqlSqlWalker); try diff --git a/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs b/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs index 4681b1d4892..33d6103ec86 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using NHibernate.Dialect.Function; using NHibernate.Engine; using NHibernate.Hql.Ast.ANTLR.Tree; @@ -9,6 +11,7 @@ using NHibernate.Type; using NHibernate.Util; using IASTNode=NHibernate.Hql.Ast.ANTLR.Tree.IASTNode; +using IQueryable = NHibernate.Persister.Entity.IQueryable; namespace NHibernate.Hql.Ast.ANTLR { @@ -32,7 +35,7 @@ public SessionFactoryHelperExtensions(ISessionFactoryImplementor sfi) public ISessionFactoryImplementor Factory { - get { return _sfi;} + get { return _sfi; } } /// @@ -67,6 +70,8 @@ private ISQLFunction RequireSQLFunction(string functionName) /// The function name. /// The first argument expression. /// the function return type given the function name and the first argument expression node. + // Since v5.3 + [Obsolete("Please use overload with a IEnumerable parameter instead.")] public IType FindFunctionReturnType(String functionName, IASTNode first) { // locate the registered function by the given name @@ -90,6 +95,44 @@ public IType FindFunctionReturnType(String functionName, IASTNode first) return sqlFunction.ReturnType(argumentType, _sfi); } + /// + /// Find the function return type given the function name and the arguments expression nodes. + /// + /// The function name. + /// The function arguments expression nodes. + /// The function return type given the function name and the arguments expression nodes. + public IType FindFunctionReturnType(string functionName, IEnumerable arguments) + { + var sqlFunction = RequireSQLFunction(functionName); + if (!(sqlFunction is ISQLFunctionExtended extendedSqlFunction)) + { +#pragma warning disable 618 + return FindFunctionReturnType(functionName, arguments.FirstOrDefault()); +#pragma warning restore 618 + } + + var argumentTypes = new List(); + if (sqlFunction is CastFunction) + { + argumentTypes.Add(TypeFactory.HeuristicType(arguments.First().NextSibling.Text)); + } + else + { + foreach (var argument in arguments) + { + IType type = null; + if (argument is SqlNode sqlNode) + { + type = sqlNode.DataType; + } + + argumentTypes.Add(type); + } + } + + return extendedSqlFunction.GetReturnType(argumentTypes, _sfi, true); + } + /// /// Given a (potentially unqualified) class name, locate its imported qualified name. /// diff --git a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs index 6ab875c8c8c..0f1c7a64603 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs @@ -128,10 +128,14 @@ private void Out(IASTNode n) var parameterNode = n as ParameterNode; if (parameterNode != null) { - var parameter = Parameter.Placeholder; - // supposed to be simplevalue - parameter.BackTrack = parameterNode.HqlParameterSpecification.GetIdsForBackTrack(sessionFactory).Single(); - writer.PushParameter(parameter); + var list = parameterNode.HqlParameterSpecification.GetIdsForBackTrack(sessionFactory).Select( + backTrack => + { + var parameter = Parameter.Placeholder; + parameter.BackTrack = backTrack; + return parameter; + }).ToList(); + Out(SqlStringHelper.ParametersList(list)); } else if (n is SqlNode) { @@ -306,6 +310,12 @@ private void EndFunctionTemplate(IASTNode m) } } + private void OutAggregateFunctionName(IASTNode m) + { + var aggregateNode = (AggregateNode) m; + Out(aggregateNode.FunctionName); + } + private void CommaBetweenParameters(String comma) { writer.CommaBetweenParameters(comma); @@ -490,7 +500,6 @@ public SqlString ToSqlString() return builder.ToSqlString(); } - #endregion } diff --git a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g index 73d04c792ec..21cb3c7521e 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g +++ b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g @@ -150,7 +150,7 @@ selectExpr ; count - : ^(COUNT { Out("count("); } ( distinctOrAll ) ? countExpr { Out(")"); } ) + : ^(c=COUNT { OutAggregateFunctionName(c); Out("("); } ( distinctOrAll ) ? countExpr { Out(")"); } ) ; distinctOrAll @@ -344,7 +344,7 @@ caseExpr ; aggregate - : ^(a=AGGREGATE { Out(a); Out("("); } expr { Out(")"); } ) + : ^(a=AGGREGATE { OutAggregateFunctionName(a); Out("("); } expr { Out(")"); } ) ; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs index d7dc2f9a0f0..eb0f40f28d0 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using Antlr.Runtime; +using NHibernate.Dialect.Function; using NHibernate.Type; using NHibernate.Hql.Ast.ANTLR.Util; @@ -19,18 +21,32 @@ public AggregateNode(IToken token) { } + public string FunctionName + { + get + { + if (SessionFactoryHelper.FindSQLFunction(Text) is ISQLFunctionExtended sqlFunction) + { + return sqlFunction.Name; + } + + return Text; + } + } + public override IType DataType { get { // Get the function return value type, based on the type of the first argument. - return SessionFactoryHelper.FindFunctionReturnType(Text, GetChild(0)); + return SessionFactoryHelper.FindFunctionReturnType(Text, (IEnumerable) this); } set { base.DataType = value; } } + public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs index fc4e1a670ea..351d29318fa 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs @@ -147,7 +147,6 @@ public SqlString SqlAssignmentFragment } return _sqlAssignmentString; } - } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/BetweenOperatorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/BetweenOperatorNode.cs index fd91e09fd3a..2ff3a10f790 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/BetweenOperatorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/BetweenOperatorNode.cs @@ -63,25 +63,13 @@ private IASTNode GetHighOperand() private static void Check(IASTNode check, IASTNode first, IASTNode second) { - var expectedTypeAwareNode = check as IExpectedTypeAwareNode; - if (expectedTypeAwareNode != null) + if (!(check is IExpectedTypeAwareNode expectedTypeAwareNode) || + expectedTypeAwareNode.ExpectedType != null) { - IType expectedType = null; - var firstNode = first as SqlNode; - if (firstNode != null) - { - expectedType = firstNode.DataType; - } - if (expectedType == null) - { - var secondNode = second as SqlNode; - if (secondNode != null) - { - expectedType = secondNode.DataType; - } - } - expectedTypeAwareNode.ExpectedType = expectedType; + return; } + + expectedTypeAwareNode.ExpectedType = (first as SqlNode)?.DataType ?? (second as SqlNode)?.DataType; } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs index 14f68649f29..60ca24ca379 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs @@ -32,32 +32,34 @@ public void Initialize() IType lhType = (lhs is SqlNode) ? ((SqlNode)lhs).DataType : null; IType rhType = (rhs is SqlNode) ? ((SqlNode)rhs).DataType : null; - if (lhs is IExpectedTypeAwareNode && rhType != null) + TrySetExpectedType(lhs, rhType, true); + TrySetExpectedType(rhs, lhType, false); + } + + private void TrySetExpectedType(IASTNode operand, IType otherOperandType, bool leftHandOperand) + { + if (!(operand is IExpectedTypeAwareNode typeAwareNode) || + otherOperandType == null || + typeAwareNode.ExpectedType != null) { - IType expectedType; + return; + } + + IType expectedType = null; - // we have something like : "? [op] rhs" - if (IsDateTimeType(rhType)) + // we have something like : "lhs [op] ?" or "? [op] rhs" + if (IsDateTimeType(otherOperandType)) + { + if (leftHandOperand) { // more specifically : "? [op] datetime" // 1) if the operator is MINUS, the param needs to be of // some datetime type // 2) if the operator is PLUS, the param needs to be of // some numeric type - expectedType = Type == HqlSqlWalker.PLUS ? NHibernateUtil.Double : rhType; + expectedType = Type == HqlSqlWalker.PLUS ? NHibernateUtil.Double : otherOperandType; } - else - { - expectedType = rhType; - } - ((IExpectedTypeAwareNode)lhs).ExpectedType = expectedType; - } - else if (rhs is ParameterNode && lhType != null) - { - IType expectedType = null; - - // we have something like : "lhs [op] ?" - if (IsDateTimeType(lhType)) + else if (Type == HqlSqlWalker.PLUS) { // more specifically : "datetime [op] ?" // 1) if the operator is MINUS, we really cannot determine @@ -65,17 +67,15 @@ public void Initialize() // numeric would be valid // 2) if the operator is PLUS, the param needs to be of // some numeric type - if (Type == HqlSqlWalker.PLUS) - { - expectedType = NHibernateUtil.Double; - } - } - else - { - expectedType = lhType; + expectedType = NHibernateUtil.Double; } - ((IExpectedTypeAwareNode)rhs).ExpectedType = expectedType; } + else + { + expectedType = otherOperandType; + } + + typeAwareNode.ExpectedType = expectedType; } public override IType DataType @@ -99,7 +99,6 @@ public override IType DataType } } - private IType ResolveDataType() { // TODO : we may also want to check that the types here map to exactly one column/JDBC-type diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryLogicOperatorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryLogicOperatorNode.cs index 100fddda77c..cae4b920ec8 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryLogicOperatorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryLogicOperatorNode.cs @@ -27,12 +27,12 @@ public BinaryLogicOperatorNode(IToken token) public IASTNode LeftHandOperand { - get { return GetChild(0);} + get { return GetChild(0); } } public IASTNode RightHandOperand { - get { return GetChild(1);} + get { return GetChild(1); } } /// @@ -65,15 +65,14 @@ public virtual void Initialize() rhsType = lhsType; } - var lshExpectedTypeAwareNode = lhs as IExpectedTypeAwareNode; - if (lshExpectedTypeAwareNode != null) + if (lhs is IExpectedTypeAwareNode lshTypeAwareNode && lshTypeAwareNode.ExpectedType == null) { - lshExpectedTypeAwareNode.ExpectedType = rhsType; + lshTypeAwareNode.ExpectedType = rhsType; } - var rshExpectedTypeAwareNode = rhs as IExpectedTypeAwareNode; - if (rshExpectedTypeAwareNode != null) + + if (rhs is IExpectedTypeAwareNode rshTypeAwareNode && rshTypeAwareNode.ExpectedType == null) { - rshExpectedTypeAwareNode.ExpectedType = lhsType; + rshTypeAwareNode.ExpectedType = lhsType; } MutateRowValueConstructorSyntaxesIfNecessary( lhsType, rhsType ); @@ -200,21 +199,21 @@ public IParameterSpecification[] GetEmbeddedParameters() return embeddedParameters.ToArray(); } - private string Translate(int valueElements, string comparisonText, string[] lhsElementTexts, string[] rhsElementTexts) + private protected string Translate(int valueElements, string comparisonText, string[] lhsElementTexts, string[] rhsElementTexts) { - var multicolumnComparisonClauses = new List(); + var multicolumnComparisonClauses = new string[valueElements]; for (int i = 0; i < valueElements; i++) { - multicolumnComparisonClauses.Add(string.Format("{0} {1} {2}", lhsElementTexts[i], comparisonText, rhsElementTexts[i])); + multicolumnComparisonClauses[i] = string.Join(" ", lhsElementTexts[i], comparisonText, rhsElementTexts[i]); } - return "(" + string.Join(" and ", multicolumnComparisonClauses.ToArray()) + ")"; + return string.Concat("(", string.Join(" and ", multicolumnComparisonClauses), ")"); } - private static string[] ExtractMutationTexts(IASTNode operand, int count) + private protected static string[] ExtractMutationTexts(IASTNode operand, int count) { - if ( operand is ParameterNode ) + if ( operand is ParameterNode ) { - return Enumerable.Repeat("?", count).ToArray(); + return ArrayHelper.Fill("?", count); } if (operand is SqlNode) { diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs index 925c0452f88..6155b54e670 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs @@ -133,13 +133,6 @@ public void Prepare() private IType[] ResolveConstructorArgumentTypes() { ISelectExpression[] argumentExpressions = CollectSelectExpressions(); - - if ( argumentExpressions == null ) - { - // return an empty Type array - return Array.Empty(); - } - IType[] types = new IType[argumentExpressions.Length]; for ( int x = 0; x < argumentExpressions.Length; x++ ) { diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs index 12e4f8073de..e381c921bbe 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs @@ -1,4 +1,6 @@ -using Antlr.Runtime; +using System.Linq; +using Antlr.Runtime; +using NHibernate.Dialect.Function; using NHibernate.Hql.Ast.ANTLR.Util; using NHibernate.Type; @@ -9,27 +11,22 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Author: josh /// Ported by: Steve Strong /// - class CountNode : AbstractSelectExpression, ISelectExpression + class CountNode : AggregateNode, ISelectExpression { public CountNode(IToken token) : base(token) { } - public override IType DataType { get { - return SessionFactoryHelper.FindFunctionReturnType(Text, null); + return SessionFactoryHelper.FindFunctionReturnType(Text, Enumerable.Empty()); } set { base.DataType = value; } } - public override void SetScalarColumnText(int i) - { - ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); - } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DeleteStatement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DeleteStatement.cs index a1b485a2c43..92fb91b7e37 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DeleteStatement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DeleteStatement.cs @@ -1,7 +1,6 @@ using System; using Antlr.Runtime; - namespace NHibernate.Hql.Ast.ANTLR.Tree { /// @@ -35,4 +34,4 @@ protected override int GetWhereClauseParentTokenType() return HqlSqlWalker.FROM; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index 18f88d42842..128addbdcbd 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -118,7 +118,6 @@ public override string Path } } - public string PropertyPath { get { return _propertyPath; } @@ -244,7 +243,6 @@ public override void Resolve(bool generateJoin, bool implicitJoin, string classA IsResolved = true; } - public FromReferenceNode GetLhs() { var lhs = ((FromReferenceNode)GetChild(0)); @@ -359,7 +357,6 @@ private void DereferenceCollection(CollectionType collectionType, bool implicitJ Walker.AddQuerySpaces( queryableCollection.CollectionSpaces ); // Always add the collection's query spaces. } - private void DereferenceEntity(EntityType entityType, bool implicitJoin, string classAlias, bool generateJoin, IASTNode parent) { CheckForCorrelatedSubquery( "dereferenceEntity" ); @@ -389,6 +386,9 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string string property = _propertyName; bool joinIsNeeded; + //For nullable entity comparisons we always need to add join (like not constrained one-to-one or not-found ignore associations) + bool comparisonWithNullableEntity = false; + if ( IsDotNode( parent ) ) { // our parent is another dot node, meaning we are being further dereferenced. @@ -396,7 +396,7 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string // entity's PK (because 'our' table would know the FK). parentAsDotNode = ( DotNode ) parent; property = parentAsDotNode._propertyName; - joinIsNeeded = generateJoin && !IsReferenceToPrimaryKey( parentAsDotNode._propertyName, entityType ); + joinIsNeeded = generateJoin && (entityType.IsNullable || !IsReferenceToPrimaryKey( parentAsDotNode._propertyName, entityType )); } else if ( ! Walker.IsSelectStatement ) { @@ -409,12 +409,18 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string } else { - joinIsNeeded = generateJoin || ((Walker.IsInSelect && !Walker.IsInCase) || (Walker.IsInFrom && !Walker.IsComparativeExpressionClause)); + comparisonWithNullableEntity = (Walker.IsComparativeExpressionClause && entityType.IsNullable); + joinIsNeeded = generateJoin || (Walker.IsInSelect && !Walker.IsInCase) || (Walker.IsInFrom && !Walker.IsComparativeExpressionClause) + || comparisonWithNullableEntity; } if ( joinIsNeeded ) { DereferenceEntityJoin( classAlias, entityType, implicitJoin, parent ); + if (comparisonWithNullableEntity) + { + _columns = FromElement.GetIdentityColumns(); + } } else { @@ -498,13 +504,8 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b // /////////////////////////////////////////////////////////////////////////////// - bool found = elem != null; - // even though we might find a pre-existing element by join path, for FromElements originating in a from-clause - // we should only ever use the found element if the aliases match (null != null here). - // Implied joins are ok to reuse only if in same from clause (are there any other cases when we should reject implied joins?). - bool useFoundFromElement = found && - (elem.IsImplied && elem.FromClause == currentFromClause || // NH different behavior (NH-3002) - AreSame(classAlias, elem.ClassAlias)); + // even though we might find a pre-existing element by join path, we may not be able to reuse it... + bool useFoundFromElement = elem != null && CanReuse(classAlias, elem); if ( ! useFoundFromElement ) { @@ -534,7 +535,6 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b { currentFromClause.AddDuplicateAlias(classAlias, elem); } - SetImpliedJoin( elem ); Walker.AddQuerySpaces( elem.EntityPersister.QuerySpaces ); @@ -546,6 +546,19 @@ private bool AreSame(String alias1, String alias2) { return !StringHelper.IsEmpty( alias1 ) && !StringHelper.IsEmpty( alias2 ) && alias1.Equals( alias2 ); } + private bool CanReuse(string classAlias, FromElement fromElement) + { + // if the from-clauses are the same, we can be a little more aggressive in terms of what we reuse + if (fromElement.FromClause == Walker.CurrentFromClause && + AreSame(classAlias, fromElement.ClassAlias)) + { + return true; + } + + // otherwise (subquery case) don't reuse the fromElement if we are processing the from-clause of the subquery + return Walker.CurrentClauseType != HqlSqlWalker.FROM; + } + private void SetImpliedJoin(FromElement elem) { _impliedJoin = elem; @@ -559,7 +572,6 @@ private void SetImpliedJoin(FromElement elem) } } - /// /// Is the given property name a reference to the primary key of the associated /// entity construed by the given entity type? @@ -611,7 +623,6 @@ private bool IsCorrelatedSubselect get { return Walker.IsSubQuery && FromElement.FromClause != Walker.CurrentFromClause; } } - private void CheckLhsIsNotCollection() { FromReferenceNode lhs = GetLhs(); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinFromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinFromElement.cs index e2f4ce33365..c534aa5c6be 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinFromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinFromElement.cs @@ -1,4 +1,6 @@ -using Antlr.Runtime; +using System; +using Antlr.Runtime; +using NHibernate.Engine; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; @@ -15,12 +17,9 @@ public EntityJoinFromElement(FromClause fromClause, IQueryable entityPersister, EntityType entityType = (EntityType) entityPersister.Type; InitializeEntity(fromClause, entityPersister.EntityName, entityPersister, entityType, alias, tableAlias); - JoinSequence = new EntityJoinJoinSequenceImpl( - SessionFactoryHelper.Factory, - entityType, - entityPersister.TableName, - tableAlias, - joinType); + //NH Specific: hibernate uses special class EntityJoinJoinSequenceImpl + JoinSequence = new JoinSequence(SessionFactoryHelper.Factory) {ForceFilter = true} + .AddJoin(entityType, tableAlias, joinType, Array.Empty()); fromClause.Walker.AddQuerySpaces(entityPersister.QuerySpaces); } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinJoinSequenceImpl.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinJoinSequenceImpl.cs deleted file mode 100644 index 21040c961bc..00000000000 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/EntityJoinJoinSequenceImpl.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using NHibernate.Engine; -using NHibernate.SqlCommand; -using NHibernate.Type; - -namespace NHibernate.Hql.Ast.ANTLR.Tree -{ - class EntityJoinJoinSequenceImpl : JoinSequence - { - private readonly EntityType _entityType; - private readonly string _tableName; - private readonly string _tableAlias; - private readonly JoinType _joinType; - - public EntityJoinJoinSequenceImpl(ISessionFactoryImplementor factory, EntityType entityType, string tableName, string tableAlias, JoinType joinType):base(factory) - { - _entityType = entityType; - _tableName = tableName; - _tableAlias = tableAlias; - _joinType = joinType; - } - - internal override JoinFragment ToJoinFragment( - IDictionary enabledFilters, - bool includeExtraJoins, - SqlString withClauseFragment, - string withClauseJoinAlias, - string withRootAlias) - { - var joinFragment = new ANSIJoinFragment(); - - var on = withClauseFragment ?? new SqlString(); - var filters = _entityType.GetOnCondition(_tableAlias, Factory, enabledFilters); - if (!string.IsNullOrEmpty(filters)) - { - on.Append(" and ").Append(filters); - } - joinFragment.AddJoin(_tableName, _tableAlias, Array.Empty(), Array.Empty(), _joinType, on); - return joinFragment; - } - } -} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs index 4c284dc84bc..d09f15bb4b8 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs @@ -163,7 +163,6 @@ public void AddCollectionJoinFromElementByPath(string path, FromElement destinat _collectionJoinFromElementsByPath.Add(path, destination); // Add the new node to the map so that we don't create it twice. } - private void AddChild(FromClause fromClause) { if (_childFromClauses == null) @@ -253,7 +252,6 @@ public void AddDuplicateAlias(string alias, FromElement element) } } - /// /// Look for an existing implicit or explicit join by the given path. /// diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs index 0a6926dea64..340eb5f7de9 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs @@ -76,6 +76,8 @@ public void SetAllPropertyFetch(bool fetch) _isAllPropertyFetch = fetch; } + // Since 5.4 + [Obsolete("This method has no more usages and will be removed in a future version.")] public void SetWithClauseFragment(String withClauseJoinAlias, SqlString withClauseFragment) { _withClauseJoinAlias = withClauseJoinAlias; @@ -121,7 +123,7 @@ public string[] FetchLazyProperties public virtual bool IsImpliedInFromClause { - get { return false; } // Since this is an explicit FROM element, it can't be implied in the FROM clause. + get { return false; } // Since this is an explicit FROM element, it can't be implied in the FROM clause. } public bool IsFetch @@ -179,6 +181,8 @@ public virtual bool IsImplied get { return false; } // This is an explicit FROM element. } + internal bool? IsPartOfJoinSequence { get; set; } + public bool IsDereferencedBySuperclassOrSubclassProperty { get @@ -311,8 +315,11 @@ public FromElement RealOrigin public SqlString WithClauseFragment { get { return _withClauseFragment; } + set { _withClauseFragment = value; } } + // Since 5.4 + [Obsolete("This method has no more usages and will be removed in a future version.")] public string WithClauseJoinAlias { get { return _withClauseJoinAlias; } @@ -457,7 +464,7 @@ public bool UseFromFragment public bool UseWhereFragment { - get { return _useWhereFragment;} + get { return _useWhereFragment; } set { _useWhereFragment = value; } } @@ -482,6 +489,19 @@ public IType GetPropertyType(string propertyName, string propertyPath) } public virtual string GetIdentityColumn() + { + var cols = GetIdentityColumns(); + string result = string.Join(", ", cols); + + if (cols.Length > 1 && Walker.IsComparativeExpressionClause) + { + return "(" + result + ")"; + } + + return result; + } + + internal string[] GetIdentityColumns() { CheckInitialized(); string table = TableAlias; @@ -490,7 +510,6 @@ public virtual string GetIdentityColumn() { throw new InvalidOperationException("No table alias for node " + this); } - string[] cols; string propertyName; if (EntityPersister != null && EntityPersister.EntityMetamodel != null && EntityPersister.EntityMetamodel.HasNonIdentifierPropertyNamedId) @@ -505,22 +524,12 @@ public virtual string GetIdentityColumn() { propertyName = NHibernate.Persister.Entity.EntityPersister.EntityID; } - if (Walker.StatementType == HqlSqlWalker.SELECT) - { - cols = GetPropertyMapping(propertyName).ToColumns(table, propertyName); - } - else - { - cols = GetPropertyMapping(propertyName).ToColumns(propertyName); - } - string result = string.Join(", ", cols); - // There used to be code here that added parentheses if the number of columns was greater than one. - // This was causing invalid queries like select (c1, c2) from x. I couldn't think of a reason that - // parentheses would be wanted around a list of columns, so I removed them. - return result; + return ToColumns(table, propertyName, Walker.StatementType == HqlSqlWalker.SELECT); } + internal bool UseTableAliases => Walker.StatementType == HqlSqlWalker.SELECT || Walker.IsSubQuery; + public void HandlePropertyBeingDereferenced(IType propertySource, string propertyName) { if (QueryableCollection != null && CollectionProperties.IsCollectionProperty(propertyName)) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs index 509d9cbbd4a..3d04fdcfd7b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs @@ -26,7 +26,6 @@ public class FromElementFactory private IQueryableCollection _queryableCollection; private CollectionType _collectionType; - /// /// Creates entity from elements. /// @@ -280,7 +279,6 @@ public FromElement CreateElementJoin(IQueryableCollection queryableCollection) return elem; } - public FromElement CreateEntityJoin( string entityClass, string tableAlias, @@ -430,7 +428,6 @@ private FromElement CreateManyToMany( return elem; } - private JoinSequence CreateJoinSequence(string roleAlias, JoinType joinType) { SessionFactoryHelperExtensions sessionFactoryHelper = _fromClause.SessionFactoryHelper; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs index d8328bfdbf8..7c405ba1b23 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs @@ -119,7 +119,12 @@ public JoinSequence JoinSequence var joinable = _persister as IJoinable; if (joinable != null) { - return _fromElement.SessionFactoryHelper.CreateJoinSequence().SetRoot(joinable, TableAlias); + // the delete and update statements created here will never be executed when IsMultiTable is true, + // only the where clause will be used by MultiTableUpdateExecutor/MultiTableDeleteExecutor. In that case + // we have to use the alias from the persister. + var useAlias = _fromElement.UseTableAliases || _fromElement.Queryable.IsMultiTable; + + return _fromElement.SessionFactoryHelper.CreateJoinSequence().SetRoot(joinable, useAlias ? TableAlias : string.Empty); } return null; // TODO: Should this really return null? If not, figure out something better to do here. @@ -189,7 +194,6 @@ public virtual string RenderScalarIdentifierSelect(int i) return buf.ToString(); } - /// /// Returns the property select SQL fragment. /// @@ -409,34 +413,17 @@ public string[] ToColumns(string tableAlias, string path, bool inSelect, bool fo } else if (_fromElement.Walker.IsSubQuery) { - // for a subquery, the alias to use depends on a few things (we - // already know this is not an overall SELECT): - // 1) if this FROM_ELEMENT represents a correlation to the - // outer-most query - // A) if the outer query represents a multi-table - // persister, we need to use the given alias - // in anticipation of one of the multi-table - // executors being used (as this subquery will - // actually be used in the "id select" phase - // of that multi-table executor) - // B) otherwise, we need to use the persister's - // table name as the column qualification - // 2) otherwise (not correlated), use the given alias - if (IsCorrelation) - { - if (IsMultiTable) - { - return propertyMapping.ToColumns(tableAlias, path); - } - else - { - return propertyMapping.ToColumns(ExtractTableName(), path); - } - } - else - { - return propertyMapping.ToColumns(tableAlias, path); - } + // We already know it's subqery for DML query. + // If this FROM_ELEMENT represents a correlation to the outer-most query we must use real table name + // for UPDATE(typically in a SET clause)/DELETE queries unless it's multi-table reference inside top level where clause + // (as this subquery will actually be used in the "id select" phase of that multi-table executor) + var useAlias = _fromElement.Walker.StatementType == HqlSqlWalker.INSERT + || (IsMultiTable && _fromElement.Walker.CurrentTopLevelClauseType == HqlSqlWalker.WHERE); + + if (!useAlias && IsCorrelation) + return propertyMapping.ToColumns(ExtractTableName(), path); + + return propertyMapping.ToColumns(tableAlias, path); } else { diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromReferenceNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromReferenceNode.cs index 94ecc061188..2656e9cab06 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromReferenceNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromReferenceNode.cs @@ -2,7 +2,6 @@ using System.Text; using Antlr.Runtime; - namespace NHibernate.Hql.Ast.ANTLR.Tree { [CLSCompliant(false)] diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/HqlSqlWalkerNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/HqlSqlWalkerNode.cs index c801aa588ba..030621af87c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/HqlSqlWalkerNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/HqlSqlWalkerNode.cs @@ -37,7 +37,6 @@ internal SessionFactoryHelperExtensions SessionFactoryHelper get { return _walker.SessionFactoryHelper; } } - public IASTFactory ASTFactory { get { return _walker.ASTFactory; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs index bf9c20028d7..a89fef53ade 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs @@ -60,4 +60,3 @@ public interface ISelectExpression string Alias { get; set; } } } - diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs index ab91df356e7..095f8b03545 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using Antlr.Runtime; using NHibernate.Dialect.Function; using NHibernate.Hql.Ast.ANTLR.Util; using NHibernate.Persister.Collection; -using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; using NHibernate.Util; +using IQueryable = NHibernate.Persister.Entity.IQueryable; namespace NHibernate.Hql.Ast.ANTLR.Tree { @@ -27,7 +28,6 @@ public override IType DataType { get { - IType type = base.DataType; if ( type != null ) { @@ -39,7 +39,7 @@ public override IType DataType return fe.DataType; } ISQLFunction sf = Walker.SessionFactoryHelper.FindSQLFunction(Text); - return sf?.ReturnType(null, Walker.SessionFactoryHelper.Factory); + return sf?.GetReturnType(Enumerable.Empty(), Walker.SessionFactoryHelper.Factory, true); } set @@ -239,7 +239,7 @@ private bool ResolveAsNakedComponentPropertyRefLhs(DotNode parent) throw new QueryException("Property '" + OriginalText + "' is not a component. Use an alias to reference associations or collections."); } - IType propertyType ; // used to set the type of the parent dot node + IType propertyType; // used to set the type of the parent dot node string propertyPath = Text + "." + NextSibling.Text; try { @@ -291,7 +291,6 @@ private bool ResolveAsNakedComponentPropertyRefRhs(DotNode parent) return true; } - private IType GetNakedPropertyType(FromElement fromElement) { if (fromElement == null) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/InLogicOperatorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/InLogicOperatorNode.cs index 5557bb1d4c6..f0b9856f76a 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/InLogicOperatorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/InLogicOperatorNode.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Antlr.Runtime; using NHibernate.Type; @@ -39,20 +40,100 @@ public override void Initialize() // some form of property ref and that the children of the in-list represent // one-or-more params. var lhsNode = lhs as SqlNode; + IType lhsType = null; if (lhsNode != null) { - IType lhsType = lhsNode.DataType; + lhsType = lhsNode.DataType; IASTNode inListChild = inList.GetChild(0); while (inListChild != null) { - var expectedTypeAwareNode = inListChild as IExpectedTypeAwareNode; - if (expectedTypeAwareNode != null) + if (inListChild is IExpectedTypeAwareNode expectedTypeAwareNode && + expectedTypeAwareNode.ExpectedType == null) { expectedTypeAwareNode.ExpectedType = lhsType; } + inListChild = inListChild.NextSibling; } } + + var sessionFactory = SessionFactoryHelper.Factory; + if (sessionFactory.Dialect.SupportsRowValueConstructorSyntaxInInList) + return; + + lhsType = lhsType ?? ExtractDataType(lhs); + if (lhsType == null) + return; + + var rhsNode = inList.GetFirstChild(); + if (rhsNode == null || !IsNodeAcceptable(rhsNode)) + return; + + var lhsColumnSpan = lhsType.GetColumnSpan(sessionFactory); + var rhsColumnSpan = rhsNode.Type == HqlSqlWalker.VECTOR_EXPR + ? rhsNode.ChildCount + : ExtractDataType(rhsNode)?.GetColumnSpan(sessionFactory) ?? 0; + + if (lhsColumnSpan > 1 && rhsColumnSpan > 1) + { + MutateRowValueConstructorSyntaxInInListSyntax(lhs, lhsColumnSpan, rhsNode, rhsColumnSpan); + } + } + + /// + /// this is possible for parameter lists and explicit lists. It is completely unreasonable for sub-queries. + /// + private static bool IsNodeAcceptable(IASTNode rhsNode) + { + return rhsNode == null /* empty IN list */ + || rhsNode is LiteralNode + || rhsNode is ParameterNode + || rhsNode.Type == HqlSqlWalker.VECTOR_EXPR; + } + + /// + /// Mutate the subtree relating to a row-value-constructor in "in" list to instead use + /// a series of ORen and ANDed predicates. This allows multi-column type comparisons + /// and explicit row-value-constructor in "in" list syntax even on databases which do + /// not support row-value-constructor in "in" list. + /// + /// For example, here we'd mutate "... where (col1, col2) in ( ('val1', 'val2'), ('val3', 'val4') ) ..." to + /// "... where (col1 = 'val1' and col2 = 'val2') or (col1 = 'val3' and val2 = 'val4') ..." + /// + private void MutateRowValueConstructorSyntaxInInListSyntax(IASTNode lhsNode, int lhsColumnSpan, IASTNode rhsNode, int rhsColumnSpan) + { + //NHibenate specific: In hibernate they recreate new tree in HQL. In NHibernate we just replace node with generated SQL + // (same as it's done in BinaryLogicOperatorNode) + + string[] lhsElementTexts = ExtractMutationTexts(lhsNode, lhsColumnSpan); + + if (lhsNode is ParameterNode lhsParam && lhsParam.HqlParameterSpecification != null) + { + AddEmbeddedParameter(lhsParam.HqlParameterSpecification); + } + + var negated = Type == HqlSqlWalker.NOT_IN; + + var andElementsNodeList = new List(); + + while (rhsNode != null) + { + string[] rhsElementTexts = ExtractMutationTexts(rhsNode, rhsColumnSpan); + if (rhsNode is ParameterNode rhsParam && rhsParam.HqlParameterSpecification != null) + { + AddEmbeddedParameter(rhsParam.HqlParameterSpecification); + } + + var text = Translate(lhsColumnSpan, "=", lhsElementTexts, rhsElementTexts); + + andElementsNodeList.Add(negated ? string.Concat("( not ", text, ")") : text); + rhsNode = rhsNode.NextSibling; + } + + ClearChildren(); + Type = HqlSqlWalker.SQL_TOKEN; + var sqlToken = string.Join(negated ? " and " : " or ", andElementsNodeList); + Text = andElementsNodeList.Count > 1 ? string.Concat("(", sqlToken, ")") : sqlToken; } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/InsertStatement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/InsertStatement.cs index 12672c4f188..4bbebd30a74 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/InsertStatement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/InsertStatement.cs @@ -25,14 +25,14 @@ public override int StatementType /// The into-clause public IntoClause IntoClause { - get{return (IntoClause)GetFirstChild();} + get{return (IntoClause)GetFirstChild(); } } /// Retrieve this insert statement's select-clause. /// The select-clause. public SelectClause SelectClause { - get{return ((QueryNode)IntoClause.NextSibling).GetSelectClause();} + get{return ((QueryNode)IntoClause.NextSibling).GetSelectClause(); } } /// Performs detailed semantic validation on this insert statement tree. @@ -42,4 +42,4 @@ public virtual void Validate() IntoClause.ValidateTypes(SelectClause); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs index 07da64e1099..74e05746f5f 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs @@ -74,6 +74,5 @@ private void ProcessText() _processedText = true; } } - } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs index f36ac08ff69..dc6c04042e7 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Antlr.Runtime; using NHibernate.Dialect.Function; @@ -194,14 +195,7 @@ private void DialectFunction(IASTNode exprList) if (_function != null) { - IASTNode child = null; - - if (exprList != null) - { - child = _methodName == "iif" ? exprList.GetChild(1) : exprList.GetChild(0); - } - - DataType = SessionFactoryHelper.FindFunctionReturnType(_methodName, child); + DataType = SessionFactoryHelper.FindFunctionReturnType(_methodName, (IEnumerable) exprList); } //TODO: /*else { diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ResultVariableRefNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ResultVariableRefNode.cs index 3fc8c8ff808..2ce02ddfab7 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ResultVariableRefNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ResultVariableRefNode.cs @@ -35,7 +35,6 @@ public void SetSelectExpression(ISelectExpression selectExpression) _selectExpression = selectExpression; } - public override SqlString RenderText(ISessionFactoryImplementor sessionFactory) { int scalarColumnIndex = _selectExpression.ScalarColumnIndex; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs index 3fca5b4f4d3..a3e4c9af4e1 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs @@ -23,6 +23,8 @@ public class SelectClause : SelectExpressionList private IType[] _queryReturnTypes; private string[][] _columnNames; private readonly List _fromElementsForLoad = new List(); + private readonly Dictionary _entityByResultTypeDic = new Dictionary(); + private ConstructorNode _constructorNode; private string[] _aliases; private int[] _columnNamesStartPositions; @@ -34,7 +36,6 @@ public SelectClause(IToken token) { } - /// /// Prepares a derived (i.e., not explicitly defined in the query) select clause. /// @@ -55,7 +56,6 @@ public void InitializeDerivedSelectClause(FromClause fromClause) ASTAppender appender = new ASTAppender(ASTFactory, this); // Get ready to start adding nodes. int size = fromElements.Count; - List sqlResultTypeList = new List(size); List queryReturnTypeList = new List(size); int k = 0; @@ -77,7 +77,6 @@ public void InitializeDerivedSelectClause(FromClause fromClause) } _fromElementsForLoad.Add(fromElement); - sqlResultTypeList.Add(type); // Generate the select expression. string text = fromElement.RenderIdentifierSelect(size, k); @@ -107,7 +106,6 @@ public void InitializeDerivedSelectClause(FromClause fromClause) FinishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList); } - /// /// Prepares an explicitly defined select clause. /// @@ -136,19 +134,20 @@ public void InitializeExplicitSelectClause(FromClause fromClause) if (expr.IsConstructor) { _constructorNode = (ConstructorNode)expr; - IList constructorArgumentTypeList = _constructorNode.ConstructorArgumentTypeList; //sqlResultTypeList.addAll( constructorArgumentTypeList ); - queryReturnTypeList.AddRange(constructorArgumentTypeList); _scalarSelect = true; - for (int j = 1; j < _constructorNode.ChildCount; j++) + var ctorSelectExpressions = _constructorNode.CollectSelectExpressions(); + for (int j = 0; j < ctorSelectExpressions.Length; j++) { - ISelectExpression se = _constructorNode.GetChild(j) as ISelectExpression; + ISelectExpression se = ctorSelectExpressions[j]; - if (se != null && IsReturnableEntity(se)) + if (IsReturnableEntity(se)) { - _fromElementsForLoad.Add(se.FromElement); + AddEntityToProjection(queryReturnTypeList.Count, se); } + + queryReturnTypeList.Add(se.DataType); } } else @@ -165,10 +164,9 @@ public void InitializeExplicitSelectClause(FromClause fromClause) { _scalarSelect = true; } - - if (IsReturnableEntity(expr)) + else if (IsReturnableEntity(expr)) { - _fromElementsForLoad.Add(expr.FromElement); + AddEntityToProjection(queryReturnTypeList.Count, expr); } // Always add the type to the return type list. @@ -208,7 +206,6 @@ public void InitializeExplicitSelectClause(FromClause fromClause) } else { - IType type = fromElement.SelectType; AddCollectionFromElement(fromElement); @@ -250,6 +247,12 @@ public void InitializeExplicitSelectClause(FromClause fromClause) FinishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList); } + private void AddEntityToProjection(int resultIndex, ISelectExpression se) + { + _entityByResultTypeDic[resultIndex] = _fromElementsForLoad.Count; + _fromElementsForLoad.Add(se.FromElement); + } + private static FromElement GetOrigin(FromElement fromElement) { var realOrigin = fromElement.RealOrigin; @@ -274,6 +277,11 @@ public IList FromElementsForLoad get { return _fromElementsForLoad; } } + /// + /// Maps QueryReturnTypes[key] to entities from FromElementsForLoad[value] + /// + internal IReadOnlyDictionary EntityByResultTypeDic => _entityByResultTypeDic; + public bool IsScalarSelect { get { return _scalarSelect; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs b/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs index c4f850f7aa3..9140e3780f0 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.Hql.Ast.ANTLR.Tree; using NHibernate.SqlCommand; @@ -48,6 +49,8 @@ public static JoinType ToHibernateJoinType(int astJoinType) return JoinType.RightOuterJoin; case HqlSqlWalker.FULL: return JoinType.FullJoin; + case HqlSqlWalker.CROSS: + return JoinType.CrossJoin; default: throw new AssertionFailure("undefined join type " + astJoinType); } @@ -64,8 +67,6 @@ public void ProcessJoins(QueryNode query) public void ProcessJoins(IRestrictableStatement query) { FromClause fromClause = query.FromClause; - var supportRootAlias = !(query is DeleteStatement || query is UpdateStatement); - IList fromElements; if ( DotNode.UseThetaStyleImplicitJoins ) { @@ -83,14 +84,50 @@ public void ProcessJoins(IRestrictableStatement query) fromElements.Add(t[i]); } } - else + else { fromElements = fromClause.GetFromElementsTyped(); + + for (var index = fromElements.Count - 1; index >= 0; index--) + { + var fromElement = fromElements[index]; + // We found an implied from element that is used in the WITH clause of another from element, so it need to become part of it's join sequence + if (fromElement.IsImplied && fromElement.IsPartOfJoinSequence == null) + { + var origin = fromElement.Origin; + while(origin.IsImplied) + { + origin = origin.Origin; + origin.IsPartOfJoinSequence = false; + } + + if (origin.WithClauseFragment?.Contains(fromElement.TableAlias + ".") == true) + { + List elements = new List(); + while(fromElement.IsImplied) + { + elements.Add(fromElement); + // This from element will be rendered as part of the origins join sequence + fromElement.Text = string.Empty; + fromElement.IsPartOfJoinSequence = true; + fromElement = fromElement.Origin; + } + + for (var i = elements.Count - 1; i >= 0; i--) + { + origin.JoinSequence.AddJoin(elements[i]); + } + } + } + } } // Iterate through the alias,JoinSequence pairs and generate SQL token nodes. foreach (FromElement fromElement in fromElements) { + if(fromElement.IsPartOfJoinSequence == true) + continue; + JoinSequence join = fromElement.JoinSequence; join.SetSelector(new JoinSequenceSelector(_walker, fromClause, fromElement)); @@ -98,18 +135,16 @@ public void ProcessJoins(IRestrictableStatement query) // the delete and update statements created here will never be executed when IsMultiTable is true, // only the where clause will be used by MultiTableUpdateExecutor/MultiTableDeleteExecutor. In that case // we have to use the alias from the persister. - AddJoinNodes( query, join, fromElement, supportRootAlias || fromElement.Queryable.IsMultiTable); + AddJoinNodes( query, join, fromElement); } } - private void AddJoinNodes(IRestrictableStatement query, JoinSequence join, FromElement fromElement, bool supportRootAlias) + private void AddJoinNodes(IRestrictableStatement query, JoinSequence join, FromElement fromElement) { JoinFragment joinFragment = join.ToJoinFragment( _walker.EnabledFilters, fromElement.UseFromFragment || fromElement.IsDereferencedBySuperclassOrSubclassProperty, - fromElement.WithClauseFragment, - fromElement.WithClauseJoinAlias, - supportRootAlias ? join.RootAlias : string.Empty + fromElement.WithClauseFragment ); SqlString frag = joinFragment.ToFromFragmentString; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs b/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs index 18c93dc3903..dd89906775b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs @@ -44,7 +44,6 @@ public class LiteralProcessor /// public static readonly int DECIMAL_LITERAL_FORMAT = EXACT; - public LiteralProcessor(HqlSqlWalker walker) { _walker = walker; @@ -131,7 +130,6 @@ public void ProcessBoolean(IASTNode constant) } } - public void ProcessConstant(SqlNode constant, bool resolveIdent) { // If the constant is an IDENT, figure out what it means... @@ -369,7 +367,6 @@ public string Format(Decimal number) try { return number.ToString(FORMAT_STRING); - } catch (Exception t) { diff --git a/src/NHibernate/Hql/Ast/ANTLR/Util/SyntheticAndFactory.cs b/src/NHibernate/Hql/Ast/ANTLR/Util/SyntheticAndFactory.cs index 922f1a9ed98..68e2bc0cb19 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Util/SyntheticAndFactory.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Util/SyntheticAndFactory.cs @@ -198,6 +198,5 @@ public virtual void AddDiscriminatorWhereFragment(IRestrictableStatement stateme statement.WhereClause.SetFirstChild(and); } } - } } diff --git a/src/NHibernate/Hql/Ast/HqlTreeBuilder.cs b/src/NHibernate/Hql/Ast/HqlTreeBuilder.cs index 49cad7607e1..dd59f2ff0f0 100755 --- a/src/NHibernate/Hql/Ast/HqlTreeBuilder.cs +++ b/src/NHibernate/Hql/Ast/HqlTreeBuilder.cs @@ -307,6 +307,11 @@ public HqlCount Count(HqlExpression child) return new HqlCount(_factory, child); } + public HqlCountBig CountBig(HqlExpression child) + { + return new HqlCountBig(_factory, child); + } + public HqlRowStar RowStar() { return new HqlRowStar(_factory); @@ -478,11 +483,21 @@ public HqlIn In(HqlExpression itemExpression, HqlTreeNode source) return new HqlIn(_factory, itemExpression, source); } + public HqlInnerJoin InnerJoin(HqlExpression expression, HqlAlias @alias) + { + return new HqlInnerJoin(_factory, expression, @alias); + } + public HqlLeftJoin LeftJoin(HqlExpression expression, HqlAlias @alias) { return new HqlLeftJoin(_factory, expression, @alias); } + public HqlCrossJoin CrossJoin(HqlExpression expression, HqlAlias @alias) + { + return new HqlCrossJoin(_factory, expression, @alias); + } + public HqlFetchJoin FetchJoin(HqlExpression expression, HqlAlias @alias) { return new HqlFetchJoin(_factory, expression, @alias); diff --git a/src/NHibernate/Hql/Ast/HqlTreeNode.cs b/src/NHibernate/Hql/Ast/HqlTreeNode.cs index 7289d5acbc5..7f6156f7dd6 100755 --- a/src/NHibernate/Hql/Ast/HqlTreeNode.cs +++ b/src/NHibernate/Hql/Ast/HqlTreeNode.cs @@ -204,6 +204,23 @@ internal HqlQuery(IASTFactory factory, params HqlStatement[] children) public class HqlIdent : HqlExpression { + private static readonly Dictionary SupportedIdentTypes = new Dictionary + { + {TypeCode.Boolean, "bool"}, + {TypeCode.Int16, "short"}, + {TypeCode.Int32, "integer"}, + {TypeCode.Int64, "long"}, + {TypeCode.UInt16, "ushort"}, + {TypeCode.UInt32, "uint"}, + {TypeCode.UInt64, "ulong"}, + {TypeCode.Decimal, "decimal"}, + {TypeCode.Single, "single"}, + {TypeCode.DateTime, "datetime"}, + {TypeCode.String, "string"}, + {TypeCode.Char, "char"}, + {TypeCode.Double, "double"} + }; + internal HqlIdent(IASTFactory factory, string ident) : base(HqlSqlWalker.IDENT, ident, factory) { @@ -211,51 +228,38 @@ internal HqlIdent(IASTFactory factory, string ident) internal HqlIdent(IASTFactory factory, System.Type type) : base(HqlSqlWalker.IDENT, "", factory) + { + if (!TryGetTypeName(type, out var typeName)) + { + throw new NotSupportedException($"Don't currently support idents of type {type.Name}"); + } + + SetText(typeName); + } + + internal static bool SupportsType(System.Type type) + { + return TryGetTypeName(type, out _); + } + + private static bool TryGetTypeName(System.Type type, out string typeName) { type = type.UnwrapIfNullable(); + if (SupportedIdentTypes.TryGetValue(System.Type.GetTypeCode(type), out typeName)) + { + return true; + } - switch (System.Type.GetTypeCode(type)) + if (type == typeof(Guid)) { - case TypeCode.Boolean: - SetText("bool"); - break; - case TypeCode.Int16: - SetText("short"); - break; - case TypeCode.Int32: - SetText("integer"); - break; - case TypeCode.Int64: - SetText("long"); - break; - case TypeCode.Decimal: - SetText("decimal"); - break; - case TypeCode.Single: - SetText("single"); - break; - case TypeCode.DateTime: - SetText("datetime"); - break; - case TypeCode.String: - SetText("string"); - break; - case TypeCode.Double: - SetText("double"); - break; - default: - if (type == typeof(Guid)) - { - SetText("guid"); - break; - } - if (type == typeof(DateTimeOffset)) - { - SetText("datetimeoffset"); - break; - } - throw new NotSupportedException(string.Format("Don't currently support idents of type {0}", type.Name)); + typeName = "guid"; + } + else if (type == typeof(DateTimeOffset)) + { + typeName = "datetimeoffset"; } + + return typeName != null; } } @@ -675,6 +679,19 @@ public HqlCount(IASTFactory factory, HqlExpression child) } } + public class HqlCountBig : HqlExpression + { + public HqlCountBig(IASTFactory factory) + : base(HqlSqlWalker.COUNT, "count_big", factory) + { + } + + public HqlCountBig(IASTFactory factory, HqlExpression child) + : base(HqlSqlWalker.COUNT, "count_big", factory, child) + { + } + } + public class HqlAs : HqlExpression { public HqlAs(IASTFactory factory, HqlExpression expression, System.Type type) @@ -822,6 +839,14 @@ public HqlJoin(IASTFactory factory, HqlExpression expression, HqlAlias @alias) : } } + public class HqlInnerJoin : HqlTreeNode + { + public HqlInnerJoin(IASTFactory factory, HqlExpression expression, HqlAlias alias) + : base(HqlSqlWalker.JOIN, "join", factory, new HqlInner(factory), expression, alias) + { + } + } + public class HqlLeftJoin : HqlTreeNode { public HqlLeftJoin(IASTFactory factory, HqlExpression expression, HqlAlias @alias) : base(HqlSqlWalker.JOIN, "join", factory, new HqlLeft(factory), expression, @alias) @@ -829,6 +854,13 @@ public class HqlLeftJoin : HqlTreeNode } } + public class HqlCrossJoin : HqlTreeNode + { + public HqlCrossJoin(IASTFactory factory, HqlExpression expression, HqlAlias @alias) : base(HqlSqlWalker.JOIN, "join", factory, new HqlCross(factory), expression, @alias) + { + } + } + public class HqlFetchJoin : HqlTreeNode { public HqlFetchJoin(IASTFactory factory, HqlExpression expression, HqlAlias @alias) @@ -876,6 +908,14 @@ public HqlBitwiseAnd(IASTFactory factory, HqlExpression lhs, HqlExpression rhs) } } + public class HqlInner : HqlTreeNode + { + public HqlInner(IASTFactory factory) + : base(HqlSqlWalker.INNER, "inner", factory) + { + } + } + public class HqlLeft : HqlTreeNode { public HqlLeft(IASTFactory factory) @@ -884,6 +924,14 @@ public HqlLeft(IASTFactory factory) } } + public class HqlCross : HqlTreeNode + { + public HqlCross(IASTFactory factory) + : base(HqlSqlWalker.CROSS, "cross", factory) + { + } + } + public class HqlAny : HqlBooleanExpression { public HqlAny(IASTFactory factory) : base(HqlSqlWalker.ANY, "any", factory) diff --git a/src/NHibernate/Hql/HolderInstantiator.cs b/src/NHibernate/Hql/HolderInstantiator.cs index acb0afb1942..8a616ceb677 100644 --- a/src/NHibernate/Hql/HolderInstantiator.cs +++ b/src/NHibernate/Hql/HolderInstantiator.cs @@ -21,7 +21,6 @@ public static HolderInstantiator GetHolderInstantiator(IResultTransformer select queryReturnAliases); } - public static IResultTransformer ResolveResultTransformer(IResultTransformer selectNewTransformer, IResultTransformer customTransformer) { return selectNewTransformer ?? customTransformer; @@ -93,4 +92,4 @@ public IResultTransformer ResultTransformer get { return transformer; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Hql/IQueryTranslator.cs b/src/NHibernate/Hql/IQueryTranslator.cs index e8a5017d078..b74bcfd2f8e 100644 --- a/src/NHibernate/Hql/IQueryTranslator.cs +++ b/src/NHibernate/Hql/IQueryTranslator.cs @@ -60,7 +60,7 @@ public partial interface IQueryTranslator // The query identifier for this translator. The query identifier is used in stats collection. // // Not ported: - //string QueryIdentifier { get;} + //string QueryIdentifier { get; } /// /// The SQL string generated by the translator. @@ -119,4 +119,4 @@ public partial interface IQueryTranslator ParameterMetadata BuildParameterMetadata(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Hql/Util/SessionFactoryHelper.cs b/src/NHibernate/Hql/Util/SessionFactoryHelper.cs index adbda34b64a..96d4384cdc3 100644 --- a/src/NHibernate/Hql/Util/SessionFactoryHelper.cs +++ b/src/NHibernate/Hql/Util/SessionFactoryHelper.cs @@ -14,7 +14,7 @@ namespace NHibernate.Hql.Util public class SessionFactoryHelper { private readonly ISessionFactoryImplementor sfi; - private readonly IDictionary collectionPropertyMappingByRole = + private readonly Dictionary collectionPropertyMappingByRole = new Dictionary(); public SessionFactoryHelper(ISessionFactoryImplementor sfi) @@ -74,8 +74,6 @@ private static string GetEntityName(string assemblyQualifiedName) return TypeNameParser.Parse(assemblyQualifiedName).Type; } - - /// /// Locate the collection persister by the collection role. /// @@ -140,7 +138,6 @@ private IEntityPersister FindEntityPersisterByName(String name) return sfi.GetEntityPersister(importedClassName); } - public System.Type GetImportedClass(string className) { string importedName = sfi.GetImportedClassName(className); @@ -154,7 +151,6 @@ public System.Type GetImportedClass(string className) return System.Type.GetType(importedName, false); } - /// /// Retrieve a PropertyMapping describing the given collection role. /// @@ -165,7 +161,6 @@ public IPropertyMapping GetCollectionPropertyMapping(String role) return collectionPropertyMappingByRole[role]; } - /* Locate the collection persister by the collection role, requiring that * such a persister exist. * @@ -198,4 +193,4 @@ public IQueryableCollection RequireQueryableCollection(String role) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/IMultiQuery.cs b/src/NHibernate/IMultiQuery.cs index 0cc6b693b33..49d1c280b80 100644 --- a/src/NHibernate/IMultiQuery.cs +++ b/src/NHibernate/IMultiQuery.cs @@ -161,7 +161,6 @@ public partial interface IMultiQuery /// The instance for method chain. IMultiQuery SetParameter(string name, object val, IType type); - /// /// Bind a value to a named query parameter, guessing the NHibernate /// from the class of the given object. diff --git a/src/NHibernate/IQueryExpression.cs b/src/NHibernate/IQueryExpression.cs index c5ed085c7d8..65bbe121f68 100755 --- a/src/NHibernate/IQueryExpression.cs +++ b/src/NHibernate/IQueryExpression.cs @@ -5,6 +5,12 @@ namespace NHibernate { + //TODO 6.0: Merge into IQueryExpression + internal interface ICacheableQueryExpression + { + bool CanCachePlan { get; } + } + public interface IQueryExpression { IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter); @@ -12,4 +18,4 @@ public interface IQueryExpression System.Type Type { get; } IList ParameterDescriptors { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/IQueryOver.cs b/src/NHibernate/IQueryOver.cs index c9d9e95d3a2..0320da91a49 100644 --- a/src/NHibernate/IQueryOver.cs +++ b/src/NHibernate/IQueryOver.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -10,7 +9,6 @@ namespace NHibernate { - public interface IQueryOver { /// @@ -159,7 +157,6 @@ public partial interface IQueryOver : IQueryOver /// (see ). /// IQueryOver ReadOnly(); - } /// @@ -176,7 +173,6 @@ public partial interface IQueryOver : IQueryOver /// public interface IQueryOver : IQueryOver { - /// /// Add criterion expressed as a lambda expression /// @@ -658,7 +654,5 @@ public interface IQueryOver : IQueryOver IQueryOverJoinBuilder Left { get; } IQueryOverJoinBuilder Right { get; } IQueryOverJoinBuilder Full { get; } - } - } diff --git a/src/NHibernate/ISession.cs b/src/NHibernate/ISession.cs index bcaf6e5a502..237dc9823c0 100644 --- a/src/NHibernate/ISession.cs +++ b/src/NHibernate/ISession.cs @@ -14,8 +14,8 @@ namespace NHibernate { - // 6.0 TODO: Convert to interface methods - public static class SessionExtensions + // 6.0 TODO: Convert most of these extensions to interface methods + public static partial class SessionExtensions { /// /// Obtain a builder with the ability to grab certain information from @@ -39,6 +39,66 @@ public static IQueryBatch CreateQueryBatch(this ISession session) { return ReflectHelper.CastOrThrow(session, "query batch").CreateQueryBatch(); } + + // 6.0 TODO: consider if it should be added as a property on ISession then obsolete this, or if it should stay here as an extension method. + /// + /// Get the current transaction if any is ongoing, else . + /// + /// The session. + /// The current transaction or .. + public static ITransaction GetCurrentTransaction(this ISession session) + => session.GetSessionImplementation().ConnectionManager.CurrentTransaction; + + /// + /// Return the persistent instance of the given entity class with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The session. + /// The entity name. + /// The entity identifier. + /// The lock mode to use for getting the entity. + /// A persistent instance, or . + public static object Get(this ISession session, string entityName, object id, LockMode lockMode) + { + return + ReflectHelper + .CastOrThrow(session, "Get with entityName and lockMode") + .Get(entityName, id, lockMode); + } + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// The lock mode to use for getting the entity. + /// A persistent instance, or . + public static T Get(this ISession session, string entityName, object id, LockMode lockMode) + { + return (T) session.Get(entityName, id, lockMode); + } + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// A persistent instance, or . + public static T Get(this ISession session, string entityName, object id) + { + return (T) session.Get(entityName, id); + } } /// @@ -762,6 +822,8 @@ public partial interface ISession : IDisposable /// /// Get the current Unit of Work and return the associated ITransaction object. /// + // Since v5.3 + [Obsolete("Use GetCurrentTransaction extension method instead, and check for null.")] ITransaction Transaction { get; } /// @@ -771,7 +833,8 @@ public partial interface ISession : IDisposable /// /// Sessions auto-join current transaction by default on their first usage within a scope. /// This can be disabled with from - /// a session builder obtained with . + /// a session builder obtained with , or with the + /// auto-join transaction configuration setting. /// /// /// This method allows to explicitly join the current transaction. It does nothing if it is already diff --git a/src/NHibernate/ISessionBuilder.cs b/src/NHibernate/ISessionBuilder.cs index 8fa6112914d..b1c5c7b4de3 100644 --- a/src/NHibernate/ISessionBuilder.cs +++ b/src/NHibernate/ISessionBuilder.cs @@ -1,14 +1,41 @@ using System.Data.Common; using NHibernate.Connection; +using NHibernate.Impl; +using NHibernate.MultiTenancy; +using NHibernate.Util; namespace NHibernate { + public static class SessionBuilderExtensions + { + /// + /// Associates session with given tenantIdentifier when multi-tenancy is enabled. + /// See + /// + public static T Tenant(this T builder, string tenantIdentifier) where T : ISessionBuilder + { + return builder.Tenant(new TenantConfiguration(tenantIdentifier)); + } + + //TODO 6.0: Merge into ISessionBuilder + /// + /// Associates session with given tenantConfig when multi-tenancy is enabled. + /// See + /// + public static T Tenant(this T builder, TenantConfiguration tenantConfig) where T: ISessionBuilder + { + ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; + return builder; + } + } + // NH specific: Java does not require this, it looks as still having a better covariance support. /// /// Represents a consolidation of all session creation options into a builder style delegate. /// public interface ISessionBuilder : ISessionBuilder { } + //TODO 6.0: Make T covariant ISessionBuilder -> ISessionBuilder /// /// Represents a consolidation of all session creation options into a builder style delegate. /// @@ -82,4 +109,4 @@ public interface ISessionBuilder where T : ISessionBuilder /// , for method chaining. T FlushMode(FlushMode flushMode); } -} \ No newline at end of file +} diff --git a/src/NHibernate/ISessionFactory.cs b/src/NHibernate/ISessionFactory.cs index 544f79d1a57..4dee98fde1d 100644 --- a/src/NHibernate/ISessionFactory.cs +++ b/src/NHibernate/ISessionFactory.cs @@ -7,12 +7,47 @@ using NHibernate.Impl; using NHibernate.Metadata; using NHibernate.Stat; +using NHibernate.Util; namespace NHibernate { // 6.0 TODO: move below methods directly in ISessionFactory then remove SessionFactoryExtension public static partial class SessionFactoryExtension { + /// + /// Evict an entry from the second-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// The name of the entity to evict. + /// + /// Tenant identifier + public static void EvictEntity(this ISessionFactory factory, string entityName, object id, string tenantIdentifier) + { + if (tenantIdentifier == null) + factory.EvictEntity(entityName, id); + + ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictEntity(entityName, id, tenantIdentifier); + } + + /// + /// Evict an entry from the process-level cache. This method occurs outside + /// of any transaction; it performs an immediate "hard" remove, so does not respect + /// any transaction isolation semantics of the usage strategy. Use with care. + /// + /// The session factory. + /// Collection role name. + /// Collection id + /// Tenant identifier + public static void EvictCollection(this ISessionFactory factory, string roleName, object id, string tenantIdentifier) + { + if (tenantIdentifier == null) + factory.EvictCollection(roleName, id); + + ReflectHelper.CastOrThrow(factory, "multi-tenancy").EvictCollection(roleName, id, tenantIdentifier); + } + /// /// Evict all entries from the process-level cache. This method occurs outside /// of any transaction; it performs an immediate "hard" remove, so does not respect @@ -296,10 +331,10 @@ public partial interface ISessionFactory : IDisposable ISession GetCurrentSession(); /// Get the statistics for this session factory - IStatistics Statistics { get;} + IStatistics Statistics { get; } /// Was this already closed? - bool IsClosed { get;} + bool IsClosed { get; } /// /// Obtain a set of the names of all filters defined on this SessionFactory. diff --git a/src/NHibernate/IStatelessSession.cs b/src/NHibernate/IStatelessSession.cs index 9fae5d78c72..483caec8224 100644 --- a/src/NHibernate/IStatelessSession.cs +++ b/src/NHibernate/IStatelessSession.cs @@ -10,8 +10,8 @@ namespace NHibernate { - // 6.0 TODO: Convert to interface methods - public static class StatelessSessionExtensions + // 6.0 TODO: Convert most of these extensions to interface methods + public static partial class StatelessSessionExtensions { /// /// Creates a for the session. @@ -22,6 +22,48 @@ public static IQueryBatch CreateQueryBatch(this IStatelessSession session) { return ReflectHelper.CastOrThrow(session, "query batch").CreateQueryBatch(); } + + // 6.0 TODO: consider if it should be added as a property on IStatelessSession then obsolete this, or if it should stay here as an extension method. + /// + /// Get the current transaction if any is ongoing, else . + /// + /// The session. + /// The current transaction or .. + public static ITransaction GetCurrentTransaction(this IStatelessSession session) + => session.GetSessionImplementation().ConnectionManager.CurrentTransaction; + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// The lock mode to use for getting the entity. + /// A persistent instance, or . + public static T Get(this IStatelessSession session, string entityName, object id, LockMode lockMode) + { + return (T) session.Get(entityName, id, lockMode); + } + + //NOTE: Keep it as extension + /// + /// Return the persistent instance of the given entity name with the given identifier, or null + /// if there is no such persistent instance. (If the instance, or a proxy for the instance, is + /// already associated with the session, return that instance or proxy.) + /// + /// The entity class. + /// The session. + /// The entity name. + /// The entity identifier. + /// A persistent instance, or . + public static T Get(this IStatelessSession session, string entityName, object id) + { + return (T) session.Get(entityName, id); + } } /// @@ -54,6 +96,8 @@ public partial interface IStatelessSession : IDisposable DbConnection Connection { get; } /// Get the current NHibernate transaction. + // Since v5.3 + [Obsolete("Use GetCurrentTransaction extension method instead, and check for null.")] ITransaction Transaction { get; } /// diff --git a/src/NHibernate/IStatelessSessionBuilder.cs b/src/NHibernate/IStatelessSessionBuilder.cs index 77641ad9e4e..a8d2ecfcac7 100644 --- a/src/NHibernate/IStatelessSessionBuilder.cs +++ b/src/NHibernate/IStatelessSessionBuilder.cs @@ -1,8 +1,34 @@ using System.Data.Common; using NHibernate.Connection; +using NHibernate.Impl; +using NHibernate.MultiTenancy; +using NHibernate.Util; namespace NHibernate { + public static class StatelessSessionBuilderExtensions + { + /// + /// Associates stateless session with given tenantIdentifier when multi-tenancy is enabled. + /// See + /// + public static T Tenant(this T builder, string tenantIdentifier) where T : ISessionBuilder + { + return builder.Tenant(new TenantConfiguration(tenantIdentifier)); + } + + //TODO 6.0: Merge into IStatelessSessionBuilder + /// + /// Associates stateless session with given tenantConfig when multi-tenancy is enabled. + /// See + /// + public static IStatelessSessionBuilder Tenant(this IStatelessSessionBuilder builder, TenantConfiguration tenantConfig) + { + ReflectHelper.CastOrThrow(builder, "multi tenancy").TenantConfiguration = tenantConfig; + return builder; + } + } + // NH different implementation: will not try to support covariant return type for specializations // until it is needed. /// @@ -38,7 +64,5 @@ public interface IStatelessSessionBuilder /// enlisted in ambient transaction. /// , for method chaining. IStatelessSessionBuilder AutoJoinTransaction(bool autoJoinTransaction); - - // NH remark: seems a bit overkill for now. On Hibernate side, they have at least another option: the tenant. } -} \ No newline at end of file +} diff --git a/src/NHibernate/Id/CounterGenerator.cs b/src/NHibernate/Id/CounterGenerator.cs index e199b00248c..8dfff2b196e 100644 --- a/src/NHibernate/Id/CounterGenerator.cs +++ b/src/NHibernate/Id/CounterGenerator.cs @@ -33,7 +33,7 @@ public object Generate(ISessionImplementor cache, object obj) // one year Count only serves to avoid collision for entities persisted in the same 100ns. Maybe it should // have been (DateTime.Now.Ticks && 0xffff) instead, with count serving to avoid collision for up to 37767 // entities in the same 6.5535ms. But changing this would be a breaking change for existing values. - return unchecked ((DateTime.Now.Ticks << 16) + Count); + return unchecked ((DateTime.UtcNow.Ticks << 16) + Count); } } } diff --git a/src/NHibernate/Id/Enhanced/OptimizerFactory.cs b/src/NHibernate/Id/Enhanced/OptimizerFactory.cs index 6305aebeb52..0adf3695551 100644 --- a/src/NHibernate/Id/Enhanced/OptimizerFactory.cs +++ b/src/NHibernate/Id/Enhanced/OptimizerFactory.cs @@ -76,7 +76,7 @@ private static IOptimizer BuildOptimizer(string type, System.Type returnClass, i } catch (Exception ex) { - Log.Error(ex, "Unable to instantiate id generator optimizer."); // FIXME: Review log message. + Log.Error(ex, "Unable to instantiate id generator optimizer."); // FIXME: Review log message. } // the default... @@ -101,6 +101,7 @@ public partial class HiLoOptimizer : OptimizerSupport private long _upperLimit; private long _lastSourceValue = -1; private long _value; + private readonly AsyncLock _asyncLock = new AsyncLock(); public HiLoOptimizer(System.Type returnClass, int incrementSize) : base(returnClass, incrementSize) { @@ -140,29 +141,32 @@ public override bool ApplyIncrementSizeToSourceValues get { return false; } } - [MethodImpl(MethodImplOptions.Synchronized)] public override object Generate(IAccessCallback callback) { - if (_lastSourceValue < 0) + using (_asyncLock.Lock()) { - _lastSourceValue = callback.GetNextValue(); - while (_lastSourceValue <= 0) + if (_lastSourceValue < 0) { _lastSourceValue = callback.GetNextValue(); - } + while (_lastSourceValue <= 0) + { + _lastSourceValue = callback.GetNextValue(); + } - // upperLimit defines the upper end of the bucket values - _upperLimit = (_lastSourceValue * IncrementSize) + 1; + // upperLimit defines the upper end of the bucket values + _upperLimit = (_lastSourceValue * IncrementSize) + 1; - // initialize value to the low end of the bucket - _value = _upperLimit - IncrementSize; - } - else if (_upperLimit <= _value) - { - _lastSourceValue = callback.GetNextValue(); - _upperLimit = (_lastSourceValue * IncrementSize) + 1; + // initialize value to the low end of the bucket + _value = _upperLimit - IncrementSize; + } + else if (_upperLimit <= _value) + { + _lastSourceValue = callback.GetNextValue(); + _upperLimit = (_lastSourceValue * IncrementSize) + 1; + } + + return Make(_value++); } - return Make(_value++); } } @@ -267,6 +271,7 @@ public partial class PooledOptimizer : OptimizerSupport, IInitialValueAwareOptim private long _hiValue = -1; private long _value; private long _initialValue; + private readonly AsyncLock _asyncLock = new AsyncLock(); public PooledOptimizer(System.Type returnClass, int incrementSize) : base(returnClass, incrementSize) { @@ -303,35 +308,38 @@ public void InjectInitialValue(long initialValue) _initialValue = initialValue; } - [MethodImpl(MethodImplOptions.Synchronized)] public override object Generate(IAccessCallback callback) { - if (_hiValue < 0) + using (_asyncLock.Lock()) { - _value = callback.GetNextValue(); - if (_value < 1) + if (_hiValue < 0) { - // unfortunately not really safe to normalize this - // to 1 as an initial value like we do the others - // because we would not be able to control this if - // we are using a sequence... - Log.Info("pooled optimizer source reported [{0}] as the initial value; use of 1 or greater highly recommended", _value); + _value = callback.GetNextValue(); + if (_value < 1) + { + // unfortunately not really safe to normalize this + // to 1 as an initial value like we do the others + // because we would not be able to control this if + // we are using a sequence... + Log.Info("pooled optimizer source reported [{0}] as the initial value; use of 1 or greater highly recommended", _value); + } + + if ((_initialValue == -1 && _value < IncrementSize) || _value == _initialValue) + _hiValue = callback.GetNextValue(); + else + { + _hiValue = _value; + _value = _hiValue - IncrementSize; + } } - - if ((_initialValue == -1 && _value < IncrementSize) || _value == _initialValue) - _hiValue = callback.GetNextValue(); - else + else if (_value >= _hiValue) { - _hiValue = _value; + _hiValue = callback.GetNextValue(); _value = _hiValue - IncrementSize; } + + return Make(_value++); } - else if (_value >= _hiValue) - { - _hiValue = callback.GetNextValue(); - _value = _hiValue - IncrementSize; - } - return Make(_value++); } } @@ -343,6 +351,7 @@ public partial class PooledLoOptimizer : OptimizerSupport { private long _lastSourceValue = -1; // last value read from db source private long _value; // the current generator value + private readonly AsyncLock _asyncLock = new AsyncLock(); public PooledLoOptimizer(System.Type returnClass, int incrementSize) : base(returnClass, incrementSize) { @@ -356,18 +365,21 @@ public PooledLoOptimizer(System.Type returnClass, int incrementSize) : base(retu } } - [MethodImpl(MethodImplOptions.Synchronized)] public override object Generate(IAccessCallback callback) { - if (_lastSourceValue < 0 || _value >= (_lastSourceValue + IncrementSize)) + using (_asyncLock.Lock()) { - _lastSourceValue = callback.GetNextValue(); - _value = _lastSourceValue; - // handle cases where initial-value is less than one (hsqldb for instance). - while (_value < 1) - _value++; + if (_lastSourceValue < 0 || _value >= (_lastSourceValue + IncrementSize)) + { + _lastSourceValue = callback.GetNextValue(); + _value = _lastSourceValue; + // handle cases where initial-value is less than one (hsqldb for instance). + while (_value < 1) + _value++; + } + + return Make(_value++); } - return Make(_value++); } public override long LastSourceValue diff --git a/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs b/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs index dd252771364..022d4101714 100644 --- a/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs +++ b/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs @@ -223,7 +223,6 @@ protected bool RequiresPooledSequence(int initialValue, int incrementSize, IOpti return (initialValue > 1 || sourceIncrementSize > 1); } - #endregion #region Implementation of IIdentifierGenerator diff --git a/src/NHibernate/Id/Enhanced/TableGenerator.cs b/src/NHibernate/Id/Enhanced/TableGenerator.cs index ff69ab2af97..60a287db13f 100644 --- a/src/NHibernate/Id/Enhanced/TableGenerator.cs +++ b/src/NHibernate/Id/Enhanced/TableGenerator.cs @@ -89,7 +89,6 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(SequenceStyleGenerator)); - public const string ConfigPreferSegmentPerEntity = "prefer_entity_table_as_segment_value"; public const string TableParam = "table_name"; @@ -115,26 +114,22 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe public const string OptimizerParam = "optimizer"; - /// /// Type mapping for the identifier. /// public IType IdentifierType { get; private set; } - /// /// The name of the table in which we store this generator's persistent state. /// public string TableName { get; private set; } - /// /// The name of the column in which we store the segment to which each row /// belongs. The value here acts as primary key. /// public string SegmentColumnName { get; private set; } - /// /// The value in the column identified by which /// corresponds to this generator instance. In other words, this value @@ -142,7 +137,6 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe /// public string SegmentValue { get; private set; } - /// /// The size of the column identified by /// in the underlying table. @@ -152,27 +146,23 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe /// public int SegmentValueLength { get; private set; } - /// /// The name of the column in which we store our persistent generator value. /// public string ValueColumnName { get; private set; } - /// /// The initial value to use when we find no previous state in the /// generator table corresponding to this instance. /// public int InitialValue { get; private set; } - /// /// The amount of increment to use. The exact implications of this /// depends on the optimizer being used, see . /// public int IncrementSize { get; private set; } - /// /// The optimizer being used by this generator. This mechanism /// allows avoiding calling the database each time a new identifier @@ -180,27 +170,24 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe /// public IOptimizer Optimizer { get; private set; } - /// /// The table access count. Only really useful for unit test assertions. /// public long TableAccessCount { get; private set; } - private SqlString selectQuery; private SqlTypes.SqlType[] selectParameterTypes; private SqlString insertQuery; private SqlTypes.SqlType[] insertParameterTypes; private SqlString updateQuery; private SqlTypes.SqlType[] updateParameterTypes; - + private readonly AsyncLock _asyncLock = new AsyncLock(); public virtual string GeneratorKey() { return TableName; } - #region Implementation of IConfigurable public virtual void Configure(IType type, IDictionary parms, Dialect.Dialect dialect) @@ -221,7 +208,6 @@ public virtual void Configure(IType type, IDictionary parms, Dia BuildUpdateQuery(); BuildInsertQuery(); - // if the increment size is greater than one, we prefer pooled optimization; but we // need to see if the user prefers POOL or POOL_LO... string defaultPooledOptimizerStrategy = PropertiesHelper.GetBoolean(Cfg.Environment.PreferPooledValuesLo, parms, false) @@ -234,7 +220,7 @@ public virtual void Configure(IType type, IDictionary parms, Dia optimizationStrategy, IdentifierType.ReturnedClass, IncrementSize, - PropertiesHelper.GetInt32(InitialParam, parms, -1) // Use -1 as default initial value here to signal that it's not set. + PropertiesHelper.GetInt32(InitialParam, parms, -1) // Use -1 as default initial value here to signal that it's not set. ); } @@ -288,7 +274,6 @@ protected string DetermineSegmentColumnName(IDictionary parms, D //return dialect.quote( normalizer.normalizeIdentifierQuoting( name ) ); } - /// /// Determine the name of the column in which we will store the generator persistent value. /// Called during configuration. @@ -303,7 +288,6 @@ protected string DetermineValueColumnName(IDictionary parms, Dia return dialect.QuoteForColumnName(name); } - /// /// Determine the segment value corresponding to this generator instance. Called during configuration. /// @@ -315,7 +299,6 @@ protected string DetermineSegmentValue(IDictionary parms) return segmentValue; } - /// /// Used in the cases where is unable to /// determine the value to use. @@ -330,7 +313,6 @@ protected string DetermineDefaultSegmentValue(IDictionary parms) return defaultToUse; } - /// /// Determine the size of the segment column. /// Called during configuration. @@ -340,19 +322,16 @@ protected int DetermineSegmentColumnSize(IDictionary parms) return PropertiesHelper.GetInt32(SegmentLengthParam, parms, DefaultSegmentLength); } - protected int DetermineInitialValue(IDictionary parms) { return PropertiesHelper.GetInt32(InitialParam, parms, DefaltInitialValue); } - protected int DetermineIncrementSize(IDictionary parms) { return PropertiesHelper.GetInt32(IncrementParam, parms, DefaultIncrementSize); } - protected void BuildSelectQuery(Dialect.Dialect dialect) { const string alias = "tbl"; @@ -373,7 +352,6 @@ protected void BuildSelectQuery(Dialect.Dialect dialect) selectParameterTypes = new[] { SqlTypes.SqlTypeFactory.GetAnsiString(SegmentValueLength) }; } - protected void BuildUpdateQuery() { updateQuery = new SqlString( @@ -389,7 +367,6 @@ protected void BuildUpdateQuery() }; } - protected void BuildInsertQuery() { insertQuery = new SqlString( @@ -402,14 +379,14 @@ protected void BuildInsertQuery() }; } - - [MethodImpl(MethodImplOptions.Synchronized)] public virtual object Generate(ISessionImplementor session, object obj) { - return Optimizer.Generate(new TableAccessCallback(session, this)); + using (_asyncLock.Lock()) + { + return Optimizer.Generate(new TableAccessCallback(session, this)); + } } - private partial class TableAccessCallback : IAccessCallback { private TableGenerator owner; @@ -431,7 +408,6 @@ public long GetNextValue() #endregion } - public override object DoWorkInCurrentTransaction(ISessionImplementor session, DbConnection conn, DbTransaction transaction) { long result; @@ -483,7 +459,6 @@ public override object DoWorkInCurrentTransaction(ISessionImplementor session, D throw; } - try { var updateCmd = session.Factory.ConnectionProvider.Driver.GenerateCommand(CommandType.Text, updateQuery, updateParameterTypes); @@ -513,7 +488,6 @@ public override object DoWorkInCurrentTransaction(ISessionImplementor session, D return result; } - public virtual string[] SqlCreateStrings(Dialect.Dialect dialect) { string createString = dialect.CreateTableString + " " + TableName @@ -526,7 +500,6 @@ public virtual string[] SqlCreateStrings(Dialect.Dialect dialect) return new string[] { createString }; } - public virtual string[] SqlDropString(Dialect.Dialect dialect) { return new[] { dialect.GetDropTableString(TableName) }; diff --git a/src/NHibernate/Id/IIdentifierGenerator.cs b/src/NHibernate/Id/IIdentifierGenerator.cs index ae6ab894a89..105648953f9 100644 --- a/src/NHibernate/Id/IIdentifierGenerator.cs +++ b/src/NHibernate/Id/IIdentifierGenerator.cs @@ -8,7 +8,6 @@ public struct IdGeneratorParmsNames public readonly static string EntityName = "entity_name"; } - /// /// The general contract between a class that generates unique /// identifiers and the . @@ -40,4 +39,4 @@ public partial interface IIdentifierGenerator /// The new identifier object Generate(ISessionImplementor session, object obj); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Id/IPostInsertIdentityPersister.cs b/src/NHibernate/Id/IPostInsertIdentityPersister.cs index 396c0989f7d..eeba73abcd1 100644 --- a/src/NHibernate/Id/IPostInsertIdentityPersister.cs +++ b/src/NHibernate/Id/IPostInsertIdentityPersister.cs @@ -7,6 +7,7 @@ using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Id { @@ -32,7 +33,7 @@ public static SqlString GetSelectByUniqueKeyString( { var uniqueKeyPropertyNames = suppliedPropertyNames ?? DetermineNameOfPropertiesToUse(entityPersister); - parameterTypes = uniqueKeyPropertyNames.Select(p => entityPersister.GetPropertyType(p)).ToArray(); + parameterTypes = uniqueKeyPropertyNames.ToArray(p => entityPersister.GetPropertyType(p)); } else if (persister is IQueryableCollection collectionPersister) { @@ -89,11 +90,11 @@ public interface IPostInsertIdentityPersister /// Get the database-specific SQL command to retrieve the last /// generated IDENTITY value. /// - string IdentitySelectString { get;} + string IdentitySelectString { get; } /// The names of the primary key columns in the root table. /// The primary key column names. - string[] RootTableKeyColumnNames { get;} + string[] RootTableKeyColumnNames { get; } /// /// Get a SQL select string that performs a select based on a unique diff --git a/src/NHibernate/Id/IdentityGenerator.cs b/src/NHibernate/Id/IdentityGenerator.cs index 22d736b81d2..6971a7dbdca 100644 --- a/src/NHibernate/Id/IdentityGenerator.cs +++ b/src/NHibernate/Id/IdentityGenerator.cs @@ -95,7 +95,6 @@ public object DetermineGeneratedIdentifier(ISessionImplementor session, object e /// public partial class BasicDelegate : AbstractSelectingDelegate, IInsertGeneratedIdentifierDelegate { - private readonly IPostInsertIdentityPersister persister; private readonly ISessionFactoryImplementor factory; @@ -124,4 +123,4 @@ protected internal override object GetResult(ISessionImplementor session, DbData } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Id/IncrementGenerator.cs b/src/NHibernate/Id/IncrementGenerator.cs index 1d4fef66ba7..ba4012f60ec 100644 --- a/src/NHibernate/Id/IncrementGenerator.cs +++ b/src/NHibernate/Id/IncrementGenerator.cs @@ -9,6 +9,7 @@ using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Id { @@ -32,6 +33,7 @@ public partial class IncrementGenerator : IIdentifierGenerator, IConfigurable private long _next; private SqlString _sql; private System.Type _returnClass; + private readonly AsyncLock _asyncLock = new AsyncLock(); /// /// @@ -85,14 +87,17 @@ public void Configure(IType type, IDictionary parms, Dialect.Dia /// /// /// - [MethodImpl(MethodImplOptions.Synchronized)] public object Generate(ISessionImplementor session, object obj) { - if (_sql != null) + using (_asyncLock.Lock()) { - GetNext(session); + if (_sql != null) + { + GetNext(session); + } + + return IdentifierGeneratorFactory.CreateNumber(_next++, _returnClass); } - return IdentifierGeneratorFactory.CreateNumber(_next++, _returnClass); } private void GetNext(ISessionImplementor session) diff --git a/src/NHibernate/Id/Insert/IBinder.cs b/src/NHibernate/Id/Insert/IBinder.cs index 9e7271f74e9..d97d3fd1c06 100644 --- a/src/NHibernate/Id/Insert/IBinder.cs +++ b/src/NHibernate/Id/Insert/IBinder.cs @@ -4,7 +4,7 @@ namespace NHibernate.Id.Insert { public partial interface IBinder { - object Entity { get;} + object Entity { get; } void BindValues(DbCommand cm); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Id/SelectGenerator.cs b/src/NHibernate/Id/SelectGenerator.cs index 9a92598c6ab..4be34ea1bc8 100644 --- a/src/NHibernate/Id/SelectGenerator.cs +++ b/src/NHibernate/Id/SelectGenerator.cs @@ -8,6 +8,7 @@ using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Id { @@ -58,7 +59,7 @@ internal SelectGeneratorDelegate(IPostInsertIdentityPersister persister, ISessio this.persister = persister; this.factory = factory; - uniqueKeySuppliedPropertyNames = suppliedUniqueKeyPropertyNames?.Split(',').Select(p => p.Trim()).ToArray(); + uniqueKeySuppliedPropertyNames = suppliedUniqueKeyPropertyNames?.Split(',').ToArray(p => p.Trim()); idSelectString = persister.GetSelectByUniqueKeyString(uniqueKeySuppliedPropertyNames, out uniqueKeyTypes); idType = persister.IdentifierType; diff --git a/src/NHibernate/Id/SequenceHiLoGenerator.cs b/src/NHibernate/Id/SequenceHiLoGenerator.cs index 8e07c079548..2d9fca0b85b 100644 --- a/src/NHibernate/Id/SequenceHiLoGenerator.cs +++ b/src/NHibernate/Id/SequenceHiLoGenerator.cs @@ -46,6 +46,7 @@ public partial class SequenceHiLoGenerator : SequenceGenerator private int lo; private long hi; private System.Type returnClass; + private readonly AsyncLock _asyncLock = new AsyncLock(); #region IConfigurable Members @@ -75,27 +76,29 @@ public override void Configure(IType type, IDictionary parms, Di /// The this id is being generated in. /// The entity for which the id is being generated. /// The new identifier as a , , or . - [MethodImpl(MethodImplOptions.Synchronized)] public override object Generate(ISessionImplementor session, object obj) { - if (maxLo < 1) + using (_asyncLock.Lock()) { - //keep the behavior consistent even for boundary usages - long val = Convert.ToInt64(base.Generate(session, obj)); - if (val == 0) - val = Convert.ToInt64(base.Generate(session, obj)); - return IdentifierGeneratorFactory.CreateNumber(val, returnClass); - } + if (maxLo < 1) + { + //keep the behavior consistent even for boundary usages + long val = Convert.ToInt64(base.Generate(session, obj)); + if (val == 0) + val = Convert.ToInt64(base.Generate(session, obj)); + return IdentifierGeneratorFactory.CreateNumber(val, returnClass); + } - if (lo > maxLo) - { - long hival = Convert.ToInt64(base.Generate(session, obj)); - lo = (hival == 0) ? 1 : 0; - hi = hival * (maxLo + 1); - if (log.IsDebugEnabled()) - log.Debug("new hi value: {0}", hival); + if (lo > maxLo) + { + long hival = Convert.ToInt64(base.Generate(session, obj)); + lo = (hival == 0) ? 1 : 0; + hi = hival * (maxLo + 1); + if (log.IsDebugEnabled()) + log.Debug("new hi value: {0}", hival); + } + return IdentifierGeneratorFactory.CreateNumber(hi + lo++, returnClass); } - return IdentifierGeneratorFactory.CreateNumber(hi + lo++, returnClass); } #endregion diff --git a/src/NHibernate/Id/TableGenerator.cs b/src/NHibernate/Id/TableGenerator.cs index ce79bf2a532..09180687efc 100644 --- a/src/NHibernate/Id/TableGenerator.cs +++ b/src/NHibernate/Id/TableGenerator.cs @@ -70,6 +70,7 @@ public partial class TableGenerator : TransactionHelper, IPersistentIdentifierGe private SqlString updateSql; private SqlType[] parameterTypes; + private readonly AsyncLock _asyncLock = new AsyncLock(); #region IConfigurable Members @@ -151,13 +152,15 @@ public virtual void Configure(IType type, IDictionary parms, Dia /// The this id is being generated in. /// The entity for which the id is being generated. /// The new identifier as a , , or . - [MethodImpl(MethodImplOptions.Synchronized)] public virtual object Generate(ISessionImplementor session, object obj) { - // This has to be done using a different connection to the containing - // transaction becase the new hi value must remain valid even if the - // containing transaction rolls back. - return DoWorkInNewTransaction(session); + using (_asyncLock.Lock()) + { + // This has to be done using a different connection to the containing + // transaction becase the new hi value must remain valid even if the + // containing transaction rolls back. + return DoWorkInNewTransaction(session); + } } #endregion diff --git a/src/NHibernate/Id/TableHiLoGenerator.cs b/src/NHibernate/Id/TableHiLoGenerator.cs index 402e5dbd467..c64de1d3323 100644 --- a/src/NHibernate/Id/TableHiLoGenerator.cs +++ b/src/NHibernate/Id/TableHiLoGenerator.cs @@ -52,6 +52,7 @@ public partial class TableHiLoGenerator : TableGenerator private long lo; private long maxLo; private System.Type returnClass; + private readonly AsyncLock _asyncLock = new AsyncLock(); #region IConfigurable Members @@ -80,26 +81,28 @@ public override void Configure(IType type, IDictionary parms, Di /// The this id is being generated in. /// The entity for which the id is being generated. /// The new identifier as a . - [MethodImpl(MethodImplOptions.Synchronized)] public override object Generate(ISessionImplementor session, object obj) { - if (maxLo < 1) + using (_asyncLock.Lock()) { - //keep the behavior consistent even for boundary usages - long val = Convert.ToInt64(base.Generate(session, obj)); - if (val == 0) - val = Convert.ToInt64(base.Generate(session, obj)); - return IdentifierGeneratorFactory.CreateNumber(val, returnClass); - } - if (lo > maxLo) - { - long hival = Convert.ToInt64(base.Generate(session, obj)); - lo = (hival == 0) ? 1 : 0; - hi = hival * (maxLo + 1); - log.Debug("New high value: {0}", hival); - } + if (maxLo < 1) + { + //keep the behavior consistent even for boundary usages + long val = Convert.ToInt64(base.Generate(session, obj)); + if (val == 0) + val = Convert.ToInt64(base.Generate(session, obj)); + return IdentifierGeneratorFactory.CreateNumber(val, returnClass); + } + if (lo > maxLo) + { + long hival = Convert.ToInt64(base.Generate(session, obj)); + lo = (hival == 0) ? 1 : 0; + hi = hival * (maxLo + 1); + log.Debug("New high value: {0}", hival); + } - return IdentifierGeneratorFactory.CreateNumber(hi + lo++, returnClass); + return IdentifierGeneratorFactory.CreateNumber(hi + lo++, returnClass); + } } #endregion diff --git a/src/NHibernate/IdentityEqualityComparer.cs b/src/NHibernate/IdentityEqualityComparer.cs index 26e52f6207d..c65a94bc836 100644 --- a/src/NHibernate/IdentityEqualityComparer.cs +++ b/src/NHibernate/IdentityEqualityComparer.cs @@ -1,14 +1,15 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace NHibernate { + // Since v5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] [Serializable] - public class IdentityEqualityComparer: IEqualityComparer + public class IdentityEqualityComparer : IEqualityComparer, IEqualityComparer { - #region IEqualityComparer Members - public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); @@ -32,8 +33,6 @@ public int GetHashCode(object obj) public new bool Equals(object x, object y) { return ReferenceEquals(x, y); - } - #endregion } } diff --git a/src/NHibernate/Impl/AbstractDetachedQuery.cs b/src/NHibernate/Impl/AbstractDetachedQuery.cs index d236cc9be59..b41e705ee6a 100644 --- a/src/NHibernate/Impl/AbstractDetachedQuery.cs +++ b/src/NHibernate/Impl/AbstractDetachedQuery.cs @@ -619,6 +619,5 @@ public IDetachedQuery CopyParametersFrom(IDetachedQueryImplementor origin) (this as IDetachedQueryImplementor).OverrideParametersFrom(origin); return this; } - } } diff --git a/src/NHibernate/Impl/AbstractQueryImpl.cs b/src/NHibernate/Impl/AbstractQueryImpl.cs index 5106a7d33e3..ba46b665466 100644 --- a/src/NHibernate/Impl/AbstractQueryImpl.cs +++ b/src/NHibernate/Impl/AbstractQueryImpl.cs @@ -79,11 +79,11 @@ protected internal virtual void VerifyParameters() } /// - /// Perform parameter validation. Used prior to executing the encapsulated query. + /// Perform parameters validation. Flatten them if needed. Used prior to executing the encapsulated query. /// /// - /// if true, the first ? will not be verified since - /// its needed for e.g. callable statements returning a out parameter + /// If true, the first positional parameter will not be verified since + /// its needed for e.g. callable statements returning an out parameter. /// protected internal virtual void VerifyParameters(bool reserveFirstParameter) { @@ -95,11 +95,15 @@ protected internal virtual void VerifyParameters(bool reserveFirstParameter) throw new QueryException("Not all named parameters have been set: " + CollectionPrinter.ToString(missingParams), QueryString); } - int positionalValueSpan = 0; - for (int i = 0; i < values.Count; i++) + var positionalValueSpan = 0; + // Values and Types may be overriden to yield refined parameters, check them + // instead of the fields. + var values = Values; + var types = Types; + for (var i = 0; i < values.Count; i++) { - object obj = types[i]; - if (values[i] == UNSET_PARAMETER || obj == UNSET_TYPE) + var type = types[i]; + if (values[i] == UNSET_PARAMETER || type == UNSET_TYPE) { if (reserveFirstParameter && i == 0) { @@ -138,7 +142,8 @@ protected internal virtual IType DetermineType(int paramPosition, object paramVa protected internal virtual IType DetermineType(int paramPosition, object paramValue) { - IType type = parameterMetadata.GetOrdinalParameterExpectedType(paramPosition + 1) ?? GuessType(paramValue); + IType type = parameterMetadata.GetOrdinalParameterExpectedType(paramPosition + 1) ?? + ParameterHelper.GuessType(paramValue, session.Factory); return type; } @@ -150,67 +155,15 @@ protected internal virtual IType DetermineType(string paramName, object paramVal protected internal virtual IType DetermineType(string paramName, object paramValue) { - IType type = parameterMetadata.GetNamedParameterExpectedType(paramName) ?? GuessType(paramValue); + IType type = parameterMetadata.GetNamedParameterExpectedType(paramName) ?? + ParameterHelper.GuessType(paramValue, session.Factory); return type; } protected internal virtual IType DetermineType(string paramName, System.Type clazz) { - IType type = parameterMetadata.GetNamedParameterExpectedType(paramName) ?? GuessType(clazz); - return type; - } - - /// - /// Guesses the from the param's value. - /// - /// The object to guess the of. - /// An for the object. - /// - /// Thrown when the param is null because the - /// can't be guess from a null value. - /// - private IType GuessType(object param) - { - if (param == null) - { - throw new ArgumentNullException("param", "The IType can not be guessed for a null value."); - } - - System.Type clazz = NHibernateProxyHelper.GetClassWithoutInitializingProxy(param); - return GuessType(clazz); - } - - /// - /// Guesses the from the . - /// - /// The to guess the of. - /// An for the . - /// - /// Thrown when the clazz is null because the - /// can't be guess from a null type. - /// - private IType GuessType(System.Type clazz) - { - if (clazz == null) - { - throw new ArgumentNullException("clazz", "The IType can not be guessed for a null value."); - } - - var type = TypeFactory.HeuristicType(clazz); - if (type == null || type is SerializableType) - { - if (session.Factory.TryGetEntityPersister(clazz.FullName) != null) - { - return NHibernateUtil.Entity(clazz); - } - - if (type == null) - { - throw new HibernateException( - "Could not determine a type for class: " + clazz.AssemblyQualifiedName); - } - } - + IType type = parameterMetadata.GetNamedParameterExpectedType(paramName) ?? + ParameterHelper.GuessType(clazz, session.Factory); return type; } @@ -306,7 +259,11 @@ public IQuery SetParameter(int position, T val) { CheckPositionalParameter(position); - return SetParameter(position, val, parameterMetadata.GetOrdinalParameterExpectedType(position + 1) ?? GuessType(typeof(T))); + return SetParameter( + position, + val, + parameterMetadata.GetOrdinalParameterExpectedType(position + 1) ?? + ParameterHelper.GuessType(typeof(T), session.Factory)); } private void CheckPositionalParameter(int position) @@ -323,7 +280,11 @@ private void CheckPositionalParameter(int position) public IQuery SetParameter(string name, T val) { - return SetParameter(name, val, parameterMetadata.GetNamedParameterExpectedType(name) ?? GuessType(typeof (T))); + return SetParameter( + name, + val, + parameterMetadata.GetNamedParameterExpectedType(name) ?? + ParameterHelper.GuessType(typeof(T), session.Factory)); } public IQuery SetParameter(string name, object val) @@ -651,6 +612,8 @@ public IQuery SetEnum(string name, Enum val) return this; } + // Since 5.3 + [Obsolete("This method was never surfaced to a query interface. Use the overload taking an object instead, and supply to it a generic IDictionary.")] public IQuery SetProperties(IDictionary map) { string[] @params = NamedParameters; @@ -674,8 +637,56 @@ public IQuery SetProperties(IDictionary map) return this; } + private IQuery SetParameters(IDictionary map) + { + foreach (var namedParam in NamedParameters) + { + if (map.TryGetValue(namedParam, out var obj)) + { + switch (obj) + { + case IEnumerable enumerable when !(enumerable is string): + SetParameterList(namedParam, enumerable); + break; + default: + SetParameter(namedParam, obj); + break; + } + } + } + return this; + } + + private IQuery SetParameters(IDictionary map) + { + foreach (var namedParam in NamedParameters) + { + var obj = map[namedParam]; + switch (obj) + { + case IEnumerable enumerable when !(enumerable is string): + SetParameterList(namedParam, enumerable); + break; + case null when map.Contains(namedParam): + default: + SetParameter(namedParam, obj); + break; + } + } + return this; + } + public IQuery SetProperties(object bean) { + if (bean is IDictionary map) + { + return SetParameters(map); + } + if (bean is IDictionary hashtable) + { + return SetParameters(hashtable); + } + System.Type clazz = bean.GetType(); string[] @params = NamedParameters; for (int i = 0; i < @params.Length; i++) @@ -738,7 +749,12 @@ public IQuery SetParameterList(string name, IEnumerable vals) } object firstValue = vals.Cast().FirstOrDefault(); - SetParameterList(name, vals, firstValue == null ? GuessType(vals.GetCollectionElementType()) : DetermineType(name, firstValue)); + SetParameterList( + name, + vals, + firstValue == null + ? ParameterHelper.GuessType(vals.GetCollectionElementType(), session.Factory) + : DetermineType(name, firstValue)); return this; } @@ -763,12 +779,13 @@ protected IDictionary NamedParameterLists get { return namedParameterLists; } } - protected IList Values + // TODO 6.0: Change type to IList + protected virtual IList Values { get { return values; } } - protected IList Types + protected virtual IList Types { get { return types; } } @@ -916,7 +933,6 @@ public IQuery SetCacheMode(CacheMode cacheMode) { this.cacheMode = cacheMode; return this; - } public IQuery SetIgnoreUknownNamedParameters(bool ignoredUnknownNamedParameters) @@ -925,7 +941,7 @@ public IQuery SetIgnoreUknownNamedParameters(bool ignoredUnknownNamedParameters) return this; } - protected internal abstract IDictionary LockModes { get;} + protected internal abstract IDictionary LockModes { get; } #endregion diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 173a8cba7bd..d2fd81b1b72 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -7,6 +7,7 @@ using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; +using NHibernate.Connection; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -17,9 +18,11 @@ using NHibernate.Loader.Custom; using NHibernate.Loader.Custom.Sql; using NHibernate.Multi; +using NHibernate.MultiTenancy; using NHibernate.Persister.Entity; using NHibernate.Transaction; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Impl { @@ -37,6 +40,8 @@ public abstract partial class AbstractSessionImpl : ISessionImplementor private bool closed; /// Get the current NHibernate transaction. + // Since v5.3 + [Obsolete("Use GetCurrentTransaction extension method instead, and check for null.")] public ITransaction Transaction => ConnectionManager.Transaction; protected bool IsTransactionCoordinatorShared { get; } @@ -54,6 +59,31 @@ public ITransactionContext TransactionContext internal AbstractSessionImpl() { } + private TenantConfiguration ValidateTenantConfiguration(ISessionFactoryImplementor factory, ISessionCreationOptions options) + { + if (factory.Settings.MultiTenancyStrategy == MultiTenancyStrategy.None) + return null; + + var tenantConfiguration = ReflectHelper.CastOrThrow(options, "multi-tenancy").TenantConfiguration; + + if (string.IsNullOrEmpty(tenantConfiguration?.TenantIdentifier)) + { + throw new ArgumentException( + $"Tenant configuration with `{nameof(TenantConfiguration.TenantIdentifier)}` defined is required for multi-tenancy.", + nameof(tenantConfiguration)); + } + + if (_factory.Settings.MultiTenancyConnectionProvider == null) + { + throw new ArgumentException( + $"`{nameof(IMultiTenancyConnectionProvider)}` is required for multi-tenancy." + + $" Provide it via '{Cfg.Environment.MultiTenancyConnectionProvider}` session factory setting." + + $" You can use `{nameof(AbstractMultiTenancyConnectionProvider)}` as a base."); + } + + return tenantConfiguration; + } + protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISessionCreationOptions options) { SessionId = factory.Settings.TrackSessionId ? Guid.NewGuid() : Guid.Empty; @@ -64,6 +94,8 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess _flushMode = options.InitialSessionFlushMode; Interceptor = options.SessionInterceptor ?? EmptyInterceptor.Instance; + _tenantConfiguration = ValidateTenantConfiguration(factory, options); + if (options is ISharedSessionCreationOptions sharedOptions && sharedOptions.IsTransactionCoordinatorShared) { // NH specific implementation: need to port Hibernate transaction management. @@ -81,7 +113,10 @@ protected internal AbstractSessionImpl(ISessionFactoryImplementor factory, ISess options.UserSuppliedConnection, options.SessionConnectionReleaseMode, Interceptor, - options.ShouldAutoJoinTransaction); + options.ShouldAutoJoinTransaction, + _tenantConfiguration == null + ? new NonContextualConnectionAccess(_factory) + : _factory.Settings.MultiTenancyConnectionProvider.GetConnectionAccess(_tenantConfiguration, _factory)); } } } @@ -106,7 +141,7 @@ public EntityKey GenerateEntityKey(object id, IEntityPersister persister) public CacheKey GenerateCacheKey(object id, IType type, string entityOrRoleName) { - return new CacheKey(id, type, entityOrRoleName, Factory); + return new CacheKey(id, type, entityOrRoleName, Factory, TenantIdentifier); } public ISessionFactoryImplementor Factory @@ -149,6 +184,7 @@ public virtual IList List(IQueryExpression query, QueryParameters paramete } } + //TODO 6.0: Make abstract public virtual IList List(CriteriaImpl criteria) { using (BeginProcess()) @@ -159,16 +195,15 @@ public virtual IList List(CriteriaImpl criteria) } } + //TODO 6.0: Make virtual public abstract void List(CriteriaImpl criteria, IList results); + //{ + // ArrayHelper.AddAll(results, List(criteria)); + //} public virtual IList List(CriteriaImpl criteria) { - using (BeginProcess()) - { - var results = new List(); - List(criteria, results); - return results; - } + return List(criteria).ToIList(); } public abstract IList ListFilter(object collection, string filter, QueryParameters parameters); @@ -385,6 +420,7 @@ public IDisposable BeginContext() } private ProcessHelper _processHelper = new ProcessHelper(); + private TenantConfiguration _tenantConfiguration; [Serializable] private sealed class ProcessHelper : IDisposable @@ -462,6 +498,15 @@ protected bool IsAlreadyDisposed /// public virtual bool TransactionInProgress => ConnectionManager.IsInActiveTransaction; + //6.0 TODO Add to ISessionImplementor + public TenantConfiguration TenantConfiguration + { + get => _tenantConfiguration; + protected set => _tenantConfiguration = value; + } + + public string TenantIdentifier => _tenantConfiguration?.TenantIdentifier; + #endregion protected internal void SetClosed() diff --git a/src/NHibernate/Impl/CriteriaImpl.cs b/src/NHibernate/Impl/CriteriaImpl.cs index a86f5062fb6..40a196ee27d 100644 --- a/src/NHibernate/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Impl/CriteriaImpl.cs @@ -22,6 +22,8 @@ public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria, ISupp private readonly List orderEntries = new List(10); private readonly Dictionary selectModes = new Dictionary(); private readonly Dictionary lockModes = new Dictionary(); + private readonly Dictionary> _entityFetchLazyProperties = new Dictionary>(); + private int maxResults = RowSelection.NoValue; private int firstResult; private int timeout = RowSelection.NoValue; @@ -167,6 +169,15 @@ public SelectMode GetSelectMode(string path) return result; } + public HashSet GetEntityFetchLazyProperties(string path) + { + if (_entityFetchLazyProperties.TryGetValue(path, out var result)) + { + return result; + } + return null; + } + public IResultTransformer ResultTransformer { get { return resultTransformer; } @@ -269,17 +280,20 @@ public ICriteria Add(ICriterion expression) public IList List() { - var results = new List(); - List(results); - return results; + return List().ToIList(); } public void List(IList results) + { + ArrayHelper.AddAll(results, List()); + } + + public IList List() { Before(); try { - session.List(this, results); + return session.List(this); } finally { @@ -287,13 +301,6 @@ public void List(IList results) } } - public IList List() - { - List results = new List(); - List(results); - return results; - } - public T UniqueResult() { object result = UniqueResult(); @@ -366,6 +373,19 @@ public ICriteria Fetch(SelectMode selectMode, string associationPath, string ali return this; } + if (selectMode == SelectMode.FetchLazyPropertyGroup) + { + StringHelper.ParsePathAndPropertyName(associationPath, out associationPath, out var propertyName); + if (_entityFetchLazyProperties.TryGetValue(associationPath, out var propertyNames)) + { + propertyNames.Add(propertyName); + } + else + { + _entityFetchLazyProperties[associationPath] = new HashSet {propertyName}; + } + } + selectModes[associationPath] = selectMode; return this; } diff --git a/src/NHibernate/Impl/EnumerableImpl.cs b/src/NHibernate/Impl/EnumerableImpl.cs index a3d338403c3..d48adaeeb4c 100644 --- a/src/NHibernate/Impl/EnumerableImpl.cs +++ b/src/NHibernate/Impl/EnumerableImpl.cs @@ -310,13 +310,13 @@ protected virtual void Dispose(bool isDisposing) _currentResult = null; _session.Batcher.CloseCommand(_cmd, _reader); } + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } #endregion diff --git a/src/NHibernate/Impl/ExpressionProcessor.cs b/src/NHibernate/Impl/ExpressionProcessor.cs index cb4a87f1ab1..6c401fd8851 100644 --- a/src/NHibernate/Impl/ExpressionProcessor.cs +++ b/src/NHibernate/Impl/ExpressionProcessor.cs @@ -1,18 +1,18 @@ - using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using NHibernate.Criterion; +using NHibernate.Dialect.Function; +using NHibernate.Engine; +using NHibernate.Type; using NHibernate.Util; using Expression = System.Linq.Expressions.Expression; namespace NHibernate.Impl { - /// /// Subquery type enumeration /// @@ -85,26 +85,29 @@ public Order CreateOrder(Func orderStringDelegate, Func /// Retrieve the property name from a supplied PropertyProjection - /// Note: throws if the supplied IProjection is not a PropertyProjection + /// Note: throws if the supplied IProjection is not a IPropertyProjection /// public string AsProperty() { if (_property != null) return _property; - var propertyProjection = _projection as PropertyProjection; + var propertyProjection = _projection as IPropertyProjection; if (propertyProjection == null) throw new InvalidOperationException("Cannot determine property for " + _projection); return propertyProjection.PropertyName; } + + internal bool IsConstant(out ConstantProjection value) => (value = _projection as ConstantProjection) != null; } - private readonly static IDictionary> _simpleExpressionCreators; - private readonly static IDictionary> _propertyExpressionCreators; - private readonly static IDictionary>> _subqueryExpressionCreatorTypes; - private readonly static IDictionary> _customMethodCallProcessors; - private readonly static IDictionary> _customProjectionProcessors; + private static readonly Dictionary> _simpleExpressionCreators; + private static readonly Dictionary> _propertyExpressionCreators; + private static readonly Dictionary>> _subqueryExpressionCreatorTypes; + private static readonly Dictionary> _customMethodCallProcessors; + private static readonly Dictionary> _customProjectionProcessors; + private static readonly Dictionary _binaryArithmethicTemplates = new Dictionary(); + private static readonly ISQLFunction _unaryNegateTemplate; static ExpressionProcessor() { @@ -199,6 +202,17 @@ static ExpressionProcessor() RegisterCustomProjection(() => Math.Round(default(double), default(int)), ProjectionsExtensions.ProcessRound); RegisterCustomProjection(() => Math.Round(default(decimal), default(int)), ProjectionsExtensions.ProcessRound); RegisterCustomProjection(() => ProjectionsExtensions.AsEntity(default(object)), ProjectionsExtensions.ProcessAsEntity); + + RegisterBinaryArithmeticExpression(ExpressionType.Add, "+"); + RegisterBinaryArithmeticExpression(ExpressionType.Subtract, "-"); + RegisterBinaryArithmeticExpression(ExpressionType.Multiply, "*"); + RegisterBinaryArithmeticExpression(ExpressionType.Divide, "/"); + _unaryNegateTemplate = new VarArgsSQLFunction("(-", string.Empty, ")"); + } + + private static void RegisterBinaryArithmeticExpression(ExpressionType type, string sqlOperand) + { + _binaryArithmethicTemplates[type] = new VarArgsSQLFunction("(", sqlOperand, ")"); } private static ICriterion Eq(ProjectionInfo property, object value) @@ -238,6 +252,26 @@ private static ICriterion Le(ProjectionInfo property, object value) /// public static object FindValue(Expression expression) { + if (expression.NodeType == ExpressionType.Constant) + return ((ConstantExpression) expression).Value; + + if (expression.NodeType == ExpressionType.MemberAccess) + { + var memberAccess = (MemberExpression) expression; + if (memberAccess.Expression == null || memberAccess.Expression.NodeType == ExpressionType.Constant) + { + var constantValue = ((ConstantExpression) memberAccess.Expression)?.Value; + var member = memberAccess.Member; + switch (member.MemberType) + { + case MemberTypes.Field: + return ((FieldInfo) member).GetValue(constantValue); + case MemberTypes.Property: + return ((PropertyInfo) member).GetValue(constantValue); + } + } + } + var valueExpression = Expression.Lambda(expression).Compile(); object value = valueExpression.DynamicInvoke(); return value; @@ -249,48 +283,78 @@ public static object FindValue(Expression expression) public static ProjectionInfo FindMemberProjection(Expression expression) { if (!IsMemberExpression(expression)) - return ProjectionInfo.ForProjection(Projections.Constant(FindValue(expression))); + return AsArithmeticProjection(expression) + ?? ProjectionInfo.ForProjection(Projections.Constant(FindValue(expression), NHibernateUtil.GuessType(expression.Type))); - var unaryExpression = expression as UnaryExpression; - if (unaryExpression != null) + var unwrapExpression = UnwrapConvertExpression(expression); + if (unwrapExpression != null) { - if (!IsConversion(unaryExpression.NodeType)) - throw new ArgumentException("Cannot interpret member from " + expression, nameof(expression)); - - return FindMemberProjection(unaryExpression.Operand); + return FindMemberProjection(unwrapExpression); } - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression != null) + if (expression.NodeType == ExpressionType.Call) { + var methodCallExpression = (MethodCallExpression) expression; var signature = Signature(methodCallExpression.Method); - Func processor; - if (_customProjectionProcessors.TryGetValue(signature, out processor)) + if (_customProjectionProcessors.TryGetValue(signature, out var processor)) { return ProjectionInfo.ForProjection(processor(methodCallExpression)); } } - var memberExpression = expression as MemberExpression; - if (memberExpression != null) + if (expression.NodeType == ExpressionType.MemberAccess) { - var signature = Signature(memberExpression.Member); - Func processor; - if (_customProjectionProcessors.TryGetValue(signature, out processor)) + var memberExpression = (MemberExpression) expression; + var signature = Signature(memberExpression.Member); + if (_customProjectionProcessors.TryGetValue(signature, out var processor)) { - return ProjectionInfo.ForProjection(processor(memberExpression)); + return ProjectionInfo.ForProjection(processor(memberExpression)); } } return ProjectionInfo.ForProperty(FindMemberExpression(expression)); } + private static Expression UnwrapConvertExpression(Expression expression) + { + return IsConversion(expression.NodeType) + ? ((UnaryExpression) expression).Operand + : null; + } + + private static ProjectionInfo AsArithmeticProjection(Expression expression) + { + if (expression.NodeType == ExpressionType.Negate) + { + var unary = (UnaryExpression) expression; + return ProjectionInfo.ForProjection( + new SqlFunctionProjection(_unaryNegateTemplate, TypeFactory.HeuristicType(unary.Type), FindMemberProjection(unary.Operand).AsProjection())); + } + + if (!_binaryArithmethicTemplates.TryGetValue(expression.NodeType, out var template)) + { + var unwrapExpression = UnwrapConvertExpression(expression); + return unwrapExpression != null ? AsArithmeticProjection(unwrapExpression) : null; + } + + var be = (BinaryExpression) expression; + return ProjectionInfo.ForProjection( + new SqlFunctionProjection( + template, + TypeFactory.HeuristicType(be.Type), + FindMemberProjection(be.Left).AsProjection(), + FindMemberProjection(be.Right).AsProjection())); + } + //http://stackoverflow.com/a/2509524/259946 private static readonly Regex GeneratedMemberNameRegex = new Regex(@"^(CS\$)?<\w*>[1-9a-s]__[a-zA-Z]+[0-9]*$", RegexOptions.Compiled | RegexOptions.Singleline); private static bool IsCompilerGeneratedMemberExpressionOfCompilerGeneratedClass(Expression expression) { - var memberExpression = expression as MemberExpression; - if (memberExpression != null && memberExpression.Member.DeclaringType != null) + if (expression.NodeType != ExpressionType.MemberAccess) + return false; + + var memberExpression = (MemberExpression) expression; + if (memberExpression.Member.DeclaringType != null) { return Attribute.GetCustomAttribute(memberExpression.Member.DeclaringType, typeof(CompilerGeneratedAttribute)) != null && GeneratedMemberNameRegex.IsMatch(memberExpression.Member.Name); @@ -307,64 +371,64 @@ private static bool IsCompilerGeneratedMemberExpressionOfCompilerGeneratedClass( /// The name of the member property public static string FindMemberExpression(Expression expression) { - var memberExpression = expression as MemberExpression; - if (memberExpression != null) + switch (expression.NodeType) { - var parentExpression = memberExpression.Expression; - if (parentExpression != null) + case ExpressionType.MemberAccess: { - if (parentExpression.NodeType == ExpressionType.MemberAccess - || parentExpression.NodeType == ExpressionType.Call) + var memberExpression = (MemberExpression) expression; + var parentExpression = memberExpression.Expression; + if (parentExpression != null) { - if (memberExpression.Member.DeclaringType.IsNullable()) + if (parentExpression.NodeType == ExpressionType.MemberAccess + || parentExpression.NodeType == ExpressionType.Call) { - // it's a Nullable, so ignore any .Value - if (memberExpression.Member.Name == "Value") - return FindMemberExpression(parentExpression); + if (memberExpression.Member.DeclaringType.IsNullable()) + { + // it's a Nullable, so ignore any .Value + if (memberExpression.Member.Name == "Value") + return FindMemberExpression(parentExpression); + } + + if (IsCompilerGeneratedMemberExpressionOfCompilerGeneratedClass(parentExpression)) + { + return memberExpression.Member.Name; + } + + return FindMemberExpression(parentExpression) + "." + memberExpression.Member.Name; } - - if (IsCompilerGeneratedMemberExpressionOfCompilerGeneratedClass(parentExpression)) + if (IsConversion(parentExpression.NodeType)) { - return memberExpression.Member.Name; + return (FindMemberExpression(parentExpression) + "." + memberExpression.Member.Name).TrimStart('.'); } - - return FindMemberExpression(parentExpression) + "." + memberExpression.Member.Name; - } - if (IsConversion(parentExpression.NodeType)) - { - return (FindMemberExpression(parentExpression) + "." + memberExpression.Member.Name).TrimStart('.'); } - } - - return memberExpression.Member.Name; - } - var unaryExpression = expression as UnaryExpression; - if (unaryExpression != null) - { - if (!IsConversion(unaryExpression.NodeType)) - throw new ArgumentException("Cannot interpret member from " + expression, nameof(expression)); - - return FindMemberExpression(unaryExpression.Operand); - } + return memberExpression.Member.Name; + } - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression != null) - { - if (methodCallExpression.Method.Name == "GetType") - return ClassMember(methodCallExpression.Object); + case ExpressionType.Call: + { + var methodCallExpression = (MethodCallExpression) expression; - if (methodCallExpression.Method.Name == "get_Item") - return FindMemberExpression(methodCallExpression.Object); + switch (methodCallExpression.Method.Name) + { + case "GetType": + return ClassMember(methodCallExpression.Object); + case "get_Item": + return FindMemberExpression(methodCallExpression.Object); + case "First": + return FindMemberExpression(methodCallExpression.Arguments[0]); + } - if (methodCallExpression.Method.Name == "First") - return FindMemberExpression(methodCallExpression.Arguments[0]); + throw new ArgumentException("Unrecognised method call in expression " + methodCallExpression, nameof(expression)); + } - throw new ArgumentException("Unrecognised method call in expression " + methodCallExpression, nameof(expression)); + case ExpressionType.Parameter: + return string.Empty; } - if (expression is ParameterExpression) - return ""; + var unwrapExpression = UnwrapConvertExpression(expression); + if (unwrapExpression != null) + return FindMemberExpression(unwrapExpression); throw new ArgumentException("Could not determine member from " + expression, nameof(expression)); } @@ -387,101 +451,82 @@ public static string FindPropertyExpression(Expression expression) /// Evaluated detached criteria public static DetachedCriteria FindDetachedCriteria(Expression expression) { - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression == null) + if (expression.NodeType != ExpressionType.Call) throw new ArgumentException("right operand should be detachedQueryInstance.As() - " + expression, nameof(expression)); - var criteriaExpression = Expression.Lambda(methodCallExpression.Object).Compile(); - QueryOver detachedQuery = (QueryOver)criteriaExpression.DynamicInvoke(); - return detachedQuery.DetachedCriteria; + var methodCallExpression = (MethodCallExpression) expression; + return ((QueryOver) FindValue(methodCallExpression.Object)).DetachedCriteria; } private static bool EvaluatesToNull(Expression expression) { - var valueExpression = Expression.Lambda(expression).Compile(); - object value = valueExpression.DynamicInvoke(); - return (value == null); + return FindValue(expression) == null; } private static System.Type FindMemberType(Expression expression) { - var memberExpression = expression as MemberExpression; - if (memberExpression != null) + switch (expression.NodeType) { - return memberExpression.Type; + case ExpressionType.MemberAccess: + return expression.Type; + case ExpressionType.Call: + return ((MethodCallExpression) expression).Method.ReturnType; } - var unaryExpression = expression as UnaryExpression; - if (unaryExpression != null) + var unwrapExpression = UnwrapConvertExpression(expression); + if (unwrapExpression != null) { - if (!IsConversion(unaryExpression.NodeType)) - throw new ArgumentException("Cannot interpret member from " + expression, nameof(expression)); - - return FindMemberType(unaryExpression.Operand); + return FindMemberType(unwrapExpression); } - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression != null) - { - return methodCallExpression.Method.ReturnType; - } + if (expression is UnaryExpression || expression is BinaryExpression) + return expression.Type; throw new ArgumentException("Could not determine member type from " + expression, nameof(expression)); } private static bool IsMemberExpression(Expression expression) { - if (expression is ParameterExpression) - return true; - - var memberExpression = expression as MemberExpression; - if (memberExpression != null) + switch (expression.NodeType) { - if (memberExpression.Expression == null) - return false; // it's a member of a static class - - if (IsMemberExpression(memberExpression.Expression)) + case ExpressionType.Parameter: return true; - // if the member has a null value, it was an alias - return EvaluatesToNull(memberExpression.Expression); - } - - var unaryExpression = expression as UnaryExpression; - if (unaryExpression != null) - { - if (!IsConversion(unaryExpression.NodeType)) - throw new ArgumentException("Cannot interpret member from " + expression, nameof(expression)); - - return IsMemberExpression(unaryExpression.Operand); - } - - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression != null) - { - string signature = Signature(methodCallExpression.Method); - if (_customProjectionProcessors.ContainsKey(signature)) - return true; + case ExpressionType.MemberAccess: + var expr = ((MemberExpression) expression).Expression; + return expr != null && // it's not a member of a static class + IsMemberExpressionOrAlias(expr); - if (methodCallExpression.Method.Name == "First") + case ExpressionType.Call: { - if (IsMemberExpression(methodCallExpression.Arguments[0])) - return true; - - return EvaluatesToNull(methodCallExpression.Arguments[0]); - } + var methodCallExpression = (MethodCallExpression) expression; - if (methodCallExpression.Method.Name == "GetType" - || methodCallExpression.Method.Name == "get_Item") - { - if (IsMemberExpression(methodCallExpression.Object)) + string signature = Signature(methodCallExpression.Method); + if (_customProjectionProcessors.ContainsKey(signature)) return true; - return EvaluatesToNull(methodCallExpression.Object); + switch (methodCallExpression.Method.Name) + { + case "First": + return IsMemberExpressionOrAlias(methodCallExpression.Arguments[0]); + case "GetType": + case "get_Item": + return IsMemberExpressionOrAlias(methodCallExpression.Object); + } + + return false; } } - return false; + var unwrapExpression = UnwrapConvertExpression(expression); + return unwrapExpression != null && IsMemberExpression(unwrapExpression); + } + + private static bool IsMemberExpressionOrAlias(Expression expr) + { + return IsMemberExpression(expr) || + // if the member has a null value, it was an alias + EvaluatesToNull(expr); } private static bool IsConversion(ExpressionType expressionType) @@ -508,21 +553,12 @@ private static object ConvertType(object value, System.Type type) throw new ArgumentException(string.Format("Cannot convert '{0}' to {1}", value, type)); } - private static ICriterion ProcessSimpleExpression(BinaryExpression be) - { - if (be.Left.NodeType == ExpressionType.Call && ((MethodCallExpression)be.Left).Method.Name == "CompareString") - return ProcessVisualBasicStringComparison(be); - - return ProcessSimpleExpression(be.Left, be.Right, be.NodeType); - } - - private static ICriterion ProcessSimpleExpression(Expression left, Expression right, ExpressionType nodeType) + private static ICriterion ProcessSimpleExpression(Expression left, TypedValue rightValue, ExpressionType nodeType) { ProjectionInfo property = FindMemberProjection(left); System.Type propertyType = FindMemberType(left); - object value = FindValue(right); - value = ConvertType(value, propertyType); + var value = ConvertType(rightValue.Value, propertyType); if (value == null) return ProcessSimpleNullExpression(property, nodeType); @@ -534,14 +570,17 @@ private static ICriterion ProcessSimpleExpression(Expression left, Expression ri return simpleExpressionCreator(property, value); } - private static ICriterion ProcessVisualBasicStringComparison(BinaryExpression be) + private static ICriterion ProcessAsVisualBasicStringComparison(Expression left, ExpressionType nodeType) { - var methodCall = (MethodCallExpression)be.Left; + if (left.NodeType != ExpressionType.Call) + { + return null; + } - if (IsMemberExpression(methodCall.Arguments[1])) - return ProcessMemberExpression(methodCall.Arguments[0], methodCall.Arguments[1], be.NodeType); - else - return ProcessSimpleExpression(methodCall.Arguments[0], methodCall.Arguments[1], be.NodeType); + var methodCall = (MethodCallExpression) left; + return methodCall.Method.Name == "CompareString" + ? ProcessMemberExpression(methodCall.Arguments[0], methodCall.Arguments[1], nodeType) + : null; } private static ICriterion ProcessSimpleNullExpression(ProjectionInfo property, ExpressionType expressionType) @@ -556,16 +595,16 @@ private static ICriterion ProcessSimpleNullExpression(ProjectionInfo property, E throw new ArgumentException("Cannot supply null value to operator " + expressionType, nameof(expressionType)); } - private static ICriterion ProcessMemberExpression(BinaryExpression be) - { - return ProcessMemberExpression(be.Left, be.Right, be.NodeType); - } - private static ICriterion ProcessMemberExpression(Expression left, Expression right, ExpressionType nodeType) { - ProjectionInfo leftProperty = FindMemberProjection(left); ProjectionInfo rightProperty = FindMemberProjection(right); + if (rightProperty.IsConstant(out var constProjection)) + { + return ProcessAsVisualBasicStringComparison(left, nodeType) + ?? ProcessSimpleExpression(left, constProjection.TypedValue, nodeType); + } + ProjectionInfo leftProperty = FindMemberProjection(left); Func propertyExpressionCreator; if (!_propertyExpressionCreators.TryGetValue(nodeType, out propertyExpressionCreator)) throw new InvalidOperationException("Unhandled property expression type: " + nodeType); @@ -603,11 +642,7 @@ private static ICriterion ProcessBinaryExpression(BinaryExpression expression) case ExpressionType.GreaterThanOrEqual: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: - if (IsMemberExpression(expression.Right)) - return ProcessMemberExpression(expression); - else - return ProcessSimpleExpression(expression); - + return ProcessMemberExpression(expression.Left, expression.Right, expression.NodeType); default: throw new NotImplementedException("Unhandled binary expression: " + expression.NodeType + ", " + expression); } @@ -615,33 +650,27 @@ private static ICriterion ProcessBinaryExpression(BinaryExpression expression) private static ICriterion ProcessBooleanExpression(Expression expression) { - if (expression is MemberExpression) + switch (expression.NodeType) { - return Restrictions.Eq(FindMemberExpression(expression), true); - } + case ExpressionType.MemberAccess: + return Restrictions.Eq(FindMemberExpression(expression), true); - var unaryExpression = expression as UnaryExpression; - if (unaryExpression != null) - { - if (unaryExpression.NodeType != ExpressionType.Not) - throw new ArgumentException("Cannot interpret member from " + expression, nameof(expression)); + case ExpressionType.Not: + { + var unaryExpression = (UnaryExpression) expression; + return IsMemberExpression(unaryExpression.Operand) + ? Restrictions.Eq(FindMemberExpression(unaryExpression.Operand), false) + : Restrictions.Not(ProcessExpression(unaryExpression.Operand)); + } - if (IsMemberExpression(unaryExpression.Operand)) - return Restrictions.Eq(FindMemberExpression(unaryExpression.Operand), false); - else - return Restrictions.Not(ProcessExpression(unaryExpression.Operand)); - } + case ExpressionType.Call: + return ProcessCustomMethodCall((MethodCallExpression) expression); - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression != null) - { - return ProcessCustomMethodCall(methodCallExpression); - } - - var typeBinaryExpression = expression as TypeBinaryExpression; - if (typeBinaryExpression != null) - { - return Restrictions.Eq(ClassMember(typeBinaryExpression.Expression), typeBinaryExpression.TypeOperand.FullName); + case ExpressionType.TypeIs: + { + var tbe = (TypeBinaryExpression) expression; + return Restrictions.Eq(ClassMember(tbe.Expression), tbe.TypeOperand.FullName); + } } throw new ArgumentException( @@ -857,4 +886,3 @@ public static void RegisterCustomProjection(Expression> function, Fun } } } - diff --git a/src/NHibernate/Impl/ExpressionQueryImpl.cs b/src/NHibernate/Impl/ExpressionQueryImpl.cs index a5f13151e91..8868e3b7cf2 100644 --- a/src/NHibernate/Impl/ExpressionQueryImpl.cs +++ b/src/NHibernate/Impl/ExpressionQueryImpl.cs @@ -192,9 +192,10 @@ public override object[] ValueArray() } } - internal class ExpandedQueryExpression : IQueryExpression + internal class ExpandedQueryExpression : IQueryExpression, ICacheableQueryExpression { private readonly IASTNode _tree; + private ICacheableQueryExpression _cacheableExpression; public ExpandedQueryExpression(IQueryExpression queryExpression, IASTNode tree, string key) { @@ -202,6 +203,7 @@ public ExpandedQueryExpression(IQueryExpression queryExpression, IASTNode tree, Key = key; Type = queryExpression.Type; ParameterDescriptors = queryExpression.ParameterDescriptors; + _cacheableExpression = queryExpression as ICacheableQueryExpression; } #region IQueryExpression Members @@ -218,6 +220,8 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter public IList ParameterDescriptors { get; private set; } #endregion + + public bool CanCachePlan => _cacheableExpression?.CanCachePlan ?? true; } internal class ParameterExpander @@ -327,7 +331,7 @@ public static IList LocateParameters(IASTNode tree, HashSet pa return detector.LocateParameters(); } - private IList LocateParameters() + private List LocateParameters() { var nodeTraverser = new NodeTraverser(this); nodeTraverser.TraverseDepthFirst(_tree); diff --git a/src/NHibernate/Impl/FilterImpl.cs b/src/NHibernate/Impl/FilterImpl.cs index 5153b82baf7..7993ebb9e1e 100644 --- a/src/NHibernate/Impl/FilterImpl.cs +++ b/src/NHibernate/Impl/FilterImpl.cs @@ -15,7 +15,7 @@ public class FilterImpl : IFilter [NonSerialized] private FilterDefinition definition; - private readonly IDictionary parameters = new Dictionary(); + private readonly Dictionary parameters = new Dictionary(); private readonly Dictionary _parameterSpans = new Dictionary(); public void AfterDeserialize(FilterDefinition factoryDefinition) diff --git a/src/NHibernate/Impl/FutureValue.cs b/src/NHibernate/Impl/FutureValue.cs index 3788550ef50..639a053f1c2 100644 --- a/src/NHibernate/Impl/FutureValue.cs +++ b/src/NHibernate/Impl/FutureValue.cs @@ -40,7 +40,6 @@ public IList TransformList(IList collection) if (ExecuteOnEval == null) return collection; - // When not null on a future value, ExecuteOnEval is fetched with PostExecuteTransformer from // IntermediateHqlTree through ExpressionToHqlTranslationResults, which requires a IQueryable // as input and directly yields the scalar result when the query is scalar. diff --git a/src/NHibernate/Impl/ISessionCreationOptions.cs b/src/NHibernate/Impl/ISessionCreationOptions.cs index 0e11afa2352..3dbd0632354 100644 --- a/src/NHibernate/Impl/ISessionCreationOptions.cs +++ b/src/NHibernate/Impl/ISessionCreationOptions.cs @@ -1,7 +1,14 @@ using System.Data.Common; +using NHibernate.MultiTenancy; namespace NHibernate.Impl { + public interface ISessionCreationOptionsWithMultiTenancy + { + //TODO 6.0: Merge to ISessionCreationOptions without setter + TenantConfiguration TenantConfiguration { get; set; } + } + /// /// Options for session creation. /// @@ -23,4 +30,4 @@ public interface ISessionCreationOptions // Todo: port PhysicalConnectionHandlingMode ConnectionReleaseMode SessionConnectionReleaseMode { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Impl/MessageHelper.cs b/src/NHibernate/Impl/MessageHelper.cs index 04b3f828c2f..04ca98fc67e 100644 --- a/src/NHibernate/Impl/MessageHelper.cs +++ b/src/NHibernate/Impl/MessageHelper.cs @@ -281,7 +281,6 @@ public static string InfoString(string entityName, string propertyName, object k /// An info string, in the form [Foo.bars#1] internal static String CollectionInfoString(ICollectionPersister persister, IPersistentCollection collection, object collectionKey, ISessionImplementor session) { - StringBuilder s = new StringBuilder(); s.Append("["); if (persister == null) diff --git a/src/NHibernate/Impl/MultiCriteriaImpl.cs b/src/NHibernate/Impl/MultiCriteriaImpl.cs index 87d22abda87..62c4ceb8f30 100644 --- a/src/NHibernate/Impl/MultiCriteriaImpl.cs +++ b/src/NHibernate/Impl/MultiCriteriaImpl.cs @@ -21,8 +21,8 @@ namespace NHibernate.Impl public partial class MultiCriteriaImpl : IMultiCriteria { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(MultiCriteriaImpl)); - private readonly IList criteriaQueries = new List(); - private readonly IList resultCollectionGenericType = new List(); + private readonly List criteriaQueries = new List(); + private readonly List resultCollectionGenericType = new List(); private readonly SessionImpl session; private readonly ISessionFactoryImplementor factory; diff --git a/src/NHibernate/Impl/MultiQueryImpl.cs b/src/NHibernate/Impl/MultiQueryImpl.cs index 0ea4089177a..120632ab7b1 100644 --- a/src/NHibernate/Impl/MultiQueryImpl.cs +++ b/src/NHibernate/Impl/MultiQueryImpl.cs @@ -27,7 +27,7 @@ public partial class MultiQueryImpl : IMultiQuery private readonly List queries = new List(); private readonly List translators = new List(); private readonly List translatorQueryMap = new List(); - private readonly IList resultCollectionGenericType = new List(); + private readonly List resultCollectionGenericType = new List(); private readonly List parameters = new List(); private IList queryResults; private readonly Dictionary queryResultPositions = new Dictionary(); @@ -773,7 +773,7 @@ private QueryParameters CreateCombinedQueryParameters() return combinedQueryParameters; } - private IList Parameters + private List Parameters { get { diff --git a/src/NHibernate/Impl/NonContextualConnectionAccess.cs b/src/NHibernate/Impl/NonContextualConnectionAccess.cs new file mode 100644 index 00000000000..2f3e2670de9 --- /dev/null +++ b/src/NHibernate/Impl/NonContextualConnectionAccess.cs @@ -0,0 +1,36 @@ +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.Impl +{ + /// + /// A non contextual connection access used when multi-tenancy is not enabled. + /// + [Serializable] + partial class NonContextualConnectionAccess : IConnectionAccess + { + private readonly ISessionFactoryImplementor _sessionFactory; + + public NonContextualConnectionAccess(ISessionFactoryImplementor connectionProvider) + { + _sessionFactory = connectionProvider; + } + + /// + public string ConnectionString => _sessionFactory.ConnectionProvider.GetConnectionString(); + + /// + public DbConnection GetConnection() + { + return _sessionFactory.ConnectionProvider.GetConnection(); + } + + /// + public void CloseConnection(DbConnection connection) + { + _sessionFactory.ConnectionProvider.CloseConnection(connection); + } + } +} diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index ae70fe57ad2..a4b72cea96d 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -21,6 +21,7 @@ using NHibernate.Id; using NHibernate.Mapping; using NHibernate.Metadata; +using NHibernate.MultiTenancy; using NHibernate.Persister; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -77,7 +78,7 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb { #region Default entity not found delegate - private class DefaultEntityNotFoundDelegate : IEntityNotFoundDelegate + internal class DefaultEntityNotFoundDelegate : IEntityNotFoundDelegate { #region IEntityNotFoundDelegate Members @@ -86,6 +87,11 @@ public void HandleEntityNotFound(string entityName, object id) throw new ObjectNotFoundException(id, entityName); } + public void HandleEntityNotFound(string entityName, string propertyName, object key) + { + throw new ObjectNotFoundByUniqueKeyException(entityName, propertyName, key); + } + #endregion } @@ -162,7 +168,7 @@ public void HandleEntityNotFound(string entityName, object id) [NonSerialized] private readonly UpdateTimestampsCache updateTimestampsCache; [NonSerialized] - private readonly IDictionary entityNameImplementorsMap = new ConcurrentDictionary(4 * System.Environment.ProcessorCount, 100); + private readonly ConcurrentDictionary entityNameImplementorsMap = new ConcurrentDictionary(4 * System.Environment.ProcessorCount, 100); private readonly string uuid; private bool disposed; @@ -211,6 +217,22 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings log.Warn(ex, "Dialect does not provide DataBaseSchema, but keywords import or auto quoting is enabled."); } + #region Serialization info + + name = settings.SessionFactoryName; + try + { + uuid = (string)UuidGenerator.Generate(null, null); + } + catch (Exception ex) + { + throw new AssertionFailure("Could not generate UUID", ex); + } + + SessionFactoryObjectFactory.AddInstance(uuid, name, this, properties); + + #endregion + #region Caches settings.CacheProvider.Start(properties); #endregion @@ -321,22 +343,6 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings } #endregion - #region Serialization info - - name = settings.SessionFactoryName; - try - { - uuid = (string)UuidGenerator.Generate(null, null); - } - catch (Exception ex) - { - throw new AssertionFailure("Could not generate UUID", ex); - } - - SessionFactoryObjectFactory.AddInstance(uuid, name, this, properties); - - #endregion - log.Debug("Instantiated session factory"); #region Schema management @@ -347,12 +353,23 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings if (settings.IsAutoUpdateSchema) { - new SchemaUpdate(cfg).Execute(false, true); + var schemaUpdate = new SchemaUpdate(cfg); + schemaUpdate.Execute(false, true); + if (settings.ThrowOnSchemaUpdate) + { + if (schemaUpdate.Exceptions.Any()) + { + throw new AggregateHibernateException( + "Schema update has failed, see inner exceptions for details", schemaUpdate.Exceptions); + } + } } + if (settings.IsAutoValidateSchema) { new SchemaValidator(cfg, settings).Validate(); } + if (settings.IsAutoDropSchema) { schemaExport = new SchemaExport(cfg); @@ -852,6 +869,16 @@ public void Dispose() /// public void Close() { + if (isClosed) + { + if (log.IsDebugEnabled()) + { + log.Debug("Already closed"); + } + + return; + } + log.Info("Closing"); isClosed = true; @@ -906,16 +933,7 @@ public void Close() public void Evict(System.Type persistentClass, object id) { - IEntityPersister p = GetEntityPersister(persistentClass.FullName); - if (p.HasCache) - { - if (log.IsDebugEnabled()) - { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id)); - } - CacheKey ck = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); - p.Cache.Remove(ck); - } + EvictEntity(persistentClass.FullName, id); } public void Evict(System.Type persistentClass) @@ -968,34 +986,45 @@ public void EvictEntity(IEnumerable entityNames) } public void EvictEntity(string entityName, object id) + { + EvictEntity(entityName, id, null); + } + + public void EvictEntity(string entityName, object id, string tenantIdentifier) { IEntityPersister p = GetEntityPersister(entityName); if (p.HasCache) { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.InfoString(p, id, this)); + LogEvict(tenantIdentifier, MessageHelper.InfoString(p, id, this)); } - CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName); + CacheKey cacheKey = GenerateCacheKeyForEvict(id, p.IdentifierType, p.RootEntityName, tenantIdentifier); p.Cache.Remove(cacheKey); } } public void EvictCollection(string roleName, object id) + { + EvictCollection(roleName, id, null); + } + + public void EvictCollection(string roleName, object id, string tenantIdentifier) { ICollectionPersister p = GetCollectionPersister(roleName); if (p.HasCache) { if (log.IsDebugEnabled()) { - log.Debug("evicting second-level cache: {0}", MessageHelper.CollectionInfoString(p, id)); + LogEvict(tenantIdentifier, MessageHelper.CollectionInfoString(p, id)); } - CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role); + + CacheKey ck = GenerateCacheKeyForEvict(id, p.KeyType, p.Role, tenantIdentifier); p.Cache.Remove(ck); } } - private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOrRoleName) + private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOrRoleName, string tenantIdentifier) { // if there is a session context, use that to generate the key. if (CurrentSessionContext != null) @@ -1006,7 +1035,12 @@ private CacheKey GenerateCacheKeyForEvict(object id, IType type, string entityOr .GenerateCacheKey(id, type, entityOrRoleName); } - return new CacheKey(id, type, entityOrRoleName, this); + if (settings.MultiTenancyStrategy != MultiTenancyStrategy.None && tenantIdentifier == null) + { + throw new ArgumentException("Use overload with tenantIdentifier or initialize CurrentSessionContext."); + } + + return new CacheKey(id, type, entityOrRoleName, this, tenantIdentifier); } public void EvictCollection(string roleName) @@ -1237,6 +1271,17 @@ public QueryPlanCache QueryPlanCache #endregion + private static void LogEvict(string tenantIdentifier, string infoString) + { + if (string.IsNullOrEmpty(tenantIdentifier)) + { + log.Debug("evicting second-level cache: {0}", infoString); + return; + } + + log.Debug("evicting second-level cache for tenant '{1}': {0}", infoString, tenantIdentifier); + } + private void Init() { statistics = new StatisticsImpl(this); @@ -1397,7 +1442,7 @@ public SessionBuilderImpl(SessionFactoryImpl sessionFactory) : base(sessionFacto } } - internal class SessionBuilderImpl : ISessionBuilder, ISessionCreationOptions where T : ISessionBuilder + internal class SessionBuilderImpl : ISessionBuilder, ISessionCreationOptions, ISessionCreationOptionsWithMultiTenancy where T : ISessionBuilder { // NH specific: implementing return type covariance with interface is a mess in .Net. private T _this; @@ -1410,7 +1455,7 @@ internal class SessionBuilderImpl : ISessionBuilder, ISessionCreationOptio private ConnectionReleaseMode _connectionReleaseMode; private FlushMode _flushMode; private bool _autoClose; - private bool _autoJoinTransaction = true; + private bool _autoJoinTransaction; public SessionBuilderImpl(SessionFactoryImpl sessionFactory) { @@ -1419,6 +1464,7 @@ public SessionBuilderImpl(SessionFactoryImpl sessionFactory) // set up default builder values... _connectionReleaseMode = sessionFactory.Settings.ConnectionReleaseMode; _autoClose = sessionFactory.Settings.IsAutoCloseSessionEnabled; + _autoJoinTransaction = sessionFactory.Settings.AutoJoinTransaction; // NH different implementation: not using Settings.IsFlushBeforeCompletionEnabled _flushMode = sessionFactory.Settings.DefaultFlushMode; } @@ -1505,6 +1551,13 @@ public virtual T FlushMode(FlushMode flushMode) _flushMode = flushMode; return _this; } + + public TenantConfiguration TenantConfiguration + { + get; + //TODO 6.0: Make protected + set; + } } // NH specific: implementing return type covariance with interface is a mess in .Net. @@ -1516,7 +1569,7 @@ public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) : base(ses } } - internal class StatelessSessionBuilderImpl : IStatelessSessionBuilder, ISessionCreationOptions where T : IStatelessSessionBuilder + internal class StatelessSessionBuilderImpl : IStatelessSessionBuilder, ISessionCreationOptionsWithMultiTenancy, ISessionCreationOptions where T : IStatelessSessionBuilder { // NH specific: implementing return type covariance with interface is a mess in .Net. private T _this; @@ -1557,6 +1610,13 @@ public IStatelessSessionBuilder AutoJoinTransaction(bool autoJoinTransaction) public IInterceptor SessionInterceptor => EmptyInterceptor.Instance; public ConnectionReleaseMode SessionConnectionReleaseMode => ConnectionReleaseMode.AfterTransaction; + + public TenantConfiguration TenantConfiguration + { + get; + //TODO 6.0: Make protected + set; + } } } } diff --git a/src/NHibernate/Impl/SessionFactoryObjectFactory.cs b/src/NHibernate/Impl/SessionFactoryObjectFactory.cs index 9c2ae03f6d5..2936f5c2405 100644 --- a/src/NHibernate/Impl/SessionFactoryObjectFactory.cs +++ b/src/NHibernate/Impl/SessionFactoryObjectFactory.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; - namespace NHibernate.Impl { /// diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index 1718f879a16..08cdefaf54a 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -16,6 +16,7 @@ using NHibernate.Intercept; using NHibernate.Loader.Criteria; using NHibernate.Loader.Custom; +using NHibernate.MultiTenancy; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.Proxy; @@ -104,6 +105,7 @@ private SessionImpl(SerializationInfo info, StreamingContext context) enabledFilterNames = (List)info.GetValue("enabledFilterNames", typeof(List)); ConnectionManager = (ConnectionManager)info.GetValue("connectionManager", typeof(ConnectionManager)); + TenantConfiguration = info.GetValue(nameof(TenantConfiguration)); } /// @@ -143,6 +145,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex info.AddValue("enabledFilterNames", enabledFilterNames, typeof(List)); info.AddValue("connectionManager", ConnectionManager, typeof(ConnectionManager)); + info.AddValue(nameof(TenantConfiguration), TenantConfiguration); } #endregion @@ -1156,48 +1159,62 @@ public object Load(System.Type entityClass, object id) return Load(entityClass.FullName, id); } + /// public T Get(object id) { - using (BeginProcess()) - { - return (T)Get(typeof(T), id); - } + return (T) Get(typeof(T), id); } + /// public T Get(object id, LockMode lockMode) { - using (BeginProcess()) - { - return (T)Get(typeof(T), id, lockMode); - } + return (T) Get(typeof(T), id, lockMode); } + /// public object Get(System.Type entityClass, object id) { return Get(entityClass.FullName, id); } - /// - /// Load the data for the object with the specified id into a newly created object - /// using "for update", if supported. A new key will be assigned to the object. - /// This should return an existing proxy where appropriate. - /// - /// If the object does not exist in the database, null is returned. - /// - /// - /// - /// - /// + /// public object Get(System.Type clazz, object id, LockMode lockMode) + { + return Get(clazz.FullName, id, lockMode); + } + + /// + public object Get(string entityName, object id, LockMode lockMode) { using (BeginProcess()) { - LoadEvent loadEvent = new LoadEvent(id, clazz.FullName, lockMode, this); + LoadEvent loadEvent = new LoadEvent(id, entityName, lockMode, this); FireLoad(loadEvent, LoadEventListener.Get); + //Note: AfterOperation call is skipped to avoid releasing the lock when outside of a transaction. return loadEvent.Result; } } + /// + public object Get(string entityName, object id) + { + using (BeginProcess()) + { + LoadEvent loadEvent = new LoadEvent(id, entityName, null, this); + bool success = false; + try + { + FireLoad(loadEvent, LoadEventListener.Get); + success = true; + return loadEvent.Result; + } + finally + { + AfterOperation(success); + } + } + } + public string GetEntityName(object obj) { using (BeginProcess()) @@ -1226,25 +1243,6 @@ public string GetEntityName(object obj) } } - public object Get(string entityName, object id) - { - using (BeginProcess()) - { - LoadEvent loadEvent = new LoadEvent(id, entityName, false, this); - bool success = false; - try - { - FireLoad(loadEvent, LoadEventListener.Get); - success = true; - return loadEvent.Result; - } - finally - { - AfterOperation(success); - } - } - } - /// /// Load the data for the object with the specified id into a newly created object. /// This is only called when lazily initializing a proxy. @@ -1266,7 +1264,6 @@ public override object ImmediateLoad(string entityName, object id) } } - /// /// Return the object with the specified id or throw exception if no row with that id exists. Defer the load, /// return a new proxy or return an existing proxy if possible. Do not check if the object was deleted. @@ -1543,17 +1540,18 @@ private void Dispose(bool isDisposing) // free managed resources that are being managed by the session if we // know this call came through Dispose() - if (isDisposing && !IsClosed) + if (isDisposing) { - Close(); + if (!IsClosed) + { + Close(); + } + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here - IsAlreadyDisposed = true; - - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } } @@ -1698,19 +1696,21 @@ public IQueryOver QueryOver(string entityName, Expression> alia } } - public override void List(CriteriaImpl criteria, IList results) + public override IList List(CriteriaImpl criteria) { using (BeginProcess()) { string[] implementors = Factory.GetImplementors(criteria.EntityOrClassName); int size = implementors.Length; + if (size == 0) + throw new QueryException(criteria.EntityOrClassName + " is not mapped"); CriteriaLoader[] loaders = new CriteriaLoader[size]; ISet spaces = new HashSet(); for (int i = 0; i < size; i++) { - loaders[i] = new CriteriaLoader( + var loader = new CriteriaLoader( GetOuterJoinLoadable(implementors[i]), Factory, criteria, @@ -1718,7 +1718,8 @@ public override void List(CriteriaImpl criteria, IList results) enabledFilters ); - spaces.UnionWith(loaders[i].QuerySpaces); + spaces.UnionWith(loader.QuerySpaces); + loaders[size - 1 - i] = loader; } AutoFlushIfRequired(spaces); @@ -1728,11 +1729,9 @@ public override void List(CriteriaImpl criteria, IList results) { try { - for (int i = size - 1; i >= 0; i--) - { - ArrayHelper.AddAll(results, loaders[i].List(this)); - } + var results = loaders.LoadAllToList(this); success = true; + return results; } catch (HibernateException) { @@ -1751,6 +1750,12 @@ public override void List(CriteriaImpl criteria, IList results) } } + //TODO 6.0: Remove (use base class implementation) + public override void List(CriteriaImpl criteria, IList results) + { + ArrayHelper.AddAll(results, List(criteria)); + } + public bool Contains(object obj) { using (BeginProcess()) @@ -2412,6 +2417,7 @@ public SharedSessionBuilderImpl(SessionImpl session) : base((SessionFactoryImpl)session.Factory) { _session = session; + TenantConfiguration = session.TenantConfiguration; SetSelf(this); } @@ -2464,6 +2470,7 @@ public SharedStatelessSessionBuilderImpl(SessionImpl session) : base((SessionFactoryImpl)session.Factory) { _session = session; + TenantConfiguration = session.TenantConfiguration; SetSelf(this); } diff --git a/src/NHibernate/Impl/SqlQueryImpl.cs b/src/NHibernate/Impl/SqlQueryImpl.cs index 0cc008d2173..5629e2f714a 100644 --- a/src/NHibernate/Impl/SqlQueryImpl.cs +++ b/src/NHibernate/Impl/SqlQueryImpl.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Engine.Query.Sql; @@ -29,6 +30,8 @@ public partial class SqlQueryImpl : AbstractQueryImpl, ISQLQuery, ISynchronizabl private readonly bool callable; private bool autoDiscoverTypes; private readonly HashSet addedQuerySpaces = new HashSet(); + private List _flattenedTypes; + private List _flattenedValues; /// Constructs a SQLQueryImpl given a sql query defined in the mappings. /// The representation of the defined sql-query. @@ -296,7 +299,66 @@ protected internal override void VerifyParameters() } } } + } + + /// + protected internal override void VerifyParameters(bool reserveFirstParameter) + { + ComputeFlattenedParameters(); + base.VerifyParameters(reserveFirstParameter); + } + // Flattening parameters is required for custom SQL loaders when entities have composite ids. + // See NH-3079 (#1117) + private void ComputeFlattenedParameters() + { + _flattenedTypes = new List(base.Types.Count * 2); + _flattenedValues = new List(base.Types.Count * 2); + FlattenTypesAndValues(base.Types, base.Values); + + void FlattenTypesAndValues(IList types, IList values) + { + for (var i = 0; i < types.Count; i++) + { + var type = types[i]; + var value = values[i]; + if (type is EntityType entityType) + { + type = entityType.GetIdentifierType(session); + value = entityType.GetIdentifier(value, session); + } + + if (type is IAbstractComponentType componentType) + { + FlattenTypesAndValues( + componentType.Subtypes, + componentType.GetPropertyValues(value, session)); + } + else + { + _flattenedTypes.Add(type); + _flattenedValues.Add(value); + } + } + } + } + + protected override IList Values => _flattenedValues ?? + throw new InvalidOperationException("Flattened parameters have not been computed"); + + protected override IList Types => _flattenedTypes ?? + throw new InvalidOperationException("Flattened parameters have not been computed"); + + public override object[] ValueArray() + { + // TODO 6.0: Change to Values.ToArray() + return _flattenedValues?.ToArray() ?? + throw new InvalidOperationException("Flattened parameters have not been computed"); + } + + public override IType[] TypeArray() + { + return Types.ToArray(); } public override IQuery SetLockMode(string alias, LockMode lockMode) @@ -310,6 +372,7 @@ public override int ExecuteUpdate() Before(); try { + ComputeFlattenedParameters(); return Session.ExecuteNativeUpdate(GenerateQuerySpecification(namedParams), GetQueryParameters(namedParams)); } finally diff --git a/src/NHibernate/Impl/StatelessSessionImpl.cs b/src/NHibernate/Impl/StatelessSessionImpl.cs index 4eb3630391e..b42b746b593 100644 --- a/src/NHibernate/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Impl/StatelessSessionImpl.cs @@ -136,7 +136,7 @@ public override void List(IQueryExpression queryExpression, QueryParameters quer } } - public override void List(CriteriaImpl criteria, IList results) + public override IList List(CriteriaImpl criteria) { using (BeginProcess()) { @@ -146,18 +146,16 @@ public override void List(CriteriaImpl criteria, IList results) CriteriaLoader[] loaders = new CriteriaLoader[size]; for (int i = 0; i < size; i++) { - loaders[i] = new CriteriaLoader(GetOuterJoinLoadable(implementors[i]), Factory, + loaders[size - 1 - i] = new CriteriaLoader(GetOuterJoinLoadable(implementors[i]), Factory, criteria, implementors[i], EnabledFilters); } bool success = false; try { - for (int i = size - 1; i >= 0; i--) - { - ArrayHelper.AddAll(results, loaders[i].List(this)); - } + var results = loaders.LoadAllToList(this); success = true; + return results; } catch (HibernateException) { @@ -171,11 +169,17 @@ public override void List(CriteriaImpl criteria, IList results) finally { AfterOperation(success); + temporaryPersistenceContext.Clear(); } - temporaryPersistenceContext.Clear(); } } + //TODO 6.0: Remove (use base class implementation) + public override void List(CriteriaImpl criteria, IList results) + { + ArrayHelper.AddAll(results, List(criteria)); + } + public override IEnumerable Enumerable(IQueryExpression queryExpression, QueryParameters queryParameters) { throw new NotImplementedException(); @@ -535,40 +539,32 @@ public void Delete(string entityName, object entity) } } - /// Retrieve a entity. + /// Retrieve an entity. /// a detached entity instance public object Get(string entityName, object id) { return Get(entityName, id, LockMode.None); } - /// Retrieve a entity. - /// + /// + /// Retrieve an entity. /// /// a detached entity instance /// public T Get(object id) { - using (BeginProcess()) - { - return (T)Get(typeof(T), id); - } - } - - private object Get(System.Type persistentClass, object id) - { - return Get(persistentClass.FullName, id); + return (T) Get(typeof(T).FullName, id); } /// - /// Retrieve a entity, obtaining the specified lock mode. + /// Retrieve an entity, obtaining the specified lock mode. /// /// a detached entity instance public object Get(string entityName, object id, LockMode lockMode) { using (BeginProcess()) { - object result = Factory.GetEntityPersister(entityName).Load(id, null, lockMode, this); + object result = Factory.GetEntityPersister(entityName).Load(id, null, lockMode ?? LockMode.None, this); if (temporaryPersistenceContext.IsLoadFinished) { temporaryPersistenceContext.Clear(); @@ -578,15 +574,12 @@ public object Get(string entityName, object id, LockMode lockMode) } /// - /// Retrieve a entity, obtaining the specified lock mode. + /// Retrieve an entity, obtaining the specified lock mode. /// /// a detached entity instance public T Get(object id, LockMode lockMode) { - using (BeginProcess()) - { - return (T)Get(typeof(T).FullName, id, lockMode); - } + return (T) Get(typeof(T).FullName, id, lockMode); } /// @@ -812,16 +805,19 @@ protected void Dispose(bool isDisposing) // free managed resources that are being managed by the session if we // know this call came through Dispose() - if (isDisposing && !IsClosed) + if (isDisposing) { - Close(); + if (!IsClosed) + { + Close(); + } + + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here - _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } _context?.Dispose(); } diff --git a/src/NHibernate/Intercept/IFieldInterceptor.cs b/src/NHibernate/Intercept/IFieldInterceptor.cs index 111e257dc60..1242ce27c95 100644 --- a/src/NHibernate/Intercept/IFieldInterceptor.cs +++ b/src/NHibernate/Intercept/IFieldInterceptor.cs @@ -10,7 +10,7 @@ public interface IFieldInterceptor { /// Is the entity considered dirty? /// True if the entity is dirty; otherwise false. - bool IsDirty { get;} + bool IsDirty { get; } /// Use to associate the entity to which we are bound to the given session. ISessionImplementor Session { get; set; } @@ -18,7 +18,7 @@ public interface IFieldInterceptor /// Is the entity to which we are bound completely initialized? // Since 5.3 [Obsolete("This property is not used and will be removed in a future version.")] - bool IsInitialized { get;} + bool IsInitialized { get; } /// The the given field initialized for the entity to which we are bound? /// The name of the field to check diff --git a/src/NHibernate/LazyInitializationException.cs b/src/NHibernate/LazyInitializationException.cs index 967e11ee653..83cf6123541 100644 --- a/src/NHibernate/LazyInitializationException.cs +++ b/src/NHibernate/LazyInitializationException.cs @@ -2,7 +2,6 @@ using System.Runtime.Serialization; using System.Security; - namespace NHibernate { /// diff --git a/src/NHibernate/Linq/Clauses/NhOuterJoinClause.cs b/src/NHibernate/Linq/Clauses/NhOuterJoinClause.cs new file mode 100644 index 00000000000..6955afd936b --- /dev/null +++ b/src/NHibernate/Linq/Clauses/NhOuterJoinClause.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq.Expressions; +using Remotion.Linq; +using Remotion.Linq.Clauses; + +namespace NHibernate.Linq.Clauses +{ + /// + /// A wrapper for that is used to mark it as an outer join. + /// + public class NhOuterJoinClause : NhClauseBase, IBodyClause, IClause, IQuerySource + { + public NhOuterJoinClause(JoinClause joinClause) + { + JoinClause = joinClause; + } + + public JoinClause JoinClause { get; } + + public string ItemName => JoinClause.ItemName; + + public System.Type ItemType => JoinClause.ItemType; + + public void TransformExpressions(Func transformation) + { + JoinClause.TransformExpressions(transformation); + } + + public IBodyClause Clone(CloneContext cloneContext) + { + return new NhOuterJoinClause(JoinClause.Clone(cloneContext)); + } + + protected override void Accept(INhQueryModelVisitor visitor, QueryModel queryModel, int index) + { + if (visitor is INhQueryModelVisitorExtended queryModelVisitorExtended) + { + queryModelVisitorExtended.VisitNhOuterJoinClause(this, queryModel, index); + } + else + { + visitor.VisitJoinClause(JoinClause, queryModel, index); + } + } + } +} diff --git a/src/NHibernate/Linq/DefaultQueryProvider.cs b/src/NHibernate/Linq/DefaultQueryProvider.cs index 801399a5379..912b640a951 100644 --- a/src/NHibernate/Linq/DefaultQueryProvider.cs +++ b/src/NHibernate/Linq/DefaultQueryProvider.cs @@ -11,6 +11,7 @@ using NHibernate.Util; using System.Threading.Tasks; using NHibernate.Multi; +using NHibernate.Param; namespace NHibernate.Linq { @@ -70,7 +71,7 @@ public DefaultQueryProvider(ISessionImplementor session, object collection) Collection = collection; } - private DefaultQueryProvider(ISessionImplementor session, object collection, NhQueryableOptions options) + protected DefaultQueryProvider(ISessionImplementor session, object collection, NhQueryableOptions options) : this(session, collection) { _options = options; @@ -100,6 +101,22 @@ public TResult Execute(Expression expression) return (TResult)Execute(expression); } + //TODO 6.0: Add to INhQueryProvider interface + public virtual IList ExecuteList(Expression expression) + { + var linqExpression = PrepareQuery(expression, out var query); + var resultTransformer = linqExpression.ExpressionToHqlTranslationResults?.PostExecuteTransformer; + if (resultTransformer == null) + { + return query.List(); + } + + return new List + { + (TResult) resultTransformer.DynamicInvoke(query.List().AsQueryable()) + }; + } + public IQueryProvider WithOptions(Action setOptions) { if (setOptions == null) throw new ArgumentNullException(nameof(setOptions)); @@ -108,6 +125,11 @@ public IQueryProvider WithOptions(Action setOptions) ? _options.Clone() : new NhQueryableOptions(); setOptions(options); + return CreateWithOptions(options); + } + + protected virtual IQueryProvider CreateWithOptions(NhQueryableOptions options) + { return new DefaultQueryProvider(Session, Collection, options); } @@ -190,7 +212,7 @@ protected virtual NhLinqExpression PrepareQuery(Expression expression, out IQuer query = Session.CreateFilter(Collection, nhLinqExpression); } - SetParameters(query, nhLinqExpression.ParameterValuesByName); + SetParameters(query, nhLinqExpression.NamedParameters); _options?.Apply(query); SetResultTransformerAndAdditionalCriteria(query, nhLinqExpression, nhLinqExpression.ParameterValuesByName); @@ -231,38 +253,19 @@ protected virtual object ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery #pragma warning restore 618 } - private static void SetParameters(IQuery query, IDictionary> parameters) + private static void SetParameters(IQuery query, IDictionary parameters) { foreach (var parameterName in query.NamedParameters) { - var param = parameters[parameterName]; - - if (param.Item1 == null) + // The parameter type will be taken from the parameter metadata + var parameter = parameters[parameterName]; + if (parameter.IsCollection) { - if (typeof(IEnumerable).IsAssignableFrom(param.Item2.ReturnedClass) && - param.Item2.ReturnedClass != typeof(string)) - { - query.SetParameterList(parameterName, null, param.Item2); - } - else - { - query.SetParameter(parameterName, null, param.Item2); - } + query.SetParameterList(parameter.Name, (IEnumerable) parameter.Value); } else { - if (param.Item1 is IEnumerable && !(param.Item1 is string)) - { - query.SetParameterList(parameterName, (IEnumerable)param.Item1); - } - else if (param.Item2 != null) - { - query.SetParameter(parameterName, param.Item1, param.Item2); - } - else - { - query.SetParameter(parameterName, param.Item1); - } + query.SetParameter(parameter.Name, parameter.Value); } } } @@ -289,7 +292,7 @@ public int ExecuteDml(QueryMode queryMode, Expression expression) var query = Session.CreateQuery(nhLinqExpression); - SetParameters(query, nhLinqExpression.ParameterValuesByName); + SetParameters(query, nhLinqExpression.NamedParameters); _options?.Apply(query); return query.ExecuteUpdate(); } diff --git a/src/NHibernate/Linq/DmlExpressionRewriter.cs b/src/NHibernate/Linq/DmlExpressionRewriter.cs index baf8e24f2c6..782318a64a7 100644 --- a/src/NHibernate/Linq/DmlExpressionRewriter.cs +++ b/src/NHibernate/Linq/DmlExpressionRewriter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; using NHibernate.Linq.Visitors; using NHibernate.Util; @@ -10,10 +9,6 @@ namespace NHibernate.Linq { public class DmlExpressionRewriter { - static readonly ConstructorInfo DictionaryConstructorInfo = typeof(Dictionary).GetConstructor(new[] {typeof(int)}); - - static readonly MethodInfo DictionaryAddMethodInfo = ReflectHelper.GetMethod>(d => d.Add(null, null)); - readonly IReadOnlyCollection _parameters; readonly Dictionary _assignments = new Dictionary(); @@ -80,39 +75,25 @@ void AddSettersFromAssignment(MemberAssignment assignment, string path) } /// - /// Converts the assignments into a lambda expression, which creates a Dictionary<string,object%gt;. + /// Converts the assignments into block of assignments /// /// /// A lambda expression representing the assignments. - static LambdaExpression ConvertAssignmentsToDictionaryExpression(IReadOnlyDictionary assignments) + static LambdaExpression ConvertAssignmentsToBlockExpression(IReadOnlyDictionary assignments) { var param = Expression.Parameter(typeof(TSource)); - var inits = new List(); + var variableAndAssignmentDic = new Dictionary(assignments.Count); foreach (var set in assignments) { var setter = set.Value; if (setter is LambdaExpression setterLambda) setter = setterLambda.Body.Replace(setterLambda.Parameters.First(), param); - inits.Add( - Expression.ElementInit( - DictionaryAddMethodInfo, - Expression.Constant(set.Key), - Expression.Convert(setter, typeof(object)))); + + var var = Expression.Variable(typeof(object), set.Key); + variableAndAssignmentDic[var] = Expression.Assign(var, Expression.Convert(setter, typeof(object))); } - //The ListInit is intentionally "infected" with the lambda parameter (param), in the form of an IIF. - //The only relevance is to make sure that the ListInit is not evaluated by the PartialEvaluatingExpressionTreeVisitor, - //which could turn it into a Constant - var listInit = Expression.ListInit( - Expression.New( - DictionaryConstructorInfo, - Expression.Condition( - Expression.Equal(param, Expression.Constant(null, typeof(TSource))), - Expression.Constant(assignments.Count), - Expression.Constant(assignments.Count))), - inits); - - return Expression.Lambda(listInit, param); + return Expression.Lambda(Expression.Block(variableAndAssignmentDic.Keys, variableAndAssignmentDic.Values), param); } public static Expression PrepareExpression(Expression sourceExpression, Expression> expression) @@ -151,7 +132,7 @@ public static Expression PrepareExpressionFromAnonymous(Expression sour public static Expression PrepareExpression(Expression sourceExpression, IReadOnlyDictionary assignments) { - var lambda = ConvertAssignmentsToDictionaryExpression(assignments); + var lambda = ConvertAssignmentsToBlockExpression(assignments); return Expression.Call( ReflectionCache.QueryableMethods.SelectDefinition.MakeGenericMethod(typeof(TSource), lambda.Body.Type), diff --git a/src/NHibernate/Linq/ExpressionTransformers/RemoveCharToIntConversion.cs b/src/NHibernate/Linq/ExpressionTransformers/RemoveCharToIntConversion.cs index 3e0c19d4ca1..aabdea69916 100644 --- a/src/NHibernate/Linq/ExpressionTransformers/RemoveCharToIntConversion.cs +++ b/src/NHibernate/Linq/ExpressionTransformers/RemoveCharToIntConversion.cs @@ -35,6 +35,19 @@ public Expression Transform(BinaryExpression expression) if (!lhsIsConvertExpression && !rhsIsConvertExpression) return expression; + // Variables are not converted to constants (E.g: o.CharProperty == charVariable) + if (lhsIsConvertExpression && rhsIsConvertExpression) + { + var lhsConvertExpression = (UnaryExpression) lhs; + var rhsConvertExpression = (UnaryExpression) rhs; + if (!IsConvertCharToInt(lhsConvertExpression) || !IsConvertCharToInt(rhsConvertExpression)) + { + return expression; + } + + return Expression.MakeBinary(expression.NodeType, lhsConvertExpression.Operand, rhsConvertExpression.Operand); + } + var lhsIsConstantExpression = IsConstantExpression(lhs); var rhsIsConstantExpression = IsConstantExpression(rhs); @@ -43,7 +56,7 @@ public Expression Transform(BinaryExpression expression) var convertExpression = lhsIsConvertExpression ? (UnaryExpression)lhs : (UnaryExpression)rhs; var constantExpression = lhsIsConstantExpression ? (ConstantExpression)lhs : (ConstantExpression)rhs; - if (convertExpression.Type == typeof(int) && convertExpression.Operand.Type == typeof(char) && constantExpression.Type == typeof(int)) + if (IsConvertCharToInt(convertExpression) && constantExpression.Type == typeof(int)) { var constant = Expression.Constant(Convert.ToChar((int)constantExpression.Value)); @@ -56,6 +69,11 @@ public Expression Transform(BinaryExpression expression) return expression; } + private static bool IsConvertCharToInt(UnaryExpression expression) + { + return expression.Type == typeof(int) && expression.Operand.Type == typeof(char); + } + private static bool IsConvertExpression(Expression expression) { return (expression.NodeType == ExpressionType.Convert); @@ -71,4 +89,4 @@ public ExpressionType[] SupportedExpressionTypes get { return _supportedExpressionTypes; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/ExpressionTransformers/RemoveRedundantCast.cs b/src/NHibernate/Linq/ExpressionTransformers/RemoveRedundantCast.cs index 538e46cb828..8feac321a1c 100644 --- a/src/NHibernate/Linq/ExpressionTransformers/RemoveRedundantCast.cs +++ b/src/NHibernate/Linq/ExpressionTransformers/RemoveRedundantCast.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using NHibernate.Util; using Remotion.Linq.Parsing.ExpressionVisitors.Transformation; namespace NHibernate.Linq.ExpressionTransformers @@ -26,6 +27,13 @@ public Expression Transform(UnaryExpression expression) return expression.Operand; } + // Reduce double casting (e.g. (long?)(long)3 => (long?)3) + if (expression.Operand.NodeType == ExpressionType.Convert && + expression.Type.UnwrapIfNullable() == expression.Operand.Type) + { + return Expression.Convert(((UnaryExpression) expression.Operand).Operand, expression.Type); + } + return expression; } @@ -34,4 +42,4 @@ public ExpressionType[] SupportedExpressionTypes get { return _supportedExpressionTypes; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/ExpressionTransformers/SimplifyCompareTransformer.cs b/src/NHibernate/Linq/ExpressionTransformers/SimplifyCompareTransformer.cs index 5dc00b7fe4b..93971a4c8fc 100644 --- a/src/NHibernate/Linq/ExpressionTransformers/SimplifyCompareTransformer.cs +++ b/src/NHibernate/Linq/ExpressionTransformers/SimplifyCompareTransformer.cs @@ -23,8 +23,7 @@ internal class SimplifyCompareTransformer : IExpressionTransformer a < b // a.CompareTo(b) op 0 => a op b - - private static readonly IDictionary ActingOperators = new Dictionary + private static readonly Dictionary ActingOperators = new Dictionary { {ExpressionType.LessThan, ExpressionType.GreaterThan}, @@ -35,13 +34,11 @@ internal class SimplifyCompareTransformer : IExpressionTransformer dummies = new Dictionary + private static readonly Dictionary dummies = new Dictionary { // Corresponds to string.Compare(a, b). - {typeof (string), ReflectHelper.GetMethod(() => DummyComparison(null, null))}, + {typeof (string), ReflectHelper.FastGetMethod(DummyComparison, default(string), default(string))}, // System.Data.Services.Providers.DataServiceProviderMethods has Compare methods for these types. - {typeof (bool), ReflectHelper.GetMethod(() => DummyComparison(false, false))}, - {typeof (bool?), ReflectHelper.GetMethod(() => DummyComparison(null, null))}, - {typeof (Guid), ReflectHelper.GetMethod(() => DummyComparison(Guid.Empty, Guid.Empty))}, - {typeof (Guid?), ReflectHelper.GetMethod(() => DummyComparison(null, null))}, + {typeof (bool), ReflectHelper.FastGetMethod(DummyComparison, false, false)}, + {typeof (bool?), ReflectHelper.FastGetMethod(DummyComparison, default(bool?), default(bool?))}, + {typeof (Guid), ReflectHelper.FastGetMethod(DummyComparison, default(Guid), default(Guid))}, + {typeof (Guid?), ReflectHelper.FastGetMethod(DummyComparison, default(Guid?), default(Guid?))}, }; - private static bool DummyComparison(T lhs, T rhs) { throw new NotSupportedException("This method is not intended to be called."); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Expressions/NhAggregatedExpression.cs b/src/NHibernate/Linq/Expressions/NhAggregatedExpression.cs index 6b705d3d92e..ab60d1a3d29 100644 --- a/src/NHibernate/Linq/Expressions/NhAggregatedExpression.cs +++ b/src/NHibernate/Linq/Expressions/NhAggregatedExpression.cs @@ -16,6 +16,8 @@ protected NhAggregatedExpression(Expression expression, System.Type type) Type = type; } + public virtual bool AllowsNullableReturnType => true; + public sealed override System.Type Type { get; } public Expression Expression { get; } diff --git a/src/NHibernate/Linq/Expressions/NhCountExpression.cs b/src/NHibernate/Linq/Expressions/NhCountExpression.cs index 6dc698add5c..e41ed926410 100644 --- a/src/NHibernate/Linq/Expressions/NhCountExpression.cs +++ b/src/NHibernate/Linq/Expressions/NhCountExpression.cs @@ -10,6 +10,8 @@ protected NhCountExpression(Expression expression, System.Type type) { } + public override bool AllowsNullableReturnType => false; + protected override Expression Accept(NhExpressionVisitor visitor) { return visitor.VisitNhCount(this); diff --git a/src/NHibernate/Linq/Functions/BaseHqlGeneratorForMethod.cs b/src/NHibernate/Linq/Functions/BaseHqlGeneratorForMethod.cs index 3a9462d49ef..aa0f1ab88f8 100644 --- a/src/NHibernate/Linq/Functions/BaseHqlGeneratorForMethod.cs +++ b/src/NHibernate/Linq/Functions/BaseHqlGeneratorForMethod.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Linq.Expressions; using System.Reflection; using NHibernate.Hql.Ast; @@ -7,10 +9,42 @@ namespace NHibernate.Linq.Functions { - public abstract class BaseHqlGeneratorForMethod : IHqlGeneratorForMethod - { - public IEnumerable SupportedMethods { get; protected set; } + public abstract class BaseHqlGeneratorForMethod : IHqlGeneratorForMethod, IHqlGeneratorForMethodExtended + { + protected static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(BaseHqlGeneratorForMethod)); - public abstract HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor); - } -} \ No newline at end of file + public IEnumerable SupportedMethods { get; protected set; } + + public abstract HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor); + + public virtual bool AllowsNullableReturnType(MethodInfo method) => true; + + /// + public virtual bool TryGetCollectionParameter(MethodCallExpression expression, out ConstantExpression collectionParameter) + { + collectionParameter = null; + return false; + } + + private protected static void LogIgnoredParameter(MethodInfo method, string paramType) + { + if (Log.IsWarnEnabled()) + Log.Warn("Method parameter of type '{0}' is ignored when converting to hql the following method: {1}", paramType, method); + } + + private protected static void LogIgnoredStringComparisonParameter(MethodInfo actualMethod, MethodInfo methodWithStringComparison) + { + if (actualMethod == methodWithStringComparison) + LogIgnoredParameter(actualMethod, nameof(StringComparison)); + } + + private protected bool LogIgnoredStringComparisonParameter(MethodInfo actualMethod, params MethodInfo[] methodsWithStringComparison) + { + if (!methodsWithStringComparison.Contains(actualMethod)) + return false; + + LogIgnoredParameter(actualMethod, nameof(StringComparison)); + return true; + } + } +} diff --git a/src/NHibernate/Linq/Functions/CompareGenerator.cs b/src/NHibernate/Linq/Functions/CompareGenerator.cs index 23ef7f8a592..9fa966525e6 100644 --- a/src/NHibernate/Linq/Functions/CompareGenerator.cs +++ b/src/NHibernate/Linq/Functions/CompareGenerator.cs @@ -12,9 +12,12 @@ namespace NHibernate.Linq.Functions { internal class CompareGenerator : BaseHqlGeneratorForMethod, IRuntimeMethodHqlGenerator { + private static readonly MethodInfo MethodWithComparer = ReflectHelper.FastGetMethod(string.Compare, default(string), default(string), default(StringComparison)); + private static readonly HashSet ActingMethods = new HashSet { - ReflectHelper.GetMethodDefinition(() => string.Compare(null, null)), + ReflectHelper.FastGetMethod(string.Compare, default(string), default(string)), + MethodWithComparer, ReflectHelper.GetMethodDefinition(s => s.CompareTo(s)), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), @@ -33,7 +36,7 @@ internal class CompareGenerator : BaseHqlGeneratorForMethod, IRuntimeMethodHqlGe ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), - ReflectHelper.GetMethodDefinition(() => decimal.Compare(default(decimal), default(decimal))), + ReflectHelper.FastGetMethod(decimal.Compare, default(decimal), default(decimal)), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), @@ -43,7 +46,10 @@ internal class CompareGenerator : BaseHqlGeneratorForMethod, IRuntimeMethodHqlGe internal static bool IsCompareMethod(MethodInfo methodInfo) { if (ActingMethods.Contains(methodInfo)) + { + LogIgnoredStringComparisonParameter(methodInfo, MethodWithComparer); return true; + } // This is .Net 4 only, and in the System.Data.Services assembly, which we don't depend directly on. return methodInfo != null && methodInfo.Name == "Compare" && @@ -51,7 +57,7 @@ internal static bool IsCompareMethod(MethodInfo methodInfo) methodInfo.DeclaringType.FullName == "System.Data.Services.Providers.DataServiceProviderMethods"; } - + public override bool AllowsNullableReturnType(MethodInfo method) => false; public CompareGenerator() { SupportedMethods = ActingMethods.ToArray(); @@ -81,7 +87,6 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, treeBuilder.Constant(-1)); } - #region IRuntimeMethodHqlGenerator methods public bool SupportsMethod(MethodInfo method) @@ -94,7 +99,6 @@ public bool SupportsMethod(MethodInfo method) return IsCompareMethod(method); } - public IHqlGeneratorForMethod GetMethodGenerator(MethodInfo method) { return this; @@ -102,4 +106,4 @@ public IHqlGeneratorForMethod GetMethodGenerator(MethodInfo method) #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Functions/ConvertGenerator.cs b/src/NHibernate/Linq/Functions/ConvertGenerator.cs index 2b1812048e0..f8e3692fffd 100644 --- a/src/NHibernate/Linq/Functions/ConvertGenerator.cs +++ b/src/NHibernate/Linq/Functions/ConvertGenerator.cs @@ -25,8 +25,8 @@ public ConvertToDateTimeGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(s => DateTime.Parse(s)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDateTime(o)) + ReflectHelper.FastGetMethod(DateTime.Parse, default(string)), + ReflectHelper.FastGetMethod(Convert.ToDateTime, default(string)) }; } } @@ -38,36 +38,35 @@ public ConvertToBooleanGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(s => Boolean.Parse(s)), - ReflectHelper.GetMethodDefinition(o => Convert.ToBoolean(o)) + ReflectHelper.FastGetMethod(bool.Parse, default(string)), + ReflectHelper.FastGetMethod(Convert.ToBoolean, default(string)) }; } } - public class ConvertToInt32Generator : ConvertToGenerator { public ConvertToInt32Generator() { SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(s => int.Parse(s)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToInt32(o)) - }; + { + ReflectHelper.FastGetMethod(int.Parse, default(string)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(bool)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(byte)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(char)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(decimal)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(double)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(float)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(int)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(long)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(object)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(sbyte)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(short)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(string)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(uint)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(ulong)), + ReflectHelper.FastGetMethod(Convert.ToInt32, default(ushort)) + }; } } @@ -76,23 +75,23 @@ public class ConvertToDecimalGenerator : ConvertToGenerator public ConvertToDecimalGenerator() { SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(s => decimal.Parse(s)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDecimal(o)) - }; + { + ReflectHelper.FastGetMethod(decimal.Parse, default(string)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(bool)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(byte)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(decimal)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(double)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(float)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(int)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(long)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(object)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(sbyte)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(short)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(string)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(uint)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(ulong)), + ReflectHelper.FastGetMethod(Convert.ToDecimal, default(ushort)) + }; } } @@ -101,23 +100,23 @@ public class ConvertToDoubleGenerator : ConvertToGenerator public ConvertToDoubleGenerator() { SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(s => double.Parse(s)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)), - ReflectHelper.GetMethodDefinition(o => Convert.ToDouble(o)) - }; + { + ReflectHelper.FastGetMethod(double.Parse, default(string)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(bool)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(byte)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(decimal)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(double)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(float)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(int)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(long)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(object)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(sbyte)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(short)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(string)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(uint)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(ulong)), + ReflectHelper.FastGetMethod(Convert.ToDouble, default(ushort)) + }; } } } diff --git a/src/NHibernate/Linq/Functions/DateTimeNowHqlGenerator.cs b/src/NHibernate/Linq/Functions/DateTimeNowHqlGenerator.cs new file mode 100644 index 00000000000..9039693fc1c --- /dev/null +++ b/src/NHibernate/Linq/Functions/DateTimeNowHqlGenerator.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Engine; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Visitors; +using NHibernate.Util; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Linq.Functions +{ + public class DateTimeNowHqlGenerator : BaseHqlGeneratorForProperty, IAllowPreEvaluationHqlGenerator + { + private static readonly MemberInfo DateTimeNow = ReflectHelper.GetProperty(() => DateTime.Now); + private static readonly MemberInfo DateTimeUtcNow = ReflectHelper.GetProperty(() => DateTime.UtcNow); + private static readonly MemberInfo DateTimeToday = ReflectHelper.GetProperty(() => DateTime.Today); + private static readonly MemberInfo DateTimeOffsetNow = ReflectHelper.GetProperty(() => DateTimeOffset.Now); + private static readonly MemberInfo DateTimeOffsetUtcNow = ReflectHelper.GetProperty(() => DateTimeOffset.UtcNow); + + private readonly Dictionary _hqlFunctions = new Dictionary() + { + { DateTimeNow, "current_timestamp" }, + { DateTimeUtcNow, "current_utctimestamp" }, + // There is also sysdate, but it is troublesome: under some databases, "sys" prefixed functions return the + // system time (time according to the server time zone) while "current" prefixed functions return the + // session time (time according to the connection time zone), thus introducing a discrepancy with + // current_timestamp. + // Moreover sysdate is registered by default as a datetime, not as a date. (It could make sense for + // Oracle, which returns a time part for dates, just dropping fractional seconds. But Oracle dialect + // overrides it as a NHibernate date, without truncating it for SQL comparisons...) + { DateTimeToday, "current_date" }, + { DateTimeOffsetNow, "current_timestamp_offset" }, + { DateTimeOffsetUtcNow, "current_utctimestamp_offset" }, + }; + + public DateTimeNowHqlGenerator() + { + SupportedProperties = new[] + { + DateTimeNow, + DateTimeUtcNow, + DateTimeToday, + DateTimeOffsetNow, + DateTimeOffsetUtcNow, + }; + } + + public override HqlTreeNode BuildHql( + MemberInfo member, + Expression expression, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor) + { + return treeBuilder.MethodCall(_hqlFunctions[member]); + } + + public bool AllowPreEvaluation(MemberInfo member, ISessionFactoryImplementor factory) + { + var functionName = _hqlFunctions[member]; + if (factory.Dialect.Functions.ContainsKey(functionName)) + return false; + + if (factory.Settings.LinqToHqlFallbackOnPreEvaluation) + return true; + + throw new QueryException( + $"Cannot translate {member.DeclaringType.Name}.{member.Name}: {functionName} is " + + $"not supported by {factory.Dialect}. Either enable the fallback on pre-evaluation " + + $"({Environment.LinqToHqlFallbackOnPreEvaluation}) or evaluate {member.Name} " + + "outside of the query."); + } + + public bool IgnoreInstance(MemberInfo member) + { + // They are all static properties + return true; + } + } +} diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index f79a3bdcb12..1fc5b80feea 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -14,7 +14,7 @@ public DecimalAddGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Add(default(decimal), default(decimal))) + ReflectHelper.FastGetMethod(decimal.Add, default(decimal), default(decimal)) }; } @@ -30,7 +30,7 @@ public DecimalDivideGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Divide(default(decimal), default(decimal))) + ReflectHelper.FastGetMethod(decimal.Divide, default(decimal), default(decimal)) }; } @@ -46,7 +46,7 @@ public DecimalMultiplyGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Multiply(default(decimal), default(decimal))) + ReflectHelper.FastGetMethod(decimal.Multiply, default(decimal), default(decimal)) }; } @@ -62,7 +62,7 @@ public DecimalSubtractGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Subtract(default(decimal), default(decimal))) + ReflectHelper.FastGetMethod(decimal.Subtract, default(decimal), default(decimal)) }; } @@ -78,7 +78,7 @@ public DecimalRemainderGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Remainder(default(decimal), default(decimal))) + ReflectHelper.FastGetMethod(decimal.Remainder, default(decimal), default(decimal)) }; } @@ -94,7 +94,7 @@ public DecimalNegateGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Negate(default(decimal))) + ReflectHelper.FastGetMethod(decimal.Negate, default(decimal)) }; } diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index ea5ab8a159c..29595877d9f 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -56,6 +56,10 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new CollectionContainsGenerator()); this.Merge(new DateTimePropertiesHqlGenerator()); + this.Merge(new DateTimeNowHqlGenerator()); + + this.Merge(new NewGuidHqlGenerator()); + this.Merge(new RandomHqlGenerator()); this.Merge(new DecimalAddGenerator()); this.Merge(new DecimalDivideGenerator()); diff --git a/src/NHibernate/Linq/Functions/DictionaryGenerator.cs b/src/NHibernate/Linq/Functions/DictionaryGenerator.cs index 6131c4d7ab8..eb583d0cd2f 100644 --- a/src/NHibernate/Linq/Functions/DictionaryGenerator.cs +++ b/src/NHibernate/Linq/Functions/DictionaryGenerator.cs @@ -25,6 +25,8 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, public class DictionaryContainsKeyGenerator : BaseHqlGeneratorForMethod { + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { return treeBuilder.In(visitor.Visit(arguments[0]).AsExpression(), treeBuilder.Indices(visitor.Visit(targetObject).AsExpression())); @@ -98,4 +100,4 @@ protected override string MethodName get { return "get_Item"; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Functions/EqualsGenerator.cs b/src/NHibernate/Linq/Functions/EqualsGenerator.cs index 27165978b34..82c15c87190 100644 --- a/src/NHibernate/Linq/Functions/EqualsGenerator.cs +++ b/src/NHibernate/Linq/Functions/EqualsGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq.Expressions; using System.Reflection; @@ -10,67 +11,69 @@ namespace NHibernate.Linq.Functions { public class EqualsGenerator : BaseHqlGeneratorForMethod { - public EqualsGenerator() + internal static HashSet Methods = new HashSet { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => string.Equals(default(string), default(string))), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.FastGetMethod(string.Equals, default(string), default(string)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.FastGetMethod(decimal.Equals, default(decimal), default(decimal)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - - ReflectHelper.GetMethodDefinition(() => decimal.Equals(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + ReflectHelper.GetMethodDefinition(x => x.Equals(default(bool))), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(x)), - ReflectHelper.GetMethodDefinition(x => x.Equals(default(bool))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(string))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(char))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(sbyte))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(byte))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(short))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(ushort))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(int))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(uint))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(long))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(ulong))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(float))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(double))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(decimal))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(Guid))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(DateTime))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(DateTimeOffset))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(TimeSpan))), + ReflectHelper.GetMethodDefinition>(x => x.Equals(default(bool))) + }; - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(string))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(char))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(sbyte))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(byte))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(short))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(ushort))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(int))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(uint))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(long))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(ulong))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(float))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(double))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(decimal))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(Guid))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(DateTime))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(DateTimeOffset))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(TimeSpan))), - ReflectHelper.GetMethodDefinition>(x => x.Equals(default(bool))) - }; + public EqualsGenerator() + { + SupportedMethods = Methods; } + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { Expression lhs = arguments.Count == 1 ? targetObject : arguments[0]; Expression rhs = arguments.Count == 1 ? arguments[0] : arguments[1]; - return treeBuilder.Equality( - visitor.Visit(lhs).ToArithmeticExpression(), - visitor.Visit(rhs).ToArithmeticExpression()); + return visitor.Visit(Expression.Equal(lhs, rhs)); } } } diff --git a/src/NHibernate/Linq/Functions/GetValueOrDefaultGenerator.cs b/src/NHibernate/Linq/Functions/GetValueOrDefaultGenerator.cs index 87c8efb01a9..cc0fa7202b9 100644 --- a/src/NHibernate/Linq/Functions/GetValueOrDefaultGenerator.cs +++ b/src/NHibernate/Linq/Functions/GetValueOrDefaultGenerator.cs @@ -9,7 +9,7 @@ namespace NHibernate.Linq.Functions { - internal class GetValueOrDefaultGenerator : IHqlGeneratorForMethod, IRuntimeMethodHqlGenerator + internal class GetValueOrDefaultGenerator : IHqlGeneratorForMethod, IRuntimeMethodHqlGenerator, IHqlGeneratorForMethodExtended { public bool SupportsMethod(MethodInfo method) { @@ -40,5 +40,14 @@ private static HqlExpression GetRhs(MethodInfo method, ReadOnlyCollection !method.ReturnType.IsValueType; + + /// + public bool TryGetCollectionParameter(MethodCallExpression expression, out ConstantExpression collectionParameter) + { + collectionParameter = null; + return false; + } } } diff --git a/src/NHibernate/Linq/Functions/IAllowPreEvaluationHqlGenerator.cs b/src/NHibernate/Linq/Functions/IAllowPreEvaluationHqlGenerator.cs new file mode 100644 index 00000000000..2cd67b7d2d1 --- /dev/null +++ b/src/NHibernate/Linq/Functions/IAllowPreEvaluationHqlGenerator.cs @@ -0,0 +1,34 @@ +using System; +using System.Reflection; +using NHibernate.Engine; + +namespace NHibernate.Linq.Functions +{ + public interface IAllowPreEvaluationHqlGenerator + { + /// + /// Should pre-evaluation be allowed for this property or method? + /// + /// The property or method. + /// The session factory. + /// + /// if the property or method should be evaluated before running the query whenever possible, + /// if it must always be translated to the equivalent HQL call. + /// + /// Implementors should return by default. Returning + /// is mainly useful when the HQL translation is a non-deterministic function call like NEWGUID() or + /// a function which value on server side can differ from the equivalent client value, like + /// . + bool AllowPreEvaluation(MemberInfo member, ISessionFactoryImplementor factory); + + /// + /// Should the instance holding the property or method be ignored? + /// + /// The property or method. + /// + /// if the property or method translation does not depend on the instance to which it + /// belongs, otherwise. + /// + bool IgnoreInstance(MemberInfo member); + } +} diff --git a/src/NHibernate/Linq/Functions/IHqlGeneratorForMethod.cs b/src/NHibernate/Linq/Functions/IHqlGeneratorForMethod.cs index 06b1545ef23..3ef7583ee6b 100644 --- a/src/NHibernate/Linq/Functions/IHqlGeneratorForMethod.cs +++ b/src/NHibernate/Linq/Functions/IHqlGeneratorForMethod.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Linq.Expressions; using System.Reflection; +using NHibernate.Engine; using NHibernate.Hql.Ast; using NHibernate.Linq.Visitors; @@ -12,4 +13,87 @@ public interface IHqlGeneratorForMethod IEnumerable SupportedMethods { get; } HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor); } -} \ No newline at end of file + + // 6.0 TODO: Merge into IHqlGeneratorForMethod + internal interface IHqlGeneratorForMethodExtended + { + bool AllowsNullableReturnType(MethodInfo method); + + /// + /// Try getting a collection parameter from . + /// + /// The method call expression. + /// Output parameter for the retrieved collection parameter. + /// Whether collection parameter was retrieved. + bool TryGetCollectionParameter(MethodCallExpression expression, out ConstantExpression collectionParameter); + } + + internal static class HqlGeneratorForMethodExtensions + { + // 6.0 TODO: Remove + public static bool AllowsNullableReturnType(this IHqlGeneratorForMethod generator, MethodInfo method) + { + if (generator is IHqlGeneratorForMethodExtended extendedGenerator) + { + return extendedGenerator.AllowsNullableReturnType(method); + } + + return true; + } + + // 6.0 TODO: Remove + public static bool TryGetCollectionParameters( + this IHqlGeneratorForMethod generator, + MethodCallExpression expression, + out ConstantExpression collectionParameter) + { + if (generator is IHqlGeneratorForMethodExtended extendedGenerator) + { + return extendedGenerator.TryGetCollectionParameter(expression, out collectionParameter); + } + + collectionParameter = null; + return false; + } + + // 6.0 TODO: merge into IHqlGeneratorForMethod + /// + /// Should pre-evaluation be allowed for this method? + /// + /// The method's HQL generator. + /// The method. + /// The session factory. + /// + /// if the method should be evaluated before running the query whenever possible, + /// if it must always be translated to the equivalent HQL call. + /// + public static bool AllowPreEvaluation( + this IHqlGeneratorForMethod generator, + MemberInfo member, + ISessionFactoryImplementor factory) + { + if (generator is IAllowPreEvaluationHqlGenerator allowPreEvalGenerator) + return allowPreEvalGenerator.AllowPreEvaluation(member, factory); + + // By default, everything should be pre-evaluated whenever possible. + return true; + } + + /// + /// Should the instance holding the method be ignored? + /// + /// The method's HQL generator. + /// The method. + /// + /// if the method translation does not depend on the instance to which it + /// belongs, otherwise. + /// + public static bool IgnoreInstance(this IHqlGeneratorForMethod generator, MemberInfo member) + { + if (generator is IAllowPreEvaluationHqlGenerator allowPreEvalGenerator) + return allowPreEvalGenerator.IgnoreInstance(member); + + return false; + } + } +} diff --git a/src/NHibernate/Linq/Functions/IHqlGeneratorForProperty.cs b/src/NHibernate/Linq/Functions/IHqlGeneratorForProperty.cs index 83650ae2185..474ea552ced 100644 --- a/src/NHibernate/Linq/Functions/IHqlGeneratorForProperty.cs +++ b/src/NHibernate/Linq/Functions/IHqlGeneratorForProperty.cs @@ -1,14 +1,46 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using NHibernate.Engine; using NHibernate.Hql.Ast; using NHibernate.Linq.Visitors; namespace NHibernate.Linq.Functions { - public interface IHqlGeneratorForProperty - { - IEnumerable SupportedProperties { get; } - HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor); - } -} \ No newline at end of file + public interface IHqlGeneratorForProperty + { + IEnumerable SupportedProperties { get; } + + HqlTreeNode BuildHql( + MemberInfo member, + Expression expression, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor); + } + + // 6.0 TODO: merge into IHqlGeneratorForProperty + public static class HqlGeneratorForPropertyExtensions + { + /// + /// Should pre-evaluation be allowed for this property? + /// + /// The property's HQL generator. + /// The property. + /// The session factory. + /// + /// if the property should be evaluated before running the query whenever possible, + /// if it must always be translated to the equivalent HQL call. + /// + public static bool AllowPreEvaluation( + this IHqlGeneratorForProperty generator, + MemberInfo member, + ISessionFactoryImplementor factory) + { + if (generator is IAllowPreEvaluationHqlGenerator allowPreEvalGenerator) + return allowPreEvalGenerator.AllowPreEvaluation(member, factory); + + // By default, everything should be pre-evaluated whenever possible. + return true; + } + } +} diff --git a/src/NHibernate/Linq/Functions/ListIndexerGenerator.cs b/src/NHibernate/Linq/Functions/ListIndexerGenerator.cs index 6435b88a476..d3bbc843097 100644 --- a/src/NHibernate/Linq/Functions/ListIndexerGenerator.cs +++ b/src/NHibernate/Linq/Functions/ListIndexerGenerator.cs @@ -12,20 +12,30 @@ namespace NHibernate.Linq.Functions { internal class ListIndexerGenerator : BaseHqlGeneratorForMethod,IRuntimeMethodHqlGenerator { + private static readonly HashSet _supportedMethods = new HashSet + { + ReflectHelper.FastGetMethodDefinition(Enumerable.ElementAt, default(IEnumerable), 0), + ReflectHelper.FastGetMethodDefinition(Queryable.ElementAt, default(IQueryable), 0) + }; + public ListIndexerGenerator() { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => Enumerable.ElementAt(null, 0)), - ReflectHelper.GetMethodDefinition(() => Queryable.ElementAt(null, 0)) - }; + SupportedMethods = _supportedMethods; } public bool SupportsMethod(MethodInfo method) { - return method != null && - method.Name == "get_Item" && - (method.IsMethodOf(typeof(IList)) || method.IsMethodOf(typeof(IList<>))); + return IsRuntimeMethodSupported(method); + } + + public static bool IsMethodSupported(MethodInfo method) + { + if (method.IsGenericMethod) + { + method = method.GetGenericMethodDefinition(); + } + + return _supportedMethods.Contains(method) || IsRuntimeMethodSupported(method); } public IHqlGeneratorForMethod GetMethodGenerator(MethodInfo method) @@ -40,5 +50,12 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, return treeBuilder.Index(collection, index); } + + private static bool IsRuntimeMethodSupported(MethodInfo method) + { + return method != null && + method.Name == "get_Item" && + (method.IsMethodOf(typeof(IList)) || method.IsMethodOf(typeof(IList<>))); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Functions/MathGenerator.cs b/src/NHibernate/Linq/Functions/MathGenerator.cs index 2b1ccdd84fc..e2e4c690aff 100644 --- a/src/NHibernate/Linq/Functions/MathGenerator.cs +++ b/src/NHibernate/Linq/Functions/MathGenerator.cs @@ -14,72 +14,72 @@ public MathGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => Math.Sin(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Cos(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Tan(default(double))), - - ReflectHelper.GetMethodDefinition(() => Math.Sinh(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Cosh(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Tanh(default(double))), - - ReflectHelper.GetMethodDefinition(() => Math.Asin(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Acos(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Atan(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Atan2(default(double), default(double))), - - ReflectHelper.GetMethodDefinition(() => Math.Sqrt(default(double))), - - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(float))), - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(long))), - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(int))), - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(short))), - ReflectHelper.GetMethodDefinition(() => Math.Abs(default(sbyte))), - - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(float))), - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(long))), - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(int))), - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(short))), - ReflectHelper.GetMethodDefinition(() => Math.Sign(default(sbyte))), - - ReflectHelper.GetMethodDefinition(() => Math.Floor(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Floor(default(double))), - ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))), - - ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(double))), - ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))), - - ReflectHelper.GetMethodDefinition(() => Math.Pow(default(double), default(double))), + ReflectHelper.FastGetMethod(Math.Sin, default(double)), + ReflectHelper.FastGetMethod(Math.Cos, default(double)), + ReflectHelper.FastGetMethod(Math.Tan, default(double)), + + ReflectHelper.FastGetMethod(Math.Sinh, default(double)), + ReflectHelper.FastGetMethod(Math.Cosh, default(double)), + ReflectHelper.FastGetMethod(Math.Tanh, default(double)), + + ReflectHelper.FastGetMethod(Math.Asin, default(double)), + ReflectHelper.FastGetMethod(Math.Acos, default(double)), + ReflectHelper.FastGetMethod(Math.Atan, default(double)), + ReflectHelper.FastGetMethod(Math.Atan2, default(double), default(double)), + + ReflectHelper.FastGetMethod(Math.Sqrt, default(double)), + + ReflectHelper.FastGetMethod(Math.Abs, default(decimal)), + ReflectHelper.FastGetMethod(Math.Abs, default(double)), + ReflectHelper.FastGetMethod(Math.Abs, default(float)), + ReflectHelper.FastGetMethod(Math.Abs, default(long)), + ReflectHelper.FastGetMethod(Math.Abs, default(int)), + ReflectHelper.FastGetMethod(Math.Abs, default(short)), + ReflectHelper.FastGetMethod(Math.Abs, default(sbyte)), + + ReflectHelper.FastGetMethod(Math.Sign, default(decimal)), + ReflectHelper.FastGetMethod(Math.Sign, default(double)), + ReflectHelper.FastGetMethod(Math.Sign, default(float)), + ReflectHelper.FastGetMethod(Math.Sign, default(long)), + ReflectHelper.FastGetMethod(Math.Sign, default(int)), + ReflectHelper.FastGetMethod(Math.Sign, default(short)), + ReflectHelper.FastGetMethod(Math.Sign, default(sbyte)), + + ReflectHelper.FastGetMethod(Math.Floor, default(decimal)), + ReflectHelper.FastGetMethod(Math.Floor, default(double)), + ReflectHelper.FastGetMethod(decimal.Floor, default(decimal)), + + ReflectHelper.FastGetMethod(Math.Ceiling, default(decimal)), + ReflectHelper.FastGetMethod(Math.Ceiling, default(double)), + ReflectHelper.FastGetMethod(decimal.Ceiling, default(decimal)), + + ReflectHelper.FastGetMethod(Math.Pow, default(double), default(double)), #if NETCOREAPP2_0 - ReflectHelper.GetMethodDefinition(() => MathF.Sin(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Cos(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Tan(default(float))), + ReflectHelper.FastGetMethod(MathF.Sin, default(float)), + ReflectHelper.FastGetMethod(MathF.Cos, default(float)), + ReflectHelper.FastGetMethod(MathF.Tan, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Sinh(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Cosh(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Tanh(default(float))), + ReflectHelper.FastGetMethod(MathF.Sinh, default(float)), + ReflectHelper.FastGetMethod(MathF.Cosh, default(float)), + ReflectHelper.FastGetMethod(MathF.Tanh, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Asin(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Acos(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Atan(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Atan2(default(float), default(float))), + ReflectHelper.FastGetMethod(MathF.Asin, default(float)), + ReflectHelper.FastGetMethod(MathF.Acos, default(float)), + ReflectHelper.FastGetMethod(MathF.Atan, default(float)), + ReflectHelper.FastGetMethod(MathF.Atan2, default(float), default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Sqrt(default(float))), + ReflectHelper.FastGetMethod(MathF.Sqrt, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Abs(default(float))), + ReflectHelper.FastGetMethod(MathF.Abs, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Sign(default(float))), + ReflectHelper.FastGetMethod(MathF.Sign, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Floor(default(float))), + ReflectHelper.FastGetMethod(MathF.Floor, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Ceiling(default(float))), + ReflectHelper.FastGetMethod(MathF.Ceiling, default(float)), - ReflectHelper.GetMethodDefinition(() => MathF.Pow(default(float), default(float))), + ReflectHelper.FastGetMethod(MathF.Pow, default(float), default(float)), #endif }; } @@ -91,7 +91,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression expression, R if (function == "pow") function = "power"; - HqlExpression firstArgument = visitor.Visit(arguments[0]).AsExpression(); + var firstArgument = visitor.Visit(arguments[0]).AsExpression(); if (arguments.Count == 2) { diff --git a/src/NHibernate/Linq/Functions/NewGuidHqlGenerator.cs b/src/NHibernate/Linq/Functions/NewGuidHqlGenerator.cs new file mode 100644 index 00000000000..7a0a935c497 --- /dev/null +++ b/src/NHibernate/Linq/Functions/NewGuidHqlGenerator.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Engine; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Visitors; +using NHibernate.Util; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Linq.Functions +{ + public class NewGuidHqlGenerator : BaseHqlGeneratorForMethod, IAllowPreEvaluationHqlGenerator + { + public NewGuidHqlGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.FastGetMethod(Guid.NewGuid) + }; + } + + public override HqlTreeNode BuildHql( + MethodInfo method, + Expression targetObject, + ReadOnlyCollection arguments, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor) + { + return treeBuilder.MethodCall("new_uuid"); + } + + public bool AllowPreEvaluation(MemberInfo member, ISessionFactoryImplementor factory) + { + if (factory.Dialect.Functions.ContainsKey("new_uuid")) + return false; + + if (factory.Settings.LinqToHqlFallbackOnPreEvaluation) + return true; + + throw new QueryException( + "Cannot translate NewGuid: new_uuid is " + + $"not supported by {factory.Dialect}. Either enable the fallback on pre-evaluation " + + $"({Environment.LinqToHqlFallbackOnPreEvaluation}) or evaluate NewGuid " + + "outside of the query."); + } + + public bool IgnoreInstance(MemberInfo member) + { + // There is only a static method + return true; + } + } +} diff --git a/src/NHibernate/Linq/Functions/QueryableGenerator.cs b/src/NHibernate/Linq/Functions/QueryableGenerator.cs index 4f3a9568c69..eaf44c64f4a 100644 --- a/src/NHibernate/Linq/Functions/QueryableGenerator.cs +++ b/src/NHibernate/Linq/Functions/QueryableGenerator.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; @@ -17,11 +18,13 @@ public AnyHqlGenerator() { ReflectionCache.QueryableMethods.AnyDefinition, ReflectionCache.QueryableMethods.AnyWithPredicateDefinition, - ReflectHelper.GetMethodDefinition(() => Enumerable.Any(null)), - ReflectHelper.GetMethodDefinition(() => Enumerable.Any(null, null)) + ReflectHelper.FastGetMethodDefinition(Enumerable.Any, default(IEnumerable)), + ReflectHelper.FastGetMethodDefinition(Enumerable.Any, default(IEnumerable), default(Func)) }; } + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { HqlAlias alias = null; @@ -55,10 +58,12 @@ public AllHqlGenerator() SupportedMethods = new[] { ReflectionCache.QueryableMethods.AllDefinition, - ReflectHelper.GetMethodDefinition(() => Enumerable.All(null, null)) + ReflectHelper.FastGetMethodDefinition(Enumerable.All, default(IEnumerable), default(Func)) }; } + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { // All has two arguments. Arg 1 is the source and arg 2 is the predicate @@ -90,7 +95,7 @@ public MinHqlGenerator() SupportedMethods = new[] { ReflectionCache.QueryableMethods.MinDefinition, - ReflectHelper.GetMethodDefinition(() => Enumerable.Min(null)) + ReflectionCache.EnumerableMethods.MinDefinition }; } @@ -107,7 +112,7 @@ public MaxHqlGenerator() SupportedMethods = new[] { ReflectionCache.QueryableMethods.MaxDefinition, - ReflectHelper.GetMethodDefinition(() => Enumerable.Max(null)) + ReflectionCache.EnumerableMethods.MaxDefinition, }; } @@ -142,10 +147,21 @@ public class CollectionContainsGenerator : BaseHqlGeneratorForMethod public CollectionContainsGenerator() { SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => Queryable.Contains(null, null)), - ReflectHelper.GetMethodDefinition(() => Enumerable.Contains(null, null)) - }; + { + ReflectHelper.FastGetMethodDefinition(Queryable.Contains, default(IQueryable), default(object)), + ReflectHelper.FastGetMethodDefinition(Enumerable.Contains, default(IEnumerable), default(object)) + }; + } + + public override bool AllowsNullableReturnType(MethodInfo method) => false; + + /// + public override bool TryGetCollectionParameter(MethodCallExpression expression, out ConstantExpression collectionParameter) + { + var argument = expression.Method.IsStatic ? expression.Arguments[0] : expression.Object; + collectionParameter = argument as ConstantExpression; + + return collectionParameter != null; } public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) @@ -170,4 +186,4 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, where)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Functions/RandomHqlGenerator.cs b/src/NHibernate/Linq/Functions/RandomHqlGenerator.cs new file mode 100644 index 00000000000..bde4895e1e5 --- /dev/null +++ b/src/NHibernate/Linq/Functions/RandomHqlGenerator.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Engine; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Visitors; +using NHibernate.Util; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Linq.Functions +{ + public class RandomHqlGenerator : BaseHqlGeneratorForMethod, IAllowPreEvaluationHqlGenerator + { + private readonly MethodInfo _nextDouble = ReflectHelper.GetMethod(r => r.NextDouble()); + private const string _randomFunctionName = "random"; + private const string _floorFunctionName = "floor"; + + public RandomHqlGenerator() + { + SupportedMethods = new[] + { + _nextDouble, + ReflectHelper.GetMethod(r => r.Next()), + ReflectHelper.GetMethod(r => r.Next(2)), + ReflectHelper.GetMethod(r => r.Next(-1, 1)) + }; + } + + public override HqlTreeNode BuildHql( + MethodInfo method, + Expression targetObject, + ReadOnlyCollection arguments, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor) + { + if (method == _nextDouble) + return treeBuilder.MethodCall(_randomFunctionName); + + switch (arguments.Count) + { + case 0: + return treeBuilder.Cast( + treeBuilder.MethodCall( + _floorFunctionName, + treeBuilder.Multiply( + treeBuilder.MethodCall(_randomFunctionName), + treeBuilder.Constant(int.MaxValue))), + typeof(int)); + case 1: + return treeBuilder.Cast( + treeBuilder.MethodCall( + _floorFunctionName, + treeBuilder.Multiply( + treeBuilder.MethodCall(_randomFunctionName), + visitor.Visit(arguments[0]).AsExpression())), + typeof(int)); + case 2: + var minValue = visitor.Visit(arguments[0]).AsExpression(); + var maxValue = visitor.Visit(arguments[1]).AsExpression(); + return treeBuilder.Cast( + treeBuilder.Add( + treeBuilder.MethodCall( + _floorFunctionName, + treeBuilder.Multiply( + treeBuilder.MethodCall(_randomFunctionName), + treeBuilder.Subtract(maxValue, minValue))), + minValue), + typeof(int)); + default: + throw new NotSupportedException(); + } + } + + /// + public bool AllowPreEvaluation(MemberInfo member, ISessionFactoryImplementor factory) + { + if (factory.Dialect.Functions.ContainsKey(_randomFunctionName) && + (member == _nextDouble || factory.Dialect.Functions.ContainsKey(_floorFunctionName))) + return false; + + if (factory.Settings.LinqToHqlFallbackOnPreEvaluation) + return true; + + var functionName = factory.Dialect.Functions.ContainsKey(_randomFunctionName) + ? _floorFunctionName + : _randomFunctionName; + throw new QueryException( + $"Cannot translate {member.DeclaringType.Name}.{member.Name}: {functionName} is " + + $"not supported by {factory.Dialect}. Either enable the fallback on pre-evaluation " + + $"({Environment.LinqToHqlFallbackOnPreEvaluation}) or evaluate {member.Name} " + + "outside of the query."); + } + + /// + public bool IgnoreInstance(MemberInfo member) + { + // The translation ignores the Random instance, so long if it was specifically seeded: the user should + // pass the random value as a local variable in the Linq query in such case. + // Returning false here would cause the method, when appearing in a select clause, to be post-evaluated. + // Contrary to pre-evaluation, the post-evaluation is done for each row so it at least would avoid having + // the same random value for each result. + // But that would still be not executed in database which would be unexpected, in my opinion. + // It would even cause failures if the random instance used for querying is shared among threads or is + // too similarly seeded between queries. + return true; + } + } +} diff --git a/src/NHibernate/Linq/Functions/RoundGenerator.cs b/src/NHibernate/Linq/Functions/RoundGenerator.cs index a0ec73ae666..b1fb8ddb0a7 100644 --- a/src/NHibernate/Linq/Functions/RoundGenerator.cs +++ b/src/NHibernate/Linq/Functions/RoundGenerator.cs @@ -14,16 +14,16 @@ public RoundGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => Math.Round(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(double), default(int))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(decimal), default(int))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), + ReflectHelper.FastGetMethod(Math.Round, default(double)), + ReflectHelper.FastGetMethod(Math.Round, default(double), default(int)), + ReflectHelper.FastGetMethod(Math.Round, default(decimal)), + ReflectHelper.FastGetMethod(Math.Round, default(decimal), default(int)), + ReflectHelper.FastGetMethod(decimal.Round, default(decimal)), + ReflectHelper.FastGetMethod(decimal.Round, default(decimal), default(int)), #if NETCOREAPP2_0 - ReflectHelper.GetMethodDefinition(() => MathF.Round(default(float))), - ReflectHelper.GetMethodDefinition(() => MathF.Round(default(float), default(int))), + ReflectHelper.FastGetMethod(MathF.Round, default(float)), + ReflectHelper.FastGetMethod(MathF.Round, default(float), default(int)), #endif }; } diff --git a/src/NHibernate/Linq/Functions/StringGenerator.cs b/src/NHibernate/Linq/Functions/StringGenerator.cs index 2d93f6c85bf..3bb0523a57c 100644 --- a/src/NHibernate/Linq/Functions/StringGenerator.cs +++ b/src/NHibernate/Linq/Functions/StringGenerator.cs @@ -10,7 +10,7 @@ namespace NHibernate.Linq.Functions { - public class LikeGenerator : IHqlGeneratorForMethod, IRuntimeMethodHqlGenerator + public class LikeGenerator : IHqlGeneratorForMethod, IRuntimeMethodHqlGenerator, IHqlGeneratorForMethodExtended { public IEnumerable SupportedMethods { @@ -35,7 +35,6 @@ public HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnly treeBuilder.Constant(escapeCharExpression.Value)); } throw new ArgumentException("The escape character must be specified as literal value or a string variable"); - } public bool SupportsMethod(MethodInfo method) @@ -58,8 +57,16 @@ public IHqlGeneratorForMethod GetMethodGenerator(MethodInfo method) { return this; } - } + public bool AllowsNullableReturnType(MethodInfo method) => false; + + /// + public bool TryGetCollectionParameter(MethodCallExpression expression, out ConstantExpression collectionParameter) + { + collectionParameter = null; + return false; + } + } public class LengthGenerator : BaseHqlGeneratorForProperty { @@ -76,13 +83,18 @@ public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, H public class StartsWithGenerator : BaseHqlGeneratorForMethod { + private static readonly MethodInfo MethodWithComparer = ReflectHelper.GetMethodDefinition(x => x.StartsWith(null, default(StringComparison))); + public StartsWithGenerator() { - SupportedMethods = new[] { ReflectHelper.GetMethodDefinition(x => x.StartsWith(null)) }; + SupportedMethods = new[] {ReflectHelper.GetMethodDefinition(x => x.StartsWith(null)), MethodWithComparer}; } + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { + LogIgnoredStringComparisonParameter(method, MethodWithComparer); return treeBuilder.Like( visitor.Visit(targetObject).AsExpression(), treeBuilder.Concat( @@ -93,13 +105,18 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, public class EndsWithGenerator : BaseHqlGeneratorForMethod { + private static readonly MethodInfo MethodWithComparer = ReflectHelper.GetMethodDefinition(x => x.EndsWith(null, default(StringComparison))); + public EndsWithGenerator() { - SupportedMethods = new[] { ReflectHelper.GetMethodDefinition(x => x.EndsWith(null)) }; + SupportedMethods = new[] {ReflectHelper.GetMethodDefinition(x => x.EndsWith(null)), MethodWithComparer,}; } + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { + LogIgnoredStringComparisonParameter(method, MethodWithComparer); return treeBuilder.Like( visitor.Visit(targetObject).AsExpression(), treeBuilder.Concat( @@ -115,6 +132,8 @@ public ContainsGenerator() SupportedMethods = new[] { ReflectHelper.GetMethodDefinition(x => x.Contains(null)) }; } + public override bool AllowsNullableReturnType(MethodInfo method) => false; + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { return treeBuilder.Like( @@ -204,6 +223,9 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, public class IndexOfGenerator : BaseHqlGeneratorForMethod { + private static readonly MethodInfo MethodWithComparer1 = ReflectHelper.GetMethodDefinition(x => x.IndexOf(string.Empty, default(StringComparison))); + private static readonly MethodInfo MethodWithComparer2 = ReflectHelper.GetMethodDefinition(x => x.IndexOf(string.Empty, 0, default(StringComparison))); + public IndexOfGenerator() { SupportedMethods = new[] @@ -211,13 +233,22 @@ public IndexOfGenerator() ReflectHelper.GetMethodDefinition(s => s.IndexOf(' ')), ReflectHelper.GetMethodDefinition(s => s.IndexOf(" ")), ReflectHelper.GetMethodDefinition(s => s.IndexOf(' ', 0)), - ReflectHelper.GetMethodDefinition(s => s.IndexOf(" ", 0)) + ReflectHelper.GetMethodDefinition(s => s.IndexOf(" ", 0)), + MethodWithComparer1, + MethodWithComparer2, }; } public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { + var argsCount = arguments.Count; + if (LogIgnoredStringComparisonParameter(method, MethodWithComparer1, MethodWithComparer2)) + { + //StringComparison is last argument, just ignore it + argsCount--; + } + HqlMethodCall locate; - if (arguments.Count == 1) + if (argsCount == 1) { locate = treeBuilder.MethodCall("locate", visitor.Visit(arguments[0]).AsExpression(), @@ -238,21 +269,32 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, public class ReplaceGenerator : BaseHqlGeneratorForMethod { +#if NETCOREAPP2_0 + private static readonly MethodInfo MethodWithComparer = ReflectHelper.GetMethodDefinition(x => x.Replace(string.Empty, string.Empty, default(StringComparison))); +#endif + public ReplaceGenerator() { SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(s => s.Replace(' ', ' ')), - ReflectHelper.GetMethodDefinition(s => s.Replace("", "")) - }; + { + ReflectHelper.GetMethodDefinition(s => s.Replace(' ', ' ')), + ReflectHelper.GetMethodDefinition(s => s.Replace("", "")), +#if NETCOREAPP2_0 + MethodWithComparer, +#endif + }; } public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { - return treeBuilder.MethodCall("replace", - visitor.Visit(targetObject).AsExpression(), - visitor.Visit(arguments[0]).AsExpression(), - visitor.Visit(arguments[1]).AsExpression()); +#if NETCOREAPP2_0 + LogIgnoredStringComparisonParameter(method, MethodWithComparer); +#endif + return treeBuilder.MethodCall( + "replace", + visitor.Visit(targetObject).AsExpression(), + visitor.Visit(arguments[0]).AsExpression(), + visitor.Visit(arguments[1]).AsExpression()); } } diff --git a/src/NHibernate/Linq/Functions/TruncateGenerator.cs b/src/NHibernate/Linq/Functions/TruncateGenerator.cs index 4bb55a2d710..0e6d6a26f9c 100644 --- a/src/NHibernate/Linq/Functions/TruncateGenerator.cs +++ b/src/NHibernate/Linq/Functions/TruncateGenerator.cs @@ -14,12 +14,12 @@ public TruncateGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), - ReflectHelper.GetMethodDefinition(() => decimal.Truncate(default(decimal))), - + ReflectHelper.FastGetMethod(Math.Truncate, default(decimal)), + ReflectHelper.FastGetMethod(Math.Truncate, default(double)), + ReflectHelper.FastGetMethod(decimal.Truncate, default(decimal)), + #if NETCOREAPP2_0 - ReflectHelper.GetMethodDefinition(() => MathF.Truncate(default(float))), + ReflectHelper.FastGetMethod(MathF.Truncate, default(float)), #endif }; } diff --git a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs index 8eb0e6f7388..e4119cbb149 100644 --- a/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs +++ b/src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs @@ -38,7 +38,8 @@ public static class AggregatingGroupByRewriter typeof(FirstResultOperator), typeof(SingleResultOperator), typeof(AnyResultOperator), - typeof(AllResultOperator) + typeof(AllResultOperator), + typeof(ContainsResultOperator), }; public static void ReWrite(QueryModel queryModel) @@ -82,8 +83,7 @@ private static void FlattenSubQuery(QueryModel queryModel, QueryModel subQueryMo var whereClause = clause as WhereClause; if (whereClause != null) { - queryModel.BodyClauses.RemoveAt(i); - queryModel.BodyClauses.Insert(i, new NhHavingClause(whereClause.Predicate)); + queryModel.BodyClauses[i] = new NhHavingClause(whereClause.Predicate); } } diff --git a/src/NHibernate/Linq/GroupBy/GroupBySelectClauseRewriter.cs b/src/NHibernate/Linq/GroupBy/GroupBySelectClauseRewriter.cs index c031d305396..ed47d2c8363 100644 --- a/src/NHibernate/Linq/GroupBy/GroupBySelectClauseRewriter.cs +++ b/src/NHibernate/Linq/GroupBy/GroupBySelectClauseRewriter.cs @@ -1,19 +1,27 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using NHibernate.Linq.Expressions; +using NHibernate.Linq.Visitors; using Remotion.Linq; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ResultOperators; using Remotion.Linq.Parsing; -using Remotion.Linq.Parsing.ExpressionVisitors; namespace NHibernate.Linq.GroupBy { //This should be renamed. It handles entire querymodels, not just select clauses internal class GroupBySelectClauseRewriter : RelinqExpressionVisitor { + private readonly static HashSet _validElementSelectorTypes = new HashSet + { + ExpressionType.ArrayIndex, + ExpressionType.New, + ExpressionType.MemberInit + }; + public static Expression ReWrite(Expression expression, GroupResultOperator groupBy, QueryModel model) { var visitor = new GroupBySelectClauseRewriter(groupBy, model); @@ -67,8 +75,8 @@ protected override Expression VisitMember(MemberExpression expression) return base.VisitMember(expression); } - if ((elementSelector is NewExpression || elementSelector.NodeType == ExpressionType.Convert) - && elementSelector.Type == expression.Expression.Type) + if (_validElementSelectorTypes.Contains(UnwrapUnary(elementSelector).NodeType) && + elementSelector.Type == expression.Expression.Type) { //TODO: probably we should check this with a visitor return Expression.MakeMemberAccess(elementSelector, expression.Member); @@ -151,11 +159,17 @@ expression.QueryModel.SelectClause.Selector is NhCountExpression countExpression return base.VisitSubQuery(expression); } - // TODO - is this safe? All we are extracting is the select clause from the sub-query. Assumes that everything // else in the subquery has been removed. If there were two subqueries, one aggregating & one not, this may not be a // valid assumption. Should probably be passed a list of aggregating subqueries that we are flattening so that we can check... return ReWrite(expression.QueryModel.SelectClause.Selector, _groupBy, _model); } + + private static Expression UnwrapUnary(Expression expression) + { + return expression is UnaryExpression unaryExpression + ? unaryExpression.Operand + : expression; + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/GroupBy/GroupKeyNominator.cs b/src/NHibernate/Linq/GroupBy/GroupKeyNominator.cs index e5149289afa..75779b6dc18 100644 --- a/src/NHibernate/Linq/GroupBy/GroupKeyNominator.cs +++ b/src/NHibernate/Linq/GroupBy/GroupKeyNominator.cs @@ -61,6 +61,27 @@ protected override Expression VisitNew(NewExpression expression) return Expression.New(expression.Constructor, expression.Arguments.Select(VisitInternal), expression.Members); } + protected override Expression VisitMemberInit(MemberInitExpression node) + { + _transformed = true; + return Expression.MemberInit((NewExpression) VisitInternal(node.NewExpression), node.Bindings.Select(VisitMemberBinding)); + } + + protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) + { + return node.Update(VisitInternal(node.Expression)); + } + + protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) + { + return node.Update(node.Initializers.Select(o => o.Update(o.Arguments.Select(VisitInternal)))); + } + + protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) + { + return node.Update(node.Bindings.Select(VisitMemberBinding)); + } + protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression expression) { // If the (sub)expression contains a QuerySourceReference, then the entire expression should be nominated diff --git a/src/NHibernate/Linq/GroupBy/PagingRewriter.cs b/src/NHibernate/Linq/GroupBy/PagingRewriter.cs index 922f1790a10..fdfbf0a9471 100644 --- a/src/NHibernate/Linq/GroupBy/PagingRewriter.cs +++ b/src/NHibernate/Linq/GroupBy/PagingRewriter.cs @@ -1,4 +1,5 @@ using System.Linq; +using NHibernate.Linq.ReWriters; using NHibernate.Linq.Visitors; using Remotion.Linq; using Remotion.Linq.Clauses; @@ -28,7 +29,7 @@ public static void ReWrite(QueryModel queryModel) private static void FlattenSubQuery(SubQueryExpression subQueryExpression, QueryModel queryModel) { - // we can not flattern subquery if outer query has body clauses. + // we can not flatten subquery if outer query has body clauses. var subQueryModel = subQueryExpression.QueryModel; var subQueryMainFromClause = subQueryModel.MainFromClause; if (queryModel.BodyClauses.Count == 0) @@ -53,7 +54,7 @@ private static void FlattenSubQuery(SubQueryExpression subQueryExpression, Query var where = new WhereClause(new SubQueryExpression(newSubQueryModel)); queryModel.BodyClauses.Add(where); - if (!queryModel.BodyClauses.OfType().Any()) + if (RemoveUnnecessaryBodyOperators.IsOrderByNeeded(queryModel) && !queryModel.BodyClauses.OfType().Any()) { var orderByClauses = subQueryModel.BodyClauses.OfType(); foreach (var orderByClause in orderByClauses) diff --git a/src/NHibernate/Linq/GroupJoin/GroupJoinAggregateDetectionVisitor.cs b/src/NHibernate/Linq/GroupJoin/GroupJoinAggregateDetectionVisitor.cs index 78ee78c35eb..20f2331bf9c 100644 --- a/src/NHibernate/Linq/GroupJoin/GroupJoinAggregateDetectionVisitor.cs +++ b/src/NHibernate/Linq/GroupJoin/GroupJoinAggregateDetectionVisitor.cs @@ -79,6 +79,12 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr } } } + // In order to detect a left join (e.g. from a in A join b in B on a.Id equals b.Id into c from b in c.DefaultIfEmpty()) + // we have to visit the subquery in order to find the group join + else if (fromClause.FromExpression is SubQueryExpression subQuery) + { + VisitSubQuery(subQuery); + } return base.VisitQuerySourceReference(expression); } diff --git a/src/NHibernate/Linq/GroupJoin/GroupJoinSelectClauseRewriter.cs b/src/NHibernate/Linq/GroupJoin/GroupJoinSelectClauseRewriter.cs index 546e7cd514e..6009f756648 100644 --- a/src/NHibernate/Linq/GroupJoin/GroupJoinSelectClauseRewriter.cs +++ b/src/NHibernate/Linq/GroupJoin/GroupJoinSelectClauseRewriter.cs @@ -33,7 +33,6 @@ protected override Expression VisitSubQuery(SubQueryExpression expression) expression.QueryModel.MainFromClause.FromExpression = groupJoin.JoinClause.InnerSequence; - // TODO - this only works if the key selectors are not composite. Needs improvement... expression.QueryModel.BodyClauses.Add(new WhereClause(Expression.Equal(innerSelector, groupJoin.JoinClause.OuterKeySelector))); } @@ -50,4 +49,4 @@ private GroupJoinClause LocateGroupJoinQuerySource(QueryModel model) return new LocateGroupJoinQuerySource(_results).Detect(model.MainFromClause.FromExpression); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/GroupJoin/NonAggregatingGroupJoinRewriter.cs b/src/NHibernate/Linq/GroupJoin/NonAggregatingGroupJoinRewriter.cs index 94da56879db..d56a9bedff4 100644 --- a/src/NHibernate/Linq/GroupJoin/NonAggregatingGroupJoinRewriter.cs +++ b/src/NHibernate/Linq/GroupJoin/NonAggregatingGroupJoinRewriter.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using NHibernate.Linq.Clauses; using NHibernate.Linq.GroupJoin; using Remotion.Linq; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; +using Remotion.Linq.Clauses.ResultOperators; using Remotion.Linq.Parsing; namespace NHibernate.Linq.Visitors @@ -14,7 +16,6 @@ public class NonAggregatingGroupJoinRewriter { private readonly QueryModel _model; private readonly IEnumerable _groupJoinClauses; - private QuerySourceUsageLocator _locator; private NonAggregatingGroupJoinRewriter(QueryModel model, IEnumerable groupJoinClauses) { @@ -67,23 +68,22 @@ private void ReWrite() // This is used to repesent an outer join, and again the "from" is removing the hierarchy. So // simply change the group join to an outer join - _locator = new QuerySourceUsageLocator(nonAggregatingJoin); + var locator = new QuerySourceUsageLocator(nonAggregatingJoin); foreach (var bodyClause in _model.BodyClauses) { - _locator.Search(bodyClause); + locator.Search(bodyClause); } - if (IsHierarchicalJoin(nonAggregatingJoin)) + if (IsHierarchicalJoin(nonAggregatingJoin, locator)) { } - else if (IsFlattenedJoin(nonAggregatingJoin)) + else if (IsFlattenedJoin(nonAggregatingJoin, locator)) { - ProcessFlattenedJoin(nonAggregatingJoin); + ProcessFlattenedJoin(nonAggregatingJoin, locator); } else if (IsOuterJoin(nonAggregatingJoin)) { - } else { @@ -93,19 +93,30 @@ private void ReWrite() } } - private void ProcessFlattenedJoin(GroupJoinClause nonAggregatingJoin) + private void ProcessFlattenedJoin(GroupJoinClause nonAggregatingJoin, QuerySourceUsageLocator locator) { + var nhJoin = locator.LeftJoin + ? new NhOuterJoinClause(nonAggregatingJoin.JoinClause) + : (IQuerySource) nonAggregatingJoin.JoinClause; + // Need to: // 1. Remove the group join and replace it with a join // 2. Remove the corresponding "from" clause (the thing that was doing the flattening) - // 3. Rewrite the selector to reference the "join" rather than the "from" clause - SwapClause(nonAggregatingJoin, nonAggregatingJoin.JoinClause); + // 3. Rewrite the query model to reference the "join" rather than the "from" clause + SwapClause(nonAggregatingJoin, (IBodyClause) nhJoin); - // TODO - don't like use of _locator here; would rather we got this passed in. Ditto on next line (esp. the cast) - _model.BodyClauses.Remove(_locator.Clauses[0]); + _model.BodyClauses.Remove((IBodyClause) locator.Usages[0]); + + SwapQuerySourceVisitor querySourceSwapper; + if (locator.LeftJoin) + { + // As we wrapped the join clause we have to update all references to the wrapped clause + querySourceSwapper = new SwapQuerySourceVisitor(nonAggregatingJoin.JoinClause, nhJoin); + _model.TransformExpressions(querySourceSwapper.Swap); + } - var querySourceSwapper = new SwapQuerySourceVisitor((IQuerySource)_locator.Clauses[0], nonAggregatingJoin.JoinClause); - _model.SelectClause.TransformExpressions(querySourceSwapper.Swap); + querySourceSwapper = new SwapQuerySourceVisitor(locator.Usages[0], nhJoin); + _model.TransformExpressions(querySourceSwapper.Swap); } // TODO - store the indexes of the join clauses when we find them, then can remove this loop @@ -115,8 +126,7 @@ private void SwapClause(IBodyClause oldClause, IBodyClause newClause) { if (_model.BodyClauses[i] == oldClause) { - _model.BodyClauses.RemoveAt(i); - _model.BodyClauses.Insert(i, newClause); + _model.BodyClauses[i] = newClause; } } } @@ -126,11 +136,11 @@ private bool IsOuterJoin(GroupJoinClause nonAggregatingJoin) return false; } - private bool IsFlattenedJoin(GroupJoinClause nonAggregatingJoin) + private bool IsFlattenedJoin(GroupJoinClause nonAggregatingJoin, QuerySourceUsageLocator locator) { - if (_locator.Clauses.Count == 1) + if (locator.Usages.Count == 1) { - var from = _locator.Clauses[0] as AdditionalFromClause; + var from = locator.Usages[0] as AdditionalFromClause; if (from != null) { @@ -141,9 +151,9 @@ private bool IsFlattenedJoin(GroupJoinClause nonAggregatingJoin) return false; } - private bool IsHierarchicalJoin(GroupJoinClause nonAggregatingJoin) + private bool IsHierarchicalJoin(GroupJoinClause nonAggregatingJoin, QuerySourceUsageLocator locator) { - return _locator.Clauses.Count == 0; + return locator.Usages.Count == 0; } // TODO - rename this and share with the AggregatingGroupJoinRewriter @@ -151,34 +161,36 @@ private IsAggregatingResults GetGroupJoinInformation(IEnumerable _clauses = new List(); public QuerySourceUsageLocator(IQuerySource querySource) { _querySource = querySource; } - public IList Clauses - { - get { return _clauses.AsReadOnly(); } - } + internal bool LeftJoin { get; private set; } + + public IList Usages { get; } = new List(); public void Search(IBodyClause clause) { + if (!(clause is IQuerySource querySource)) + { + return; + } + _references = false; clause.TransformExpressions(ExpressionSearcher); if (_references) { - _clauses.Add(clause); + Usages.Add(querySource); } } @@ -197,5 +209,22 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr return expression; } + + protected override Expression VisitSubQuery(SubQueryExpression expression) + { + if (IsLeftJoin(expression.QueryModel)) + { + LeftJoin = true; + expression.QueryModel.MainFromClause.TransformExpressions(ExpressionSearcher); + } + + return expression; + } + + private static bool IsLeftJoin(QueryModel subQueryModel) + { + return subQueryModel.ResultOperators.Count == 1 && + subQueryModel.ResultOperators[0] is DefaultIfEmptyResultOperator; + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/GroupResultOperatorExtensions.cs b/src/NHibernate/Linq/GroupResultOperatorExtensions.cs index 2a510db8685..2c73e050340 100644 --- a/src/NHibernate/Linq/GroupResultOperatorExtensions.cs +++ b/src/NHibernate/Linq/GroupResultOperatorExtensions.cs @@ -19,12 +19,31 @@ private static IEnumerable ExtractKeyExpressions(this Expression exp { // Recursively extract key expressions from nested initializers // --> new object[] { ((object)new object[] { x.A, x.B }), x.C } - // --> x.A, x.B, x.C - if (expr is NewExpression) - return (expr as NewExpression).Arguments.SelectMany(ExtractKeyExpressions); - if (expr is NewArrayExpression) - return (expr as NewArrayExpression).Expressions.SelectMany(ExtractKeyExpressions); + // --> new List { x.A, x.B } + // --> new CustomType(x.A, x.B) { C = x.C } + if (expr is NewExpression newExpression) + return newExpression.Arguments.SelectMany(ExtractKeyExpressions); + if (expr is NewArrayExpression newArrayExpression) + return newArrayExpression.Expressions.SelectMany(ExtractKeyExpressions); + if (expr is MemberInitExpression memberInitExpression) + return memberInitExpression.NewExpression.Arguments.SelectMany(ExtractKeyExpressions) + .Union(memberInitExpression.Bindings.SelectMany(ExtractKeyExpressions)); return new[] { expr }; } + + private static IEnumerable ExtractKeyExpressions(MemberBinding memberBinding) + { + switch (memberBinding) + { + case MemberAssignment memberAssignment: + return memberAssignment.Expression.ExtractKeyExpressions(); + case MemberMemberBinding memberMemberBinding: + return memberMemberBinding.Bindings.SelectMany(ExtractKeyExpressions); + case MemberListBinding memberListBinding: + return memberListBinding.Initializers.SelectMany(o => o.Arguments).SelectMany(ExtractKeyExpressions); + default: + return Enumerable.Empty(); + } + } } } diff --git a/src/NHibernate/Linq/INhQueryModelVisitor.cs b/src/NHibernate/Linq/INhQueryModelVisitor.cs index 3d4ce54fe34..42899774213 100644 --- a/src/NHibernate/Linq/INhQueryModelVisitor.cs +++ b/src/NHibernate/Linq/INhQueryModelVisitor.cs @@ -11,4 +11,10 @@ public interface INhQueryModelVisitor: IQueryModelVisitor void VisitNhHavingClause(NhHavingClause nhWhereClause, QueryModel queryModel, int index); } + + // TODO 6.0: Move members into INhQueryModelVisitor + internal interface INhQueryModelVisitorExtended : INhQueryModelVisitor + { + void VisitNhOuterJoinClause(NhOuterJoinClause nhOuterJoinClause, QueryModel queryModel, int index); + } } diff --git a/src/NHibernate/Linq/IntermediateHqlTree.cs b/src/NHibernate/Linq/IntermediateHqlTree.cs index e9fa4bba70e..17417319e41 100644 --- a/src/NHibernate/Linq/IntermediateHqlTree.cs +++ b/src/NHibernate/Linq/IntermediateHqlTree.cs @@ -127,6 +127,11 @@ public void AddFromClause(HqlTreeNode from) _root.NodesPreOrder.OfType().First().AddChild(from); } + internal HqlRange GetFromRangeClause() + { + return _root.NodesPreOrder.OfType().First().Children.OfType().FirstOrDefault(); + } + public void AddSelectClause(HqlTreeNode select) { _root.NodesPreOrder.OfType().First().AddChild(select); diff --git a/src/NHibernate/Linq/LinqExtensionMethods.cs b/src/NHibernate/Linq/LinqExtensionMethods.cs index 0d7c8182997..12b84e6e18c 100644 --- a/src/NHibernate/Linq/LinqExtensionMethods.cs +++ b/src/NHibernate/Linq/LinqExtensionMethods.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using NHibernate.Engine; +using static NHibernate.Util.ReflectionCache.QueryableMethods; namespace NHibernate.Linq { @@ -2389,8 +2390,11 @@ public static class LinqExtensionMethods async Task> InternalToListAsync() { - var result = await provider.ExecuteAsync>(source.Expression, cancellationToken).ConfigureAwait(false); - return result.ToList(); + //TODO 6.0: Replace with provider.ExecuteListAsync + var result = provider is DefaultQueryProvider nhQueryProvider + ? await nhQueryProvider.ExecuteListAsync(source.Expression, cancellationToken).ConfigureAwait(false) + : await provider.ExecuteAsync>(source.Expression, cancellationToken).ConfigureAwait(false); + return (result as List) ?? result.ToList(); } } @@ -2463,6 +2467,74 @@ public static IFutureValue ToFutureValue(this IQuerya #pragma warning restore CS0618 // Type or member is obsolete } + #region LeftJoin + + // Code based on: https://stackoverflow.com/a/18782867 + /// + /// Correlates the elements of two sequences based on matching keys. The default equality comparer is used to compare keys. + /// + /// The first sequence to join. + /// The sequence to join to the first sequence. + /// A dynamic function to extract the join key from each element of the first sequence. + /// A dynamic function to extract the join key from each element of the second sequence. + /// A dynamic function to create a result element from two matching elements. + /// An obtained by performing a left join on two sequences. + public static IQueryable LeftJoin( + this IQueryable outer, + IQueryable inner, + Expression> outerKeySelector, + Expression> innerKeySelector, + Expression> resultSelector) + { + outer = outer ?? throw new ArgumentNullException(nameof(outer)); + inner = inner ?? throw new ArgumentNullException(nameof(inner)); + outerKeySelector = outerKeySelector ?? throw new ArgumentNullException(nameof(outerKeySelector)); + innerKeySelector = innerKeySelector ?? throw new ArgumentNullException(nameof(innerKeySelector)); + resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + + Expression, LeftJoinIntermediate>> groupJoinResultSelector = + (oneOuter, manyInners) => new LeftJoinIntermediate + { + OneOuter = oneOuter, + ManyInners = manyInners + }; + var groupJoin = GroupJoinDefinition.MakeGenericMethod( + typeof(TOuter), + typeof(TInner), + typeof(TKey), + typeof(LeftJoinIntermediate)); + var selectMany = SelectManyDefinition.MakeGenericMethod( + typeof(LeftJoinIntermediate), + typeof(TInner), + typeof(TResult)); + var exprGroupJoin = Expression.Call( + groupJoin, + outer.Expression, + inner.Expression, + outerKeySelector, + innerKeySelector, + groupJoinResultSelector); + var selectManyCollectionSelector = (Expression, IEnumerable>>) + (t => t.ManyInners.DefaultIfEmpty()); + var outerParameter = resultSelector.Parameters[0]; + var paramNew = Expression.Parameter(typeof(LeftJoinIntermediate)); + var outerProperty = Expression.Property(paramNew, nameof(LeftJoinIntermediate.OneOuter)); + var selectManyResultSelector = Expression.Lambda( + ReplacingExpressionVisitor.Replace(outerParameter, outerProperty, resultSelector.Body), + paramNew, + resultSelector.Parameters[1]); + + return outer.Provider.CreateQuery( + Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector)); + } + + private class LeftJoinIntermediate + { + public TOuter OneOuter { get; set; } + public IEnumerable ManyInners { get; set; } + } + + #endregion /// /// Allows to set NHibernate query options. @@ -2516,7 +2588,7 @@ public static IQueryable Timeout(this IQueryable query, int timeout) public static IQueryable WithLock(this IQueryable query, LockMode lockMode) { - var method = ReflectHelper.GetMethod(() => WithLock(query, lockMode)); + var method = ReflectHelper.FastGetMethod(WithLock, query, lockMode); var callExpression = Expression.Call(method, query.Expression, Expression.Constant(lockMode)); diff --git a/src/NHibernate/Linq/LinqLogging.cs b/src/NHibernate/Linq/LinqLogging.cs index c6d121cf6eb..be589d9db49 100644 --- a/src/NHibernate/Linq/LinqLogging.cs +++ b/src/NHibernate/Linq/LinqLogging.cs @@ -28,7 +28,6 @@ internal static void LogExpression(string msg, Expression expression) } } - /// /// Replace all occurrences of ConstantExpression where the value is an NHibernate /// proxy with a ParameterExpression. The name of the parameter will be a string @@ -48,4 +47,4 @@ protected override Expression VisitConstant(ConstantExpression expression) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/NestedSelects/NestedSelectRewriter.cs b/src/NHibernate/Linq/NestedSelects/NestedSelectRewriter.cs index 4e4ee3220fc..6cf5d244245 100644 --- a/src/NHibernate/Linq/NestedSelects/NestedSelectRewriter.cs +++ b/src/NHibernate/Linq/NestedSelects/NestedSelectRewriter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -16,13 +17,13 @@ namespace NHibernate.Linq.NestedSelects static class NestedSelectRewriter { private static readonly MethodInfo CastMethod = - ReflectHelper.GetMethod(() => Enumerable.Cast(null)); - private static readonly MethodInfo GroupByMethod = ReflectHelper.GetMethod( - () => Enumerable.GroupBy(null, null, (Func)null)); - private static readonly MethodInfo WhereMethod = ReflectHelper.GetMethod( - () => Enumerable.Where(null, (Func)null)); - private static readonly MethodInfo ObjectReferenceEquals = ReflectHelper.GetMethod( - () => ReferenceEquals(null, null)); + ReflectHelper.FastGetMethod(Enumerable.Cast, default(IEnumerable)); + private static readonly MethodInfo GroupByMethod = + ReflectHelper.FastGetMethod(Enumerable.GroupBy, default(IEnumerable), default(Func), default(Func)); + private static readonly MethodInfo WhereMethod = + ReflectHelper.FastGetMethod(Enumerable.Where, default(IEnumerable), default(Func)); + private static readonly MethodInfo ObjectReferenceEquals = + ReflectHelper.FastGetMethod(ReferenceEquals, default(object), default(object)); private static readonly PropertyInfo IGroupingKeyProperty = (PropertyInfo) ReflectHelper.GetProperty, Tuple>(g => g.Key); @@ -44,6 +45,9 @@ public static void ReWrite(QueryModel queryModel, ISessionFactory sessionFactory replacements.Add(expression, processed); } + if (!replacements.Any()) + return; + var key = Expression.Property(group, IGroupingKeyProperty); var expressions = new List(); @@ -92,6 +96,11 @@ private static Expression ProcessExpression(QueryModel queryModel, ISessionFacto private static Expression ProcessSubquery(ISessionFactory sessionFactory, ICollection elementExpression, QueryModel queryModel, Expression @group, QueryModel subQueryModel) { + var resultTypeOverride = subQueryModel.ResultTypeOverride; + if (resultTypeOverride != null && !resultTypeOverride.IsArray && !IsEnumerableOfT(resultTypeOverride)) + return null; + + SubQueryFromClauseFlattener.ReWrite(subQueryModel); var subQueryMainFromClause = subQueryModel.MainFromClause; var restrictions = subQueryModel.BodyClauses @@ -120,6 +129,15 @@ private static Expression ProcessSubquery(ISessionFactory sessionFactory, IColle return BuildSubCollectionQuery(sessionFactory, elementExpression, @group, source, selector, elementType, collectionType); } + private static bool IsEnumerableOfT(System.Type type) + { + if (!type.IsGenericType) + return false; + + var typeDef = type.GetGenericTypeDefinition(); + return typeDef == typeof(IEnumerable<>) || typeDef == typeof(IQueryable<>); + } + private static Expression ProcessMemberExpression(ISessionFactory sessionFactory, ICollection elementExpression, QueryModel queryModel, Expression @group, Expression memberExpression) { var join = new NhJoinClause(new NameGenerator(queryModel).GetNewName(), diff --git a/src/NHibernate/Linq/NestedSelects/SelectClauseRewriter.cs b/src/NHibernate/Linq/NestedSelects/SelectClauseRewriter.cs index aa1a869c611..dae294cb48d 100644 --- a/src/NHibernate/Linq/NestedSelects/SelectClauseRewriter.cs +++ b/src/NHibernate/Linq/NestedSelects/SelectClauseRewriter.cs @@ -38,6 +38,17 @@ public override Expression Visit(Expression expression) return base.Visit(expression); } + protected override Expression VisitUnary(UnaryExpression node) + { + if (node.NodeType == ExpressionType.Convert && + (node.Operand is MemberExpression || node.Operand is QuerySourceReferenceExpression)) + { + return AddAndConvertExpression(node.Operand, node.Type); + } + + return base.VisitUnary(node); + } + protected override Expression VisitMember(MemberExpression expression) { return AddAndConvertExpression(expression); @@ -49,6 +60,11 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr } private Expression AddAndConvertExpression(Expression expression) + { + return AddAndConvertExpression(expression, expression.Type); + } + + private Expression AddAndConvertExpression(Expression expression, System.Type type) { expressions.Add(new ExpressionHolder { Expression = expression, Tuple = tuple }); @@ -56,7 +72,7 @@ private Expression AddAndConvertExpression(Expression expression) Expression.ArrayIndex( Expression.Property(parameter, Tuple.ItemsProperty), Expression.Constant(expressions.Count - 1)), - expression.Type); + type); } } } \ No newline at end of file diff --git a/src/NHibernate/Linq/NhLinqDmlExpression.cs b/src/NHibernate/Linq/NhLinqDmlExpression.cs index 1c5bd7bb20c..b246e86caef 100644 --- a/src/NHibernate/Linq/NhLinqDmlExpression.cs +++ b/src/NHibernate/Linq/NhLinqDmlExpression.cs @@ -5,18 +5,15 @@ namespace NHibernate.Linq { public class NhLinqDmlExpression : NhLinqExpression { - protected override QueryMode QueryMode { get; } - /// /// Entity type to insert or update when the expression is a DML. /// protected override System.Type TargetType => typeof(T); public NhLinqDmlExpression(QueryMode queryMode, Expression expression, ISessionFactoryImplementor sessionFactory) - : base(expression, sessionFactory) + : base(queryMode, expression, sessionFactory) { Key = $"{queryMode.ToString().ToUpperInvariant()} {Key}"; - QueryMode = queryMode; } } } diff --git a/src/NHibernate/Linq/NhLinqExpression.cs b/src/NHibernate/Linq/NhLinqExpression.cs index 50ec325c47f..4c41b4c489c 100644 --- a/src/NHibernate/Linq/NhLinqExpression.cs +++ b/src/NHibernate/Linq/NhLinqExpression.cs @@ -11,7 +11,7 @@ namespace NHibernate.Linq { - public class NhLinqExpression : IQueryExpression + public class NhLinqExpression : IQueryExpression, ICacheableQueryExpression { public string Key { get; protected set; } @@ -32,14 +32,25 @@ public class NhLinqExpression : IQueryExpression public ExpressionToHqlTranslationResults ExpressionToHqlTranslationResults { get; private set; } - protected virtual QueryMode QueryMode => QueryMode.Select; + protected virtual QueryMode QueryMode { get; } + + internal IDictionary NamedParameters { get; } private readonly Expression _expression; private readonly IDictionary _constantToParameterMap; public NhLinqExpression(Expression expression, ISessionFactoryImplementor sessionFactory) + : this(QueryMode.Select, expression, sessionFactory) { - _expression = NhRelinqQueryParser.PreTransform(expression); + } + + internal NhLinqExpression(QueryMode queryMode, Expression expression, ISessionFactoryImplementor sessionFactory) + { + QueryMode = queryMode; + var preTransformResult = NhRelinqQueryParser.PreTransform( + expression, + new PreTransformationParameters(queryMode, sessionFactory)); + _expression = preTransformResult.Expression; // We want logging to be as close as possible to the original expression sent from the // application. But if we log before partial evaluation done in PreTransform, the log won't @@ -47,12 +58,12 @@ public NhLinqExpression(Expression expression, ISessionFactoryImplementor sessio // referenced from the main query. LinqLogging.LogExpression("Expression (partially evaluated)", _expression); - _constantToParameterMap = ExpressionParameterVisitor.Visit(ref _expression, sessionFactory); - - ParameterValuesByName = _constantToParameterMap.Values.ToDictionary(p => p.Name, - p => System.Tuple.Create(p.Value, p.Type)); + _constantToParameterMap = ExpressionParameterVisitor.Visit(preTransformResult); - Key = ExpressionKeyVisitor.Visit(_expression, _constantToParameterMap); + ParameterValuesByName = _constantToParameterMap.Values.Distinct().ToDictionary(p => p.Name, + p => System.Tuple.Create(p.Value, p.Type)); + NamedParameters = _constantToParameterMap.Values.Distinct().ToDictionary(p => p.Name); + Key = ExpressionKeyVisitor.Visit(_expression, _constantToParameterMap, sessionFactory); Type = _expression.Type; @@ -78,6 +89,8 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter var requiredHqlParameters = new List(); var queryModel = NhRelinqQueryParser.Parse(_expression); + queryModel.TransformExpressions(TransparentIdentifierRemovingExpressionVisitor.ReplaceTransparentIdentifiers); + ParameterTypeLocator.SetParameterTypes(_constantToParameterMap, queryModel, TargetType, sessionFactory, true); var visitorParameters = new VisitorParameters(sessionFactory, _constantToParameterMap, requiredHqlParameters, new QuerySourceNamer(), TargetType, QueryMode); @@ -88,7 +101,7 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter ParameterDescriptors = requiredHqlParameters.AsReadOnly(); - CanCachePlan = CanCachePlan && + CanCachePlan = CanCachePlan && visitorParameters.CanCachePlan && // If some constants do not have matching HQL parameters, their values from first query will // be embedded in the plan and reused for subsequent queries: do not cache the plan. !ParameterValuesByName diff --git a/src/NHibernate/Linq/NhQueryable.cs b/src/NHibernate/Linq/NhQueryable.cs index f1ef60f137a..949f851c923 100644 --- a/src/NHibernate/Linq/NhQueryable.cs +++ b/src/NHibernate/Linq/NhQueryable.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using NHibernate.Engine; @@ -17,7 +18,7 @@ interface IEntityNameProvider /// /// Provides the main entry point to a LINQ query. /// - public class NhQueryable : QueryableBase, IEntityNameProvider + public class NhQueryable : QueryableBase, IEntityNameProvider, IEnumerable { // This constructor is called by our users, create a new IQueryExecutor. public NhQueryable(ISessionImplementor session) @@ -32,7 +33,6 @@ public NhQueryable(ISessionImplementor session, string entityName) EntityName = entityName; } - // This constructor is called indirectly by LINQ's query methods, just pass to base. public NhQueryable(IQueryProvider provider, Expression expression) : this(provider, expression, typeof(T).FullName) @@ -58,5 +58,14 @@ public override string ToString() { return "NHibernate.Linq.NhQueryable`1[" + EntityName + "]"; } + + IEnumerator IEnumerable.GetEnumerator() + { + //TODO 6.0: Cast to INhQueryProvider + return + Provider is DefaultQueryProvider nhProvider + ? nhProvider.ExecuteList(Expression).GetEnumerator() + : base.GetEnumerator(); + } } } diff --git a/src/NHibernate/Linq/NhQueryableOptions.cs b/src/NHibernate/Linq/NhQueryableOptions.cs index 3ae5b94d297..b6a8a77de7b 100644 --- a/src/NHibernate/Linq/NhQueryableOptions.cs +++ b/src/NHibernate/Linq/NhQueryableOptions.cs @@ -14,6 +14,7 @@ public class NhQueryableOptions protected int? Timeout { get; private set; } protected bool? ReadOnly { get; private set; } protected string Comment { get; private set; } + protected FlushMode? FlushMode { get; private set; } #pragma warning disable 618 /// @@ -112,6 +113,17 @@ public NhQueryableOptions SetComment(string comment) Comment = comment; return this; } + + /// + /// Override the current session flush mode, just for this query. + /// + /// The flush mode to use for the query. + /// (for method chaining). + public NhQueryableOptions SetFlushMode(FlushMode flushMode) + { + FlushMode = flushMode; + return this; + } protected internal NhQueryableOptions Clone() { @@ -122,7 +134,8 @@ protected internal NhQueryableOptions Clone() CacheRegion = CacheRegion, Timeout = Timeout, ReadOnly = ReadOnly, - Comment = Comment + Comment = Comment, + FlushMode = FlushMode }; } @@ -145,6 +158,9 @@ protected internal void Apply(IQuery query) if (!string.IsNullOrEmpty(Comment)) query.SetComment(Comment); + + if (FlushMode.HasValue) + query.SetFlushMode(FlushMode.Value); } } } diff --git a/src/NHibernate/Linq/NhRelinqQueryParser.cs b/src/NHibernate/Linq/NhRelinqQueryParser.cs index bafd050280b..eba4a3d6283 100644 --- a/src/NHibernate/Linq/NhRelinqQueryParser.cs +++ b/src/NHibernate/Linq/NhRelinqQueryParser.cs @@ -1,10 +1,13 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using NHibernate.Engine; using NHibernate.Linq.ExpressionTransformers; using NHibernate.Linq.Visitors; +using NHibernate.Param; using NHibernate.Util; using Remotion.Linq; using Remotion.Linq.EagerFetching.Parsing; @@ -18,16 +21,9 @@ namespace NHibernate.Linq public static class NhRelinqQueryParser { private static readonly QueryParser QueryParser; - private static readonly IExpressionTreeProcessor PreProcessor; static NhRelinqQueryParser() { - var preTransformerRegistry = new ExpressionTransformerRegistry(); - // NH-3247: must remove .Net compiler char to int conversion before - // parameterization occurs. - preTransformerRegistry.Register(new RemoveCharToIntConversion()); - PreProcessor = new TransformingExpressionTreeProcessor(preTransformerRegistry); - var transformerRegistry = ExpressionTransformerRegistry.CreateDefault(); transformerRegistry.Register(new RemoveRedundantCast()); transformerRegistry.Register(new SimplifyCompareTransformer()); @@ -44,22 +40,57 @@ static NhRelinqQueryParser() QueryParser = new QueryParser(expressionTreeParser); } + // Obsolete since v5.3 /// - /// Applies the minimal transformations required before parameterization, + /// Applies the minimal transformations required before parametrization, /// expression key computing and parsing. /// /// The expression to transform. /// The transformed expression. + [Obsolete("Use overload with PreTransformationParameters parameter")] public static Expression PreTransform(Expression expression) { - var partiallyEvaluatedExpression = NhPartialEvaluatingExpressionVisitor.EvaluateIndependentSubtrees(expression); - return PreProcessor.Process(partiallyEvaluatedExpression); + // In order to keep the old behavior use a DML query mode to skip detecting variables, + // which will then generate parameters for each constant expression + return PreTransform(expression, new PreTransformationParameters(QueryMode.Delete, null)).Expression; + } + + /// + /// Applies the minimal transformations required before parametrization, + /// expression key computing and parsing. + /// + /// The expression to transform. + /// The parameters used in the transformation process. + /// that contains the transformed expression. + public static PreTransformationResult PreTransform(Expression expression, PreTransformationParameters parameters) + { + parameters.EvaluatableExpressionFilter = new NhEvaluatableExpressionFilter(parameters.SessionFactory); + parameters.QueryVariables = new Dictionary(); + + var partiallyEvaluatedExpression = NhPartialEvaluatingExpressionVisitor + .EvaluateIndependentSubtrees(expression, parameters); + + return new PreTransformationResult( + parameters.PreTransformer.Invoke(partiallyEvaluatedExpression), + parameters.SessionFactory, + parameters.QueryVariables); } public static QueryModel Parse(Expression expression) { return QueryParser.GetParsedQuery(expression); } + + internal static Func CreatePreTransformer(IExpressionTransformerRegistrar expressionTransformerRegistrar) + { + var preTransformerRegistry = new ExpressionTransformerRegistry(); + // NH-3247: must remove .Net compiler char to int conversion before + // parameterization occurs. + preTransformerRegistry.Register(new RemoveCharToIntConversion()); + expressionTransformerRegistrar?.Register(preTransformerRegistry); + + return new TransformingExpressionTreeProcessor(preTransformerRegistry).Process; + } } public class NHibernateNodeTypeProvider : INodeTypeProvider @@ -71,25 +102,25 @@ public NHibernateNodeTypeProvider() var methodInfoRegistry = new MethodInfoBasedNodeTypeRegistry(); methodInfoRegistry.Register( - new[] { ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.Fetch(null, null)) }, + new[] { ReflectHelper.FastGetMethodDefinition(EagerFetchingExtensionMethods.Fetch, default(IQueryable), default(Expression>)) }, typeof(FetchOneExpressionNode)); methodInfoRegistry.Register( - new[] { ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.FetchLazyProperties(null)) }, + new[] { ReflectHelper.FastGetMethodDefinition(EagerFetchingExtensionMethods.FetchLazyProperties, default(IQueryable)) }, typeof(FetchLazyPropertiesExpressionNode)); methodInfoRegistry.Register( - new[] { ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.FetchMany(null, null)) }, + new[] { ReflectHelper.FastGetMethodDefinition(EagerFetchingExtensionMethods.FetchMany, default(IQueryable), default(Expression>>)) }, typeof(FetchManyExpressionNode)); methodInfoRegistry.Register( - new[] { ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.ThenFetch(null, null)) }, + new[] { ReflectHelper.FastGetMethodDefinition(EagerFetchingExtensionMethods.ThenFetch, default(INhFetchRequest), default(Expression>)) }, typeof(ThenFetchOneExpressionNode)); methodInfoRegistry.Register( - new[] { ReflectHelper.GetMethodDefinition(() => EagerFetchingExtensionMethods.ThenFetchMany(null, null)) }, + new[] { ReflectHelper.FastGetMethodDefinition( EagerFetchingExtensionMethods.ThenFetchMany, default(INhFetchRequest), default(Expression>>)) }, typeof(ThenFetchManyExpressionNode)); methodInfoRegistry.Register( new[] { - ReflectHelper.GetMethodDefinition(() => default(IQueryable).WithLock(LockMode.Read)), - ReflectHelper.GetMethodDefinition(() => default(IEnumerable).WithLock(LockMode.Read)) + ReflectHelper.FastGetMethodDefinition(LinqExtensionMethods.WithLock, default(IQueryable), default(LockMode)), + ReflectHelper.FastGetMethodDefinition(LinqExtensionMethods.WithLock, default(IEnumerable), default(LockMode)) }, typeof(LockExpressionNode)); diff --git a/src/NHibernate/Linq/QuerySourceNamer.cs b/src/NHibernate/Linq/QuerySourceNamer.cs index cccc767e44b..a989b83c113 100644 --- a/src/NHibernate/Linq/QuerySourceNamer.cs +++ b/src/NHibernate/Linq/QuerySourceNamer.cs @@ -10,8 +10,8 @@ namespace NHibernate.Linq /// public class QuerySourceNamer { - private readonly IDictionary _map = new Dictionary(); - private readonly IList _names = new List(); + private readonly Dictionary _map = new Dictionary(); + private readonly List _names = new List(); private int _differentiator = 1; public void Add(IQuerySource querySource) @@ -22,6 +22,14 @@ public void Add(IQuerySource querySource) _map.Add(querySource, CreateUniqueName(querySource.ItemName)); } + internal void Add(IQuerySource querySource, string name) + { + if (_map.ContainsKey(querySource)) + return; + + _map.Add(querySource, name); + } + public string GetName(IQuerySource querySource) { string result; @@ -53,4 +61,4 @@ private string CreateUniqueName(string proposedName) return uniqueName; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs index 576bb65a9ea..a664bb0d101 100644 --- a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs +++ b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs @@ -1,8 +1,10 @@ using System; using System.Linq; +using System.Linq.Expressions; using NHibernate.Engine; using NHibernate.Linq.Clauses; using NHibernate.Linq.Visitors; +using NHibernate.Util; using Remotion.Linq; using Remotion.Linq.Clauses; @@ -10,11 +12,10 @@ namespace NHibernate.Linq.ReWriters { internal interface IIsEntityDecider { - bool IsEntity(System.Type type); - bool IsIdentifier(System.Type type, string propertyName); + bool IsEntity(MemberExpression expression, out bool isIdentifier); } - public class AddJoinsReWriter : NhQueryModelVisitorBase, IIsEntityDecider + public class AddJoinsReWriter : NhQueryModelVisitorBase, IIsEntityDecider, INhQueryModelVisitorExtended { private readonly ISessionFactoryImplementor _sessionFactory; private readonly MemberExpressionJoinDetector _memberExpressionJoinDetector; @@ -24,8 +25,8 @@ private AddJoinsReWriter(ISessionFactoryImplementor sessionFactory, QueryModel q { _sessionFactory = sessionFactory; var joiner = new Joiner(queryModel); - _memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner); - _whereJoinDetector = new WhereJoinDetector(this, joiner); + _memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner, _sessionFactory); + _whereJoinDetector = new WhereJoinDetector(this, joiner, _sessionFactory); } public static void ReWrite(QueryModel queryModel, VisitorParameters parameters) @@ -59,15 +60,43 @@ public override void VisitNhHavingClause(NhHavingClause havingClause, QueryModel _whereJoinDetector.Transform(havingClause); } + public void VisitNhOuterJoinClause(NhOuterJoinClause nhOuterJoinClause, QueryModel queryModel, int index) + { + VisitJoinClause(nhOuterJoinClause.JoinClause); + } + + public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, int index) + { + VisitJoinClause(joinClause); + } + + private void VisitJoinClause(JoinClause joinClause) + { + joinClause.InnerSequence = _whereJoinDetector.Transform(joinClause.InnerSequence); + } + + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] public bool IsEntity(System.Type type) { return _sessionFactory.GetImplementors(type.FullName).Any(); } + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] public bool IsIdentifier(System.Type type, string propertyName) { var metadata = _sessionFactory.GetClassMetadata(type); return metadata != null && propertyName.Equals(metadata.IdentifierPropertyName); } + + bool IIsEntityDecider.IsEntity(MemberExpression expression, out bool isIdentifier) + { + isIdentifier = + ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out var mappedType, out var entityPersister, out _, out var memberPath) + && entityPersister?.IdentifierPropertyName == memberPath; + + return mappedType?.IsEntityType == true; + } } } diff --git a/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs b/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs index b83be7077b4..c67039b1863 100644 --- a/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs +++ b/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs @@ -31,6 +31,14 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que _expander.Transform(selectClause); } + public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel) + { + if (fromClause.FromExpression is SubQueryExpression subqueryExpression) + { + VisitQueryModel(subqueryExpression.QueryModel); + } + } + public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index) { _expander.Transform(ordering); diff --git a/src/NHibernate/Linq/ReWriters/MoveOrderByToEndRewriter.cs b/src/NHibernate/Linq/ReWriters/MoveOrderByToEndRewriter.cs index d5434b6e347..a8bce6db4b7 100644 --- a/src/NHibernate/Linq/ReWriters/MoveOrderByToEndRewriter.cs +++ b/src/NHibernate/Linq/ReWriters/MoveOrderByToEndRewriter.cs @@ -14,11 +14,10 @@ public static void ReWrite(QueryModel queryModel) int len = queryModel.BodyClauses.Count; for(int i=0; i= 0; i--) + { + if (bodyClauses[i] is OrderByClause) + bodyClauses.RemoveAt(i); + } + } + + internal static bool IsOrderByNeeded(QueryModel queryModel) + { + switch (queryModel.ResultOperators.Count) + { + case 1: + var r = queryModel.ResultOperators[0]; + return !(r is AnyResultOperator || r is AllResultOperator || r is ContainsResultOperator); + case 0: + var s = queryModel.SelectClause.Selector; + return !(s is NhAggregatedExpression) || s is NhDistinctExpression; + } + + return true; + } + public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index) { if (resultOperator is CountResultOperator || resultOperator is LongCountResultOperator) { - // For count operators, we can remove any order-by result operators + // For count operators, we can remove any order-by clause var bodyClauses = queryModel.BodyClauses.OfType().ToList(); foreach (var orderby in bodyClauses) { diff --git a/src/NHibernate/Linq/ReWriters/ResultOperatorRemover.cs b/src/NHibernate/Linq/ReWriters/ResultOperatorRemover.cs index 987bb3cef9b..8992847221c 100644 --- a/src/NHibernate/Linq/ReWriters/ResultOperatorRemover.cs +++ b/src/NHibernate/Linq/ReWriters/ResultOperatorRemover.cs @@ -47,6 +47,5 @@ public override void VisitMainFromClause(MainFromClause fromClause, QueryModel q VisitQueryModel(subQueryExpression.QueryModel); base.VisitMainFromClause(fromClause, queryModel); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs b/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs index 3c6d3a4231f..86ce3072eab 100644 --- a/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs +++ b/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs @@ -24,6 +24,14 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que _expander.Transform(selectClause); } + public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel) + { + if (fromClause.FromExpression is SubQueryExpression subqueryExpression) + { + VisitQueryModel(subqueryExpression.QueryModel); + } + } + public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index) { _expander.Transform(ordering); @@ -95,7 +103,6 @@ protected override Expression VisitConditional(ConditionalExpression node) var newTest = Visit(node.Test); _nominate.Pop(); - _nominate.Push(false); var newTrue = Visit(node.IfTrue); if (_nominate.Pop() && _subQueryCollectionType.IsAssignableFrom(newTrue.Type)) diff --git a/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs b/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs index 494faecd89a..fdce29edd9a 100644 --- a/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs +++ b/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs @@ -7,7 +7,10 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using NHibernate.Engine; using NHibernate.Param; +using NHibernate.Type; +using NHibernate.Util; using Remotion.Linq.Parsing; namespace NHibernate.Linq.Visitors @@ -22,22 +25,46 @@ namespace NHibernate.Linq.Visitors public class ExpressionKeyVisitor : RelinqExpressionVisitor { private readonly IDictionary _constantToParameterMap; + private readonly ISessionFactoryImplementor _sessionFactory; readonly StringBuilder _string = new StringBuilder(); - private ExpressionKeyVisitor(IDictionary constantToParameterMap) + private ExpressionKeyVisitor( + IDictionary constantToParameterMap, + ISessionFactoryImplementor sessionFactory) { _constantToParameterMap = constantToParameterMap; + _sessionFactory = sessionFactory; } + // Since v5.3 + [Obsolete("Use the overload with ISessionFactoryImplementor parameter")] public static string Visit(Expression expression, IDictionary parameters) { - var visitor = new ExpressionKeyVisitor(parameters); + var visitor = new ExpressionKeyVisitor(parameters, null); visitor.Visit(expression); return visitor.ToString(); } + /// + /// Generates the key for the expression. + /// + /// The expression. + /// The session factory. + /// Parameters found in . + /// The key for the expression. + public static string Visit( + Expression rootExpression, + IDictionary parameters, + ISessionFactoryImplementor sessionFactory) + { + var visitor = new ExpressionKeyVisitor(parameters, sessionFactory); + visitor.Visit(rootExpression); + + return visitor.ToString(); + } + public override string ToString() { return _string.ToString(); @@ -86,49 +113,70 @@ protected override Expression VisitConstant(ConstantExpression expression) throw new InvalidOperationException("Cannot visit a constant without a constant to parameter map."); if (_constantToParameterMap.TryGetValue(expression, out param)) { - // Nulls generate different query plans. X = variable generates a different query depending on if variable is null or not. - if (param.Value == null) - { - _string.Append("NULL"); - } - else - { - var value = param.Value as IEnumerable; - if (value != null && !(value is string) && !value.Cast().Any()) - { - _string.Append("EmptyList"); - } - else - { - _string.Append(param.Name); - } - } + VisitParameter(param); } else { - if (expression.Value == null) - { - _string.Append("NULL"); - } - else - { - var value = expression.Value as IEnumerable; - if (value != null && !(value is string) && !(value is IQueryable)) - { - _string.Append("{"); - _string.Append(String.Join(",", value.Cast())); - _string.Append("}"); - } - else - { - _string.Append(expression.Value); - } - } + VisitConstantValue(expression.Value); } return base.VisitConstant(expression); } + private void VisitConstantValue(object value) + { + if (value == null) + { + _string.Append("NULL"); + return; + } + + if (value is IEnumerable enumerable && !(value is IQueryable)) + { + _string.Append("{"); + _string.Append(string.Join(",", enumerable.Cast())); + _string.Append("}"); + return; + } + + // When MappedAs is used we have to put all sql types information in the key in order to + // distinct when different precisions/sizes are used. + if (_sessionFactory != null && value is IType type) + { + _string.Append(type.Name); + _string.Append('['); + _string.Append(string.Join(",", type.SqlTypes(_sessionFactory).Select(o => o.ToString()))); + _string.Append(']'); + return; + } + + _string.Append(value); + } + + private void VisitParameter(NamedParameter param) + { + // Nulls generate different query plans. X = variable generates a different query depending on if variable is null or not. + if (param.Value == null) + { + _string.Append("NULL"); + return; + } + + if (param.IsCollection && !((IEnumerable) param.Value).Cast().Any()) + { + _string.Append("EmptyList"); + } + else + { + _string.Append(param.Name); + } + + // Add the type in order to avoid invalid parameter conversions (string -> char) + _string.Append("<"); + _string.Append(param.Value.GetType()); + _string.Append(">"); + } + private T AppendCommas(T expression) where T : Expression { Visit(expression); @@ -159,6 +207,20 @@ protected override Expression VisitMember(MemberExpression expression) return expression; } +#if NETCOREAPP2_0 + protected override Expression VisitInvocation(InvocationExpression expression) + { + if (ExpressionsHelper.TryGetDynamicMemberBinder(expression, out var memberBinder)) + { + Visit(expression.Arguments[1]); + FormatBinder(memberBinder); + return expression; + } + + return base.VisitInvocation(expression); + } +#endif + protected override Expression VisitMethodCall(MethodCallExpression expression) { Visit(expression.Object); @@ -218,8 +280,8 @@ protected override Expression VisitQuerySourceReference(Remotion.Linq.Clauses.Ex protected override Expression VisitDynamic(DynamicExpression expression) { - FormatBinder(expression.Binder); Visit(expression.Arguments, AppendCommas); + FormatBinder(expression.Binder); return expression; } @@ -229,7 +291,7 @@ private void VisitMethod(MethodInfo methodInfo) if (methodInfo.IsGenericMethod) { _string.Append('['); - _string.Append(string.Join(",", methodInfo.GetGenericArguments().Select(a => a.AssemblyQualifiedName).ToArray())); + _string.Append(string.Join(",", methodInfo.GetGenericArguments().Select(a => a.AssemblyQualifiedName))); _string.Append(']'); } } diff --git a/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs b/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs index 11e52cdb23e..a70109bd59c 100644 --- a/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs +++ b/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs @@ -1,9 +1,11 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using NHibernate.Engine; +using NHibernate.Linq.Functions; using NHibernate.Param; using NHibernate.Type; using NHibernate.Util; @@ -17,47 +19,57 @@ namespace NHibernate.Linq.Visitors public class ExpressionParameterVisitor : RelinqExpressionVisitor { private readonly Dictionary _parameters = new Dictionary(); + private readonly Dictionary _variableParameters = new Dictionary(); + private readonly HashSet _collectionParameters = new HashSet(); + private readonly IDictionary _queryVariables; private readonly ISessionFactoryImplementor _sessionFactory; + private readonly ILinqToHqlGeneratorsRegistry _functionRegistry; - private static readonly MethodInfo QueryableSkipDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Skip(null, 0)); - private static readonly MethodInfo QueryableTakeDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Take(null, 0)); - private static readonly MethodInfo EnumerableSkipDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Skip(null, 0)); - private static readonly MethodInfo EnumerableTakeDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Take(null, 0)); - - private readonly ICollection _pagingMethods = new HashSet - { - QueryableSkipDefinition, QueryableTakeDefinition, - EnumerableSkipDefinition, EnumerableTakeDefinition - }; - + private static readonly HashSet PagingMethods = new HashSet + { + ReflectionCache.EnumerableMethods.SkipDefinition, + ReflectionCache.EnumerableMethods.TakeDefinition, + ReflectionCache.QueryableMethods.SkipDefinition, + ReflectionCache.QueryableMethods.TakeDefinition + }; + + // Since v5.3 + [Obsolete("Please use overload with preTransformationResult parameter instead.")] public ExpressionParameterVisitor(ISessionFactoryImplementor sessionFactory) { _sessionFactory = sessionFactory; } - public static IDictionary Visit(Expression expression, ISessionFactoryImplementor sessionFactory) + public ExpressionParameterVisitor(PreTransformationResult preTransformationResult) { - return Visit(ref expression, sessionFactory); + _sessionFactory = preTransformationResult.SessionFactory; + _queryVariables = preTransformationResult.QueryVariables; + _functionRegistry = _sessionFactory.Settings.LinqToHqlGeneratorsRegistry; } - internal static IDictionary Visit(ref Expression expression, ISessionFactoryImplementor sessionFactory) + // Since v5.3 + [Obsolete("Please use overload with preTransformationResult parameter instead.")] + public static IDictionary Visit(Expression expression, ISessionFactoryImplementor sessionFactory) { var visitor = new ExpressionParameterVisitor(sessionFactory); + visitor.Visit(expression); - expression = visitor.Visit(expression); + return visitor._parameters; + } + public static IDictionary Visit(PreTransformationResult preTransformationResult) + { + var visitor = new ExpressionParameterVisitor(preTransformationResult); + visitor.Visit(preTransformationResult.Expression); return visitor._parameters; } protected override Expression VisitMethodCall(MethodCallExpression expression) { - if (expression.Method.Name == nameof(LinqExtensionMethods.MappedAs) && expression.Method.DeclaringType == typeof(LinqExtensionMethods)) + if (VisitorUtil.IsMappedAs(expression.Method)) { var rawParameter = Visit(expression.Arguments[0]); + // TODO 6.0: Remove below code and return expression as this logic is now inside ConstantTypeLocator var parameter = rawParameter as ConstantExpression; var type = expression.Arguments[1] as ConstantExpression; if (parameter == null) @@ -78,10 +90,10 @@ protected override Expression VisitMethodCall(MethodCallExpression expression) ? expression.Method.GetGenericMethodDefinition() : expression.Method; - if (_pagingMethods.Contains(method) && !_sessionFactory.Dialect.SupportsVariableLimit) + if (PagingMethods.Contains(method) && !_sessionFactory.Dialect.SupportsVariableLimit) { - //TODO: find a way to make this code cleaner var query = Visit(expression.Arguments[0]); + //TODO 6.0: Remove the below code and return expression var arg = expression.Arguments[1]; if (query == expression.Arguments[0]) @@ -90,6 +102,13 @@ protected override Expression VisitMethodCall(MethodCallExpression expression) return Expression.Call(null, expression.Method, query, arg); } + if (_functionRegistry != null && + _functionRegistry.TryGetGenerator(method, out var generator) && + generator.TryGetCollectionParameters(expression, out var collectionParameter)) + { + _collectionParameters.Add(collectionParameter); + } + if (VisitorUtil.IsDynamicComponentDictionaryGetter(expression, _sessionFactory)) { return expression; @@ -98,6 +117,20 @@ protected override Expression VisitMethodCall(MethodCallExpression expression) return base.VisitMethodCall(expression); } +#if NETCOREAPP2_0 + protected override Expression VisitInvocation(InvocationExpression expression) + { + if (ExpressionsHelper.TryGetDynamicMemberBinder(expression, out _)) + { + // Avoid adding System.Runtime.CompilerServices.CallSite instance as a parameter + base.Visit(expression.Arguments[1]); + return expression; + } + + return base.VisitInvocation(expression); + } +#endif + protected override Expression VisitConstant(ConstantExpression expression) { if (!_parameters.ContainsKey(expression) && !typeof(IQueryable).IsAssignableFrom(expression.Type) && !IsNullObject(expression)) @@ -108,11 +141,14 @@ protected override Expression VisitConstant(ConstantExpression expression) // We have a bit more information about the null parameter value. // Figure out a type so that HQL doesn't break on the null. (Related to NH-2430) + // In v5.3 types are calculated by ParameterTypeLocator, this logic is only for back compatibility. + // TODO 6.0: Remove if (expression.Value == null) type = NHibernateUtil.GuessType(expression.Type); // Constant characters should be sent as strings - if (expression.Type == typeof(char)) + // TODO 6.0: Remove + if (_queryVariables == null && expression.Type == typeof(char)) { value = value.ToString(); } @@ -122,12 +158,37 @@ protected override Expression VisitConstant(ConstantExpression expression) // comes up, it would be nice to combine the HQL parameter type determination code // and the Expression information. - _parameters.Add(expression, new NamedParameter("p" + (_parameters.Count + 1), value, type)); + NamedParameter parameter = null; + if (_queryVariables != null && + _queryVariables.TryGetValue(expression, out var variable) && + !_variableParameters.TryGetValue(variable, out parameter)) + { + parameter = CreateParameter(expression, value, type); + _variableParameters.Add(variable, parameter); + } + + if (parameter == null) + { + parameter = CreateParameter(expression, value, type); + } + + _parameters.Add(expression, parameter); + + return base.VisitConstant(expression); } return base.VisitConstant(expression); } + private NamedParameter CreateParameter(ConstantExpression expression, object value, IType type) + { + return new NamedParameter( + "p" + (_parameters.Count + 1), + value, + type, + _collectionParameters.Contains(expression)); + } + private static bool IsNullObject(ConstantExpression expression) { return expression.Type == typeof(Object) && expression.Value == null; diff --git a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs index a5722a9a96b..0022897b09f 100644 --- a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs +++ b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs @@ -1,13 +1,18 @@ using System; +using System.Collections.Generic; +using System.Data; using System.Dynamic; using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; +using NHibernate.Dialect.Function; using NHibernate.Engine.Query; using NHibernate.Hql.Ast; +using NHibernate.Hql.Ast.ANTLR; using NHibernate.Linq.Expressions; using NHibernate.Linq.Functions; using NHibernate.Param; +using NHibernate.Type; using NHibernate.Util; using Remotion.Linq.Clauses.Expressions; @@ -18,22 +23,23 @@ public class HqlGeneratorExpressionVisitor : IHqlExpressionVisitor private readonly HqlTreeBuilder _hqlTreeBuilder = new HqlTreeBuilder(); private readonly VisitorParameters _parameters; private readonly ILinqToHqlGeneratorsRegistry _functionRegistry; + private readonly NullableExpressionDetector _nullableExpressionDetector; + private readonly Dictionary _notCastableExpressions = new Dictionary(); public static HqlTreeNode Visit(Expression expression, VisitorParameters parameters) { - return new HqlGeneratorExpressionVisitor(parameters).VisitExpression(expression); + return new HqlGeneratorExpressionVisitor(parameters).Visit(expression); } public HqlGeneratorExpressionVisitor(VisitorParameters parameters) { _functionRegistry = parameters.SessionFactory.Settings.LinqToHqlGeneratorsRegistry; _parameters = parameters; + _nullableExpressionDetector = new NullableExpressionDetector(_parameters.SessionFactory, _functionRegistry); } - public ISessionFactory SessionFactory { get { return _parameters.SessionFactory; } } - public HqlTreeNode Visit(Expression expression) { return VisitExpression(expression); @@ -222,33 +228,50 @@ private HqlTreeNode VisitNhNominated(NhNominatedExpression nhNominatedExpression private HqlTreeNode VisitInvocationExpression(InvocationExpression expression) { - //This is an ugly workaround for dynamic expressions. - //Unfortunately we can not tap into the expression tree earlier to intercept the dynamic expression - if (expression.Arguments.Count == 2 && - expression.Arguments[0] is ConstantExpression constant && - constant.Value is CallSite site && - site.Binder is GetMemberBinder binder) +#if NETCOREAPP2_0 + if (ExpressionsHelper.TryGetDynamicMemberBinder(expression, out var binder)) { return _hqlTreeBuilder.Dot( VisitExpression(expression.Arguments[1]).AsExpression(), _hqlTreeBuilder.Ident(binder.Name)); } - +#endif return VisitExpression(expression.Expression); } protected HqlTreeNode VisitNhAverage(NhAverageExpression expression) { + // We need to cast the argument when its type is different from Average method return type, + // otherwise the result may be incorrect. In SQL Server avg always returns int + // when the argument is int. var hqlExpression = VisitExpression(expression.Expression).AsExpression(); - if (expression.Type != expression.Expression.Type) - hqlExpression = _hqlTreeBuilder.Cast(hqlExpression, expression.Type); + hqlExpression = IsCastRequired(expression.Expression, expression.Type, out _) + ? (HqlExpression) _hqlTreeBuilder.Cast(hqlExpression, expression.Type) + : _hqlTreeBuilder.TransparentCast(hqlExpression, expression.Type); + // In Oracle the avg function can return a number with up to 40 digits which cannot be retrieved from the data reader due to the lack of such + // numeric type in .NET. In order to avoid that we have to add a cast to trim the number so that it can be converted into a .NET numeric type. return _hqlTreeBuilder.Cast(_hqlTreeBuilder.Average(hqlExpression), expression.Type); } protected HqlTreeNode VisitNhCount(NhCountExpression expression) { - return _hqlTreeBuilder.Cast(_hqlTreeBuilder.Count(VisitExpression(expression.Expression).AsExpression()), expression.Type); + string functionName; + HqlExpression countHqlExpression; + if (expression is NhLongCountExpression) + { + functionName = "count_big"; + countHqlExpression = _hqlTreeBuilder.CountBig(VisitExpression(expression.Expression).AsExpression()); + } + else + { + functionName = "count"; + countHqlExpression = _hqlTreeBuilder.Count(VisitExpression(expression.Expression).AsExpression()); + } + + return IsCastRequired(functionName, expression.Expression, expression.Type) + ? (HqlTreeNode) _hqlTreeBuilder.Cast(countHqlExpression, expression.Type) + : _hqlTreeBuilder.TransparentCast(countHqlExpression, expression.Type); } protected HqlTreeNode VisitNhMin(NhMinExpression expression) @@ -263,7 +286,9 @@ protected HqlTreeNode VisitNhMax(NhMaxExpression expression) protected HqlTreeNode VisitNhSum(NhSumExpression expression) { - return _hqlTreeBuilder.Cast(_hqlTreeBuilder.Sum(VisitExpression(expression.Expression).AsExpression()), expression.Type); + return IsCastRequired("sum", expression.Expression, expression.Type) + ? (HqlTreeNode) _hqlTreeBuilder.Cast(_hqlTreeBuilder.Sum(VisitExpression(expression.Expression).AsExpression()), expression.Type) + : _hqlTreeBuilder.TransparentCast(_hqlTreeBuilder.Sum(VisitExpression(expression.Expression).AsExpression()), expression.Type); } protected HqlTreeNode VisitNhDistinct(NhDistinctExpression expression) @@ -285,6 +310,17 @@ private HqlTreeNode VisitVBStringComparisonExpression(VBStringComparisonExpressi protected HqlTreeNode VisitBinaryExpression(BinaryExpression expression) { + // There are some cases where we do not want to add a sql cast: + // - When comparing numeric types that do not have their own operator (e.g. short == short) + // - When comparing a member expression with a parameter of similar type (e.g. o.Short == intParameter) + var leftType = GetExpressionType(expression.Left); + var rightType = GetExpressionType(expression.Right); + if (leftType != null && leftType == rightType) + { + _notCastableExpressions.Add(expression.Left, leftType); + _notCastableExpressions.Add(expression.Right, rightType); + } + if (expression.NodeType == ExpressionType.Equal) { return TranslateEqualityComparison(expression); @@ -294,6 +330,8 @@ protected HqlTreeNode VisitBinaryExpression(BinaryExpression expression) return TranslateInequalityComparison(expression); } + _nullableExpressionDetector.SearchForNotNullMemberChecks(expression); + var lhs = VisitExpression(expression.Left).AsExpression(); var rhs = VisitExpression(expression.Right).AsExpression(); @@ -349,6 +387,23 @@ protected HqlTreeNode VisitBinaryExpression(BinaryExpression expression) throw new InvalidOperationException(); } + private System.Type GetExpressionType(Expression expression) + { + switch (expression.NodeType) + { + case ExpressionType.Constant: + return _parameters.ConstantToParameterMap.TryGetValue((ConstantExpression) expression, out var param) + ? param.Type?.ReturnedClass + : null; + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var operand = ((UnaryExpression) expression).Operand; + return GetExpressionType(operand) ?? operand.Type.UnwrapIfNullable(); + } + + return null; + } + private HqlTreeNode TranslateInequalityComparison(BinaryExpression expression) { var lhs = VisitExpression(expression.Left).ToArithmeticExpression(); @@ -375,8 +430,8 @@ private HqlTreeNode TranslateInequalityComparison(BinaryExpression expression) return _hqlTreeBuilder.IsNotNull(lhs); } - var lhsNullable = IsNullable(lhs); - var rhsNullable = IsNullable(rhs); + var lhsNullable = _nullableExpressionDetector.IsNullable(expression.Left, expression); + var rhsNullable = _nullableExpressionDetector.IsNullable(expression.Right, expression); var inequality = _hqlTreeBuilder.Inequality(lhs, rhs); @@ -438,8 +493,8 @@ private HqlTreeNode TranslateEqualityComparison(BinaryExpression expression) return _hqlTreeBuilder.IsNull((lhs)); } - var lhsNullable = IsNullable(lhs); - var rhsNullable = IsNullable(rhs); + var lhsNullable = _nullableExpressionDetector.IsNullable(expression.Left, expression); + var rhsNullable = _nullableExpressionDetector.IsNullable(expression.Right, expression); var equality = _hqlTreeBuilder.Equality(lhs, rhs); @@ -458,12 +513,6 @@ private HqlTreeNode TranslateEqualityComparison(BinaryExpression expression) _hqlTreeBuilder.IsNull(rhs2))); } - static bool IsNullable(HqlExpression original) - { - var hqlDot = original as HqlDot; - return hqlDot != null && hqlDot.Children.Last() is HqlIdent; - } - protected HqlTreeNode VisitUnaryExpression(UnaryExpression expression) { switch (expression.NodeType) @@ -477,13 +526,18 @@ protected HqlTreeNode VisitUnaryExpression(UnaryExpression expression) case ExpressionType.Convert: case ExpressionType.ConvertChecked: case ExpressionType.TypeAs: - if ((expression.Operand.Type.IsPrimitive || expression.Operand.Type == typeof(Decimal)) && - (expression.Type.IsPrimitive || expression.Type == typeof(Decimal))) + var castable = !_notCastableExpressions.TryGetValue(expression, out var castType); + if (castable) { - return _hqlTreeBuilder.Cast(VisitExpression(expression.Operand).AsExpression(), expression.Type); + castType = expression.Type; } - return VisitExpression(expression.Operand); + return IsCastRequired(expression.Operand, castType, out var existType) && castable + ? _hqlTreeBuilder.Cast(VisitExpression(expression.Operand).AsExpression(), castType) + // Make a transparent cast when an IType exists, so that it can be used to retrieve the value from the data reader + : existType && HqlIdent.SupportsType(castType) + ? _hqlTreeBuilder.TransparentCast(VisitExpression(expression.Operand).AsExpression(), castType) + : VisitExpression(expression.Operand); } throw new NotSupportedException(expression.ToString()); @@ -581,8 +635,79 @@ protected HqlTreeNode VisitSubQueryExpression(SubQueryExpression expression) protected HqlTreeNode VisitNewArrayExpression(NewArrayExpression expression) { - var expressionSubTree = expression.Expressions.Select(exp => VisitExpression(exp)).ToArray(); + var expressionSubTree = expression.Expressions.ToArray(exp => VisitExpression(exp)); return _hqlTreeBuilder.ExpressionSubTreeHolder(expressionSubTree); } + + private bool IsCastRequired(Expression expression, System.Type toType, out bool existType) + { + existType = false; + return toType != typeof(object) && + expression.Type.UnwrapIfNullable() != toType.UnwrapIfNullable() && + IsCastRequired(ExpressionsHelper.GetType(_parameters, expression), TypeFactory.GetDefaultTypeFor(toType), out existType); + } + + private bool IsCastRequired(IType type, IType toType, out bool existType) + { + // A type can be null when casting an entity into a base class, in that case we should not cast + if (type == null || toType == null || Equals(type, toType)) + { + existType = false; + return false; + } + + var sqlTypes = type.SqlTypes(_parameters.SessionFactory); + var toSqlTypes = toType.SqlTypes(_parameters.SessionFactory); + if (sqlTypes.Length != 1 || toSqlTypes.Length != 1) + { + existType = false; + return false; // Casting a multi-column type is not possible + } + + existType = true; + if (sqlTypes[0].DbType == toSqlTypes[0].DbType) + { + return false; + } + + if (type.ReturnedClass.IsEnum && sqlTypes[0].DbType.IsStringType()) + { + existType = false; + return false; // Never cast an enum that is mapped as string, the type will provide a string for the parameter value + } + + // Some dialects can map several sql types into one, cast only if the dialect types are different + if (!_parameters.SessionFactory.Dialect.TryGetCastTypeName(sqlTypes[0], out var castTypeName) || + !_parameters.SessionFactory.Dialect.TryGetCastTypeName(toSqlTypes[0], out var toCastTypeName)) + { + return false; // The dialect does not support such cast + } + + return castTypeName != toCastTypeName; + } + + private bool IsCastRequired(string sqlFunctionName, Expression argumentExpression, System.Type returnType) + { + var argumentType = ExpressionsHelper.GetType(_parameters, argumentExpression); + if (argumentType == null || returnType == typeof(object)) + { + return false; + } + + var returnNhType = TypeFactory.GetDefaultTypeFor(returnType); + if (returnNhType == null) + { + return true; // Fallback to the old behavior + } + + var sqlFunction = _parameters.SessionFactory.SQLFunctionRegistry.FindSQLFunction(sqlFunctionName); + if (sqlFunction == null) + { + return true; // Fallback to the old behavior + } + + var fnReturnType = sqlFunction.GetEffectiveReturnType(new[] {argumentType}, _parameters.SessionFactory, false); + return fnReturnType == null || IsCastRequired(fnReturnType, returnNhType, out _); + } } } diff --git a/src/NHibernate/Linq/Visitors/IExpressionTransformerRegistrar.cs b/src/NHibernate/Linq/Visitors/IExpressionTransformerRegistrar.cs new file mode 100644 index 00000000000..c71a970325f --- /dev/null +++ b/src/NHibernate/Linq/Visitors/IExpressionTransformerRegistrar.cs @@ -0,0 +1,16 @@ +using Remotion.Linq.Parsing.ExpressionVisitors.Transformation; + +namespace NHibernate.Linq.Visitors +{ + /// + /// Provides a way to register custom transformers for expressions. + /// + public interface IExpressionTransformerRegistrar + { + /// + /// Registers additional transformers on the expression transformer registry. + /// + /// The expression transformer registry. + void Register(ExpressionTransformerRegistry expressionTransformerRegistry); + } +} diff --git a/src/NHibernate/Linq/Visitors/JoinBuilder.cs b/src/NHibernate/Linq/Visitors/JoinBuilder.cs index b84a72c7ac3..a898389fae5 100644 --- a/src/NHibernate/Linq/Visitors/JoinBuilder.cs +++ b/src/NHibernate/Linq/Visitors/JoinBuilder.cs @@ -25,7 +25,10 @@ internal Joiner(QueryModel queryModel) { _nameGenerator = new NameGenerator(queryModel); _queryModel = queryModel; + AddJoinMethod = AddJoin; } + + internal System.Action AddJoinMethod { get; } public IEnumerable Joins { @@ -39,7 +42,7 @@ public Expression AddJoin(Expression expression, string key) if (!_joins.TryGetValue(key, out join)) { join = new NhJoinClause(_nameGenerator.GetNewName(), expression.Type, expression); - _queryModel.BodyClauses.Add(join); + AddJoinMethod(_queryModel, join); _joins.Add(key, join); } @@ -72,6 +75,11 @@ public bool CanAddJoin(Expression expression) return resultOperatorBase != null && _queryModel.ResultOperators.Contains(resultOperatorBase); } + private void AddJoin(QueryModel queryModel, NhJoinClause joinClause) + { + queryModel.BodyClauses.Add(joinClause); + } + private class QuerySourceExtractor : RelinqExpressionVisitor { private IQuerySource _querySource; @@ -90,4 +98,4 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/LeftJoinRewriter.cs b/src/NHibernate/Linq/Visitors/LeftJoinRewriter.cs index 34d8b1ab6c6..913fd5e355f 100644 --- a/src/NHibernate/Linq/Visitors/LeftJoinRewriter.cs +++ b/src/NHibernate/Linq/Visitors/LeftJoinRewriter.cs @@ -43,8 +43,7 @@ public override void VisitAdditionalFromClause(AdditionalFromClause fromClause, queryModel.TransformExpressions(ex => ReferenceReplacingExpressionVisitor.ReplaceClauseReferences(ex, innerSelectorMapping, false)); - queryModel.BodyClauses.RemoveAt(index); - queryModel.BodyClauses.Insert(index, @join); + queryModel.BodyClauses[index] = @join; InsertBodyClauses(subQueryModel.BodyClauses.Where(b => !(b is WhereClause)), queryModel, index + 1); var innerBodyClauseMapping = new QuerySourceMapping(); @@ -68,4 +67,4 @@ private static bool IsLeftJoin(QueryModel subQueryModel) subQueryModel.ResultOperators[0] is DefaultIfEmptyResultOperator; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs b/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs index 934fba8ec94..019769fccb1 100644 --- a/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs +++ b/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; +using NHibernate.Engine; using NHibernate.Linq.Expressions; using NHibernate.Linq.ReWriters; using Remotion.Linq.Clauses; @@ -18,21 +19,30 @@ internal class MemberExpressionJoinDetector : RelinqExpressionVisitor { private readonly IIsEntityDecider _isEntityDecider; private readonly IJoiner _joiner; + private readonly ISessionFactoryImplementor _sessionFactory; private bool _requiresJoinForNonIdentifier; private bool _preventJoinsInConditionalTest; private bool _hasIdentifier; private int _memberExpressionDepth; - public MemberExpressionJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner) + public MemberExpressionJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner, ISessionFactoryImplementor sessionFactory) { _isEntityDecider = isEntityDecider; _joiner = joiner; + _sessionFactory = sessionFactory; } protected override Expression VisitMember(MemberExpression expression) { - var isIdentifier = _isEntityDecider.IsIdentifier(expression.Expression.Type, expression.Member.Name); + // A static member expression such as DateTime.Now has a null Expression. + if (expression.Expression == null) + { + // A static member call is never a join, and it is not an instance member access either. + return base.VisitMember(expression); + } + + var isEntity = _isEntityDecider.IsEntity(expression, out var isIdentifier); if (isIdentifier) _hasIdentifier = true; if (!isIdentifier) @@ -43,11 +53,11 @@ protected override Expression VisitMember(MemberExpression expression) if (!isIdentifier) _memberExpressionDepth--; - if (_isEntityDecider.IsEntity(expression.Type) && + if (isEntity && ((_requiresJoinForNonIdentifier && !_hasIdentifier) || _memberExpressionDepth > 0) && _joiner.CanAddJoin(expression)) { - var key = ExpressionKeyVisitor.Visit(expression, null); + var key = ExpressionKeyVisitor.Visit(expression, null, _sessionFactory); return _joiner.AddJoin(result, key); } diff --git a/src/NHibernate/Linq/Visitors/NhPartialEvaluatingExpressionVisitor.cs b/src/NHibernate/Linq/Visitors/NhPartialEvaluatingExpressionVisitor.cs index ae520e1904d..c92403cbc82 100644 --- a/src/NHibernate/Linq/Visitors/NhPartialEvaluatingExpressionVisitor.cs +++ b/src/NHibernate/Linq/Visitors/NhPartialEvaluatingExpressionVisitor.cs @@ -1,42 +1,231 @@ +// Copyright (c) rubicon IT GmbH, www.rubicon.eu +// +// See the NOTICE file distributed with this work for additional information +// regarding copyright ownership. rubicon licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may not use this +// file except in compliance with the License. You may obtain a copy of the +// License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// + using System; using System.Linq; using System.Linq.Expressions; using NHibernate.Collection; -using Remotion.Linq.Clauses.Expressions; +using NHibernate.Engine; +using NHibernate.Linq.Functions; +using NHibernate.Util; using Remotion.Linq.Parsing; -using Remotion.Linq.Parsing.ExpressionVisitors; using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation; namespace NHibernate.Linq.Visitors { - internal class NhPartialEvaluatingExpressionVisitor : RelinqExpressionVisitor, IPartialEvaluationExceptionExpressionVisitor + // Copied from Relinq and added logic for detecting and linking variables with evaluated constant expressions + /// + /// Takes an expression tree and first analyzes it for evaluatable subtrees (using ), i.e. + /// subtrees that can be pre-evaluated before actually generating the query. Examples for evaluatable subtrees are operations on constant + /// values (constant folding), access to closure variables (variables used by the LINQ query that are defined in an outer scope), or method + /// calls on known objects or their members. In a second step, it replaces all of the evaluatable subtrees (top-down and non-recursive) by + /// their evaluated counterparts. + /// + /// + /// This visitor visits each tree node at most twice: once via the for analysis and once + /// again to replace nodes if possible (unless the parent node has already been replaced). + /// + internal sealed class NhPartialEvaluatingExpressionVisitor : RelinqExpressionVisitor { + #region Relinq adjusted code + + /// + /// Takes an expression tree and finds and evaluates all its evaluatable subtrees. + /// + public static Expression EvaluateIndependentSubtrees( + Expression expressionTree, + PreTransformationParameters preTransformationParameters) + { + var partialEvaluationInfo = EvaluatableTreeFindingExpressionVisitor.Analyze( + expressionTree, + preTransformationParameters.EvaluatableExpressionFilter); + var visitor = new NhPartialEvaluatingExpressionVisitor(partialEvaluationInfo, preTransformationParameters); + + return visitor.Visit(expressionTree); + } + + // _partialEvaluationInfo contains a list of the expressions that are safe to be evaluated. + private readonly PartialEvaluationInfo _partialEvaluationInfo; + private readonly PreTransformationParameters _preTransformationParameters; + + private NhPartialEvaluatingExpressionVisitor( + PartialEvaluationInfo partialEvaluationInfo, + PreTransformationParameters preTransformationParameters) + { + _partialEvaluationInfo = partialEvaluationInfo; + _preTransformationParameters = preTransformationParameters; + } + + public override Expression Visit(Expression expression) + { + // Only evaluate expressions which do not use any of the surrounding parameter expressions. Don't evaluate + // lambda expressions (even if you could), we want to analyze those later on. + if (expression == null) + return null; + + if (expression.NodeType == ExpressionType.Lambda || !_partialEvaluationInfo.IsEvaluatableExpression(expression) || + #region NH additions + // Variables should be evaluated only when they are part of an evaluatable expression (e.g. o => string.Format("...", variable)) + ContainsVariable(expression)) + #endregion + return base.Visit(expression); + + Expression evaluatedExpression; + try + { + evaluatedExpression = EvaluateSubtree(expression); + } + catch (Exception ex) + { + // Evaluation caused an exception. Skip evaluation of this expression and proceed as if it weren't evaluable. + var baseVisitedExpression = base.Visit(expression); + + throw new HibernateException($"Evaluation failure on {baseVisitedExpression}", ex); + } + + if (evaluatedExpression != expression) + { + evaluatedExpression = EvaluateIndependentSubtrees(evaluatedExpression, _preTransformationParameters); + } + + #region NH additions + + // When having multiple level closure, we have to evaluate each closure independently + if (evaluatedExpression is ConstantExpression constantExpression) + { + evaluatedExpression = VisitConstant(constantExpression); + } + + // Variables in expressions are never a constant, they are encapsulated as fields of a compiler generated class. + if (expression.NodeType != ExpressionType.Constant && + _preTransformationParameters.MinimizeParameters && + evaluatedExpression is ConstantExpression variableConstant && + !_preTransformationParameters.QueryVariables.ContainsKey(variableConstant) && + ExpressionsHelper.IsVariable(expression, out var path, out var closureContext)) + { + _preTransformationParameters.QueryVariables.Add(variableConstant, new QueryVariable(path, closureContext)); + } + + #endregion + + return evaluatedExpression; + } + + /// + /// Evaluates an evaluatable subtree, i.e. an independent expression tree that is compilable and executable + /// without any data being passed in. The result of the evaluation is returned as a ; if the subtree + /// is already a , no evaluation is performed. + /// + /// The subtree to be evaluated. + /// A holding the result of the evaluation. + private Expression EvaluateSubtree(Expression subtree) + { + if (subtree.NodeType == ExpressionType.Constant) + { + var constantExpression = (ConstantExpression) subtree; + var valueAsIQueryable = constantExpression.Value as IQueryable; + if (valueAsIQueryable != null && valueAsIQueryable.Expression != constantExpression) + return valueAsIQueryable.Expression; + + return constantExpression; + } + else + { + Expression> lambdaWithoutParameters = Expression.Lambda>(Expression.Convert(subtree, typeof(object))); + var compiledLambda = lambdaWithoutParameters.Compile(); + + object value = compiledLambda(); + return Expression.Constant(value, subtree.Type); + } + } + + #region NH additions + + private bool ContainsVariable(Expression expression) + { + if (!(expression is UnaryExpression unaryExpression) || + // Avoid detecting expression variables as parameters + typeof(Expression).IsAssignableFrom(expression.Type)) + { + return false; + } + + return ExpressionsHelper.IsVariable(unaryExpression.Operand, out _, out _) || + // Check whether the variable is casted due to comparison with a nullable expression + // (e.g. o.NullableShort == shortVariable) + unaryExpression.Operand is UnaryExpression subUnaryExpression && + unaryExpression.Type.UnwrapIfNullable() == subUnaryExpression.Type && + ExpressionsHelper.IsVariable(subUnaryExpression.Operand, out _, out _); + } + + #endregion + + #endregion + protected override Expression VisitConstant(ConstantExpression expression) { if (expression.Value is Expression value) { - return EvaluateIndependentSubtrees(value); + return EvaluateIndependentSubtrees(value, _preTransformationParameters); } - return base.VisitConstant(expression); } + } + + internal struct QueryVariable : IEquatable + { + public QueryVariable(string path, object closureContext) + { + Path = path; + ClosureContext = closureContext; + } + + public string Path { get; } + + public object ClosureContext { get; } + + public override bool Equals(object obj) + { + return obj is QueryVariable other && Equals(other); + } - public static Expression EvaluateIndependentSubtrees(Expression expression) + public override int GetHashCode() { - var evaluatedExpression = PartialEvaluatingExpressionVisitor.EvaluateIndependentSubtrees(expression, new NhEvaluatableExpressionFilter()); - return new NhPartialEvaluatingExpressionVisitor().Visit(evaluatedExpression); + unchecked + { + return (Path.GetHashCode() * 397) ^ ClosureContext.GetHashCode(); + } } - public Expression VisitPartialEvaluationException(PartialEvaluationExceptionExpression partialEvaluationExceptionExpression) + public bool Equals(QueryVariable other) { - throw new HibernateException( - $"Evaluation failure on {partialEvaluationExceptionExpression.EvaluatedExpression}", - partialEvaluationExceptionExpression.Exception); + return Path == other.Path && ReferenceEquals(ClosureContext, other.ClosureContext); } } internal class NhEvaluatableExpressionFilter : EvaluatableExpressionFilterBase { + private readonly ISessionFactoryImplementor _sessionFactory; + + internal NhEvaluatableExpressionFilter(ISessionFactoryImplementor sessionFactory) + { + _sessionFactory = sessionFactory; + } + public override bool IsEvaluatableConstant(ConstantExpression node) { if (node.Value is IPersistentCollection && node.Value is IQueryable) @@ -47,6 +236,18 @@ public override bool IsEvaluatableConstant(ConstantExpression node) return base.IsEvaluatableConstant(node); } + public override bool IsEvaluatableMember(MemberExpression node) + { + if (node == null) + throw new ArgumentNullException(nameof(node)); + + if (_sessionFactory == null || _sessionFactory.Settings.LinqToHqlLegacyPreEvaluation || + !_sessionFactory.Settings.LinqToHqlGeneratorsRegistry.TryGetGenerator(node.Member, out var generator)) + return true; + + return generator.AllowPreEvaluation(node.Member, _sessionFactory); + } + public override bool IsEvaluatableMethodCall(MethodCallExpression node) { if (node == null) @@ -54,9 +255,15 @@ public override bool IsEvaluatableMethodCall(MethodCallExpression node) var attributes = node.Method .GetCustomAttributes(typeof(LinqExtensionMethodAttributeBase), false) - .Cast().ToArray(); - return attributes.Length == 0 || - attributes.Any(a => a.PreEvaluation == LinqExtensionPreEvaluation.AllowPreEvaluation); + .ToArray(x => (LinqExtensionMethodAttributeBase) x); + if (attributes.Length > 0) + return attributes.Any(a => a.PreEvaluation == LinqExtensionPreEvaluation.AllowPreEvaluation); + + if (_sessionFactory == null || _sessionFactory.Settings.LinqToHqlLegacyPreEvaluation || + !_sessionFactory.Settings.LinqToHqlGeneratorsRegistry.TryGetGenerator(node.Method, out var generator)) + return true; + + return generator.AllowPreEvaluation(node.Method, _sessionFactory); } } } diff --git a/src/NHibernate/Linq/Visitors/NullableExpressionDetector.cs b/src/NHibernate/Linq/Visitors/NullableExpressionDetector.cs new file mode 100644 index 00000000000..0fa6e4da40d --- /dev/null +++ b/src/NHibernate/Linq/Visitors/NullableExpressionDetector.cs @@ -0,0 +1,303 @@ +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using NHibernate.Engine; +using NHibernate.Linq.Clauses; +using NHibernate.Linq.Expressions; +using NHibernate.Linq.Functions; +using NHibernate.Util; +using Remotion.Linq.Clauses; +using Remotion.Linq.Clauses.Expressions; +using Remotion.Linq.Clauses.ResultOperators; + +namespace NHibernate.Linq.Visitors +{ + internal class NullableExpressionDetector + { + private static readonly HashSet NotNullOperators = new HashSet + { + typeof(AllResultOperator), + typeof(AnyResultOperator), + typeof(ContainsResultOperator), + typeof(CountResultOperator), + typeof(LongCountResultOperator) + }; + + private readonly Dictionary> _equalityNotNullMembers = + new Dictionary>(); + private readonly ISessionFactoryImplementor _sessionFactory; + private readonly ILinqToHqlGeneratorsRegistry _functionRegistry; + + public NullableExpressionDetector(ISessionFactoryImplementor sessionFactory, ILinqToHqlGeneratorsRegistry functionRegistry) + { + _sessionFactory = sessionFactory; + _functionRegistry = functionRegistry; + } + + public void SearchForNotNullMemberChecks(BinaryExpression expression) + { + // Check for a member not null check that has a not equals expression + // Example: o.Status != null && o.Status != "New" + // Example: (o.Status != null && o.OldStatus != null) && (o.Status != o.OldStatus) + // Example: (o.Status != null && o.OldStatus != null) && (o.Status == o.OldStatus) + // Example: o.Status != null && (o.OldStatus != null && o.Status == o.OldStatus) + if ( + _equalityNotNullMembers.ContainsKey(expression) || + !IsAndOrAndAlso(expression) || + ( + !IsAndOrAndAlso(expression.Right) && + !IsEqualOrNotEqual(expression.Right) + ) || + ( + !IsAndOrAndAlso(expression.Left) && + !IsEqualOrNotEqual(expression.Left) + )) + { + return; + } + + // Find all not null members and cache them for each binary expression that is found, + // in order to verify whether the member in a binary expression is nullable or not + FindAllNotNullMembers(expression, new List()); + } + + public bool IsNullable(Expression expression, BinaryExpression equalityExpression) + { + switch (expression.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.TypeAs: + // a cast will not return null if the operand is not null (as long as TypeAs is not translated to + // try_convert in SQL). + return IsNullable(((UnaryExpression) expression).Operand, equalityExpression); + case ExpressionType.Not: + case ExpressionType.And: + case ExpressionType.Or: + case ExpressionType.ExclusiveOr: + case ExpressionType.LeftShift: + case ExpressionType.RightShift: + case ExpressionType.AndAlso: + case ExpressionType.OrElse: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + return false; + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Power: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + var binaryExpression = (BinaryExpression) expression; + return IsNullable(binaryExpression.Left, equalityExpression) || IsNullable(binaryExpression.Right, equalityExpression); + case ExpressionType.ArrayIndex: + return true; // for indexed lists we cannot determine whether the item will be null or not + case ExpressionType.Coalesce: + return IsNullable(((BinaryExpression) expression).Right, equalityExpression); + case ExpressionType.Conditional: + var conditionalExpression = (ConditionalExpression) expression; + return IsNullable(conditionalExpression.IfTrue, equalityExpression) || + IsNullable(conditionalExpression.IfFalse, equalityExpression); + case ExpressionType.Call: + var methodInfo = ((MethodCallExpression) expression).Method; + return !_functionRegistry.TryGetGenerator(methodInfo, out var method) || method.AllowsNullableReturnType(methodInfo); + case ExpressionType.MemberAccess: + return IsNullable((MemberExpression) expression, equalityExpression); + case ExpressionType.Extension: + return IsNullableExtension(expression, equalityExpression); + case ExpressionType.TypeIs: // an equal or in operator will be generated and those cannot return null + case ExpressionType.NewArrayInit: + return false; + case ExpressionType.Constant: + return VisitorUtil.IsNullConstant(expression); + case ExpressionType.Parameter: + return !expression.Type.IsValueType; + default: + return true; + } + } + + private bool IsNullable(MemberExpression memberExpression, BinaryExpression equalityExpression) + { + if (_functionRegistry.TryGetGenerator(memberExpression.Member, out _)) + { + // The expression can be null when the member is static (e.g. DateTime.Now). + // In such cases we suppose that the value cannot be null. + if (memberExpression.Expression == null) + { + return false; + } + + // We have to skip the property as it will be converted to a function that can return null + // if the argument is null + return IsNullable(memberExpression.Expression, equalityExpression); + } + + var memberType = memberExpression.Member.GetPropertyOrFieldType(); + if (memberType?.IsValueType == true && !memberType.IsNullable()) + { + return IsNullable(memberExpression.Expression, equalityExpression); + } + + // Check if there was a not null check prior or after the equality expression + if (IsEqualOrNotEqual(equalityExpression) && + _equalityNotNullMembers.TryGetValue(equalityExpression, out var notNullMembers) && + notNullMembers.Any(o => AreEqual(o, memberExpression))) + { + return false; + } + + if (!ExpressionsHelper.TryGetMappedNullability(_sessionFactory, memberExpression, out var nullable) || nullable) + { + return true; // The expression contains one or many unsupported nodes or the member is nullable + } + + return IsNullable(memberExpression.Expression, equalityExpression); + } + + private bool IsNullableExtension(Expression extensionExpression, BinaryExpression equalityExpression) + { + switch (extensionExpression) + { + case QuerySourceReferenceExpression querySourceReferenceExpression: + switch (querySourceReferenceExpression.ReferencedQuerySource) + { + case MainFromClause _: + return false; // we reached to the root expression, there were no nullable expressions + case NhJoinClause joinClause: + return IsNullable(joinClause.FromExpression, equalityExpression); + default: + return true; // unknown query source + } + case SubQueryExpression subQueryExpression: + if (subQueryExpression.QueryModel.SelectClause.Selector is NhAggregatedExpression subQueryAggregatedExpression) + { + return subQueryAggregatedExpression.AllowsNullableReturnType; + } + else if (subQueryExpression.QueryModel.ResultOperators.Any(o => NotNullOperators.Contains(o.GetType()))) + { + return false; + } + + return true; + case NhAggregatedExpression aggregatedExpression: + return aggregatedExpression.AllowsNullableReturnType; + default: + return true; // a query can return null and we cannot calculate it as it is not yet executed + } + } + + private static bool TryGetMemberAccess(Expression expression, out MemberExpression memberExpression) + { + memberExpression = expression as MemberExpression; + if (memberExpression != null) + { + return true; + } + + // Nullable members can be wrapped in a convert expression + if (expression is UnaryExpression unaryExpression) + { + memberExpression = unaryExpression.Operand as MemberExpression; + } + + return memberExpression != null; + } + + private void FindAllNotNullMembers(Expression expression, List notNullMembers) + { + if (!(expression is BinaryExpression binaryExpression)) + { + return; + } + + // We may have multiple conditions + // Example: o.Status != null && o.OldStatus != null + // Example: o.Status != null && (o.OldStatus != null && o.Test > 0) + if (IsAndOrAndAlso(expression)) + { + FindAllNotNullMembers(binaryExpression, notNullMembers); + } + else if (IsEqualOrNotEqual(expression)) + { + FindNotNullMember(binaryExpression, notNullMembers); + } + } + + private void FindAllNotNullMembers(BinaryExpression binaryExpression, List notNullMembers) + { + _equalityNotNullMembers.Add(binaryExpression, notNullMembers); + FindAllNotNullMembers(binaryExpression.Left, notNullMembers); + FindAllNotNullMembers(binaryExpression.Right, notNullMembers); + } + + private void FindNotNullMember(BinaryExpression binaryExpression, List notNullMembers) + { + _equalityNotNullMembers[binaryExpression] = notNullMembers; + if (binaryExpression.NodeType != ExpressionType.NotEqual) + { + return; + } + + MemberExpression memberExpression; + if (VisitorUtil.IsNullConstant(binaryExpression.Right) && TryGetMemberAccess(binaryExpression.Left, out memberExpression)) + { + notNullMembers.Add(memberExpression); + } + else if (VisitorUtil.IsNullConstant(binaryExpression.Left) && TryGetMemberAccess(binaryExpression.Right, out memberExpression)) + { + notNullMembers.Add(memberExpression); + } + } + + private static bool AreEqual(MemberExpression memberExpression, MemberExpression otherMemberExpression) + { + if (memberExpression.Member != otherMemberExpression.Member || + memberExpression.Expression.NodeType != otherMemberExpression.Expression.NodeType) + { + return false; + } + + switch (memberExpression.Expression) + { + case QuerySourceReferenceExpression querySourceReferenceExpression: + if (otherMemberExpression.Expression is QuerySourceReferenceExpression otherQuerySourceReferenceExpression) + { + return querySourceReferenceExpression.ReferencedQuerySource == + otherQuerySourceReferenceExpression.ReferencedQuerySource; + } + + return false; + // Components have a nested member expression + case MemberExpression nestedMemberExpression: + if (otherMemberExpression.Expression is MemberExpression otherNestedMemberExpression) + { + return AreEqual(nestedMemberExpression, otherNestedMemberExpression); + } + + return false; + default: + return memberExpression.Expression == otherMemberExpression.Expression; + } + } + + private static bool IsAndOrAndAlso(Expression expression) + { + return expression.NodeType == ExpressionType.And || + expression.NodeType == ExpressionType.AndAlso; + } + + private static bool IsEqualOrNotEqual(Expression expression) + { + return expression.NodeType == ExpressionType.Equal || + expression.NodeType == ExpressionType.NotEqual; + } + } +} diff --git a/src/NHibernate/Linq/Visitors/PagingRewriterSelectClauseVisitor.cs b/src/NHibernate/Linq/Visitors/PagingRewriterSelectClauseVisitor.cs index 5e996b1f311..156e2daf9d5 100644 --- a/src/NHibernate/Linq/Visitors/PagingRewriterSelectClauseVisitor.cs +++ b/src/NHibernate/Linq/Visitors/PagingRewriterSelectClauseVisitor.cs @@ -2,7 +2,6 @@ using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Parsing; -using Remotion.Linq.Parsing.ExpressionVisitors; namespace NHibernate.Linq.Visitors { diff --git a/src/NHibernate/Linq/Visitors/ParameterTypeLocator.cs b/src/NHibernate/Linq/Visitors/ParameterTypeLocator.cs new file mode 100644 index 00000000000..9cab3d6cd26 --- /dev/null +++ b/src/NHibernate/Linq/Visitors/ParameterTypeLocator.cs @@ -0,0 +1,417 @@ +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using NHibernate.Engine; +using NHibernate.Linq.Functions; +using NHibernate.Param; +using NHibernate.Persister.Collection; +using NHibernate.Type; +using NHibernate.Util; +using Remotion.Linq; +using Remotion.Linq.Clauses; +using Remotion.Linq.Clauses.Expressions; +using Remotion.Linq.Clauses.ResultOperators; +using Remotion.Linq.Parsing; +using TypeExtensions = NHibernate.Util.TypeExtensions; + +namespace NHibernate.Linq.Visitors +{ + /// + /// Locates parameter actual type based on its usage. + /// + public static class ParameterTypeLocator + { + /// + /// List of for which the should be related to the other side + /// of a (e.g. o.MyEnum == MyEnum.Option -> MyEnum.Option should have o.MyEnum as a related + /// ). + /// + private static readonly HashSet ValidBinaryExpressionTypes = new HashSet + { + ExpressionType.Equal, + ExpressionType.NotEqual, + ExpressionType.GreaterThanOrEqual, + ExpressionType.GreaterThan, + ExpressionType.LessThan, + ExpressionType.LessThanOrEqual, + ExpressionType.Coalesce, + ExpressionType.Assign + }; + + /// + /// List of for which the should be copied across + /// as related (e.g. (o.MyEnum ?? MyEnum.Option) == MyEnum.Option2 -> MyEnum.Option2 should have o.MyEnum as a related + /// ). + /// + private static readonly HashSet NonVoidOperators = new HashSet + { + ExpressionType.Coalesce, + ExpressionType.Conditional + }; + + + /// + /// Set query parameter types based on the given query model. + /// + /// The query parameters. + /// The query model. + /// The target entity type. + /// The session factory. + public static void SetParameterTypes( + IDictionary parameters, + QueryModel queryModel, + System.Type targetType, + ISessionFactoryImplementor sessionFactory) + { + SetParameterTypes(parameters, queryModel, targetType, sessionFactory, false); + } + + internal static void SetParameterTypes( + IDictionary parameters, + QueryModel queryModel, + System.Type targetType, + ISessionFactoryImplementor sessionFactory, + bool removeMappedAsCalls) + { + if (parameters.Count == 0) + { + return; + } + + var visitor = new ConstantTypeLocatorVisitor(removeMappedAsCalls, targetType, parameters, sessionFactory); + queryModel.TransformExpressions(visitor.Visit); + + foreach (var pair in visitor.ParameterConstants) + { + var namedParameter = pair.Key; + var constantExpressions = pair.Value; + // In case any of the constants has the type set, use it (e.g. MappedAs) + namedParameter.Type = constantExpressions.Select(o => visitor.ConstantExpressions[o]).FirstOrDefault(o => o != null); + if (namedParameter.Type != null) + { + continue; + } + + namedParameter.Type = GetParameterType(sessionFactory, constantExpressions, visitor, namedParameter); + } + } + + private static IType GetCandidateType( + ISessionFactoryImplementor sessionFactory, + IEnumerable constantExpressions, + ConstantTypeLocatorVisitor visitor, + System.Type constantType) + { + IType candidateType = null; + foreach (var expression in constantExpressions) + { + // In order to get the actual type we have to check first the related member expressions, as + // an enum is translated in a numeric type when used in a BinaryExpression and also it can be mapped as string. + // By getting the type from a related member expression we also get the correct length in case of StringType + // or precision when having a DecimalType. + if (!visitor.RelatedExpressions.TryGetValue(expression, out var relatedExpressions)) + continue; + foreach (var relatedExpression in relatedExpressions) + { + if (!ExpressionsHelper.TryGetMappedType(sessionFactory, relatedExpression, out var mappedType, out _, out _, out _)) + continue; + + if (mappedType.IsCollectionType) + { + var collection = (IQueryableCollection) ((IAssociationType) mappedType).GetAssociatedJoinable(sessionFactory); + mappedType = collection.ElementType; + } + + if (candidateType == null) + candidateType = mappedType; + else if (!candidateType.Equals(mappedType)) + return null; + } + } + + if (candidateType == null) + return null; + + // When comparing an integral column with a real parameter, the parameter type must remain real type + // and the column needs to be casted in order to prevent invalid results (e.g. Where(o => o.Integer >= 2.2d)). + if (constantType.IsRealNumberType() && candidateType.ReturnedClass.IsIntegralNumberType()) + return null; + + return candidateType; + } + + private static IType GetParameterType( + ISessionFactoryImplementor sessionFactory, + HashSet constantExpressions, + ConstantTypeLocatorVisitor visitor, + NamedParameter namedParameter) + { + // All constant expressions have the same type/value + var constantExpression = constantExpressions.First(); + var constantType = constantExpression.Type.UnwrapIfNullable(); + var candidateType = GetCandidateType(sessionFactory, constantExpressions, visitor, constantType); + if (candidateType != null) + { + return candidateType; + } + + // No related MemberExpressions was found, guess the type by value or its type when null. + // When a numeric parameter is compared to different columns with different types (e.g. Where(o => o.Single >= singleParam || o.Double <= singleParam)) + // do not change the parameter type, but instead cast the parameter when comparing with different column types. + return constantExpression.Value != null + ? ParameterHelper.TryGuessType(constantExpression.Value, sessionFactory, namedParameter.IsCollection) + : ParameterHelper.TryGuessType(constantType, sessionFactory, namedParameter.IsCollection); + } + + private class ConstantTypeLocatorVisitor : RelinqExpressionVisitor + { + private readonly bool _removeMappedAsCalls; + private readonly System.Type _targetType; + private readonly IDictionary _parameters; + private readonly ISessionFactoryImplementor _sessionFactory; + public readonly Dictionary ConstantExpressions = + new Dictionary(); + public readonly Dictionary> ParameterConstants = + new Dictionary>(); + public readonly Dictionary> RelatedExpressions = + new Dictionary>(); + + public ConstantTypeLocatorVisitor( + bool removeMappedAsCalls, + System.Type targetType, + IDictionary parameters, + ISessionFactoryImplementor sessionFactory) + { + _removeMappedAsCalls = removeMappedAsCalls; + _targetType = targetType; + _sessionFactory = sessionFactory; + _parameters = parameters; + } + + protected override Expression VisitBinary(BinaryExpression node) + { + node = (BinaryExpression) base.VisitBinary(node); + if (!ValidBinaryExpressionTypes.Contains(node.NodeType)) + { + return node; + } + + var left = UnwrapUnary(node.Left); + var right = UnwrapUnary(node.Right); + if (node.NodeType == ExpressionType.Assign) + { + VisitAssign(left, right); + } + else + { + AddRelatedExpression(node, left, right); + AddRelatedExpression(node, right, left); + } + + return node; + } + + protected override Expression VisitConditional(ConditionalExpression node) + { + node = (ConditionalExpression) base.VisitConditional(node); + var ifTrue = UnwrapUnary(node.IfTrue); + var ifFalse = UnwrapUnary(node.IfFalse); + AddRelatedExpression(node, ifTrue, ifFalse); + AddRelatedExpression(node, ifFalse, ifTrue); + + return node; + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (VisitorUtil.IsMappedAs(node.Method)) + { + var rawParameter = Visit(node.Arguments[0]); + var parameter = rawParameter as ConstantExpression; + var type = node.Arguments[1] as ConstantExpression; + if (parameter == null) + throw new HibernateException( + $"{nameof(LinqExtensionMethods.MappedAs)} must be called on an expression which can be evaluated as " + + $"{nameof(ConstantExpression)}. It was call on {rawParameter?.GetType().Name ?? "null"} instead."); + if (type == null) + throw new HibernateException( + $"{nameof(LinqExtensionMethods.MappedAs)} type must be supplied as {nameof(ConstantExpression)}. " + + $"It was {node.Arguments[1]?.GetType().Name ?? "null"} instead."); + + ConstantExpressions[parameter] = (IType) type.Value; + + return _removeMappedAsCalls + ? rawParameter + : node; + } + + if (EqualsGenerator.Methods.Contains(node.Method) || CompareGenerator.IsCompareMethod(node.Method)) + { + node = (MethodCallExpression) base.VisitMethodCall(node); + var left = UnwrapUnary(node.Method.IsStatic ? node.Arguments[0] : node.Object); + var right = UnwrapUnary(node.Method.IsStatic ? node.Arguments[1] : node.Arguments[0]); + AddRelatedExpression(node, left, right); + AddRelatedExpression(node, right, left); + + return node; + } + + return base.VisitMethodCall(node); + } + + protected override Expression VisitConstant(ConstantExpression node) + { + if (node.Value is IEntityNameProvider || RelatedExpressions.ContainsKey(node) || !_parameters.TryGetValue(node, out var param)) + { + return node; + } + + RelatedExpressions.Add(node, new HashSet()); + ConstantExpressions.Add(node, null); + if (!ParameterConstants.TryGetValue(param, out var set)) + { + set = new HashSet(); + ParameterConstants.Add(param, set); + } + + set.Add(node); + + return node; + } + + protected override Expression VisitSubQuery(SubQueryExpression node) + { + if (!TryLinkContainsMethod(node.QueryModel)) + { + node.QueryModel.TransformExpressions(Visit); + } + + return node; + } + + private bool TryLinkContainsMethod(QueryModel queryModel) + { + // ReLinq wraps all ResultOperatorExpressionNodeBase into a SubQueryExpression. In case of + // ContainsResultOperator where the constant expression is dislocated from the related expression, + // we have to manually link the related expressions. + if (queryModel.ResultOperators.Count != 1 || + !(queryModel.ResultOperators[0] is ContainsResultOperator containsOperator) || + !(queryModel.SelectClause.Selector is QuerySourceReferenceExpression querySourceReference) || + !(querySourceReference.ReferencedQuerySource is MainFromClause mainFromClause)) + { + return false; + } + + var left = UnwrapUnary(Visit(mainFromClause.FromExpression)); + var right = UnwrapUnary(Visit(containsOperator.Item)); + // The constant is on the left side (e.g. db.Users.Where(o => users.Contains(o))) + // The constant is on the right side (e.g. db.Customers.Where(o => o.Orders.Contains(item))) + if (left.NodeType != ExpressionType.Constant && right.NodeType != ExpressionType.Constant) + { + return false; + } + + // Copy all found MemberExpressions to the constant expression + // (e.g. values.Contains(o.Name != o.Name2 ? o.Enum1 : o.Enum2) -> copy o.Enum1 and o.Enum2) + AddRelatedExpression(null, left, right); + AddRelatedExpression(null, right, left); + + return true; + } + + private void VisitAssign(Expression leftNode, Expression rightNode) + { + // Insert and Update statements have assign expressions, where the left side is a parameter and its name + // represents the property path to be assigned + if (!(leftNode is ParameterExpression parameterExpression) || + !(rightNode is ConstantExpression constantExpression)) + { + return; + } + + var entityName = _sessionFactory.TryGetGuessEntityName(_targetType); + if (entityName == null) + { + return; + } + + var persister = _sessionFactory.GetEntityPersister(entityName); + ConstantExpressions[constantExpression] = persister.EntityMetamodel.GetPropertyType(parameterExpression.Name); + } + + private void AddRelatedExpression(Expression node, Expression left, Expression right) + { + if (left.NodeType == ExpressionType.MemberAccess || + IsDynamicMember(left) || + left is QuerySourceReferenceExpression) + { + AddRelatedExpression(right, left); + if (node != null && NonVoidOperators.Contains(node.NodeType)) + { + AddRelatedExpression(node, left); + } + } + + // Copy all found MemberExpressions to the other side + // (e.g. (o.Prop ?? constant1) == constant2 -> copy o.Prop to constant2) + if (RelatedExpressions.TryGetValue(left, out var set)) + { + foreach (var nestedMemberExpression in set) + { + AddRelatedExpression(right, nestedMemberExpression); + if (node != null && NonVoidOperators.Contains(node.NodeType)) + { + AddRelatedExpression(node, nestedMemberExpression); + } + } + } + } + + private void AddRelatedExpression(Expression expression, Expression relatedExpression) + { + if (!RelatedExpressions.TryGetValue(expression, out var set)) + { + set = new HashSet(); + RelatedExpressions.Add(expression, set); + } + + set.Add(relatedExpression); + } + + private bool IsDynamicMember(Expression expression) + { + switch (expression) + { +#if NETCOREAPP2_0 + case InvocationExpression invocationExpression: + // session.Query().Where("Properties.Name == @0", "First Product") + return ExpressionsHelper.TryGetDynamicMemberBinder(invocationExpression, out _); +#endif + case DynamicExpression dynamicExpression: + return dynamicExpression.Binder is GetMemberBinder; + case MethodCallExpression methodCallExpression: + // session.Query() where p.Properties["Name"] == "First Product" select p + return VisitorUtil.TryGetPotentialDynamicComponentDictionaryMember(methodCallExpression, out _); + default: + return false; + } + } + } + + /// + /// Unwraps . + /// + /// The expression to unwrap. + /// The unwrapped expression. + private static Expression UnwrapUnary(Expression expression) + { + while (expression is UnaryExpression unaryExpression) + { + expression = unaryExpression.Operand; + } + + return expression; + } + } +} diff --git a/src/NHibernate/Linq/Visitors/PossibleValueSet.cs b/src/NHibernate/Linq/Visitors/PossibleValueSet.cs index 6a262d6a998..8bbdbee70a0 100644 --- a/src/NHibernate/Linq/Visitors/PossibleValueSet.cs +++ b/src/NHibernate/Linq/Visitors/PossibleValueSet.cs @@ -314,7 +314,6 @@ public PossibleValueSet MemberAccess(System.Type resultType) #endregion - /// /// Verify that ExpressionType of both this and the other set is bool or nullable bool, /// and return the negotiated type (nullable bool if either side is nullable). @@ -331,7 +330,6 @@ private System.Type DetermineBoolType(PossibleValueSet otherSet) return typeof(bool); } - /// /// Verify that ExpressionType is bool or nullable bool. /// diff --git a/src/NHibernate/Linq/Visitors/PreTransformationParameters.cs b/src/NHibernate/Linq/Visitors/PreTransformationParameters.cs new file mode 100644 index 00000000000..c5f713216a8 --- /dev/null +++ b/src/NHibernate/Linq/Visitors/PreTransformationParameters.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using NHibernate.Engine; +using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation; + +namespace NHibernate.Linq.Visitors +{ + /// + /// Contains the information needed by to perform an early transformation. + /// + public class PreTransformationParameters + { + private static readonly Func DefaultPreTransformer = NhRelinqQueryParser.CreatePreTransformer(null); + + /// + /// The default constructor. + /// + /// The query mode of the expression to pre-transform. + /// The session factory used in the pre-transform process. + public PreTransformationParameters(QueryMode queryMode, ISessionFactoryImplementor sessionFactory) + { + QueryMode = queryMode; + SessionFactory = sessionFactory; + PreTransformer = sessionFactory.Settings.LinqPreTransformer ?? DefaultPreTransformer; + // Skip detecting variables for DML queries as HQL does not support reusing parameters for them. + MinimizeParameters = QueryMode == QueryMode.Select; + } + + /// + /// The query mode of the expression to pre-transform. + /// + public QueryMode QueryMode { get; } + + /// + /// The session factory used in the pre-transform process. + /// + public ISessionFactoryImplementor SessionFactory { get; } + + /// + /// The transformer that will be used to pre-transform the query expression. + /// + internal Func PreTransformer { get; } + + /// + /// Whether to minimize the number of parameters for variables. + /// + public bool MinimizeParameters { get; set; } + + /// + /// The filter which decides whether a part of the expression will be pre-evalauted or not. + /// + internal IEvaluatableExpressionFilter EvaluatableExpressionFilter { get; set; } + + /// + /// A dictionary of that were evaluated from variables. + /// + internal IDictionary QueryVariables { get; set; } + } +} diff --git a/src/NHibernate/Linq/Visitors/PreTransformationResult.cs b/src/NHibernate/Linq/Visitors/PreTransformationResult.cs new file mode 100644 index 00000000000..6f55ddc7bba --- /dev/null +++ b/src/NHibernate/Linq/Visitors/PreTransformationResult.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq.Expressions; +using NHibernate.Engine; + +namespace NHibernate.Linq.Visitors +{ + /// + /// The result of method. + /// + public class PreTransformationResult + { + internal PreTransformationResult( + Expression expression, + ISessionFactoryImplementor sessionFactory, + IDictionary queryVariables) + { + Expression = expression; + SessionFactory = sessionFactory; + QueryVariables = queryVariables; + } + + /// + /// The transformed expression. + /// + public Expression Expression { get; } + + /// + /// The session factory used in the pre-transform process. + /// + public ISessionFactoryImplementor SessionFactory { get; } + + /// + /// A dictionary of that were evaluated from variables. + /// + internal IDictionary QueryVariables { get; } + } +} diff --git a/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs index 1a87c9705a1..3c95d90c9ab 100644 --- a/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs +++ b/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; using NHibernate.Hql.Ast; @@ -14,13 +15,16 @@ using NHibernate.Util; using Remotion.Linq; using Remotion.Linq.Clauses; +using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ResultOperators; using Remotion.Linq.Clauses.StreamedData; using Remotion.Linq.EagerFetching; +using OrderByClause = Remotion.Linq.Clauses.OrderByClause; +using SelectClause = Remotion.Linq.Clauses.SelectClause; namespace NHibernate.Linq.Visitors { - public class QueryModelVisitor : NhQueryModelVisitorBase, INhQueryModelVisitor + public class QueryModelVisitor : NhQueryModelVisitorBase, INhQueryModelVisitor, INhQueryModelVisitorExtended { private readonly QueryMode _queryMode; @@ -62,6 +66,9 @@ public static ExpressionToHqlTranslationResults GenerateHqlQuery(QueryModel quer // Rewrite paging PagingRewriter.ReWrite(queryModel); + //Remove unnecessary order-by clauses + RemoveUnnecessaryBodyOperators.RemoveUnnecessaryOrderByClauses(queryModel); + // Flatten pointless subqueries QueryReferenceExpressionFlattener.ReWrite(queryModel); @@ -118,6 +125,9 @@ public static ExpressionToHqlTranslationResults GenerateHqlQuery(QueryModel quer public ResultOperatorRewriterResult RewrittenOperatorResult { get; private set; } + internal Dictionary RelatedJoinFetchRequests { get; } = + new Dictionary(); + static QueryModelVisitor() { // TODO - reflection to build map @@ -315,22 +325,16 @@ public override void VisitMainFromClause(MainFromClause fromClause, QueryModel q public override void VisitAdditionalFromClause(AdditionalFromClause fromClause, QueryModel queryModel, int index) { var querySourceName = VisitorParameters.QuerySourceNamer.GetName(fromClause); - + var fromExpressionTree = HqlGeneratorExpressionVisitor.Visit(fromClause.FromExpression, VisitorParameters); + var alias = _hqlTree.TreeBuilder.Alias(querySourceName); if (fromClause.FromExpression is MemberExpression) { // It's a join - _hqlTree.AddFromClause( - _hqlTree.TreeBuilder.Join( - HqlGeneratorExpressionVisitor.Visit(fromClause.FromExpression, VisitorParameters).AsExpression(), - _hqlTree.TreeBuilder.Alias(querySourceName))); + _hqlTree.AddFromClause(_hqlTree.TreeBuilder.Join(fromExpressionTree.AsExpression(), alias)); } else { - // TODO - exact same code as in MainFromClause; refactor this out - _hqlTree.AddFromClause( - _hqlTree.TreeBuilder.Range( - HqlGeneratorExpressionVisitor.Visit(fromClause.FromExpression, VisitorParameters), - _hqlTree.TreeBuilder.Alias(querySourceName))); + _hqlTree.AddFromClause(CreateCrossJoin(fromExpressionTree, alias)); } base.VisitAdditionalFromClause(fromClause, queryModel, index); @@ -339,6 +343,11 @@ public override void VisitAdditionalFromClause(AdditionalFromClause fromClause, public override void VisitNhJoinClause(NhJoinClause joinClause, QueryModel queryModel, int index) { var querySourceName = VisitorParameters.QuerySourceNamer.GetName(joinClause); + var fetchRequest = GetRelatedFetchRequest(queryModel, joinClause); + if (fetchRequest != null) + { + RelatedJoinFetchRequests.Add(joinClause, fetchRequest); + } var expression = HqlGeneratorExpressionVisitor.Visit(joinClause.FromExpression, VisitorParameters).AsExpression(); var alias = _hqlTree.TreeBuilder.Alias(querySourceName); @@ -346,11 +355,15 @@ public override void VisitNhJoinClause(NhJoinClause joinClause, QueryModel query HqlTreeNode hqlJoin; if (joinClause.IsInner) { - hqlJoin = _hqlTree.TreeBuilder.Join(expression, alias); + hqlJoin = fetchRequest != null + ? _hqlTree.TreeBuilder.FetchJoin(expression, alias) + : (HqlTreeNode) _hqlTree.TreeBuilder.Join(expression, alias); } else { - hqlJoin = _hqlTree.TreeBuilder.LeftJoin(expression, alias); + hqlJoin = fetchRequest != null + ? _hqlTree.TreeBuilder.LeftFetchJoin(expression, alias) + : (HqlTreeNode) _hqlTree.TreeBuilder.LeftJoin(expression, alias); } foreach (var withClause in joinClause.Restrictions) @@ -365,7 +378,7 @@ public override void VisitNhJoinClause(NhJoinClause joinClause, QueryModel query public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index) { PreviousEvaluationType = CurrentEvaluationType; - CurrentEvaluationType = resultOperator.GetOutputDataInfo(PreviousEvaluationType); + CurrentEvaluationType = GetOutputDataInfo(resultOperator, PreviousEvaluationType); if (resultOperator is ClientSideTransformOperator) { @@ -382,6 +395,59 @@ public override void VisitResultOperator(ResultOperatorBase resultOperator, Quer ResultOperatorMap.Process(resultOperator, this, _hqlTree); } + private FetchOneRequest GetRelatedFetchRequest(QueryModel queryModel, NhJoinClause joinClause) + { + if (joinClause.Restrictions.Count > 0 || + !(joinClause.FromExpression is MemberExpression memberExpression) || + !(memberExpression.Expression is QuerySourceReferenceExpression querySource) || + !IsFetchSupported(queryModel)) + { + return null; + } + + if (querySource.ReferencedQuerySource is MainFromClause) + { + return queryModel.ResultOperators.OfType().FirstOrDefault(o => o.RelationMember == memberExpression.Member); + } + + if (querySource.ReferencedQuerySource is NhJoinClause parentJoinClause && + RelatedJoinFetchRequests.TryGetValue(parentJoinClause, out var parentFetchRequest)) + { + return parentFetchRequest.InnerFetchRequests.OfType().FirstOrDefault(o => o.RelationMember == memberExpression.Member); + } + + return null; + } + + private static bool IsFetchSupported(QueryModel queryModel) + { + // Hql does not support fetch with group by and select + return + !queryModel.ResultOperators.Any(o => o is GroupResultOperator) && + queryModel.SelectClause.Selector is QuerySourceReferenceExpression selectSource && + selectSource.ReferencedQuerySource == queryModel.MainFromClause; + } + + private static IStreamedDataInfo GetOutputDataInfo(ResultOperatorBase resultOperator, IStreamedDataInfo evaluationType) + { + //ContainsResultOperator contains data integrity check so for `values.Contains(x)` it checks that 'x' is proper type to be used inside 'values.Contains()' + //Due to some reasons (possibly NH expression rewritings) those types might be incompatible (known case NH-3155 - group by subquery). So resultOperator.GetOutputDataInfo throws something like: + //System.ArgumentException : The items of the input sequence of type 'System.Linq.IGrouping`2[System.Object[],EntityType]' are not compatible with the item expression of type 'System.Int32'. + //Parameter name: inputInfo + //at Remotion.Linq.Clauses.ResultOperators.ContainsResultOperator.GetOutputDataInfo(StreamedSequenceInfo inputInfo) + //But in this place we don't really care about types involving inside expression, all we need to know is operation result which is bool for Contains + //So let's skip possible type exception mismatch if it allows to generate proper SQL + switch (resultOperator) + { + case ContainsResultOperator _: + case AnyResultOperator _: + case AllResultOperator _: + return new StreamedScalarValueInfo(typeof(bool)); + } + + return resultOperator.GetOutputDataInfo(evaluationType); + } + public override void VisitSelectClause(SelectClause selectClause, QueryModel queryModel) { CurrentEvaluationType = selectClause.GetOutputDataInfo(); @@ -418,20 +484,20 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que private void VisitInsertClause(Expression expression) { - var listInit = expression as ListInitExpression + var assignments = expression as BlockExpression ?? throw new QueryException("Malformed insert expression"); var insertedType = VisitorParameters.TargetEntityType; var idents = new List(); var selectColumns = new List(); //Extract the insert clause from the projected ListInit - foreach (var assignment in listInit.Initializers) + foreach (BinaryExpression assignment in assignments.Expressions) { - var member = (ConstantExpression)assignment.Arguments[0]; - var value = assignment.Arguments[1]; + var propName = ((ParameterExpression) assignment.Left).Name; + var value = assignment.Right; //The target property - idents.Add(_hqlTree.TreeBuilder.Ident((string)member.Value)); + idents.Add(_hqlTree.TreeBuilder.Ident(propName)); var valueHql = HqlGeneratorExpressionVisitor.Visit(value, VisitorParameters).AsExpression(); selectColumns.Add(valueHql); @@ -447,16 +513,15 @@ private void VisitInsertClause(Expression expression) private void VisitUpdateClause(Expression expression) { - var listInit = expression as ListInitExpression + var assignments = expression as BlockExpression ?? throw new QueryException("Malformed update expression"); - foreach (var initializer in listInit.Initializers) + foreach (BinaryExpression assigment in assignments.Expressions) { - var member = (ConstantExpression)initializer.Arguments[0]; - var setter = initializer.Arguments[1]; + var propName = ((ParameterExpression) assigment.Left).Name; + var setter = assigment.Right; var setterHql = HqlGeneratorExpressionVisitor.Visit(setter, VisitorParameters).AsExpression(); - _hqlTree.AddSet(_hqlTree.TreeBuilder.Equality(_hqlTree.TreeBuilder.Ident((string)member.Value), - setterHql)); + _hqlTree.AddSet(_hqlTree.TreeBuilder.Equality(_hqlTree.TreeBuilder.Ident(propName), setterHql)); } } @@ -497,15 +562,25 @@ public override void VisitOrderByClause(OrderByClause orderByClause, QueryModel public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, int index) { - var equalityVisitor = new EqualityHqlGenerator(VisitorParameters); - var whereClause = equalityVisitor.Visit(joinClause.InnerKeySelector, joinClause.OuterKeySelector); + AddJoin(joinClause, queryModel, true); + } - _hqlTree.AddWhereClause(whereClause); + public void VisitNhOuterJoinClause(NhOuterJoinClause outerJoinClause, QueryModel queryModel, int index) + { + AddJoin(outerJoinClause.JoinClause, queryModel, false); + } - _hqlTree.AddFromClause( - _hqlTree.TreeBuilder.Range( - HqlGeneratorExpressionVisitor.Visit(joinClause.InnerSequence, VisitorParameters), - _hqlTree.TreeBuilder.Alias(joinClause.ItemName))); + private void AddJoin(JoinClause joinClause, QueryModel queryModel, bool innerJoin) + { + var equalityVisitor = new EqualityHqlGenerator(VisitorParameters); + var withClause = equalityVisitor.Visit(joinClause.InnerKeySelector, joinClause.OuterKeySelector); + var alias = _hqlTree.TreeBuilder.Alias(VisitorParameters.QuerySourceNamer.GetName(joinClause)); + var joinExpression = HqlGeneratorExpressionVisitor.Visit(joinClause.InnerSequence, VisitorParameters); + var join = innerJoin + ? _hqlTree.TreeBuilder.InnerJoin(joinExpression.AsExpression(), alias) + : (HqlTreeNode) _hqlTree.TreeBuilder.LeftJoin(joinExpression.AsExpression(), alias); + join.AddChild(_hqlTree.TreeBuilder.With(withClause)); + _hqlTree.AddFromClause(join); } public override void VisitGroupJoinClause(GroupJoinClause groupJoinClause, QueryModel queryModel, int index) @@ -532,5 +607,22 @@ public override void VisitNhWithClause(NhWithClause withClause, QueryModel query var expression = HqlGeneratorExpressionVisitor.Visit(withClause.Predicate, VisitorParameters).ToBooleanExpression(); _hqlTree.AddWhereClause(expression); } + + private HqlTreeNode CreateCrossJoin(HqlTreeNode joinExpression, HqlAlias alias) + { + if (VisitorParameters.SessionFactory.Dialect.SupportsCrossJoin) + { + return _hqlTree.TreeBuilder.CrossJoin(joinExpression.AsExpression(), alias); + } + + // Simulate cross join as a inner join on 1=1 + var join = _hqlTree.TreeBuilder.InnerJoin(joinExpression.AsExpression(), alias); + var onExpression = _hqlTree.TreeBuilder.Equality( + _hqlTree.TreeBuilder.True(), + _hqlTree.TreeBuilder.True()); + join.AddChild(_hqlTree.TreeBuilder.With(onExpression)); + + return join; + } } } diff --git a/src/NHibernate/Linq/Visitors/QuerySourceIdentifier.cs b/src/NHibernate/Linq/Visitors/QuerySourceIdentifier.cs index 475a13050c8..30bc1cde345 100644 --- a/src/NHibernate/Linq/Visitors/QuerySourceIdentifier.cs +++ b/src/NHibernate/Linq/Visitors/QuerySourceIdentifier.cs @@ -14,7 +14,7 @@ namespace NHibernate.Linq.Visitors /// the HQL expression tree) means a query source may be referenced by a QuerySourceReference /// before it has been identified - and named. /// - public class QuerySourceIdentifier : NhQueryModelVisitorBase + public class QuerySourceIdentifier : NhQueryModelVisitorBase, INhQueryModelVisitorExtended { private readonly QuerySourceNamer _namer; @@ -58,6 +58,12 @@ public override void VisitNhJoinClause(NhJoinClause joinClause, QueryModel query _namer.Add(joinClause); } + public void VisitNhOuterJoinClause(NhOuterJoinClause outerJoinClause, QueryModel queryModel, int index) + { + _namer.Add(outerJoinClause); + _namer.Add(outerJoinClause.JoinClause, _namer.GetName(outerJoinClause)); + } + public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index) { var groupBy = resultOperator as GroupResultOperator; @@ -73,4 +79,4 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que public QuerySourceNamer Namer { get { return _namer; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessContains.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessContains.cs index 17fc7850425..169b1211eb5 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessContains.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessContains.cs @@ -59,8 +59,9 @@ private static HqlAlias GetFromAlias(HqlTreeNode node) private static bool IsEmptyList(HqlParameter source, VisitorParameters parameters) { var parameterName = source.NodesPreOrder.Single(n => n is HqlIdent).AstNode.Text; - var parameterValue = parameters.ConstantToParameterMap.Single(p => p.Value.Name == parameterName).Key.Value; + // Multiple constants may be linked to the same parameter, take the first matching parameter + var parameterValue = parameters.ConstantToParameterMap.First(p => p.Value.Name == parameterName).Key.Value; return !((IEnumerable)parameterValue).Cast().Any(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs index 102d1cb6dc2..a042af3c12d 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs @@ -13,21 +13,39 @@ public void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMode var querySource = QuerySourceLocator.FindQuerySource( queryModelVisitor.Model, resultOperator.RelationMember.DeclaringType); + var name = queryModelVisitor.VisitorParameters.QuerySourceNamer.GetName(querySource); - Process(resultOperator, queryModelVisitor, tree, querySource.ItemName); + Process(resultOperator, queryModelVisitor, tree, name); } public void Process(FetchRequestBase resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree, string sourceAlias) + { + Process(resultOperator, queryModelVisitor, tree, null, sourceAlias); + } + + private void Process( + FetchRequestBase resultOperator, + QueryModelVisitor queryModelVisitor, + IntermediateHqlTree tree, + HqlTreeNode currentNode, + string sourceAlias) { var memberPath = tree.TreeBuilder.Dot( tree.TreeBuilder.Ident(sourceAlias), tree.TreeBuilder.Ident(resultOperator.RelationMember.Name)); - Process(resultOperator, queryModelVisitor, tree, memberPath, null); + Process(resultOperator, queryModelVisitor, tree, memberPath, currentNode, null); } - private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree, HqlDot memberPath, IType propType) + private void Process( + FetchRequestBase resultOperator, + QueryModelVisitor queryModelVisitor, + IntermediateHqlTree tree, + HqlDot memberPath, + HqlTreeNode currentNode, + IType propType) { + string alias = null; if (resultOperator is FetchOneRequest) { if (propType == null) @@ -39,8 +57,14 @@ private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMod if (propType != null && !propType.IsAssociationType) { - tree.AddFromLastChildClause(tree.TreeBuilder.Fetch()); - tree.AddFromLastChildClause(memberPath); + if (currentNode == null) + { + currentNode = tree.GetFromRangeClause() + ?? throw new InvalidOperationException($"Property {resultOperator.RelationMember.Name} cannot be fetched for this type of query."); + } + + currentNode.AddChild(tree.TreeBuilder.Fetch()); + currentNode.AddChild(memberPath); ComponentType componentType = null; foreach (var innerFetch in resultOperator.InnerFetchRequests) @@ -60,20 +84,31 @@ private void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMod memberPath, tree.TreeBuilder.Ident(innerFetch.RelationMember.Name)); - Process(innerFetch, queryModelVisitor, tree, memberPath, componentType.Subtypes[subTypeIndex]); + Process(innerFetch, queryModelVisitor, tree, memberPath, currentNode, componentType.Subtypes[subTypeIndex]); } return; } + + var relatedJoin = queryModelVisitor.RelatedJoinFetchRequests.FirstOrDefault(o => o.Value == resultOperator).Key; + if (relatedJoin != null) + { + alias = queryModelVisitor.VisitorParameters.QuerySourceNamer.GetName(relatedJoin); + } + } + + if (alias == null) + { + alias = queryModelVisitor.Model.GetNewName("_"); + currentNode = tree.TreeBuilder.LeftFetchJoin(memberPath, tree.TreeBuilder.Alias(alias)); + tree.AddFromClause(currentNode); } - var alias = queryModelVisitor.Model.GetNewName("_"); - tree.AddFromClause(tree.TreeBuilder.LeftFetchJoin(memberPath, tree.TreeBuilder.Alias(alias))); tree.AddDistinctRootOperator(); foreach (var innerFetch in resultOperator.InnerFetchRequests) { - Process(innerFetch, queryModelVisitor, tree, alias); + Process(innerFetch, queryModelVisitor, tree, currentNode, alias); } } } diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetchLazyProperties.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetchLazyProperties.cs index ee0a7969d4c..ca5fd8c4384 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetchLazyProperties.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetchLazyProperties.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Linq.Visitors.ResultOperatorProcessors +namespace NHibernate.Linq.Visitors.ResultOperatorProcessors { internal class ProcessFetchLazyProperties : IResultOperatorProcessor { diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessResultOperatorReturn.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessResultOperatorReturn.cs index f621e9a8461..8a7a769dd38 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessResultOperatorReturn.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessResultOperatorReturn.cs @@ -8,12 +8,12 @@ namespace NHibernate.Linq.Visitors.ResultOperatorProcessors { public class ProcessResultOperatorReturn { - public HqlTreeNode TreeNode { get; set;} + public HqlTreeNode TreeNode { get; set; } public Action>> AdditionalCriteria { get; set; } - public LambdaExpression ListTransformer { get; set;} - public LambdaExpression PostExecuteTransformer { get; set;} - public HqlBooleanExpression WhereClause { get; set;} - public HqlGroupBy GroupBy { get; set;} + public LambdaExpression ListTransformer { get; set; } + public LambdaExpression PostExecuteTransformer { get; set; } + public HqlBooleanExpression WhereClause { get; set; } + public HqlGroupBy GroupBy { get; set; } public HqlTreeNode AdditionalFrom { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs b/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs index 2db121b9316..52bf954101d 100644 --- a/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs +++ b/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using NHibernate.Engine; using NHibernate.Linq.Functions; using NHibernate.Linq.Expressions; +using NHibernate.Param; using NHibernate.Util; using Remotion.Linq.Parsing; @@ -15,6 +17,8 @@ namespace NHibernate.Linq.Visitors class SelectClauseHqlNominator : RelinqExpressionVisitor { private readonly ILinqToHqlGeneratorsRegistry _functionRegistry; + private readonly ISessionFactoryImplementor _sessionFactory; + private readonly VisitorParameters _parameters; /// /// The expression parts that can be converted to pure HQL. @@ -35,6 +39,8 @@ class SelectClauseHqlNominator : RelinqExpressionVisitor public SelectClauseHqlNominator(VisitorParameters parameters) { _functionRegistry = parameters.SessionFactory.Settings.LinqToHqlGeneratorsRegistry; + _sessionFactory = parameters.SessionFactory; + _parameters = parameters; } internal Expression Nominate(Expression expression) @@ -114,11 +120,14 @@ private bool IsRegisteredFunction(Expression expression) if (expression.NodeType == ExpressionType.Call) { var methodCallExpression = (MethodCallExpression) expression; - IHqlGeneratorForMethod methodGenerator; - if (_functionRegistry.TryGetGenerator(methodCallExpression.Method, out methodGenerator)) + if (_functionRegistry.TryGetGenerator(methodCallExpression.Method, out var methodGenerator)) { - return methodCallExpression.Object == null || // is static or extension method - methodCallExpression.Object.NodeType != ExpressionType.Constant; // does not belong to parameter + // is static or extension method + return methodCallExpression.Object == null || + // does not belong to parameter + methodCallExpression.Object.NodeType != ExpressionType.Constant || + // does not ignore the parameter it belongs to + methodGenerator.IgnoreInstance(methodCallExpression.Method); } } else if (expression is NhSumExpression || @@ -130,7 +139,6 @@ expression is NhMaxExpression || return true; } return false; - } private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool projectConstantsInHql) @@ -147,6 +155,11 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj // Constants will only be evaluated in HQL if they're inside a method call if (expression.NodeType == ExpressionType.Constant) { + if (!projectConstantsInHql && _parameters.ConstantToParameterMap.ContainsKey((ConstantExpression)expression)) + { + _parameters.CanCachePlan = false; + } + return projectConstantsInHql; } @@ -166,8 +179,10 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj return projectConstantsInHql; } - // Assume all is good - return true; + return !(expression is MemberExpression memberExpression) || // Assume all is good + // Nominate only expressions that represent a mapped property or a translatable method call + ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out _, out _, out _, out _) || + _functionRegistry.TryGetGenerator(memberExpression.Member, out _); } private static bool CanBeEvaluatedInHqlStatementShortcut(Expression expression) @@ -175,4 +190,4 @@ private static bool CanBeEvaluatedInHqlStatementShortcut(Expression expression) return expression is NhCountExpression; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs b/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs index 82603cda269..df1cdfb3daa 100644 --- a/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs +++ b/src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs @@ -93,7 +93,7 @@ public override Expression Visit(Expression expression) } private static readonly MethodInfo ConvertChangeType = - ReflectHelper.GetMethod(() => System.Convert.ChangeType(default(object), default(System.Type))); + ReflectHelper.FastGetMethod(System.Convert.ChangeType, default(object), default(System.Type)); private static Expression Convert(Expression expression, System.Type type) { diff --git a/src/NHibernate/Linq/Visitors/SimplifyConditionalVisitor.cs b/src/NHibernate/Linq/Visitors/SimplifyConditionalVisitor.cs index 20047942e40..528bb3ce916 100644 --- a/src/NHibernate/Linq/Visitors/SimplifyConditionalVisitor.cs +++ b/src/NHibernate/Linq/Visitors/SimplifyConditionalVisitor.cs @@ -25,7 +25,6 @@ protected override Expression VisitConditional(ConditionalExpression expression) return base.VisitConditional(expression); } - protected override Expression VisitBinary(BinaryExpression expression) { // See NH-3423. Conditional expression where the test expression is a comparison @@ -45,13 +44,11 @@ protected override Expression VisitBinary(BinaryExpression expression) return base.VisitBinary(expression); } - private static bool IsConstruction(Expression expression) { return expression is NewExpression || expression is MemberInitExpression; } - private static bool IsConstructionToNullComparison(Expression expression) { var testExpression = expression as BinaryExpression; @@ -68,4 +65,4 @@ private static bool IsConstructionToNullComparison(Expression expression) return false; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/SubQueryFromClauseFlattener.cs b/src/NHibernate/Linq/Visitors/SubQueryFromClauseFlattener.cs index 82b33c27b1d..51d276340c8 100644 --- a/src/NHibernate/Linq/Visitors/SubQueryFromClauseFlattener.cs +++ b/src/NHibernate/Linq/Visitors/SubQueryFromClauseFlattener.cs @@ -4,6 +4,7 @@ using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ExpressionVisitors; +using Remotion.Linq.Clauses.ResultOperators; using Remotion.Linq.EagerFetching; namespace NHibernate.Linq.Visitors @@ -15,7 +16,8 @@ public class SubQueryFromClauseFlattener : NhQueryModelVisitorBase typeof(LockResultOperator), typeof(FetchLazyPropertiesResultOperator), typeof(FetchOneRequest), - typeof(FetchManyRequest) + typeof(FetchManyRequest), + typeof(AsQueryableResultOperator) }; public static void ReWrite(QueryModel queryModel) diff --git a/src/NHibernate/Linq/Visitors/TransparentIdentifierRemovingExpressionVisitor.cs b/src/NHibernate/Linq/Visitors/TransparentIdentifierRemovingExpressionVisitor.cs new file mode 100644 index 00000000000..7016438e321 --- /dev/null +++ b/src/NHibernate/Linq/Visitors/TransparentIdentifierRemovingExpressionVisitor.cs @@ -0,0 +1,121 @@ +// Copyright (c) rubicon IT GmbH, www.rubicon.eu +// +// See the NOTICE file distributed with this work for additional information +// regarding copyright ownership. rubicon licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may not use this +// file except in compliance with the License. You may obtain a copy of the +// License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// + +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Remotion.Linq; +using Remotion.Linq.Clauses.Expressions; +using Remotion.Linq.Parsing; +using MemberBinding = Remotion.Linq.Parsing.ExpressionVisitors.MemberBindings.MemberBinding; + +namespace NHibernate.Linq.Visitors +{ + // Copied from Relinq and added a fallback for comparing two member info by DeclaringType and Name + // 6.0 TODO: drop if https://github.com/OData/WebApi/issues/2108 is fixed and add a possible breaking + // change requiring to upgrade OData. (See https://github.com/nhibernate/nhibernate-core/pull/2322#discussion_r401215456 ) + // Use this version in order to support expressions that were created programmatically and do not mimic what the C# compiler generates. + // Consider removing this if https://re-motion.atlassian.net/projects/RMLNQ/issues/RMLNQ-121 is fixed and we upgrade ReLinq. + /// + /// Replaces expression patterns of the form new T { x = 1, y = 2 }.x () or + /// new T ( x = 1, y = 2 ).x () to 1 (or 2 if y is accessed instead of x). + /// Expressions are also replaced within subqueries; the is changed by the replacement operations, it is not copied. + /// + internal sealed class TransparentIdentifierRemovingExpressionVisitor : RelinqExpressionVisitor + { + public static Expression ReplaceTransparentIdentifiers(Expression expression) + { + Expression expressionBeforeRemove; + Expression expressionAfterRemove = expression; + + // Run again and again until no replacements have been made. + do + { + expressionBeforeRemove = expressionAfterRemove; + expressionAfterRemove = new TransparentIdentifierRemovingExpressionVisitor().Visit(expressionAfterRemove); + } while (expressionAfterRemove != expressionBeforeRemove); + + return expressionAfterRemove; + } + + private TransparentIdentifierRemovingExpressionVisitor() + { + } + + protected override Expression VisitMember(MemberExpression memberExpression) + { + var memberBindings = GetMemberBindingsCreatedByExpression(memberExpression.Expression); + if (memberBindings == null) + return base.VisitMember(memberExpression); + + var matchingAssignment = memberBindings + .Where(binding => binding.MatchesReadAccess(memberExpression.Member)) + .LastOrDefault(); + + // Added logic: In some cases (e.g OData), the member can be from a different derived class, in such case + // we need to check the member DeclaringType instead of ReflectedType + if (matchingAssignment == null && memberExpression.Expression.NodeType == ExpressionType.MemberInit) + { + matchingAssignment = memberBindings + .Where(binding => AreEqual(binding.BoundMember, memberExpression.Member)) + .LastOrDefault(); + } + + if (matchingAssignment == null) + return base.VisitMember(memberExpression); + else + return matchingAssignment.AssociatedExpression; + } + + protected override Expression VisitSubQuery(SubQueryExpression expression) + { + expression.QueryModel.TransformExpressions(ReplaceTransparentIdentifiers); + return expression; // Note that we modifiy the (mutable) QueryModel, we return an unchanged expression + } + + private IEnumerable GetMemberBindingsCreatedByExpression(Expression expression) + { + var memberInitExpression = expression as MemberInitExpression; + if (memberInitExpression != null) + { + return memberInitExpression.Bindings + .Where(binding => binding is MemberAssignment) + .Select(assignment => MemberBinding.Bind(assignment.Member, ((MemberAssignment) assignment).Expression)); + } + else + { + var newExpression = expression as NewExpression; + if (newExpression != null && newExpression.Members != null) + return GetMemberBindingsForNewExpression(newExpression); + else + return null; + } + } + + private IEnumerable GetMemberBindingsForNewExpression(NewExpression newExpression) + { + for (int i = 0; i < newExpression.Members.Count; ++i) + yield return MemberBinding.Bind(newExpression.Members[i], newExpression.Arguments[i]); + } + + private static bool AreEqual(MemberInfo memberInfo, MemberInfo toComapre) + { + return memberInfo.DeclaringType == toComapre.DeclaringType && memberInfo.Name == toComapre.Name; + } + } +} diff --git a/src/NHibernate/Linq/Visitors/VisitorParameters.cs b/src/NHibernate/Linq/Visitors/VisitorParameters.cs index a4d2a1f2c65..cf53c6ec119 100644 --- a/src/NHibernate/Linq/Visitors/VisitorParameters.cs +++ b/src/NHibernate/Linq/Visitors/VisitorParameters.cs @@ -23,6 +23,8 @@ public class VisitorParameters public QueryMode RootQueryMode { get; } + internal bool CanCachePlan { get; set; } = true; + public VisitorParameters( ISessionFactoryImplementor sessionFactory, IDictionary constantToParameterMap, @@ -39,4 +41,4 @@ public VisitorParameters( RootQueryMode = rootQueryMode; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Linq/Visitors/VisitorUtil.cs b/src/NHibernate/Linq/Visitors/VisitorUtil.cs index c4fa337d572..40dbaeb4fb7 100644 --- a/src/NHibernate/Linq/Visitors/VisitorUtil.cs +++ b/src/NHibernate/Linq/Visitors/VisitorUtil.cs @@ -13,25 +13,12 @@ public static class VisitorUtil { public static bool IsDynamicComponentDictionaryGetter(MethodInfo method, Expression targetObject, IEnumerable arguments, ISessionFactory sessionFactory, out string memberName) { - memberName = null; - - // A dynamic component must be an IDictionary with a string key. - - if (method.Name != "get_Item" || !typeof(IDictionary).IsAssignableFrom(targetObject.Type) && !typeof(IDictionary).IsAssignableFrom(targetObject.Type)) - return false; - - var key = arguments.First() as ConstantExpression; - if (key == null || key.Type != typeof(string)) - return false; - - // The potential member name - memberName = (string)key.Value; - - // Need the owning member (the dictionary). - var member = targetObject as MemberExpression; - if (member == null) + if (!TryGetPotentialDynamicComponentDictionaryMember(method, targetObject, arguments, out memberName)) + { return false; + } + var member = (MemberExpression) targetObject; var memberPath = member.Member.Name; var metaData = sessionFactory.GetClassMetadata(member.Expression.Type); @@ -81,7 +68,6 @@ public static bool IsDynamicComponentDictionaryGetter(MethodCallExpression expre return IsDynamicComponentDictionaryGetter(expression, sessionFactory, out memberName); } - public static bool IsNullConstant(Expression expression) { var constantExpression = expression as ConstantExpression; @@ -91,7 +77,6 @@ public static bool IsNullConstant(Expression expression) constantExpression.Value == null; } - public static bool IsBooleanConstant(Expression expression, out bool value) { var constantExpr = expression as ConstantExpression; @@ -133,5 +118,42 @@ public static string GetMemberPath(this MemberExpression memberExpression) } return path; } + + internal static bool TryGetPotentialDynamicComponentDictionaryMember(MethodCallExpression expression, out string memberName) + { + return TryGetPotentialDynamicComponentDictionaryMember( + expression.Method, + expression.Object, + expression.Arguments, + out memberName); + } + + internal static bool TryGetPotentialDynamicComponentDictionaryMember( + MethodInfo method, + Expression targetObject, + IEnumerable arguments, + out string memberName) + { + memberName = null; + // A dynamic component must be an IDictionary with a string key. + if (method.Name != "get_Item" || + targetObject.NodeType != ExpressionType.MemberAccess || // Need the owning member (the dictionary). + !(arguments.First() is ConstantExpression key) || + key.Type != typeof(string) || + (!typeof(IDictionary).IsAssignableFrom(targetObject.Type) && !typeof(IDictionary).IsAssignableFrom(targetObject.Type))) + { + return false; + } + + // The potential member name + memberName = (string) key.Value; + return true; + } + + internal static bool IsMappedAs(MethodInfo methodInfo) + { + return methodInfo.Name == nameof(LinqExtensionMethods.MappedAs) && + methodInfo.DeclaringType == typeof(LinqExtensionMethods); + } } } diff --git a/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs b/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs index 66508f01eef..689457a7403 100644 --- a/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs +++ b/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using NHibernate.Linq.Clauses; +using NHibernate.Engine; using NHibernate.Linq.ReWriters; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; @@ -62,6 +62,7 @@ internal class WhereJoinDetector : RelinqExpressionVisitor // TODO: There are a number of types of expressions that we didn't handle here due to time constraints. For example, the ?: operator could be checked easily. private readonly IIsEntityDecider _isEntityDecider; private readonly IJoiner _joiner; + private readonly ISessionFactoryImplementor _sessionFactory; private readonly Stack _handled = new Stack(); @@ -71,16 +72,28 @@ internal class WhereJoinDetector : RelinqExpressionVisitor // The following is used for member expressions traversal. private int _memberExpressionDepth; - internal WhereJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner) + internal WhereJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner, ISessionFactoryImplementor sessionFactory) { _isEntityDecider = isEntityDecider; _joiner = joiner; + _sessionFactory = sessionFactory; + } + + public Expression Transform(Expression expression) + { + var result = Visit(expression); + PostTransform(); + return result; } public void Transform(IClause whereClause) { whereClause.TransformExpressions(Visit); + PostTransform(); + } + private void PostTransform() + { var values = _values.Pop(); foreach (var memberExpression in values.MemberExpressions) @@ -278,7 +291,7 @@ protected override Expression VisitSubQuery(SubQueryExpression expression) return expression; } - // We would usually get NULL if one of our inner member expresions was null. + // We would usually get NULL if one of our inner member expressions was null. // However, it's possible a method call will convert the null value from the failed join into a non-null value. // This could be optimized by actually checking what the method does. For example StartsWith("s") would leave null as null and would still allow us to inner join. //protected override Expression VisitMethodCall(MethodCallExpression expression) @@ -296,8 +309,15 @@ protected override Expression VisitMember(MemberExpression expression) // I'm not sure what processing re-linq does to strange member expressions. // TODO: I suspect this code doesn't add the right joins for the last case. - var isIdentifier = _isEntityDecider.IsIdentifier(expression.Expression.Type, expression.Member.Name); + // A static member expression such as DateTime.Now has a null Expression. + if (expression.Expression == null) + { + // A static member call is never a join, and it is not an instance member access either: leave + // the current value on stack, untouched. + return base.VisitMember(expression); + } + var isEntity = _isEntityDecider.IsEntity(expression, out var isIdentifier); if (!isIdentifier) _memberExpressionDepth++; @@ -307,11 +327,11 @@ protected override Expression VisitMember(MemberExpression expression) _memberExpressionDepth--; ExpressionValues values = _values.Pop().Operation(pvs => pvs.MemberAccess(expression.Type)); - if (_isEntityDecider.IsEntity(expression.Type)) + if (isEntity) { // Don't add joins for things like a.B == a.C where B and C are entities. // We only need to join B when there's something like a.B.D. - var key = ExpressionKeyVisitor.Visit(expression, null); + var key = ExpressionKeyVisitor.Visit(expression, null, _sessionFactory); if (_memberExpressionDepth > 0 && _joiner.CanAddJoin(expression)) { @@ -321,7 +341,7 @@ protected override Expression VisitMember(MemberExpression expression) values.MemberExpressionValuesIfEmptyOuterJoined[key] = PossibleValueSet.CreateNull(expression.Type); } SetResultValues(values); - + return result; } diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 0767d041f1f..5fa438cc85a 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using NHibernate.Criterion; using NHibernate.Engine; -using NHibernate.Loader.Criteria; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.Type; @@ -62,6 +61,7 @@ protected void InitProjection(SqlString projectionString, SqlString whereString, var associations = new OuterJoinableAssociation[countEntities]; var eagerProps = new bool[countEntities]; var suffixes = new string[countEntities]; + var fetchLazyProperties = new ISet[countEntities]; for (var i = 0; i < countEntities; i++) { var e = entityProjections[i]; @@ -70,6 +70,11 @@ protected void InitProjection(SqlString projectionString, SqlString whereString, { eagerProps[i] = true; } + + if (e.FetchLazyPropertyGroups?.Count > 0) + { + fetchLazyProperties[i] = new HashSet(e.FetchLazyPropertyGroups); + } suffixes[i] = e.ColumnAliasSuffix; } @@ -77,6 +82,7 @@ protected void InitProjection(SqlString projectionString, SqlString whereString, Suffixes = suffixes; EagerPropertyFetches = eagerProps; + EntityFetchLazyProperties = fetchLazyProperties; } else { @@ -105,7 +111,6 @@ private OuterJoinableAssociation CreateAssociation(EntityType entityType, string CollectionHelper.EmptyDictionary()); } - private void InitStatementString(OuterJoinableAssociation rootAssociation, SqlString projection, SqlString condition, SqlString orderBy, SqlString groupBy, SqlString having, LockMode lockMode) { SqlString selectClause = projection; @@ -115,7 +120,7 @@ private void InitStatementString(OuterJoinableAssociation rootAssociation, SqlSt Suffixes = BasicLoader.GenerateSuffixes(joins + 1); var suffix = Suffixes[joins]; - selectClause = new SqlString(GetSelectFragment(rootAssociation, suffix, null) + SelectString(associations)); + selectClause = new SqlString(rootAssociation.GetSelectFragment(suffix, null, null) + SelectString(associations)); } JoinFragment ojf = MergeOuterJoins(associations); diff --git a/src/NHibernate/Loader/BasicLoader.cs b/src/NHibernate/Loader/BasicLoader.cs index 2d3ddafd877..97c0781da4b 100644 --- a/src/NHibernate/Loader/BasicLoader.cs +++ b/src/NHibernate/Loader/BasicLoader.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using NHibernate.Engine; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -50,7 +51,7 @@ protected override void PostInstantiate() { bagCount++; } - collectionDescriptors[i] = new GeneratedCollectionAliases(collectionPersisters[i], collectionSuffixes[i]); + collectionDescriptors[i] = new GeneratedCollectionAliases(GetCollectionUserProvidedAlias(i), collectionPersisters[i], collectionSuffixes[i]); } } else @@ -66,6 +67,11 @@ protected override void PostInstantiate() } } + protected virtual IDictionary GetCollectionUserProvidedAlias(int index) + { + return null; + } + private static bool IsBag(ICollectionPersister collectionPersister) { var type = collectionPersister.CollectionType.GetType(); diff --git a/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs b/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs index b00e32c5e98..04605581112 100644 --- a/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs +++ b/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs @@ -85,10 +85,9 @@ private void InitStatementString(string alias, int batchSize, SqlString subquery SqlString = select.ToSqlString(); } - public override string ToString() { return GetType().FullName + '(' + collectionPersister.Role + ')'; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Criteria/ComponentCollectionCriteriaInfoProvider.cs b/src/NHibernate/Loader/Criteria/ComponentCollectionCriteriaInfoProvider.cs index ba9c9a4e159..0dfce6c01b4 100644 --- a/src/NHibernate/Loader/Criteria/ComponentCollectionCriteriaInfoProvider.cs +++ b/src/NHibernate/Loader/Criteria/ComponentCollectionCriteriaInfoProvider.cs @@ -9,7 +9,7 @@ namespace NHibernate.Loader.Criteria public class ComponentCollectionCriteriaInfoProvider : ICriteriaInfoProvider { private readonly IQueryableCollection persister; - private readonly IDictionary subTypes = new Dictionary(); + private readonly Dictionary subTypes = new Dictionary(); public ComponentCollectionCriteriaInfoProvider(IQueryableCollection persister) { @@ -66,4 +66,4 @@ public IType GetType(String relativePath) #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs index 4ec1d841976..107163b7b68 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs @@ -28,9 +28,9 @@ public class CriteriaJoinWalker : AbstractEntityJoinWalker //the user visible aliases, which are unknown to the superclass, //these are not the actual "physical" SQL aliases private readonly string[] userAliases; - private readonly IList userAliasList = new List(); - private readonly IList resultTypeList = new List(); - private readonly IList includeInResultRowList = new List(); + private readonly List userAliasList = new List(); + private readonly List resultTypeList = new List(); + private readonly List includeInResultRowList = new List(); private static readonly INHibernateLogger logger = NHibernateLogger.For(typeof(CriteriaJoinWalker)); @@ -81,9 +81,9 @@ protected override void AddAssociations() { var tableAlias = translator.GetSQLAlias(entityJoinInfo.Criteria); var criteriaPath = entityJoinInfo.Criteria.Alias; //path for entity join is equal to alias - var criteriaAlias = entityJoinInfo.Criteria.Alias; + var criteriaPathAlias = entityJoinInfo.Criteria.Alias; var persister = entityJoinInfo.Persister as IOuterJoinLoadable; - AddExplicitEntityJoinAssociation(persister, tableAlias, translator.GetJoinType(criteriaPath, criteriaAlias), criteriaPath, criteriaAlias); + AddExplicitEntityJoinAssociation(persister, tableAlias, translator.GetJoinType(criteriaPath, criteriaPathAlias), criteriaPath, criteriaPathAlias); IncludeInResultIfNeeded(persister, entityJoinInfo.Criteria, tableAlias, criteriaPath); //collect mapped associations for entity join WalkEntityTree(persister, tableAlias, criteriaPath, 1); @@ -94,27 +94,30 @@ protected override void WalkEntityTree(IOuterJoinLoadable persister, string alia { // NH different behavior (NH-1476, NH-1760, NH-1785) base.WalkEntityTree(persister, alias, path, currentDepth); - WalkCompositeComponentIdTree(persister, alias, path); + WalkCompositeComponentIdTree(persister, alias, path, currentDepth); } protected override OuterJoinableAssociation CreateRootAssociation() { - var selectMode = GetSelectMode(string.Empty); + var path = string.Empty; + var selectMode = GetSelectMode(path); if (selectMode == SelectMode.JoinOnly || selectMode == SelectMode.Skip) { throw new NotSupportedException($"SelectMode {selectMode} for root entity is not supported. Use {nameof(SelectMode)}.{nameof(SelectMode.ChildFetch)} instead."); } - return new OuterJoinableAssociation( - Persister.EntityType, - null, - null, - Alias, - JoinType.LeftOuterJoin, - null, - Factory, - CollectionHelper.EmptyDictionary(), - selectMode); + return InitAssociation( + new OuterJoinableAssociation( + Persister.EntityType, + null, + null, + Alias, + JoinType.LeftOuterJoin, + null, + Factory, + CollectionHelper.EmptyDictionary(), + selectMode), + path); } protected override SelectMode GetSelectMode(string path) @@ -122,14 +125,19 @@ protected override SelectMode GetSelectMode(string path) return translator.RootCriteria.GetSelectMode(path); } - private void WalkCompositeComponentIdTree(IOuterJoinLoadable persister, string alias, string path) + protected override ISet GetEntityFetchLazyProperties(string path) + { + return translator.RootCriteria.GetEntityFetchLazyProperties(path); + } + + private void WalkCompositeComponentIdTree(IOuterJoinLoadable persister, string alias, string path, int currentDepth) { IType type = persister.IdentifierType; string propertyName = persister.IdentifierPropertyName; if (type != null && type.IsComponentType) { ILhsAssociationTypeSqlInfo associationTypeSQLInfo = JoinHelper.GetIdLhsSqlInfo(alias, persister, Factory); - WalkComponentTree((IAbstractComponentType) type, 0, alias, SubPath(path, propertyName), 0, associationTypeSQLInfo); + WalkComponentTree((IAbstractComponentType) type, 0, alias, SubPath(path, propertyName), currentDepth, associationTypeSQLInfo); } } @@ -197,6 +205,7 @@ protected override JoinType GetJoinType(IAssociationType type, FetchMode config, case SelectMode.Fetch: case SelectMode.FetchLazyProperties: + case SelectMode.FetchLazyPropertyGroup: case SelectMode.ChildFetch: case SelectMode.JoinOnly: IsDuplicateAssociation(lhsTable, lhsColumns, type); //deliberately ignore return value! diff --git a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs index 8f4b99e58c0..e3a5ff5dfda 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaLoader.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaLoader.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Linq; using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Param; @@ -12,6 +13,28 @@ namespace NHibernate.Loader.Criteria { + internal static partial class CriteriaLoaderExtensions + { + /// + /// Loads all loaders results to single typed list + /// + internal static List LoadAllToList(this IList loaders, ISessionImplementor session) + { + var subresults = new List(loaders.Count); + foreach(var l in loaders) + { + subresults.Add(l.List(session)); + } + + var results = new List(subresults.Sum(r => r.Count)); + foreach(var list in subresults) + { + ArrayHelper.AddAll(results, list); + } + return results; + } + } + /// /// A Loader for queries. /// @@ -52,8 +75,9 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f userAliases = walker.UserAliases; ResultTypes = walker.ResultTypes; includeInResultRow = walker.IncludeInResultRow; - resultRowLength = ArrayHelper.CountTrue(IncludeInResultRow); + resultRowLength = ArrayHelper.CountTrue(includeInResultRow); childFetchEntities = walker.ChildFetchEntities; + EntityFetchLazyProperties = walker.EntityFetchLazyProperties; // fill caching objects only if there is a projection if (translator.HasProjection) { @@ -61,6 +85,10 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f } PostInstantiate(); + if (!translator.HasProjection) + { + CachePersistersWithCollections(ArrayHelper.IndexesOf(includeInResultRow, true)); + } } // Not ported: scroll (not supported) @@ -95,6 +123,8 @@ protected override bool IsChildFetchEntity(int i) return childFetchEntities?[i] == true; } + protected override ISet[] EntityFetchLazyProperties { get; } + public IList List(ISessionImplementor session) { return List(session, translator.GetQueryParameters(), querySpaces); @@ -117,7 +147,6 @@ protected override object GetResultColumnOrRow(object[] row, IResultTransformer .TransformTuple(GetResultRow(row, rs, session), ResultRowAliases); } - protected override object[] GetResultRow(object[] row, DbDataReader rs, ISessionImplementor session) { object[] result; diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index 620cd936c0f..955c4802dd3 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -35,18 +35,18 @@ public class EntityJoinInfo private int indexForAlias = 0; private readonly List entityProjections = new List(); - private readonly IDictionary criteriaInfoMap = + private readonly Dictionary criteriaInfoMap = new Dictionary(); - private readonly IDictionary nameCriteriaInfoMap = + private readonly Dictionary nameCriteriaInfoMap = new Dictionary(); private readonly HashSet uncacheableCollectionPersisters = new HashSet(); - private readonly ISet criteriaCollectionPersisters = new HashSet(); - private readonly IDictionary criteriaSQLAliasMap = new Dictionary(); + private readonly HashSet criteriaCollectionPersisters = new HashSet(); + private readonly Dictionary criteriaSQLAliasMap = new Dictionary(); private readonly Dictionary sqlAliasToCriteriaAliasMap = new Dictionary(); private readonly Dictionary> associationAliasToChildrenAliasesMap = new Dictionary>(); - private readonly IDictionary aliasCriteriaMap = new Dictionary(); + private readonly Dictionary aliasCriteriaMap = new Dictionary(); private readonly Dictionary associationPathCriteriaMap = new Dictionary(); private readonly Dictionary associationPathJoinTypesMap = new Dictionary(); private readonly Dictionary withClauseMap = new Dictionary(); @@ -55,7 +55,7 @@ public class EntityJoinInfo private readonly ICollection collectedParameterSpecifications; private readonly ICollection namedParameters; - private readonly ISet subQuerySpaces = new HashSet(); + private readonly HashSet subQuerySpaces = new HashSet(); private Dictionary entityJoins = new Dictionary(); private readonly IQueryable rootPersister; @@ -237,7 +237,6 @@ internal IType ResultType(ICriteria criteria) //return Factory.getTypeResolver().getTypeFactory().manyToOne(getEntityName(criteria)); } - public IType[] ProjectedTypes { get { return rootCriteria.Projection.GetTypes(rootCriteria, this); } @@ -507,7 +506,6 @@ private void CreateCriteriaEntityNameMap() criteriaInfoMap.Add(rootCriteria, rootProvider); nameCriteriaInfoMap.Add(rootProvider.Name, rootProvider); - foreach (var me in associationPathCriteriaMap) { var info = GetPathInfo(me.Key.Path, rootProvider); @@ -780,18 +778,8 @@ public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName) if (projectionTypes == null) { - //it does not refer to an alias of a projection, - //look for a property - - if (TryGetType(subcriteria, propertyName, out var type)) - { - return type; - } - if (outerQueryTranslator != null) - { - return outerQueryTranslator.GetTypeUsingProjection(subcriteria, propertyName); - } - throw new QueryException("Could not find property " + propertyName); + //it does not refer to an alias of a projection, look for a property + return GetType(subcriteria, propertyName); } else { @@ -806,10 +794,13 @@ public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName) public IType GetType(ICriteria subcriteria, string propertyName) { - if(!TryParseCriteriaPath(subcriteria, propertyName, out var entityName, out var entityPropName, out _)) - throw new QueryException("Could not find property " + propertyName); + if (TryGetType(subcriteria, propertyName, out var resultType)) + return resultType; + + if (outerQueryTranslator != null) + return outerQueryTranslator.GetType(subcriteria, propertyName); - return GetPropertyMapping(entityName).ToType(entityPropName); + throw new QueryException("Could not find property " + propertyName); } public bool TryGetType(ICriteria subcriteria, string propertyName, out IType type) @@ -846,8 +837,8 @@ public TypedValue GetTypedValue(ICriteria subcriteria, string propertyName, obje private Persister.Entity.IPropertyMapping GetPropertyMapping(string entityName) { - ICriteriaInfoProvider info ; - if (nameCriteriaInfoMap.TryGetValue(entityName, out info)==false) + ICriteriaInfoProvider info; + if (nameCriteriaInfoMap.TryGetValue(entityName, out info) == false) throw new InvalidOperationException("Could not find criteria info provider for: " + entityName); return info.PropertyMapping; } @@ -865,6 +856,23 @@ public string GetEntityName(ICriteria subcriteria, string propertyName) return GetEntityName(subcriteria); } + /// + /// Substitute the SQL aliases in template. + /// + public SqlString RenderSQLAliases(SqlString sqlTemplate) + { + var result = criteriaSQLAliasMap + .Where(p => !string.IsNullOrEmpty(p.Key.Alias)) + .Aggregate(sqlTemplate, (current, p) => current.Replace("{" + p.Key.Alias + "}", p.Value)); + + if (outerQueryTranslator != null) + { + return outerQueryTranslator.RenderSQLAliases(result); + } + + return result; + } + public string GetSQLAlias(ICriteria criteria, string propertyName) { if (StringHelper.IsNotRoot(propertyName, out var root)) @@ -1033,7 +1041,6 @@ public string[] GetColumnAliasesUsingProjection(ICriteria subcriteria, string pr private void CreateSubQuerySpaces() { - var subQueries = rootCriteria.IterateExpressionEntries() .Select(x => x.Criterion) @@ -1047,8 +1054,7 @@ private void CreateSubQuerySpaces() var translator = new CriteriaQueryTranslator(sessionFactory, criteriaImpl, criteriaImpl.EntityOrClassName, RootSqlAlias); subQuerySpaces.UnionWith(translator.GetQuerySpaces()); } - - } + } private IQueryable GetQueryablePersister(string entityName) { @@ -1075,4 +1081,3 @@ private bool TryParseCriteriaPath(ICriteria subcriteria, string path, out string } } } - diff --git a/src/NHibernate/Loader/Criteria/ScalarCollectionCriteriaInfoProvider.cs b/src/NHibernate/Loader/Criteria/ScalarCollectionCriteriaInfoProvider.cs index c6b8e19eb8b..108e4ace1b3 100644 --- a/src/NHibernate/Loader/Criteria/ScalarCollectionCriteriaInfoProvider.cs +++ b/src/NHibernate/Loader/Criteria/ScalarCollectionCriteriaInfoProvider.cs @@ -47,6 +47,5 @@ public IType GetType(String relativePath) //not sure what things are going to be passed here, how about 'id', maybe 'index' or 'key' or 'elements' ??? return PropertyMapping.ToType(relativePath); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs b/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs index 1b8a7f2f2d5..3ff5fd9a3ba 100644 --- a/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs +++ b/src/NHibernate/Loader/Custom/ColumnCollectionAliases.cs @@ -1,7 +1,5 @@ -using System.Collections; using System.Collections.Generic; using NHibernate.Persister.Collection; -using NHibernate.Util; namespace NHibernate.Loader.Custom { @@ -15,19 +13,13 @@ public class ColumnCollectionAliases : ICollectionAliases private readonly string[] indexAliases; private readonly string[] elementAliases; private readonly string identifierAlias; - private readonly IDictionary userProvidedAliases; public ColumnCollectionAliases(IDictionary userProvidedAliases, ISqlLoadableCollection persister) { - this.userProvidedAliases = userProvidedAliases; - - keyAliases = GetUserProvidedAliases("key", persister.KeyColumnNames); - - indexAliases = GetUserProvidedAliases("index", persister.IndexColumnNames); - - elementAliases = GetUserProvidedAliases("element", persister.ElementColumnNames); - - identifierAlias = GetUserProvidedAlias("id", persister.IdentifierColumnName); + keyAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropKey, persister.KeyColumnNames); + indexAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropIndex, persister.IndexColumnNames); + elementAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropElement, persister.ElementColumnNames); + identifierAlias = GetUserProvidedAlias(userProvidedAliases, CollectionPersister.PropId, persister.IdentifierColumnName); } /// @@ -90,30 +82,24 @@ private static string Join(string[] aliases) : string.Join(", ", aliases); } - private string[] GetUserProvidedAliases(string propertyPath, string[] defaultAliases) + private static string[] GetUserProvidedAliases(IDictionary userProvidedAliases, string propertyPath, string[] defaultAliases) { - string[] result; - if (!userProvidedAliases.TryGetValue(propertyPath, out result)) - { - return defaultAliases; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var result)) { return result; } + + return defaultAliases; } - private string GetUserProvidedAlias(string propertyPath, string defaultAlias) + private static string GetUserProvidedAlias(IDictionary userProvidedAliases, string propertyPath, string defaultAlias) { - string[] columns; - if (!userProvidedAliases.TryGetValue(propertyPath, out columns)) - { - return defaultAlias; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var columns)) { return columns[0]; } + + return defaultAlias; } } } diff --git a/src/NHibernate/Loader/Custom/CustomLoader.cs b/src/NHibernate/Loader/Custom/CustomLoader.cs index 7fc21cab2af..dea693cf540 100644 --- a/src/NHibernate/Loader/Custom/CustomLoader.cs +++ b/src/NHibernate/Loader/Custom/CustomLoader.cs @@ -24,7 +24,7 @@ public partial class CustomLoader : Loader // the types will also have to be discovered at cache hit, from the cache results. private readonly SqlString sql; - private readonly ISet querySpaces = new HashSet(); + private readonly HashSet querySpaces = new HashSet(); private List parametersSpecifications; private readonly IQueryable[] entityPersisters; @@ -275,7 +275,7 @@ public override ILoadable[] EntityPersisters get { return entityPersisters; } } - protected override ICollectionPersister[] CollectionPersisters + protected internal override ICollectionPersister[] CollectionPersisters { get { return collectionPersisters; } } diff --git a/src/NHibernate/Loader/Custom/Sql/SQLCustomQuery.cs b/src/NHibernate/Loader/Custom/Sql/SQLCustomQuery.cs index 57ca5afb81c..3d2b421aae4 100644 --- a/src/NHibernate/Loader/Custom/Sql/SQLCustomQuery.cs +++ b/src/NHibernate/Loader/Custom/Sql/SQLCustomQuery.cs @@ -18,7 +18,7 @@ public class SQLCustomQuery : ICustomQuery private static readonly INHibernateLogger log = NHibernateLogger.For(typeof (SQLCustomQuery)); private readonly List customQueryReturns = new List(); - private readonly ISet querySpaces = new HashSet(); + private readonly HashSet querySpaces = new HashSet(); private readonly SqlString sql; private readonly List parametersSpecifications; diff --git a/src/NHibernate/Loader/GeneratedCollectionAliases.cs b/src/NHibernate/Loader/GeneratedCollectionAliases.cs index 71b127fb60e..932272ea19f 100644 --- a/src/NHibernate/Loader/GeneratedCollectionAliases.cs +++ b/src/NHibernate/Loader/GeneratedCollectionAliases.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using NHibernate.Persister.Collection; -using NHibernate.Util; namespace NHibernate.Loader { @@ -16,38 +15,39 @@ public class GeneratedCollectionAliases : ICollectionAliases private readonly string[] indexAliases; private readonly string[] elementAliases; private readonly string identifierAlias; - private readonly IDictionary userProvidedAliases; - public GeneratedCollectionAliases(IDictionary userProvidedAliases, ICollectionPersister persister, - string suffix) + public GeneratedCollectionAliases(IDictionary userProvidedAliases, ICollectionPersister persister, string suffix) { this.suffix = suffix; - this.userProvidedAliases = userProvidedAliases; - keyAliases = GetUserProvidedAliases("key", persister.GetKeyColumnAliases(suffix)); - - indexAliases = GetUserProvidedAliases("index", persister.GetIndexColumnAliases(suffix)); + keyAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropKey, persister.GetKeyColumnAliases(suffix)); + indexAliases = GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropIndex, persister.GetIndexColumnAliases(suffix)); // NH-1612: Add aliases for all composite element properties to support access // to individual composite element properties in elements. elementAliases = persister.ElementType.IsComponentType - ? GetUserProvidedCompositeElementAliases(persister.GetElementColumnAliases(suffix)) - : GetUserProvidedAliases("element", persister.GetElementColumnAliases(suffix)); + ? GetUserProvidedCompositeElementAliases(userProvidedAliases, persister.GetElementColumnAliases(suffix)) + : GetUserProvidedAliases(userProvidedAliases, CollectionPersister.PropElement, persister.GetElementColumnAliases(suffix)); - identifierAlias = GetUserProvidedAlias("id", persister.GetIdentifierColumnAlias(suffix)); + identifierAlias = GetUserProvidedAlias(userProvidedAliases, CollectionPersister.PropId, persister.GetIdentifierColumnAlias(suffix)); } public GeneratedCollectionAliases(ICollectionPersister persister, string str) - : this(CollectionHelper.EmptyDictionary(), persister, str) {} + : this(null, persister, str) {} - private string[] GetUserProvidedCompositeElementAliases(string[] defaultAliases) + private static string[] GetUserProvidedCompositeElementAliases(IDictionary userProvidedAliases, string[] defaultAliases) { var aliases = new List(); - foreach (KeyValuePair userProvidedAlias in userProvidedAliases) + if (userProvidedAliases != null) { - if (userProvidedAlias.Key.StartsWith("element.", StringComparison.Ordinal)) + foreach (var userProvidedAlias in userProvidedAliases) { - aliases.AddRange(userProvidedAlias.Value); + if (userProvidedAlias.Key.StartsWith( + CollectionPersister.PropElement + ".", + StringComparison.Ordinal)) + { + aliases.AddRange(userProvidedAlias.Value); + } } } @@ -103,40 +103,31 @@ public override string ToString() base.ToString(), suffix, Join(keyAliases), Join(indexAliases), Join(elementAliases), identifierAlias); } - private static string Join(IEnumerable aliases) + private static string Join(string[] aliases) { - if (aliases == null) - { - return null; - } - - return string.Join(", ", aliases); + return aliases == null + ? null + : string.Join(", ", aliases); } - private string[] GetUserProvidedAliases(string propertyPath, string[] defaultAliases) + private static string[] GetUserProvidedAliases(IDictionary userProvidedAliases, string propertyPath, string[] defaultAliases) { - string[] result; - if (!userProvidedAliases.TryGetValue(propertyPath, out result)) - { - return defaultAliases; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var result)) { return result; } + + return defaultAliases; } - private string GetUserProvidedAlias(string propertyPath, string defaultAlias) + private static string GetUserProvidedAlias(IDictionary userProvidedAliases, string propertyPath, string defaultAlias) { - string[] columns; - if (!userProvidedAliases.TryGetValue(propertyPath, out columns)) - { - return defaultAlias; - } - else + if (userProvidedAliases != null && userProvidedAliases.TryGetValue(propertyPath, out var columns)) { return columns[0]; } + + return defaultAlias; } } } diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 9bb71eb2d3b..5fdda0cde17 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -44,8 +44,8 @@ public partial class QueryLoader : BasicLoader private readonly NullableDictionary _sqlAliasByEntityAlias = new NullableDictionary(); private int _selectLength; private LockMode[] _defaultLockModes; - private IType[] _cacheTypes; private ISet _uncacheableCollectionPersisters; + private IReadOnlyDictionary _entityByResultTypeDic; public QueryLoader(QueryTranslatorImpl queryTranslator, ISessionFactoryImplementor factory, SelectClause selectClause) : base(factory) @@ -119,7 +119,7 @@ protected override bool[] EntityEagerPropertyFetches get { return _entityEagerPropertyFetches; } } - protected override HashSet[] EntityFetchLazyProperties + protected override ISet[] EntityFetchLazyProperties { get { return _entityFetchLazyProperties; } } @@ -198,16 +198,15 @@ protected override string[] CollectionSuffixes get { return _collectionSuffixes; } } - protected override ICollectionPersister[] CollectionPersisters + protected internal override ICollectionPersister[] CollectionPersisters { get { return _collectionPersisters; } } - public override IType[] CacheTypes => _cacheTypes; - private void Initialize(SelectClause selectClause) { IList fromElementList = selectClause.FromElementsForLoad; + _entityByResultTypeDic = selectClause.EntityByResultTypeDic; _hasScalars = selectClause.IsScalarSelect; _scalarColumnNames = selectClause.ColumnNames; @@ -224,7 +223,6 @@ private void Initialize(SelectClause selectClause) _collectionPersisters = new IQueryableCollection[length]; _collectionOwners = new int[length]; _collectionSuffixes = new string[length]; - CollectionFetches = new bool[length]; for (int i = 0; i < length; i++) { @@ -234,7 +232,6 @@ private void Initialize(SelectClause selectClause) // collectionSuffixes[i] = collectionFromElement.getColumnAliasSuffix(); // collectionSuffixes[i] = Integer.toString( i ) + "_"; _collectionSuffixes[i] = collectionFromElement.CollectionSuffix; - CollectionFetches[i] = collectionFromElement.IsFetch; } } @@ -248,8 +245,6 @@ private void Initialize(SelectClause selectClause) _includeInSelect = new bool[size]; _owners = new int[size]; _ownerAssociationTypes = new EntityType[size]; - EntityFetches = new bool[size]; - var cacheTypes = new List(ResultTypes); for (int i = 0; i < size; i++) { @@ -272,11 +267,6 @@ private void Initialize(SelectClause selectClause) _sqlAliasSuffixes[i] = (size == 1) ? "" : i + "_"; // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); _includeInSelect[i] = !element.IsFetch; - EntityFetches[i] = element.IsFetch; - if (element.IsFetch) - { - cacheTypes.Add(_entityPersisters[i].Type); - } if (_includeInSelect[i]) { _selectLength++; @@ -297,16 +287,10 @@ private void Initialize(SelectClause selectClause) } } - if (_collectionPersisters != null) - { - cacheTypes.AddRange(_collectionPersisters.Where((t, i) => CollectionFetches[i]).Select(t => t.CollectionType)); - } - - _cacheTypes = cacheTypes.ToArray(); - //NONE, because its the requested lock mode, not the actual! _defaultLockModes = ArrayHelper.Fill(LockMode.None, size); _uncacheableCollectionPersisters = _queryTranslator.UncacheableCollectionPersisters; + CachePersistersWithCollections(ArrayHelper.IndexesOf(_includeInSelect, true)); } public IList List(ISessionImplementor session, QueryParameters queryParameters) @@ -375,7 +359,9 @@ protected override object[] GetResultRow(object[] row, DbDataReader rs, ISession resultRow = new object[queryCols]; for (int i = 0; i < queryCols; i++) { - resultRow[i] = ResultTypes[i].NullSafeGet(rs, scalarColumns[i], session, null); + resultRow[i] = _entityByResultTypeDic.TryGetValue(i, out var rowIndex) + ? row[rowIndex] + : ResultTypes[i].NullSafeGet(rs, scalarColumns[i], session, null); } } else diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index 5e1398f4607..4ed49b21db6 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -58,6 +58,7 @@ public string[] Aliases public bool[] EagerPropertyFetches { get; set; } public bool[] ChildFetchEntities { get; set; } + public ISet[] EntityFetchLazyProperties { get; set; } public int[] CollectionOwners { @@ -135,11 +136,11 @@ protected JoinWalker(ISessionFactoryImplementor factory, IDictionary private void AddAssociationToJoinTreeIfNecessary(IAssociationType type, string[] aliasedLhsColumns, - string alias, string path, string subPathAlias, int currentDepth, JoinType joinType) + string alias, string path, string pathAlias, int currentDepth, JoinType joinType) { if (joinType >= JoinType.InnerJoin) { - AddAssociationToJoinTree(type, aliasedLhsColumns, alias, path, subPathAlias, currentDepth, joinType); + AddAssociationToJoinTree(type, aliasedLhsColumns, alias, path, pathAlias, currentDepth, joinType); } } @@ -163,29 +164,33 @@ protected virtual SqlString GetWithClause(string path, string pathAlias) /// of associations to be fetched by outerjoin /// private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhsColumns, string alias, - string path, string subPathAlias, int currentDepth, JoinType joinType) + string path, string pathAlias, int currentDepth, JoinType joinType) { IJoinable joinable = type.GetAssociatedJoinable(Factory); - string subalias = GenerateTableAlias(associations.Count + 1, path, subPathAlias, joinable); + string subalias = GenerateTableAlias(associations.Count + 1, path, pathAlias, joinable); + var qc = joinable.IsCollection ? (IQueryableCollection) joinable : null; var assoc = - new OuterJoinableAssociation( - type, - alias, - aliasedLhsColumns, - subalias, - joinType, - GetWithClause(path, subPathAlias), - Factory, - enabledFilters, - GetSelectMode(path)); + InitAssociation( + new OuterJoinableAssociation( + type, + alias, + aliasedLhsColumns, + subalias, + joinType, + //for many-to-many with clause is applied with OuterJoinableAssociation created for entity persister so simply skip it here + qc?.IsManyToMany == true ? null :GetWithClause(path, pathAlias), + Factory, + enabledFilters, + GetSelectMode(path)), + path); assoc.ValidateJoin(path); AddAssociation(assoc); int nextDepth = currentDepth + 1; - if (!joinable.IsCollection) + if (qc == null) { IOuterJoinLoadable pjl = joinable as IOuterJoinLoadable; if (pjl != null) @@ -193,9 +198,7 @@ private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhs } else { - IQueryableCollection qc = joinable as IQueryableCollection; - if (qc != null) - WalkCollectionTree(qc, subalias, path, subPathAlias, nextDepth); + WalkCollectionTree(qc, subalias, path, pathAlias, nextDepth); } } @@ -204,6 +207,11 @@ protected virtual SelectMode GetSelectMode(string path) return SelectMode.Undefined; } + protected virtual ISet GetEntityFetchLazyProperties(string path) + { + return null; + } + private struct DependentAlias2 { public DependentAlias2(string alias, ICollection dependsOn) @@ -306,7 +314,7 @@ protected void WalkCollectionTree(IQueryableCollection persister, string alias) /// /// For a collection role, return a list of associations to be fetched by outerjoin /// - private void WalkCollectionTree(IQueryableCollection persister, string alias, string path, string subPathAlias, int currentDepth) + private void WalkCollectionTree(IQueryableCollection persister, string alias, string path, string pathAlias, int currentDepth) { if (persister.IsOneToMany) { @@ -334,7 +342,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st associationType, persister.FetchMode, path, - subPathAlias, + pathAlias, persister.TableName, lhsColumns, !useInnerJoin, @@ -346,7 +354,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st aliasedLhsColumns, alias, path, - subPathAlias, + pathAlias, currentDepth - 1, joinType); } @@ -358,7 +366,6 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st persister, alias, path, - subPathAlias, currentDepth); } } @@ -369,22 +376,29 @@ internal void AddExplicitEntityJoinAssociation( string tableAlias, JoinType joinType, string path, - string alias) + string pathAlias) { OuterJoinableAssociation assoc = - new OuterJoinableAssociation( + InitAssociation(new OuterJoinableAssociation( persister.EntityType, string.Empty, Array.Empty(), tableAlias, joinType, - GetWithClause(path, alias), + GetWithClause(path, pathAlias), Factory, enabledFilters, - GetSelectMode(path)); + GetSelectMode(path)) {ForceFilter = true}, + path); AddAssociation(assoc); } + internal OuterJoinableAssociation InitAssociation(OuterJoinableAssociation association, string path) + { + association.EntityFetchLazyProperties = GetEntityFetchLazyProperties(path); + return association; + } + private void WalkEntityAssociationTree(IAssociationType associationType, IOuterJoinLoadable persister, int propertyNumber, string alias, string path, bool nullable, int currentDepth, ILhsAssociationTypeSqlInfo associationTypeSQLInfo) @@ -505,7 +519,7 @@ protected void WalkComponentTree(IAbstractComponentType componentType, int begin /// For a composite element, add to a list of associations to be fetched by outerjoin /// private void WalkCompositeElementTree(IAbstractComponentType compositeType, string[] cols, - IQueryableCollection persister, string alias, string path, string subPathAlias, int currentDepth) + IQueryableCollection persister, string alias, string path, int currentDepth) { IType[] types = compositeType.Subtypes; string[] propertyNames = compositeType.PropertyNames; @@ -525,26 +539,30 @@ private void WalkCompositeElementTree(IAbstractComponentType compositeType, stri string subpath = SubPath(path, propertyNames[i]); bool[] propertyNullability = compositeType.PropertyNullability; - var joinType = - GetJoinType( + var subPathAliases = GetChildAliases(alias, subpath); + foreach (var subPathAlias in subPathAliases) + { + var joinType = + GetJoinType( + associationType, + compositeType.GetFetchMode(i), + subpath, + subPathAlias, + persister.TableName, + lhsColumns, + propertyNullability == null || propertyNullability[i], + currentDepth, + compositeType.GetCascadeStyle(i)); + + AddAssociationToJoinTreeIfNecessary( associationType, - compositeType.GetFetchMode(i), - subpath, + aliasedLhsColumns, alias, - persister.TableName, - lhsColumns, - propertyNullability == null || propertyNullability[i], + subpath, + subPathAlias, currentDepth, - compositeType.GetCascadeStyle(i)); - - AddAssociationToJoinTreeIfNecessary( - associationType, - aliasedLhsColumns, - alias, - subpath, - subPathAlias, - currentDepth, - joinType); + joinType); + } } else if (types[i].IsComponentType) { @@ -555,7 +573,6 @@ private void WalkCompositeElementTree(IAbstractComponentType compositeType, stri persister, alias, subpath, - subPathAlias, currentDepth); } begin += length; @@ -802,10 +819,9 @@ protected SqlString MergeOrderings(SqlString ass, SqlString orderBy) { if (ass.Length == 0) return orderBy; - else if (orderBy.Length == 0) + if (orderBy.Length == 0) return ass; - else - return ass.Append(StringHelper.CommaSpace).Append(orderBy); + return orderBy.Append(StringHelper.CommaSpace, ass); } protected SqlString MergeOrderings(string ass, SqlString orderBy) { @@ -833,21 +849,29 @@ protected JoinFragment MergeOuterJoins(IList associati } else { - oj.AddJoins(outerjoin); // NH Different behavior : NH1179 and NH1293 - // Apply filters in Many-To-One association - if (enabledFiltersForManyToOne.Count > 0) + // Apply filters for entity joins and Many-To-One associations + SqlString filter = null; + if (oj.ForceFilter || enabledFiltersForManyToOne.Count > 0) { string manyToOneFilterFragment = oj.Joinable.FilterFragment(oj.RHSAlias, enabledFiltersForManyToOne); bool joinClauseDoesNotContainsFilterAlready = - outerjoin.ToFromFragmentString.IndexOfCaseInsensitive(manyToOneFilterFragment) == -1; + oj.On?.IndexOfCaseInsensitive(manyToOneFilterFragment) == -1; if (joinClauseDoesNotContainsFilterAlready) { - // Ensure that the join condition is added to the join, not the where clause. - // Adding the condition to the where clause causes left joins to become inner joins. - outerjoin.AddFromFragmentString(new SqlString(manyToOneFilterFragment)); + filter = new SqlString(manyToOneFilterFragment); } } + + if (TableGroupJoinHelper.ProcessAsTableGroupJoin(new[] {oj}, new[] {oj.On, filter}, true, outerjoin, alias => true, factory)) + continue; + + oj.AddJoins(outerjoin); + + // Ensure that the join condition is added to the join, not the where clause. + // Adding the condition to the where clause causes left joins to become inner joins. + if (SqlStringHelper.IsNotEmpty(filter)) + outerjoin.AddFromFragmentString(filter); } last = oj; } @@ -964,42 +988,96 @@ protected SqlStringBuilder WhereString(string alias, string[] columnNames, int b { // if not a composite key, use "foo in (?, ?, ?)" for batching // if no batch, and not a composite key, use "foo = ?" - string tableAlias = GenerateAliasForColumn(alias, columnNames[0]); - InFragment inf = new InFragment().SetColumn(tableAlias, columnNames[0]); - - for (int i = 0; i < batchSize; i++) - inf.AddValue(Parameter.Placeholder); + var columnName = columnNames[0]; - return new SqlStringBuilder(inf.ToFragmentString()); - } - else - { - var fragments = new ConditionalFragment[batchSize]; - for (int i = 0; i < batchSize; i++) + var tableAlias = GenerateAliasForColumn(alias, columnName); + var qualifiedName = !string.IsNullOrEmpty(tableAlias) + ? tableAlias + StringHelper.Dot + columnName + : columnName; + + var whereString = new SqlStringBuilder(batchSize * 5); + whereString.Add(qualifiedName); + if (batchSize == 1) { - fragments[i] = new ConditionalFragment() - .SetTableAlias(alias) - .SetCondition(columnNames, Parameter.GenerateParameters(columnNames.Length)); + whereString.Add("=").Add(Parameter.Placeholder); } + else + { + bool added = false; - var whereString = new SqlStringBuilder(); + whereString.Add(" in ("); + for (var i = 0; i < batchSize; i++) + { + var value = Parameter.Placeholder; + if (added) + { + whereString.Add(StringHelper.CommaSpace); + } - if (fragments.Length == 1) + whereString.Add(value); + + added = true; + } + + whereString.Add(StringHelper.ClosedParen); + } + + return whereString; + } + else + { + if (batchSize == 1) { // if no batch, use "foo = ? and bar = ?" - whereString.Add(fragments[0].ToSqlStringFragment()); + var whereString = new SqlStringBuilder(columnNames.Length * 4); + ColumnFragment(whereString, alias, columnNames); + return whereString; } else { // if batching, use "( (foo = ? and bar = ?) or (foo = ? and bar = ?) )" - var df = new DisjunctionFragment(fragments); + var whereString = new SqlStringBuilder(); whereString.Add(StringHelper.OpenParen); - whereString.Add(df.ToFragmentString()); + + var added = false; + for (var i = 0; i < batchSize; i++) + { + if (added) + { + whereString.Add(" or "); + } + + whereString.Add("("); + ColumnFragment(whereString, alias, columnNames); + whereString.Add(")"); + added = true; + } whereString.Add(StringHelper.ClosedParen); + return whereString; } + } + } - return whereString; + private static void ColumnFragment(SqlStringBuilder builder, string alias, string[] columnNames) + { + //foo = ? and bar = ? + var prefix = alias + StringHelper.Dot; + var added = false; + foreach (var columnName in columnNames) + { + if (added) + { + builder.Add(" and "); + } + + builder + .Add(prefix) + .Add(columnName) + .Add("=") + .Add(Parameter.Placeholder); + + added = true; } } @@ -1019,6 +1097,7 @@ protected void InitPersisters(IList associations, Lock owners = new int[joins]; ownerAssociationTypes = new EntityType[joins]; lockModeArray = ArrayHelper.Fill(lockMode, joins); + EntityFetchLazyProperties = new ISet[joins]; int i = 0; int j = 0; @@ -1068,6 +1147,7 @@ private void FillEntityPersisterProperties(int i, OuterJoinableAssociation oj, I aliases[i] = oj.RHSAlias; EagerPropertyFetches[i] = oj.SelectMode == SelectMode.FetchLazyProperties; ChildFetchEntities[i] = oj.SelectMode == SelectMode.ChildFetch; + EntityFetchLazyProperties[i] = oj.EntityFetchLazyProperties; } /// @@ -1098,8 +1178,7 @@ public string SelectString(IList associations) ? null : collectionSuffixes[collectionAliasCount]; - string selectFragment = - GetSelectFragment(join, entitySuffix, collectionSuffix, next); + string selectFragment = join.GetSelectFragment(entitySuffix, collectionSuffix, next); if (!string.IsNullOrWhiteSpace(selectFragment)) { @@ -1117,41 +1196,11 @@ public string SelectString(IList associations) } } + //Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] protected static string GetSelectFragment(OuterJoinableAssociation join, string entitySuffix, string collectionSuffix, OuterJoinableAssociation next = null) { - switch (join.SelectMode) - { - case SelectMode.Undefined: - case SelectMode.Fetch: -#pragma warning disable 618 - return join.Joinable.SelectFragment( - next?.Joinable, - next?.RHSAlias, - join.RHSAlias, - entitySuffix, - collectionSuffix, - join.ShouldFetchCollectionPersister()); -#pragma warning restore 618 - - case SelectMode.FetchLazyProperties: - return ReflectHelper.CastOrThrow(join.Joinable, "fetch lazy propertie") - .SelectFragment( - next?.Joinable, - next?.RHSAlias, - join.RHSAlias, - entitySuffix, - collectionSuffix, - join.ShouldFetchCollectionPersister(), - true); - - case SelectMode.ChildFetch: - return ReflectHelper.CastOrThrow(join.Joinable, "child fetch select mode").IdentifierSelectFragment(join.RHSAlias, entitySuffix); - - case SelectMode.JoinOnly: - return string.Empty; - default: - throw new ArgumentOutOfRangeException(nameof(join.SelectMode), $"{join.SelectMode} is unexpected."); - } + return join.GetSelectFragment(entitySuffix, collectionSuffix, next); } } } diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 58b4f45d570..8069cc9a544 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -17,7 +17,6 @@ using NHibernate.Exceptions; using NHibernate.Hql.Util; using NHibernate.Impl; -using NHibernate.Intercept; using NHibernate.Param; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; @@ -52,8 +51,21 @@ namespace NHibernate.Loader /// public abstract partial class Loader { - private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(Loader)); + /// + /// DTO for providing all query cache related details + /// + public sealed class QueryCacheInfo + { + public IType[] CacheTypes { get; set; } + + /// + /// Loader.EntityPersister indexes to be cached. + /// + public IReadOnlyList AdditionalEntities { get; set; } + } + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(Loader)); + private Lazy _cacheInfo; private readonly ISessionFactoryImplementor _factory; private readonly SessionFactoryHelper _helper; private ColumnNameCache _columnNameCache; @@ -95,7 +107,7 @@ protected virtual bool[] EntityEagerPropertyFetches /// /// An array of hash sets indicating which lazy properties will be fetched for an entity persister. /// - protected virtual HashSet[] EntityFetchLazyProperties + protected virtual ISet[] EntityFetchLazyProperties { get { return null; } } @@ -156,11 +168,18 @@ public virtual bool IsSubselectLoadingEnabled /// public IType[] ResultTypes { get; protected set; } - public bool[] EntityFetches { get; protected set; } + public IType[] CacheTypes => CacheInfo?.CacheTypes ?? ResultTypes; - public bool[] CollectionFetches { get; protected set; } + public virtual QueryCacheInfo CacheInfo => _cacheInfo?.Value; - public virtual IType[] CacheTypes => ResultTypes; + /// + /// Cache all additional persisters and collection persisters that were loaded by query (fetched entities and collections) + /// + /// Persister indexes that are cached as part of query result (so present in ResultTypes) + protected void CachePersistersWithCollections(IEnumerable resultTypePersisters) + { + _cacheInfo = new Lazy(() => GetQueryCacheInfo(resultTypePersisters)); + } public ISessionFactoryImplementor Factory { @@ -186,7 +205,7 @@ public ISessionFactoryImplementor Factory /// An (optional) persister for a collection to be initialized; only collection loaders /// return a non-null value /// - protected virtual ICollectionPersister[] CollectionPersisters + protected internal virtual ICollectionPersister[] CollectionPersisters { get { return null; } } @@ -263,7 +282,6 @@ private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session return DoQueryAndInitializeNonLazyCollections(session, queryParameters, returnProxies, null, null); } - private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies, IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder) @@ -569,7 +587,7 @@ protected bool HasSubselectLoadableCollections() return false; } - private static ISet[] Transpose(IList keys) + private static ISet[] Transpose(List keys) { ISet[] result = new ISet[keys[0].Length]; for (int j = 0; j < result.Length; j++) @@ -587,7 +605,7 @@ private static ISet[] Transpose(IList keys) return result; } - internal void CreateSubselects(IList keys, QueryParameters queryParameters, ISessionImplementor session) + internal void CreateSubselects(List keys, QueryParameters queryParameters, ISessionImplementor session) { if (keys.Count > 1) { @@ -607,7 +625,7 @@ internal void CreateSubselects(IList keys, QueryParameters queryPar } } - private IEnumerable CreateSubselects(IList keys, QueryParameters queryParameters) + private IEnumerable CreateSubselects(List keys, QueryParameters queryParameters) { // see NH-2123 NH-2125 ISet[] keySets = Transpose(keys); @@ -702,6 +720,18 @@ internal void InitializeEntitiesAndCollections( cacheBatcher.ExecuteBatch(); } + /// + /// Stops further collection population without actual collection initialization. + /// + internal void StopLoadingCollections(ISessionImplementor session, DbDataReader reader) + { + var collectionPersisters = CollectionPersisters; + if (collectionPersisters == null || collectionPersisters.Length == 0) + return; + + session.PersistenceContext.LoadContexts.GetCollectionLoadContext(reader).StopLoadingCollections(collectionPersisters); + } + private void EndCollectionLoad(DbDataReader reader, ISessionImplementor session, ICollectionPersister collectionPersister, CacheBatcher cacheBatcher) { @@ -725,7 +755,6 @@ protected virtual IResultTransformer ResolveResultTransformer(IResultTransformer return resultTransformer; } - /// /// Are rows transformed immediately after being read from the ResultSet? /// @@ -735,13 +764,11 @@ protected virtual bool AreResultSetRowsTransformedImmediately() return false; } - public virtual IList GetResultList(IList results, IResultTransformer resultTransformer) { return results; } - /// /// Returns the aliases that correspond to a result row. /// @@ -751,7 +778,6 @@ protected virtual string[] ResultRowAliases get { return null; } } - /// /// Get the actual object that is returned in the user-visible result list. /// @@ -1151,7 +1177,7 @@ private bool IsEagerPropertyFetchEnabled(int i) return array != null && array[i]; } - private HashSet GetFetchLazyProperties(int i) + private ISet GetFetchLazyProperties(int i) { var array = EntityFetchLazyProperties; return array?[i]; @@ -1855,14 +1881,16 @@ internal QueryKey GenerateQueryKey(ISessionImplementor session, QueryParameters { ISet filterKeys = FilterKey.CreateFilterKeys(session.EnabledFilters); return new QueryKey(Factory, SqlString, queryParameters, filterKeys, - CreateCacheableResultTransformer(queryParameters)); + CreateCacheableResultTransformer(queryParameters), session.GetTenantIdentifier()); } private CacheableResultTransformer CreateCacheableResultTransformer(QueryParameters queryParameters) { + bool skipTransformer = QueryCacheResultBuilder.IsCacheWithFetches(this); + return CacheableResultTransformer.Create( queryParameters.ResultTransformer, ResultRowAliases, IncludeInResultRow, - queryParameters.HasAutoDiscoverScalarTypes, SqlString); + queryParameters.HasAutoDiscoverScalarTypes, SqlString, skipTransformer); } private IList GetResultFromQueryCache( @@ -2073,6 +2101,31 @@ protected bool TryGetLimitString(Dialect.Dialect dialect, SqlString queryString, return false; } + private QueryCacheInfo GetQueryCacheInfo(IEnumerable resultTypePersisters) + { + var resultTypes = ResultTypes.EmptyIfNull(); + + var cacheTypes = new List(resultTypes.Count + EntityPersisters.Length + CollectionPersisters?.Length ?? 0); + cacheTypes.AddRange(resultTypes); + + int[] additionalEntities = null; + if (EntityPersisters.Length > 0) + { + additionalEntities = Enumerable.Range(0, EntityPersisters.Length).Except(resultTypePersisters).ToArray(); + cacheTypes.AddRange(additionalEntities.Select(i => EntityPersisters[i].EntityMetamodel.EntityType)); + } + + cacheTypes.AddRange(CollectionPersisters.EmptyIfNull().Select(p => p.CollectionType)); + + return cacheTypes.Count == resultTypes.Count + ? null + : new QueryCacheInfo + { + CacheTypes = cacheTypes.ToArray(), + AdditionalEntities = additionalEntities.EmptyIfNull(), + }; + } + #endregion } } diff --git a/src/NHibernate/Loader/OuterJoinLoader.cs b/src/NHibernate/Loader/OuterJoinLoader.cs index eb5d515b114..04874e5ef5e 100644 --- a/src/NHibernate/Loader/OuterJoinLoader.cs +++ b/src/NHibernate/Loader/OuterJoinLoader.cs @@ -94,7 +94,7 @@ protected override string[] Aliases get { return aliases; } } - protected override ICollectionPersister[] CollectionPersisters + protected internal override ICollectionPersister[] CollectionPersisters { get { return collectionPersisters; } } diff --git a/src/NHibernate/Loader/OuterJoinableAssociation.cs b/src/NHibernate/Loader/OuterJoinableAssociation.cs index 106ef7dd067..1bded2a7b43 100644 --- a/src/NHibernate/Loader/OuterJoinableAssociation.cs +++ b/src/NHibernate/Loader/OuterJoinableAssociation.cs @@ -9,7 +9,7 @@ namespace NHibernate.Loader { - public sealed class OuterJoinableAssociation + public sealed class OuterJoinableAssociation : IJoin { private readonly IAssociationType joinableType; private readonly IJoinable joinable; @@ -49,7 +49,7 @@ public OuterJoinableAssociation(IAssociationType joinableType, String lhsAlias, rhsColumns = JoinHelper.GetRHSColumnNames(joinableType, factory); on = new SqlString(joinableType.GetOnCondition(rhsAlias, factory, enabledFilters)); if (SqlStringHelper.IsNotEmpty(withClause)) - on = on.Append(" and ( ").Append(withClause).Append(" )"); + on = on.Append(" and ( ", withClause, " )"); this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application } @@ -98,6 +98,14 @@ public SelectMode SelectMode get { return _selectMode; } } + public ISet EntityFetchLazyProperties { get; set; } + + internal bool ForceFilter { get; set; } + + string[] IJoin.LHSColumns => lhsColumns; + string IJoin.Alias => RHSAlias; + IAssociationType IJoin.AssociationType => JoinableType; + public int GetOwner(IList associations) { if (IsEntityType || IsCollection) @@ -166,7 +174,7 @@ public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection colle SqlString condition = string.Empty.Equals(manyToManyFilter) ? on : SqlStringHelper.IsEmpty(on) ? new SqlString(manyToManyFilter) : - on.Append(" and ").Append(manyToManyFilter); + on.Append(" and ", manyToManyFilter); outerjoin.AddJoin(joinable.TableName, rhsAlias, lhsColumns, rhsColumns, joinType, condition); outerjoin.AddJoins(joinable.FromJoinFragment(rhsAlias, false, true), @@ -194,5 +202,58 @@ internal bool ShouldFetchCollectionPersister() throw new ArgumentOutOfRangeException(nameof(SelectMode), SelectMode.ToString()); } + + internal string GetSelectFragment(string entitySuffix, string collectionSuffix, OuterJoinableAssociation next) + { + switch (SelectMode) + { + case SelectMode.Undefined: + case SelectMode.Fetch: +#pragma warning disable 618 + return Joinable.SelectFragment( + next?.Joinable, + next?.RHSAlias, + RHSAlias, + entitySuffix, + collectionSuffix, + ShouldFetchCollectionPersister()); +#pragma warning restore 618 + + case SelectMode.FetchLazyProperties: +#pragma warning disable 618 + return ReflectHelper.CastOrThrow(Joinable, "fetch lazy properties") + .SelectFragment( + next?.Joinable, + next?.RHSAlias, + RHSAlias, + entitySuffix, + collectionSuffix, + ShouldFetchCollectionPersister(), + true); +#pragma warning restore 618 + + case SelectMode.FetchLazyPropertyGroup: + return ReflectHelper.CastOrThrow(Joinable, "fetch lazy property") + .SelectFragment( + next?.Joinable, + next?.RHSAlias, + RHSAlias, + collectionSuffix, + ShouldFetchCollectionPersister(), + new EntityLoadInfo(entitySuffix) + { + LazyProperties = EntityFetchLazyProperties, + IncludeLazyProps = SelectMode == SelectMode.FetchLazyProperties, + }); + case SelectMode.ChildFetch: + return ReflectHelper.CastOrThrow(Joinable, "child fetch select mode") + .IdentifierSelectFragment(RHSAlias, entitySuffix); + + case SelectMode.JoinOnly: + return string.Empty; + default: + throw new ArgumentOutOfRangeException(nameof(SelectMode), $"{SelectMode} is unexpected."); + } + } } } diff --git a/src/NHibernate/Logging.cs b/src/NHibernate/Logging.cs index a2af219798a..2039c18abb6 100644 --- a/src/NHibernate/Logging.cs +++ b/src/NHibernate/Logging.cs @@ -1,6 +1,5 @@ using System; -using System.Configuration; -using System.Linq; +using NHibernate.Cfg; using System.Runtime.CompilerServices; namespace NHibernate @@ -49,7 +48,6 @@ public interface INHibernateLoggerFactory /// public static class NHibernateLogger { - private const string nhibernateLoggerConfKey = "nhibernate-logger"; private static INHibernateLoggerFactory _loggerFactory; #pragma warning disable 618 @@ -135,9 +133,8 @@ public static INHibernateLogger For(System.Type type) private static string GetNhibernateLoggerClass() { - var nhibernateLogger = ConfigurationManager.AppSettings.Keys.Cast().FirstOrDefault(k => nhibernateLoggerConfKey.Equals(k, StringComparison.OrdinalIgnoreCase)); - string nhibernateLoggerClass = null; - if (string.IsNullOrEmpty(nhibernateLogger)) + var nhibernateLoggerClass = ConfigurationProvider.Current.GetLoggerFactoryClassName(); + if (nhibernateLoggerClass == null) { // look for log4net if (Log4NetLoggerFactory.Log4NetAssembly != null) @@ -145,10 +142,6 @@ private static string GetNhibernateLoggerClass() nhibernateLoggerClass = typeof(Log4NetLoggerFactory).AssemblyQualifiedName; } } - else - { - nhibernateLoggerClass = ConfigurationManager.AppSettings[nhibernateLogger]; - } return nhibernateLoggerClass; } diff --git a/src/NHibernate/Logging.extensions.cs b/src/NHibernate/Logging.extensions.cs index 357fb4bff80..6fe3af50c00 100644 --- a/src/NHibernate/Logging.extensions.cs +++ b/src/NHibernate/Logging.extensions.cs @@ -113,7 +113,6 @@ public static void Debug(this INHibernateLogger logger, string message) logger.Log(NHibernateLogLevel.Debug, new NHibernateLogValues(message, null), null); } - // catch any method calls with an Exception argument second as they would otherwise silently be consumed by `params object[] args`. /// diff --git a/src/NHibernate/Logging.obsolete.cs b/src/NHibernate/Logging.obsolete.cs index fa70325e399..5fb79302892 100644 --- a/src/NHibernate/Logging.obsolete.cs +++ b/src/NHibernate/Logging.obsolete.cs @@ -78,7 +78,6 @@ public interface ILoggerFactory IInternalLogger LoggerFor(System.Type type); } - [Obsolete("Use NHibernateLogger instead.")] public class LoggerProvider { @@ -346,7 +345,7 @@ public class NoLoggingInternalLogger: IInternalLogger { public bool IsErrorEnabled { - get { return false;} + get { return false; } } public bool IsFatalEnabled diff --git a/src/NHibernate/Mapping/AbstractAuxiliaryDatabaseObject.cs b/src/NHibernate/Mapping/AbstractAuxiliaryDatabaseObject.cs index 96349fcd47b..7d13f64bd94 100644 --- a/src/NHibernate/Mapping/AbstractAuxiliaryDatabaseObject.cs +++ b/src/NHibernate/Mapping/AbstractAuxiliaryDatabaseObject.cs @@ -56,6 +56,5 @@ public void SetParameterValues(IDictionary parameters) { this.parameters = parameters; } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/IAnyMapper.cs b/src/NHibernate/Mapping/ByCode/IAnyMapper.cs index 0a7e49c5d7a..d418455629d 100644 --- a/src/NHibernate/Mapping/ByCode/IAnyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/IAnyMapper.cs @@ -22,11 +22,10 @@ public interface IAnyMapper : IEntityPropertyMapper /// The class associated to the specific . void MetaValue(object value, System.Type entityType); - void Cascade(Cascade cascadeStyle); void Index(string indexName); void Lazy(bool isLazy); void Update(bool consideredInUpdateQuery); void Insert(bool consideredInInsertQuery); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/ICompositeIdMapper.cs b/src/NHibernate/Mapping/ByCode/ICompositeIdMapper.cs index 72029634360..d0fbab78eac 100644 --- a/src/NHibernate/Mapping/ByCode/ICompositeIdMapper.cs +++ b/src/NHibernate/Mapping/ByCode/ICompositeIdMapper.cs @@ -29,5 +29,4 @@ public interface IComponentAsIdAttributesMapper : IAccessorPropertyM public interface IComponentAsIdMapper : IComponentAsIdAttributesMapper, IMinimalPlainPropertyContainerMapper { } - -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/IDynamicComponentAttributesMapper.cs b/src/NHibernate/Mapping/ByCode/IDynamicComponentAttributesMapper.cs index d580b5cc1e7..2b9ea1879ae 100644 --- a/src/NHibernate/Mapping/ByCode/IDynamicComponentAttributesMapper.cs +++ b/src/NHibernate/Mapping/ByCode/IDynamicComponentAttributesMapper.cs @@ -18,5 +18,4 @@ public interface IDynamicComponentAttributesMapper : IEntityProperty public interface IDynamicComponentMapper : IDynamicComponentAttributesMapper, IPropertyContainerMapper { } - -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/IOneToOneMapper.cs b/src/NHibernate/Mapping/ByCode/IOneToOneMapper.cs index 9f1738f1732..d375d94ada2 100644 --- a/src/NHibernate/Mapping/ByCode/IOneToOneMapper.cs +++ b/src/NHibernate/Mapping/ByCode/IOneToOneMapper.cs @@ -15,6 +15,9 @@ public interface IOneToOneMapper : IEntityPropertyMapper void Formula(string formula); void ForeignKey(string foreignKeyName); void Class(System.Type clazz); + + //6.0 TODO: Uncomment + //void Fetch(FetchKind fetchMode); } public interface IOneToOneMapper : IOneToOneMapper @@ -35,5 +38,11 @@ public static void Formulas(this IOneToOneMapper mapper, params string[] formula var o2oMapper = ReflectHelper.CastOrThrow(mapper, "Setting many formula"); o2oMapper.Formulas(formulas); } + + public static void Fetch(this IOneToOneMapper mapper, FetchKind fetchMode) + { + var o2oMapper = ReflectHelper.CastOrThrow(mapper, "Setting fetch"); + o2oMapper.Fetch(fetchMode); + } } } diff --git a/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs b/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs index 9441c73676f..2565d66c4ca 100644 --- a/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs +++ b/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs @@ -42,10 +42,29 @@ public interface IBasePlainPropertyContainerMapper : IMinimalPlainPr { void Component(Expression> property, Action> mapping); void Component(Expression> property); + /// + /// Maps a non-generic dictionary property as a dynamic component. + /// + /// The property to map. + /// The template for the component. It should either be a (usually + /// anonymous) type having the same properties than the component, or an + /// IDictionary<string, System.Type> of property names with their type. + /// The mapping of the component. + /// The type of the template. void Component(Expression> property, TComponent dynamicComponentTemplate, Action> mapping); void Component(string notVisiblePropertyOrFieldName, Action> mapping); void Component(string notVisiblePropertyOrFieldName); + /// + /// Maps a property or field as a dynamic component. The property can be a C# dynamic or a dictionary of + /// property names to their value. + /// + /// The property or field name to map. + /// The template for the component. It should either be a (usually + /// anonymous) type having the same properties than the component, or an + /// IDictionary<string, System.Type> of property names with their type. + /// The mapping of the component. + /// The type of the template. void Component(string notVisiblePropertyOrFieldName, TComponent dynamicComponentTemplate, Action> mapping); void Any(Expression> property, System.Type idTypeOfMetaType, Action mapping) where TProperty : class; @@ -61,6 +80,17 @@ public interface IPlainPropertyContainerMapper : IBasePlainPropertyC public static class BasePlainPropertyContainerMapperExtensions { //6.0 TODO: Merge into IBasePlainPropertyContainerMapper<> interface + /// + /// Maps a generic IDictionary<string, object> property as a dynamic component. + /// + /// The mapper. + /// The property to map. + /// The template for the component. It should either be a (usually + /// anonymous) type having the same properties than the component, or an + /// IDictionary<string, System.Type> of property names with their type. + /// The mapping of the component. + /// The type of the mapped class. + /// The type of the template. public static void Component( this IBasePlainPropertyContainerMapper mapper, Expression>> property, diff --git a/src/NHibernate/Mapping/ByCode/Impl/AnyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/AnyMapper.cs index 233d92f3928..afccf75e570 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/AnyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/AnyMapper.cs @@ -4,6 +4,7 @@ using System.Reflection; using NHibernate.Cfg.MappingSchema; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Mapping.ByCode.Impl { @@ -159,7 +160,7 @@ public void MetaValue(object value, System.Type entityType) metavalueKey, existingClassMetavalue, newClassMetavalue)); } values[metavalueKey] = newClassMetavalue; - any.metavalue = values.Select(vd => new HbmMetaValue {value = vd.Key, @class = vd.Value}).ToArray(); + any.metavalue = values.ToArray(vd => new HbmMetaValue {value = vd.Key, @class = vd.Value}); } public void Cascade(Cascade cascadeStyle) diff --git a/src/NHibernate/Mapping/ByCode/Impl/ClassMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/ClassMapper.cs index 02636dd8860..8b397778849 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/ClassMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/ClassMapper.cs @@ -329,13 +329,22 @@ public void Persister() where T : IEntityPersister public void Synchronize(params string[] table) { if (table == null) - { return; + + var existingSyncs = classMapping.synchronize != null + ? new HashSet(classMapping.synchronize.Select(x => x.table)) + : new HashSet(); + + foreach (var t in table) + { + var cleanedName = t?.Trim(); + if (!string.IsNullOrEmpty(cleanedName)) + { + existingSyncs.Add(cleanedName); + } } - var existingSyncs = new HashSet(classMapping.synchronize != null ? classMapping.synchronize.Select(x => x.table) : Enumerable.Empty()); - System.Array.ForEach(table.Where(x => x != null).Select(tableName => tableName.Trim()).Where(cleanedName => !"".Equals(cleanedName)).ToArray(), - x => existingSyncs.Add(x.Trim())); - classMapping.synchronize = existingSyncs.Select(x => new HbmSynchronize {table = x}).ToArray(); + + classMapping.synchronize = existingSyncs.ToArray(x => new HbmSynchronize { table = x }); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/CollectionIdMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/CollectionIdMapper.cs index 1189d33380f..7ea3d38f160 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CollectionIdMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CollectionIdMapper.cs @@ -1,7 +1,7 @@ using System; -using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Mapping.ByCode.Impl { @@ -67,12 +67,13 @@ private void ApplyGenerator(IGeneratorDef generator) object generatorParameters = generator.Params; if (generatorParameters != null) { - hbmGenerator.param = (from pi in generatorParameters.GetType().GetProperties() - let pname = pi.Name - let pvalue = pi.GetValue(generatorParameters, null) - select - new HbmParam { name = pname, Text = new[] { ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString() } }). - ToArray(); + hbmGenerator.param = generatorParameters.GetType().GetProperties().ToArray( + pi => + { + var pvalue = pi.GetValue(generatorParameters, null); + return + new HbmParam {name = pi.Name, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }); } else { @@ -93,4 +94,4 @@ private void AutosetTypeThroughGeneratorDef(IGeneratorDef generator) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentCustomizer.cs index 470b5a7b020..b8d96369928 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentCustomizer.cs @@ -39,7 +39,7 @@ public void Parent(Expression> parent) wh public void Parent(string notVisiblePropertyOrFieldName, Action parentMapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); AddCustomizer(m => m.Parent(member, parentMapping)); } diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentElementCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentElementCustomizer.cs index 60c529de125..4a0bf6c2e05 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentElementCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentElementCustomizer.cs @@ -98,7 +98,6 @@ public static MemberInfo GetPropertyOrFieldMatchingNameOrThrow(string memberName return result; } - public void Property(Expression> property, Action mapping) { MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/DynamicComponentCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/DynamicComponentCustomizer.cs index d409c6d57ee..fdeff23a056 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/DynamicComponentCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/DynamicComponentCustomizer.cs @@ -1,21 +1,40 @@ using System; +using System.Reflection; namespace NHibernate.Mapping.ByCode.Impl.CustomizersImpl { - public class DynamicComponentCustomizer : PropertyContainerCustomizer, IDynamicComponentMapper + public class DynamicComponentCustomizer : PropertyContainerCustomizer, + IDynamicComponentMapper { - public DynamicComponentCustomizer(IModelExplicitDeclarationsHolder explicitDeclarationsHolder, ICustomizersHolder customizersHolder, PropertyPath propertyPath) + private readonly System.Type _componentType; + + public DynamicComponentCustomizer( + IModelExplicitDeclarationsHolder explicitDeclarationsHolder, + ICustomizersHolder customizersHolder, + PropertyPath propertyPath) + : this(typeof(TComponent), explicitDeclarationsHolder, customizersHolder, propertyPath) + { + } + + internal DynamicComponentCustomizer( + System.Type componentType, + IModelExplicitDeclarationsHolder explicitDeclarationsHolder, + ICustomizersHolder customizersHolder, + PropertyPath propertyPath) : base(explicitDeclarationsHolder, customizersHolder, propertyPath) { if (propertyPath == null) { - throw new ArgumentNullException("propertyPath"); + throw new ArgumentNullException(nameof(propertyPath)); } + if (explicitDeclarationsHolder == null) { - throw new ArgumentNullException("explicitDeclarationsHolder"); + throw new ArgumentNullException(nameof(explicitDeclarationsHolder)); } - explicitDeclarationsHolder.AddAsDynamicComponent(propertyPath.LocalMember, typeof(TComponent)); + + _componentType = componentType; + explicitDeclarationsHolder.AddAsDynamicComponent(propertyPath.LocalMember, _componentType); } #region IDynamicComponentMapper Members @@ -51,5 +70,15 @@ public void Unique(bool unique) } #endregion + + protected override MemberInfo GetRequiredPropertyOrFieldByName(string memberName) + { + var result = _componentType.GetPropertyOrFieldMatchingName(memberName); + if (result == null) + { + throw new MappingException(string.Format("Member not found. The member '{0}' does not exists in type {1}", memberName, _componentType.FullName)); + } + return result; + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinedSubclassCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinedSubclassCustomizer.cs index 8d96c58925b..3dfbfabb19c 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinedSubclassCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinedSubclassCustomizer.cs @@ -76,7 +76,6 @@ public void Persister() where T : IEntityPersister public void Synchronize(params string[] table) { CustomizersHolder.AddCustomizer(typeof(TEntity), (IJoinedSubclassAttributesMapper m) => m.Synchronize(table)); - } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs index b76e28f6eee..e6a9c4f5e3e 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs @@ -1,9 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Reflection.Emit; namespace NHibernate.Mapping.ByCode.Impl.CustomizersImpl { @@ -55,7 +55,7 @@ protected virtual void RegisterNoVisiblePropertyMapping(string notVisiblePropert { // even seems repetitive, before unify this registration with the registration using Expression take in account that reflection operations // done unsing expressions are faster than those done with pure reflection. - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity)); RegistePropertyMapping(mapping, memberOf); } @@ -94,14 +94,48 @@ protected void RegisterComponentMapping(Action + /// Maps a non-generic dictionary property as a dynamic component. + /// + /// The property to map. + /// The template for the component. It should either be a (usually + /// anonymous) type having the same properties than the component, or an + /// IDictionary<string, System.Type> of property names with their type. + /// The mapping of the component. + /// The type of the template. public void Component(Expression> property, TComponent dynamicComponentTemplate, Action> mapping) { - RegisterDynamicComponentMapping(property, mapping); + if (dynamicComponentTemplate is IEnumerable> template) + { + var componentType = CreateDynamicComponentTypeFromTemplate(template); + RegisterDynamicComponentMapping(property, componentType, mapping); + } + else + { + RegisterDynamicComponentMapping(property, mapping); + } } + /// + /// Maps a generic IDictionary<string, object> property as a dynamic component. + /// + /// The property to map. + /// The template for the component. It should either be a (usually + /// anonymous) type having the same properties than the component, or an + /// IDictionary<string, System.Type> of property names with their type. + /// The mapping of the component. + /// The type of the template. public void Component(Expression>> property, TComponent dynamicComponentTemplate, Action> mapping) where TComponent : class { - RegisterDynamicComponentMapping(property, mapping); + if (dynamicComponentTemplate is IEnumerable> template) + { + var componentType = CreateDynamicComponentTypeFromTemplate(template); + RegisterDynamicComponentMapping(property, componentType, mapping); + } + else + { + RegisterDynamicComponentMapping(property, mapping); + } } protected virtual void RegisterDynamicComponentMapping(Expression> property, Action> mapping) @@ -110,6 +144,13 @@ protected virtual void RegisterDynamicComponentMapping(Expression(Expression> property, System.Type componentType, Action> mapping) + { + MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); + MemberInfo memberOf = TypeExtensions.DecodeMemberAccessExpressionOf(property); + RegisterDynamicComponentMapping(componentType, mapping, member, memberOf); + } protected virtual void RegisterDynamicComponentMapping(Expression>> property, Action> mapping) where TComponent : class { @@ -118,6 +159,21 @@ protected virtual void RegisterDynamicComponentMapping(Expression(Expression>> property, System.Type componentType, Action> mapping) + { + MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); + MemberInfo memberOf = TypeExtensions.DecodeMemberAccessExpressionOf(property); + RegisterDynamicComponentMapping(componentType, mapping, member, memberOf); + } + + protected void RegisterDynamicComponentMapping(System.Type componentType, Action> mapping, params MemberInfo[] members) + { + foreach (var member in members) + { + mapping(new DynamicComponentCustomizer(componentType, explicitDeclarationsHolder, CustomizersHolder, new PropertyPath(PropertyPath, member))); + } + } + protected void RegisterDynamicComponentMapping(Action> mapping, params MemberInfo[] members) { foreach (var member in members) @@ -126,6 +182,50 @@ protected void RegisterDynamicComponentMapping(Action> template) + { + var assemblyName = new AssemblyName("MyDynamicAssembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); + var typeBuilder = moduleBuilder.DefineType( + "MyDynamicType", + TypeAttributes.Public | TypeAttributes.Serializable); + + foreach (var property in template) + { + var propertyBuilder = typeBuilder.DefineProperty( + property.Key, + PropertyAttributes.HasDefault, + property.Value, + null); + var getMethodBuilder = typeBuilder.DefineMethod( + "get_" + property.Key, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + property.Value, + System.Type.EmptyTypes); + var setMethodBuilder = typeBuilder.DefineMethod( + "set_" + property.Key, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + typeof(void), + new[] { property.Value }); + + var getILGenerator = getMethodBuilder.GetILGenerator(); + getILGenerator.Emit(OpCodes.Ldarg_0); + getILGenerator.Emit(OpCodes.Nop); + getILGenerator.Emit(OpCodes.Ret); + + var setILGenerator = setMethodBuilder.GetILGenerator(); + setILGenerator.Emit(OpCodes.Ldarg_0); + setILGenerator.Emit(OpCodes.Ldarg_1); + setILGenerator.Emit(OpCodes.Ret); + + propertyBuilder.SetGetMethod(getMethodBuilder); + propertyBuilder.SetSetMethod(setMethodBuilder); + } + + return typeBuilder.CreateTypeInfo(); + } + public void ManyToOne(Expression> property, Action mapping) where TProperty : class { @@ -165,7 +265,7 @@ public void OneToOne(Expression> property, A public void OneToOne(string notVisiblePropertyOrFieldName, Action> mapping) where TProperty : class { - var member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + var member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); var propertyOrFieldType = member.GetPropertyOrFieldType(); if (typeof(TProperty) != propertyOrFieldType) { @@ -362,14 +462,13 @@ protected virtual void RegisterIdBagMapping(Action(string notVisiblePropertyOrFieldName, Action> collectionMapping, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); AssertCollectionElementType(notVisiblePropertyOrFieldName, member); MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity)); RegisterSetMapping(collectionMapping, mapping, member, memberOf); } - private static void AssertCollectionElementType(string propertyOrFieldName, MemberInfo memberInfo) { System.Type collectionElementType = memberInfo.GetPropertyOrFieldType().DetermineCollectionElementType(); @@ -384,7 +483,6 @@ private static void AssertCollectionElementType(string propertyOrField } } - public void Set(string notVisiblePropertyOrFieldName, Action> collectionMapping) { Set(notVisiblePropertyOrFieldName, collectionMapping, x => { }); @@ -392,7 +490,7 @@ public void Set(string notVisiblePropertyOrFieldName, Action(string notVisiblePropertyOrFieldName, Action> collectionMapping, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); AssertCollectionElementType(notVisiblePropertyOrFieldName, member); MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity)); @@ -406,7 +504,7 @@ public void Bag(string notVisiblePropertyOrFieldName, Action(string notVisiblePropertyOrFieldName, Action> collectionMapping, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); AssertCollectionElementType(notVisiblePropertyOrFieldName, member); MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity)); @@ -420,7 +518,7 @@ public void List(string notVisiblePropertyOrFieldName, Action(string notVisiblePropertyOrFieldName, Action> collectionMapping, Action> keyMapping, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); var propertyOrFieldType = member.GetPropertyOrFieldType(); var keyType = propertyOrFieldType.DetermineDictionaryKeyType(); var collectionElementType = propertyOrFieldType.DetermineDictionaryValueType(); @@ -445,7 +543,7 @@ public void Map(string notVisiblePropertyOrFieldName, Action(string notVisiblePropertyOrFieldName, Action> collectionMapping, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); AssertCollectionElementType(notVisiblePropertyOrFieldName, member); MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity)); @@ -459,7 +557,7 @@ public void IdBag(string notVisiblePropertyOrFieldName, Action(string notVisiblePropertyOrFieldName, Action mapping) where TProperty : class { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); var propertyOrFieldType = member.GetPropertyOrFieldType(); if (!typeof(TProperty).Equals(propertyOrFieldType)) { @@ -472,7 +570,7 @@ public void ManyToOne(string notVisiblePropertyOrFieldName, Action(string notVisiblePropertyOrFieldName, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); var propertyOrFieldType = member.GetPropertyOrFieldType(); if (!typeof(TComponent).Equals(propertyOrFieldType)) { @@ -488,16 +586,35 @@ public void Component(string notVisiblePropertyOrFieldName) Component(notVisiblePropertyOrFieldName, x => { }); } + /// + /// Maps a property or field as a dynamic component. The property can be a C# dynamic or a dictionary of + /// property names to their value. + /// + /// The property or field name to map. + /// The template for the component. It should either be a (usually + /// anonymous) type having the same properties than the component, or an + /// IDictionary<string, System.Type> of property names with their type. + /// The mapping of the component. + /// The type of the template. public void Component(string notVisiblePropertyOrFieldName, TComponent dynamicComponentTemplate, Action> mapping) { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity)); - RegisterDynamicComponentMapping(mapping, member, memberOf); + + if (dynamicComponentTemplate is IEnumerable> template) + { + var componentType = CreateDynamicComponentTypeFromTemplate(template); + RegisterDynamicComponentMapping(componentType, mapping, member, memberOf); + } + else + { + RegisterDynamicComponentMapping(mapping, member, memberOf); + } } public void Any(string notVisiblePropertyOrFieldName, System.Type idTypeOfMetaType, Action mapping) where TProperty : class { - MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName); + MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName); var propertyOrFieldType = member.GetPropertyOrFieldType(); if (!typeof(TProperty).Equals(propertyOrFieldType)) { @@ -508,6 +625,14 @@ public void Any(string notVisiblePropertyOrFieldName, System.Type idT RegisterAnyMapping(mapping, idTypeOfMetaType, member, memberOf); } + protected virtual MemberInfo GetRequiredPropertyOrFieldByName(string memberName) + { +#pragma warning disable 618 + return GetPropertyOrFieldMatchingNameOrThrow(memberName); +#pragma warning restore 618 + } + + [Obsolete("Please use GetRequiredPropertyOrFieldByName instead.")] public static MemberInfo GetPropertyOrFieldMatchingNameOrThrow(string memberName) { var result = typeof(TEntity).GetPropertyOrFieldMatchingName(memberName); diff --git a/src/NHibernate/Mapping/ByCode/Impl/ElementMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/ElementMapper.cs index 54cf4f5eb88..f4e519c65a0 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/ElementMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/ElementMapper.cs @@ -147,9 +147,8 @@ public void Formulas(params string[] formulas) ResetColumnPlainValues(); elementMapping.Items = formulas - .Select( - f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => (object) new HbmFormula {Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None)}); } #endregion @@ -195,15 +194,16 @@ public void Type(System.Type persistentType, object parameters) { elementMapping.type1 = null; var hbmType = new HbmType - { - name = persistentType.AssemblyQualifiedName, - param = (from pi in parameters.GetType().GetProperties() - let pname = pi.Name - let pvalue = pi.GetValue(parameters, null) - select - new HbmParam {name = pname, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}) - .ToArray() - }; + { + name = persistentType.AssemblyQualifiedName, + param = parameters.GetType().GetProperties().ToArray( + pi => + { + var pvalue = pi.GetValue(parameters, null); + return + new HbmParam {name = pi.Name, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }) + }; elementMapping.type = hbmType; } else diff --git a/src/NHibernate/Mapping/ByCode/Impl/GeneratorMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/GeneratorMapper.cs index 16c48bc5831..a58f22475ca 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/GeneratorMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/GeneratorMapper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Util; namespace NHibernate.Mapping.ByCode.Impl { @@ -30,16 +31,14 @@ public void Params(IDictionary generatorParameters) return; } - _generator.param = (from pi in generatorParameters - let pname = pi.Key - let pvalue = pi.Value - select new HbmParam - { - name = pname, - Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()} - }).ToArray(); + _generator.param = generatorParameters.ToArray( + pi => + { + object pvalue = pi.Value; + return new HbmParam {name = pi.Key, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }); } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/Impl/IdMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/IdMapper.cs index 759065ffb2c..4fc77568ac6 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/IdMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/IdMapper.cs @@ -5,6 +5,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Type; using NHibernate.UserTypes; +using NHibernate.Util; namespace NHibernate.Mapping.ByCode.Impl { @@ -83,12 +84,12 @@ public void Type(System.Type persistentType, object parameters) var hbmType = new HbmType { name = persistentType.AssemblyQualifiedName, - param = (from pi in parameters.GetType().GetProperties() - let pname = pi.Name - let pvalue = pi.GetValue(parameters, null) - select - new HbmParam {name = pname, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}) - .ToArray() + param = parameters.GetType().GetProperties().ToArray( + pi => + { + object pvalue = pi.GetValue(parameters, null); + return new HbmParam {name = pi.Name, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }) }; hbmId.type = hbmType; } @@ -184,12 +185,13 @@ private void ApplyGenerator(IGeneratorDef generator) object generatorParameters = generator.Params; if (generatorParameters != null) { - hbmGenerator.param = (from pi in generatorParameters.GetType().GetProperties() - let pname = pi.Name - let pvalue = pi.GetValue(generatorParameters, null) - select - new HbmParam {name = pname, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}). - ToArray(); + hbmGenerator.param = generatorParameters.GetType().GetProperties().ToArray( + pi => + { + var pvalue = pi.GetValue(generatorParameters, null); + return + new HbmParam {name = pi.Name, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }); } else { diff --git a/src/NHibernate/Mapping/ByCode/Impl/JoinedSubclassMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/JoinedSubclassMapper.cs index c7b34aebbaf..f44e196425e 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/JoinedSubclassMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/JoinedSubclassMapper.cs @@ -87,13 +87,22 @@ public void Persister() where T : IEntityPersister public void Synchronize(params string[] table) { if (table == null) - { return; + + var existingSyncs = classMapping.synchronize != null + ? new HashSet(classMapping.synchronize.Select(x => x.table)) + : new HashSet(); + + foreach (var t in table) + { + var cleanedName = t?.Trim(); + if (!string.IsNullOrEmpty(cleanedName)) + { + existingSyncs.Add(cleanedName); + } } - var existingSyncs = new HashSet(classMapping.synchronize != null ? classMapping.synchronize.Select(x => x.table) : Enumerable.Empty()); - System.Array.ForEach(table.Where(x => x != null).Select(tableName => tableName.Trim()).Where(cleanedName => !"".Equals(cleanedName)).ToArray(), - x => existingSyncs.Add(x.Trim())); - classMapping.synchronize = existingSyncs.Select(x => new HbmSynchronize { table = x }).ToArray(); + + classMapping.synchronize = existingSyncs.ToArray(x => new HbmSynchronize { table = x }); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/KeyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/KeyMapper.cs index d312b6f6489..f594ff87248 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/KeyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/KeyMapper.cs @@ -55,7 +55,6 @@ public void Column(Action columnMapper) } } - public void Columns(params Action[] columnMapper) { ResetColumnPlainValues(); @@ -150,4 +149,4 @@ private void ResetColumnPlainValues() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/Impl/KeyPropertyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/KeyPropertyMapper.cs index 63cfe9144c6..c5866c91f00 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/KeyPropertyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/KeyPropertyMapper.cs @@ -5,6 +5,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Type; using NHibernate.UserTypes; +using NHibernate.Util; namespace NHibernate.Mapping.ByCode.Impl { @@ -100,15 +101,16 @@ public void Type(System.Type persistentType, object parameters) { propertyMapping.type1 = null; var hbmType = new HbmType - { - name = persistentType.AssemblyQualifiedName, - param = (from pi in parameters.GetType().GetProperties() - let pname = pi.Name - let pvalue = pi.GetValue(parameters, null) - select - new HbmParam {name = pname, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}) - .ToArray() - }; + { + name = persistentType.AssemblyQualifiedName, + param = parameters.GetType().GetProperties().ToArray( + pi => + { + var pvalue = pi.GetValue(parameters, null); + return + new HbmParam {name = pi.Name, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }) + }; propertyMapping.type = hbmType; } else diff --git a/src/NHibernate/Mapping/ByCode/Impl/ManyToAnyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/ManyToAnyMapper.cs index 8fad148094e..2019868fae0 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/ManyToAnyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/ManyToAnyMapper.cs @@ -3,6 +3,7 @@ using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Mapping.ByCode.Impl { @@ -134,7 +135,7 @@ public void MetaValue(object value, System.Type entityType) metavalueKey, existingClassMetavalue, newClassMetavalue)); } values[metavalueKey] = newClassMetavalue; - manyToAny.metavalue = values.Select(vd => new HbmMetaValue { value = vd.Key, @class = vd.Value }).ToArray(); + manyToAny.metavalue = values.ToArray(vd => new HbmMetaValue {value = vd.Key, @class = vd.Value}); } private void CheckMetaTypeImmutability(string nhTypeName) diff --git a/src/NHibernate/Mapping/ByCode/Impl/ManyToManyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/ManyToManyMapper.cs index 15235885145..8c096a47af6 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/ManyToManyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/ManyToManyMapper.cs @@ -62,16 +62,15 @@ public void Column(Action columnMapper) public void Columns(params Action[] columnMapper) { ResetColumnPlainValues(); - int i = 1; - var columns = new List(columnMapper.Length); - foreach (var action in columnMapper) + var columns = new HbmColumn[columnMapper.Length]; + for (var i = 0; i < columnMapper.Length; i++) { var hbm = new HbmColumn(); - string defaultColumnName = elementType.Name + i++; - action(new ColumnMapper(hbm, defaultColumnName)); - columns.Add(hbm); + string defaultColumnName = elementType.Name + i + 1; + columnMapper[i](new ColumnMapper(hbm, defaultColumnName)); + columns[i] = hbm; } - manyToMany.Items = columns.ToArray(); + manyToMany.Items = columns; } public void Column(string name) @@ -134,9 +133,8 @@ public void Formulas(params string[] formulas) ResetColumnPlainValues(); manyToMany.Items = formulas - .Select( - f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => (object) new HbmFormula {Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None)}); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/ManyToOneMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/ManyToOneMapper.cs index 053fbee7e9a..c43670a55e1 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/ManyToOneMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/ManyToOneMapper.cs @@ -174,9 +174,8 @@ public void Formulas(params string[] formulas) ResetColumnPlainValues(); _manyToOne.Items = formulas - .Select( - f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }); } #endregion @@ -224,16 +223,16 @@ public void Column(Action columnMapper) public void Columns(params Action[] columnMapper) { ResetColumnPlainValues(); - int i = 1; - var columns = new List(columnMapper.Length); - foreach (var action in columnMapper) + + var columns = new HbmColumn[columnMapper.Length]; + for (var i = 0; i < columnMapper.Length; i++) { var hbm = new HbmColumn(); - string defaultColumnName = (_member != null ? _member.Name : "unnamedcolumn") + i++; - action(new ColumnMapper(hbm, defaultColumnName)); - columns.Add(hbm); + string defaultColumnName = (_member != null ? _member.Name : "unnamedcolumn") + i + 1; + columnMapper[i](new ColumnMapper(hbm, defaultColumnName)); + columns[i] = hbm; } - _manyToOne.Items = columns.ToArray(); + _manyToOne.Items = columns; } public void Column(string name) diff --git a/src/NHibernate/Mapping/ByCode/Impl/MapKeyManyToManyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/MapKeyManyToManyMapper.cs index 2cba9abdac8..fa8534d0299 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/MapKeyManyToManyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/MapKeyManyToManyMapper.cs @@ -71,9 +71,8 @@ public void Formulas(params string[] formulas) ResetColumnPlainValues(); mapping.Items = formulas - .Select( - f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => (object) new HbmFormula {Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None)}); } #endregion @@ -109,16 +108,15 @@ public void Column(Action columnMapper) public void Columns(params Action[] columnMapper) { mapping.column = null; - int i = 1; - var columns = new List(columnMapper.Length); - foreach (var action in columnMapper) + var columns = new HbmColumn[columnMapper.Length]; + for (var i = 0; i < columnMapper.Length; i++) { var hbm = new HbmColumn(); - string defaultColumnName = DefaultColumnName + i++; - action(new ColumnMapper(hbm, defaultColumnName)); - columns.Add(hbm); + string defaultColumnName = DefaultColumnName + i + 1; + columnMapper[i](new ColumnMapper(hbm, defaultColumnName)); + columns[i] = hbm; } - mapping.Items = columns.ToArray(); + mapping.Items = columns; } public void Column(string name) diff --git a/src/NHibernate/Mapping/ByCode/Impl/MapKeyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/MapKeyMapper.cs index 626ed16339e..44f80af1d9e 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/MapKeyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/MapKeyMapper.cs @@ -57,16 +57,15 @@ public void Column(Action columnMapper) public void Columns(params Action[] columnMapper) { ResetColumnPlainValues(); - int i = 1; - var columns = new List(columnMapper.Length); - foreach (var action in columnMapper) + var columns = new HbmColumn[columnMapper.Length]; + for (var i = 0; i < columnMapper.Length; i++) { var hbm = new HbmColumn(); - string defaultColumnName = DefaultColumnName + i++; - action(new ColumnMapper(hbm, defaultColumnName)); - columns.Add(hbm); + string defaultColumnName = DefaultColumnName + i + 1; + columnMapper[i](new ColumnMapper(hbm, defaultColumnName)); + columns[i] = hbm; } - hbmMapKey.Items = columns.ToArray(); + hbmMapKey.Items = columns; } public void Column(string name) @@ -153,9 +152,8 @@ public void Formulas(params string[] formulas) ResetColumnPlainValues(); hbmMapKey.Items = formulas - .Select( - f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => (object) new HbmFormula {Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None)}); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/OneToOneMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/OneToOneMapper.cs index 53fa18920b5..5d5d35f2f0a 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/OneToOneMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/OneToOneMapper.cs @@ -124,9 +124,8 @@ public void Formulas(params string[] formulas) _oneToOne.formula1 = null; _oneToOne.formula = formulas - .Select( - f => new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => new HbmFormula {Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None)}); } public void ForeignKey(string foreignKeyName) @@ -135,6 +134,12 @@ public void ForeignKey(string foreignKeyName) } #endregion + + public void Fetch(FetchKind fetchMode) + { + _oneToOne.fetch = fetchMode.ToHbm(); + _oneToOne.fetchSpecified = true; + } } public class OneToOneMapper : OneToOneMapper, IOneToOneMapper diff --git a/src/NHibernate/Mapping/ByCode/Impl/PropertyMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/PropertyMapper.cs index b95843319e9..0d5fe2db4a8 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/PropertyMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/PropertyMapper.cs @@ -107,15 +107,15 @@ public void Type(System.Type persistentType, object parameters) { propertyMapping.type1 = null; var hbmType = new HbmType - { - name = persistentType.AssemblyQualifiedName, - param = (from pi in parameters.GetType().GetProperties() - let pname = pi.Name - let pvalue = pi.GetValue(parameters, null) - select - new HbmParam {name = pname, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}) - .ToArray() - }; + { + name = persistentType.AssemblyQualifiedName, + param = parameters.GetType().GetProperties().ToArray( + pi => + { + var pvalue = pi.GetValue(parameters, null); + return new HbmParam {name = pi.Name, Text = new[] {ReferenceEquals(pvalue, null) ? "null" : pvalue.ToString()}}; + }) + }; propertyMapping.type = hbmType; } else @@ -172,16 +172,15 @@ public void Column(Action columnMapper) public void Columns(params Action[] columnMapper) { ResetColumnPlainValues(); - int i = 1; - var columns = new List(columnMapper.Length); - foreach (var action in columnMapper) + var columns = new HbmColumn[columnMapper.Length]; + for (var i = 0; i < columnMapper.Length; i++) { var hbm = new HbmColumn(); - string defaultColumnName = (member != null ? member.Name : "unnamedcolumn") + i++; - action(new ColumnMapper(hbm, defaultColumnName)); - columns.Add(hbm); + string defaultColumnName = (member != null ? member.Name : "unnamedcolumn") + i + 1; + columnMapper[i](new ColumnMapper(hbm, defaultColumnName)); + columns[i] = hbm; } - propertyMapping.Items = columns.ToArray(); + propertyMapping.Items = columns; } public void Column(string name) @@ -312,9 +311,8 @@ public void Formulas(params string[] formulas) ResetColumnPlainValues(); propertyMapping.Items = formulas - .Select( - f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }) - .ToArray(); + .ToArray( + f => (object) new HbmFormula { Text = f.Split(StringHelper.LineSeparators, StringSplitOptions.None) }); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/SubclassMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/SubclassMapper.cs index d20052f92a9..fda00098024 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/SubclassMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/SubclassMapper.cs @@ -135,13 +135,22 @@ public void Persister() where T : IEntityPersister public void Synchronize(params string[] table) { if (table == null) - { return; + + var existingSyncs = classMapping.synchronize != null + ? new HashSet(classMapping.synchronize.Select(x => x.table)) + : new HashSet(); + + foreach (var t in table) + { + var cleanedName = t?.Trim(); + if (!string.IsNullOrEmpty(cleanedName)) + { + existingSyncs.Add(cleanedName); + } } - var existingSyncs = new HashSet(classMapping.synchronize != null ? classMapping.synchronize.Select(x => x.table) : Enumerable.Empty()); - System.Array.ForEach(table.Where(x => x != null).Select(tableName => tableName.Trim()).Where(cleanedName => !"".Equals(cleanedName)).ToArray(), - x => existingSyncs.Add(x.Trim())); - classMapping.synchronize = existingSyncs.Select(x => new HbmSynchronize { table = x }).ToArray(); + + classMapping.synchronize = existingSyncs.ToArray(x => new HbmSynchronize { table = x }); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/Impl/UnionSubclassMapper.cs b/src/NHibernate/Mapping/ByCode/Impl/UnionSubclassMapper.cs index 173cefa6ebb..d225c7cd1f7 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/UnionSubclassMapper.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/UnionSubclassMapper.cs @@ -89,13 +89,22 @@ public void Persister() where T : IEntityPersister public void Synchronize(params string[] table) { if (table == null) - { return; + + var existingSyncs = classMapping.synchronize != null + ? new HashSet(classMapping.synchronize.Select(x => x.table)) + : new HashSet(); + + foreach (var t in table) + { + var cleanedName = t?.Trim(); + if (!string.IsNullOrEmpty(cleanedName)) + { + existingSyncs.Add(cleanedName); + } } - var existingSyncs = new HashSet(classMapping.synchronize != null ? classMapping.synchronize.Select(x => x.table) : Enumerable.Empty()); - System.Array.ForEach(table.Where(x => x != null).Select(tableName => tableName.Trim()).Where(cleanedName => !"".Equals(cleanedName)).ToArray(), - x => existingSyncs.Add(x.Trim())); - classMapping.synchronize = existingSyncs.Select(x => new HbmSynchronize { table = x }).ToArray(); + + classMapping.synchronize = existingSyncs.ToArray(x => new HbmSynchronize { table = x }); } #endregion diff --git a/src/NHibernate/Mapping/ByCode/ModelExplicitDeclarationsHolderExtensions.cs b/src/NHibernate/Mapping/ByCode/ModelExplicitDeclarationsHolderExtensions.cs index e952ce848c8..8149a93b93e 100644 --- a/src/NHibernate/Mapping/ByCode/ModelExplicitDeclarationsHolderExtensions.cs +++ b/src/NHibernate/Mapping/ByCode/ModelExplicitDeclarationsHolderExtensions.cs @@ -1,5 +1,3 @@ -using System.Linq; - namespace NHibernate.Mapping.ByCode { public static class ModelExplicitDeclarationsHolderExtensions @@ -11,34 +9,34 @@ public static void Merge(this IModelExplicitDeclarationsHolder destination, IMod return; } - System.Array.ForEach(source.RootEntities.ToArray(), destination.AddAsRootEntity); - System.Array.ForEach(source.Components.ToArray(), destination.AddAsComponent); - System.Array.ForEach(source.TablePerClassEntities.ToArray(), destination.AddAsTablePerClassEntity); - System.Array.ForEach(source.TablePerClassHierarchyEntities.ToArray(), destination.AddAsTablePerClassHierarchyEntity); - System.Array.ForEach(source.TablePerConcreteClassEntities.ToArray(), destination.AddAsTablePerConcreteClassEntity); + foreach (var o in source.RootEntities) destination.AddAsRootEntity(o); + foreach (var o in source.Components) destination.AddAsComponent(o); + foreach (var o in source.TablePerClassEntities) destination.AddAsTablePerClassEntity(o); + foreach (var o in source.TablePerClassHierarchyEntities) destination.AddAsTablePerClassHierarchyEntity(o); + foreach (var o in source.TablePerConcreteClassEntities) destination.AddAsTablePerConcreteClassEntity(o); - System.Array.ForEach(source.OneToOneRelations.ToArray(), destination.AddAsOneToOneRelation); - System.Array.ForEach(source.ManyToOneRelations.ToArray(), destination.AddAsManyToOneRelation); - System.Array.ForEach(source.KeyManyToManyRelations.ToArray(), destination.AddAsManyToManyKeyRelation); - System.Array.ForEach(source.ItemManyToManyRelations.ToArray(), destination.AddAsManyToManyItemRelation); - System.Array.ForEach(source.ManyToAnyRelations.ToArray(), destination.AddAsManyToAnyRelation); - System.Array.ForEach(source.OneToManyRelations.ToArray(), destination.AddAsOneToManyRelation); - System.Array.ForEach(source.Any.ToArray(), destination.AddAsAny); + foreach (var o in source.OneToOneRelations) destination.AddAsOneToOneRelation(o); + foreach (var o in source.ManyToOneRelations) destination.AddAsManyToOneRelation(o); + foreach (var o in source.KeyManyToManyRelations) destination.AddAsManyToManyKeyRelation(o); + foreach (var o in source.ItemManyToManyRelations) destination.AddAsManyToManyItemRelation(o); + foreach (var o in source.ManyToAnyRelations) destination.AddAsManyToAnyRelation(o); + foreach (var o in source.OneToManyRelations) destination.AddAsOneToManyRelation(o); + foreach (var o in source.Any) destination.AddAsAny(o); - System.Array.ForEach(source.Poids.ToArray(), destination.AddAsPoid); - System.Array.ForEach(source.ComposedIds.ToArray(), destination.AddAsPartOfComposedId); - System.Array.ForEach(source.VersionProperties.ToArray(), destination.AddAsVersionProperty); - System.Array.ForEach(source.NaturalIds.ToArray(), destination.AddAsNaturalId); + foreach (var o in source.Poids) destination.AddAsPoid(o); + foreach (var o in source.ComposedIds) destination.AddAsPartOfComposedId(o); + foreach (var o in source.VersionProperties) destination.AddAsVersionProperty(o); + foreach (var o in source.NaturalIds) destination.AddAsNaturalId(o); - System.Array.ForEach(source.Sets.ToArray(), destination.AddAsSet); - System.Array.ForEach(source.Bags.ToArray(), destination.AddAsBag); - System.Array.ForEach(source.IdBags.ToArray(), destination.AddAsIdBag); - System.Array.ForEach(source.Lists.ToArray(), destination.AddAsList); - System.Array.ForEach(source.Arrays.ToArray(), destination.AddAsArray); - System.Array.ForEach(source.Dictionaries.ToArray(), destination.AddAsMap); - System.Array.ForEach(source.Properties.ToArray(), destination.AddAsProperty); - System.Array.ForEach(source.PersistentMembers.ToArray(), destination.AddAsPersistentMember); - System.Array.ForEach(source.SplitDefinitions.ToArray(), destination.AddAsPropertySplit); + foreach (var o in source.Sets) destination.AddAsSet(o); + foreach (var o in source.Bags) destination.AddAsBag(o); + foreach (var o in source.IdBags) destination.AddAsIdBag(o); + foreach (var o in source.Lists) destination.AddAsList(o); + foreach (var o in source.Arrays) destination.AddAsArray(o); + foreach (var o in source.Dictionaries) destination.AddAsMap(o); + foreach (var o in source.Properties) destination.AddAsProperty(o); + foreach (var o in source.PersistentMembers) destination.AddAsPersistentMember(o); + foreach (var o in source.SplitDefinitions) destination.AddAsPropertySplit(o); foreach (var dynamicComponent in source.DynamicComponents) { var template = source.GetDynamicComponentTemplate(dynamicComponent); @@ -46,4 +44,4 @@ public static void Merge(this IModelExplicitDeclarationsHolder destination, IMod } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ByCode/TypeExtensions.cs b/src/NHibernate/Mapping/ByCode/TypeExtensions.cs index 7b18cb19407..fae20ec1af5 100644 --- a/src/NHibernate/Mapping/ByCode/TypeExtensions.cs +++ b/src/NHibernate/Mapping/ByCode/TypeExtensions.cs @@ -212,7 +212,6 @@ public static System.Type DetermineCollectionElementType(this System.Type generi return null; } - public static System.Type DetermineRequiredCollectionElementType(this MemberInfo collectionProperty) { System.Type propertyType = collectionProperty.GetPropertyOrFieldType(); @@ -230,7 +229,6 @@ public static System.Type DetermineRequiredCollectionElementType(this MemberInfo return collectionElementType; } - public static System.Type DetermineCollectionElementOrDictionaryValueType(this System.Type genericCollection) { if (genericCollection.IsGenericType) diff --git a/src/NHibernate/Mapping/Collection.cs b/src/NHibernate/Mapping/Collection.cs index de4c97a58f2..5ebcd301ebc 100644 --- a/src/NHibernate/Mapping/Collection.cs +++ b/src/NHibernate/Mapping/Collection.cs @@ -75,7 +75,6 @@ public abstract class Collection : IFetchable, IValue, IFilterable private readonly HashSet synchronizedTables = new HashSet(); private IDictionary typeParameters; - protected Collection(PersistentClass owner) { this.owner = owner; diff --git a/src/NHibernate/Mapping/Column.cs b/src/NHibernate/Mapping/Column.cs index f129174b732..4425ba98be1 100644 --- a/src/NHibernate/Mapping/Column.cs +++ b/src/NHibernate/Mapping/Column.cs @@ -181,7 +181,6 @@ public string GetAlias(Dialect.Dialect dialect, Table table) return GetAlias(maxAliasLength) + suffix; } - /// /// Gets or sets if the column can have null values in it. /// diff --git a/src/NHibernate/Mapping/Component.cs b/src/NHibernate/Mapping/Component.cs index 9febe1a6845..c9f1409cf6f 100644 --- a/src/NHibernate/Mapping/Component.cs +++ b/src/NHibernate/Mapping/Component.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate.Tuple.Component; using NHibernate.Type; using NHibernate.Util; @@ -72,12 +73,7 @@ public override IEnumerable ColumnIterator { get { - List> iters = new List>(); - foreach (Property property in PropertyIterator) - { - iters.Add(property.ColumnIterator); - } - return new JoinedEnumerable(iters); + return PropertyIterator.SelectMany(x => x.ColumnIterator); } } diff --git a/src/NHibernate/Mapping/Constraint.cs b/src/NHibernate/Mapping/Constraint.cs index f8e8faf2091..5647d4f4906 100644 --- a/src/NHibernate/Mapping/Constraint.cs +++ b/src/NHibernate/Mapping/Constraint.cs @@ -91,11 +91,15 @@ public void AddColumns(IEnumerable columnIterator) { foreach (Column col in columnIterator) { - if (!col.IsFormula) - AddColumn(col); + AddColumn(col); } } + public void AddColumns(IEnumerable columnIterator) + { + AddColumns(columnIterator.OfType()); + } + /// /// Gets the number of columns that this Constraint contains. /// diff --git a/src/NHibernate/Mapping/DenormalizedTable.cs b/src/NHibernate/Mapping/DenormalizedTable.cs index 24e2a14c5ab..4852025ddc3 100644 --- a/src/NHibernate/Mapping/DenormalizedTable.cs +++ b/src/NHibernate/Mapping/DenormalizedTable.cs @@ -18,7 +18,7 @@ public DenormalizedTable(Table includedTable) public override IEnumerable ColumnIterator { - get { return new JoinedEnumerable(includedTable.ColumnIterator, base.ColumnIterator); } + get { return includedTable.ColumnIterator.Concat(base.ColumnIterator); } } public override IEnumerable UniqueKeyIterator @@ -39,17 +39,28 @@ public override IEnumerable IndexIterator { get { - List indexes = new List(); IEnumerable includedIdxs = includedTable.IndexIterator; foreach (Index parentIndex in includedIdxs) { + var sharedIndex = GetIndex(parentIndex.Name); + if (sharedIndex != null) + { + sharedIndex.AddColumns(parentIndex.ColumnIterator); + sharedIndex.IsInherited = true; + continue; + } Index index = new Index(); - index.Name = Name + parentIndex.Name; + index.Name = parentIndex.Name; + index.IsInherited = true; index.Table = this; index.AddColumns(parentIndex.ColumnIterator); - indexes.Add(index); + yield return index; + } + + foreach (var index in base.IndexIterator) + { + yield return index; } - return new JoinedEnumerable(indexes, base.IndexIterator); } } diff --git a/src/NHibernate/Mapping/IMetaAttributable.cs b/src/NHibernate/Mapping/IMetaAttributable.cs index f0026a3da19..b2400681dc2 100644 --- a/src/NHibernate/Mapping/IMetaAttributable.cs +++ b/src/NHibernate/Mapping/IMetaAttributable.cs @@ -8,7 +8,7 @@ public interface IMetaAttributable /// /// Meta-Attribute collection. /// - IDictionary MetaAttributes { get;set;} + IDictionary MetaAttributes { get; set; } /// /// Retrieve the diff --git a/src/NHibernate/Mapping/IValue.cs b/src/NHibernate/Mapping/IValue.cs index f3991aed143..1941b3dc563 100644 --- a/src/NHibernate/Mapping/IValue.cs +++ b/src/NHibernate/Mapping/IValue.cs @@ -39,7 +39,7 @@ public interface IValue /// /// Gets a indicating if this Value is unique. /// - bool IsAlternateUniqueKey { get;} + bool IsAlternateUniqueKey { get; } /// /// Gets a indicating if this Value can have @@ -82,4 +82,4 @@ public interface IValue object Accept(IValueVisitor visitor); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/IdentifierCollection.cs b/src/NHibernate/Mapping/IdentifierCollection.cs index d5bed3a207f..416091bd6be 100644 --- a/src/NHibernate/Mapping/IdentifierCollection.cs +++ b/src/NHibernate/Mapping/IdentifierCollection.cs @@ -1,6 +1,5 @@ using System; using NHibernate.Engine; -using NHibernate.Util; namespace NHibernate.Mapping { @@ -40,10 +39,10 @@ public override void CreatePrimaryKey() if (!IsOneToMany) { PrimaryKey pk = new PrimaryKey(); - pk.AddColumns(new SafetyEnumerable(Identifier.ColumnIterator)); + pk.AddColumns(Identifier.ColumnIterator); CollectionTable.PrimaryKey = pk; } - //else // Create an index on the key columns? + //else // Create an index on the key columns? } /// @@ -60,4 +59,4 @@ public override void Validate(IMapping mapping) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/Index.cs b/src/NHibernate/Mapping/Index.cs index 74729189c9b..a29ec92152a 100644 --- a/src/NHibernate/Mapping/Index.cs +++ b/src/NHibernate/Mapping/Index.cs @@ -68,7 +68,8 @@ public static string BuildSqlDropIndexString(Dialect.Dialect dialect, Table tabl /// public string SqlCreateString(Dialect.Dialect dialect, IMapping p, string defaultCatalog, string defaultSchema) { - return BuildSqlCreateIndexString(dialect, Name, Table, ColumnIterator, false, defaultCatalog, defaultSchema); + var indexName = IsInherited ? Table.Name + Name : Name; + return BuildSqlCreateIndexString(dialect, indexName, Table, ColumnIterator, false, defaultCatalog, defaultSchema); } /// @@ -144,6 +145,11 @@ public string Name set { name = value; } } + /// + /// Is this index inherited from the base class mapping + /// + public bool IsInherited { get; set; } + public bool ContainsColumn(Column column) { return columns.Contains(column); diff --git a/src/NHibernate/Mapping/IndexedCollection.cs b/src/NHibernate/Mapping/IndexedCollection.cs index 248598506c0..715d3b03b46 100644 --- a/src/NHibernate/Mapping/IndexedCollection.cs +++ b/src/NHibernate/Mapping/IndexedCollection.cs @@ -1,6 +1,5 @@ using System; using NHibernate.Engine; -using NHibernate.Util; namespace NHibernate.Mapping { @@ -40,7 +39,7 @@ public override void CreatePrimaryKey() if (!IsOneToMany) { PrimaryKey pk = new PrimaryKey(); - pk.AddColumns(new SafetyEnumerable(Key.ColumnIterator)); + pk.AddColumns(Key.ColumnIterator); // index should be last column listed bool isFormula = false; @@ -52,11 +51,11 @@ public override void CreatePrimaryKey() if (isFormula) { //if it is a formula index, use the element columns in the PK - pk.AddColumns(new SafetyEnumerable(Element.ColumnIterator)); + pk.AddColumns(Element.ColumnIterator); } else { - pk.AddColumns(new SafetyEnumerable(Index.ColumnIterator)); + pk.AddColumns(Index.ColumnIterator); } CollectionTable.PrimaryKey = pk; diff --git a/src/NHibernate/Mapping/Join.cs b/src/NHibernate/Mapping/Join.cs index 1656c87eff1..8e574900ada 100644 --- a/src/NHibernate/Mapping/Join.cs +++ b/src/NHibernate/Mapping/Join.cs @@ -100,7 +100,7 @@ public void CreatePrimaryKey() pk.Name = PK_ALIAS.ToAliasString(table.Name); table.PrimaryKey = pk; - pk.AddColumns(Key.ColumnIterator.OfType()); + pk.AddColumns(Key.ColumnIterator); } public int PropertySpan @@ -164,7 +164,6 @@ public bool IsLazy } return isLazy.Value; } - } public virtual bool IsOptional @@ -173,4 +172,4 @@ public virtual bool IsOptional set { isOptional = value; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/ManyToOne.cs b/src/NHibernate/Mapping/ManyToOne.cs index 72d9a106aba..7500f7f7997 100644 --- a/src/NHibernate/Mapping/ManyToOne.cs +++ b/src/NHibernate/Mapping/ManyToOne.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using NHibernate.Type; -using NHibernate.Util; using System; +using System.Linq; namespace NHibernate.Mapping { @@ -77,8 +77,7 @@ public void CreatePropertyRefConstraints(IDictionary pe if (!HasFormula && !"none".Equals(ForeignKeyName, StringComparison.OrdinalIgnoreCase)) { - - IEnumerable ce = new SafetyEnumerable(property.ColumnIterator); + IEnumerable ce = property.ColumnIterator.OfType(); // NH : Ensure that related columns have same length ForeignKey.AlignColumns(ConstraintColumns, ce); diff --git a/src/NHibernate/Mapping/OneToOne.cs b/src/NHibernate/Mapping/OneToOne.cs index 20454af6b0b..c8d82fa5653 100644 --- a/src/NHibernate/Mapping/OneToOne.cs +++ b/src/NHibernate/Mapping/OneToOne.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate.Type; using NHibernate.Util; @@ -40,10 +41,7 @@ public override void CreateForeignKey() } /// - public override IEnumerable ConstraintColumns - { - get { return new SafetyEnumerable(identifier.ColumnIterator); } - } + public override IEnumerable ConstraintColumns => identifier.ColumnIterator.OfType(); /// public bool IsConstrained @@ -103,4 +101,4 @@ public override IType Type } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/PersistentClass.cs b/src/NHibernate/Mapping/PersistentClass.cs index f85fa394989..b248dcb3dc8 100644 --- a/src/NHibernate/Mapping/PersistentClass.cs +++ b/src/NHibernate/Mapping/PersistentClass.cs @@ -41,8 +41,8 @@ public abstract class PersistentClass : IFilterable, IMetaAttributable, ISqlCust private IDictionary metaAttributes; private readonly List joins = new List(); private readonly List subclassJoins = new List(); - private readonly IDictionary filters = new Dictionary(); - private readonly ISet synchronizedTables = new HashSet(); + private readonly Dictionary filters = new Dictionary(); + private readonly HashSet synchronizedTables = new HashSet(); private string loaderName; private bool? isAbstract; private bool hasSubselectLoadableCollections; @@ -215,13 +215,7 @@ public virtual IEnumerable SubclassIterator { get { - IEnumerable[] iters = new IEnumerable[subclasses.Count + 1]; - int i = 0; - foreach (Subclass subclass in subclasses) - iters[i++] = subclass.SubclassIterator; - - iters[i] = subclasses; - return new JoinedEnumerable(iters); + return subclasses.SelectMany(s => s.SubclassIterator).Concat(subclasses); } } @@ -229,14 +223,8 @@ public virtual IEnumerable SubclassClosureIterator { get { - List> iters = new List>(); - iters.Add(new SingletonEnumerable(this)); - foreach (Subclass clazz in SubclassIterator) - iters.Add(clazz.SubclassClosureIterator); - - return new JoinedEnumerable(iters); + return new[] {this}.Concat(SubclassIterator.SelectMany(x => x.SubclassClosureIterator)); } - } public virtual Table IdentityTable @@ -323,19 +311,15 @@ public virtual IEnumerable SubclassPropertyClosureIterator { get { - List> iters = new List>(); - iters.Add(PropertyClosureIterator); - iters.Add(subclassProperties); - foreach (Join join in subclassJoins) - iters.Add(join.PropertyIterator); - - return new JoinedEnumerable(iters); + return PropertyClosureIterator + .Concat(subclassProperties) + .Concat(subclassJoins.SelectMany(x => x.PropertyIterator)); } } public virtual IEnumerable SubclassJoinClosureIterator { - get { return new JoinedEnumerable(JoinClosureIterator, subclassJoins); } + get { return JoinClosureIterator.Concat(subclassJoins); } } /// @@ -346,7 +330,7 @@ public virtual IEnumerable SubclassJoinClosureIterator /// It adds the TableClosureIterator and the subclassTables into the IEnumerable. public virtual IEnumerable SubclassTableClosureIterator { - get { return new JoinedEnumerable
(TableClosureIterator, subclassTables); } + get { return TableClosureIterator.Concat(subclassTables); } } public bool IsLazy @@ -456,12 +440,7 @@ public virtual IEnumerable PropertyIterator { get { - List> iterators = new List>(); - iterators.Add(properties); - foreach (Join join in joins) - iterators.Add(join.PropertyIterator); - - return new JoinedEnumerable(iterators); + return properties.Concat(joins.SelectMany(x => x.PropertyIterator)); } } @@ -782,7 +761,7 @@ public virtual void CreatePrimaryKey(Dialect.Dialect dialect) pk.Name = PKAlias.ToAliasString(table.Name); table.PrimaryKey = pk; - pk.AddColumns(new SafetyEnumerable(Key.ColumnIterator)); + pk.AddColumns(Key.ColumnIterator); } /// @@ -1104,7 +1083,6 @@ protected internal void CheckPropertyColumnDuplication(ISet distinctColu CheckColumnDuplication(distinctColumns, prop.ColumnIterator); } } - } protected internal virtual void CheckColumnDuplication() @@ -1186,6 +1164,5 @@ public bool HasNaturalId() } public abstract bool IsLazyPropertiesCacheable { get; } - } } diff --git a/src/NHibernate/Mapping/RootClass.cs b/src/NHibernate/Mapping/RootClass.cs index 59210b16467..56e6dd4d9c3 100644 --- a/src/NHibernate/Mapping/RootClass.cs +++ b/src/NHibernate/Mapping/RootClass.cs @@ -89,12 +89,12 @@ public override IEnumerable PropertyClosureIterator /// public override IEnumerable
TableClosureIterator { - get { return new SingletonEnumerable
(Table); } + get { return new[] {Table}; } } public override IEnumerable KeyClosureIterator { - get { return new SingletonEnumerable(Key); } + get { return new[] {Key}; } } /// @@ -170,7 +170,6 @@ public virtual ISet
IdentityTables } return tables; } - } internal override int NextSubclassId() diff --git a/src/NHibernate/Mapping/SimpleValue.cs b/src/NHibernate/Mapping/SimpleValue.cs index ab48f501f11..29de8c8927b 100644 --- a/src/NHibernate/Mapping/SimpleValue.cs +++ b/src/NHibernate/Mapping/SimpleValue.cs @@ -37,10 +37,7 @@ public SimpleValue(Table table) this.table = table; } - public virtual IEnumerable ConstraintColumns - { - get { return new SafetyEnumerable(columns); } - } + public virtual IEnumerable ConstraintColumns => columns.OfType(); public string ForeignKeyName { diff --git a/src/NHibernate/Mapping/SingleTableSubclass.cs b/src/NHibernate/Mapping/SingleTableSubclass.cs index 618be14daca..5f0d8368120 100644 --- a/src/NHibernate/Mapping/SingleTableSubclass.cs +++ b/src/NHibernate/Mapping/SingleTableSubclass.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate.Util; namespace NHibernate.Mapping @@ -12,7 +13,7 @@ public SingleTableSubclass(PersistentClass superclass) protected internal override IEnumerable NonDuplicatedPropertyIterator { - get { return new JoinedEnumerable(Superclass.UnjoinedPropertyIterator, UnjoinedPropertyIterator); } + get { return Superclass.UnjoinedPropertyIterator.Concat(UnjoinedPropertyIterator); } } protected internal override IEnumerable DiscriminatorColumnIterator @@ -39,4 +40,4 @@ public override void Validate(Engine.IMapping mapping) base.Validate(mapping); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/Subclass.cs b/src/NHibernate/Mapping/Subclass.cs index 1ca0f915208..90129783f49 100644 --- a/src/NHibernate/Mapping/Subclass.cs +++ b/src/NHibernate/Mapping/Subclass.cs @@ -58,7 +58,7 @@ public override bool IsInherited /// public override IEnumerable PropertyClosureIterator { - get { return new JoinedEnumerable(Superclass.PropertyClosureIterator, PropertyIterator); } + get { return Superclass.PropertyClosureIterator.Concat(PropertyIterator); } } /// @@ -75,12 +75,12 @@ public override IEnumerable PropertyClosureIterator /// public override IEnumerable
TableClosureIterator { - get { return new JoinedEnumerable
(Superclass.TableClosureIterator, new SingletonEnumerable
(Table)); } + get { return Superclass.TableClosureIterator.Concat(new[] {Table}); } } public override IEnumerable KeyClosureIterator { - get { return new JoinedEnumerable(Superclass.KeyClosureIterator, new SingletonEnumerable(Key)); } + get { return Superclass.KeyClosureIterator.Concat(new[] {Key}); } } /// @@ -143,7 +143,7 @@ public override int PropertyClosureSpan public override IEnumerable JoinClosureIterator { - get { return new JoinedEnumerable(Superclass.JoinClosureIterator, base.JoinClosureIterator); } + get { return Superclass.JoinClosureIterator.Concat(base.JoinClosureIterator); } } public override ISet SynchronizedTables diff --git a/src/NHibernate/Mapping/Table.cs b/src/NHibernate/Mapping/Table.cs index 625b4aa056e..3082a9c02b0 100644 --- a/src/NHibernate/Mapping/Table.cs +++ b/src/NHibernate/Mapping/Table.cs @@ -1052,7 +1052,6 @@ public IEnumerable ValidateColumns(Dialect.Dialect dialect, IMapping map return validationErrors; } - #region Nested type: ForeignKeyKey [Serializable] internal class ForeignKeyKey : IEqualityComparer diff --git a/src/NHibernate/Mapping/ToOne.cs b/src/NHibernate/Mapping/ToOne.cs index d327bbc4b7a..c959216d4b4 100644 --- a/src/NHibernate/Mapping/ToOne.cs +++ b/src/NHibernate/Mapping/ToOne.cs @@ -72,7 +72,7 @@ public override bool IsValid(Engine.IMapping mapping) return base.IsValid(mapping); } - public override abstract IType Type { get;} + public override abstract IType Type { get; } public override bool IsTypeSpecified { diff --git a/src/NHibernate/Metadata/IClassMetadata.cs b/src/NHibernate/Metadata/IClassMetadata.cs index 32367ce3ae2..7a06f433eb7 100644 --- a/src/NHibernate/Metadata/IClassMetadata.cs +++ b/src/NHibernate/Metadata/IClassMetadata.cs @@ -56,13 +56,13 @@ public interface IClassMetadata bool[] PropertyNullability { get; } /// Get the "laziness" of the properties of this class - bool[] PropertyLaziness { get;} + bool[] PropertyLaziness { get; } /// Which properties hold the natural id? - int[] NaturalIdentifierProperties { get;} + int[] NaturalIdentifierProperties { get; } /// Does this entity extend a mapped superclass? - bool IsInherited { get;} + bool IsInherited { get; } #region stuff that is persister-centric and/or EntityInfo-centric @@ -146,4 +146,4 @@ public interface IClassMetadata #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Multi/IQueryBatchItem.cs b/src/NHibernate/Multi/IQueryBatchItem.cs index 6e69f1d15f1..0eb11051ae0 100644 --- a/src/NHibernate/Multi/IQueryBatchItem.cs +++ b/src/NHibernate/Multi/IQueryBatchItem.cs @@ -80,4 +80,10 @@ public partial interface IQueryBatchItem /// void ExecuteNonBatched(); } + + //TODO 6.0: Remove along with ignore rule for IQueryBatchItem.ProcessResults in AsyncGenerator.yml + internal partial interface IQueryBatchItemWithAsyncProcessResults + { + void ProcessResults(); + } } diff --git a/src/NHibernate/Multi/LinqBatchItem.cs b/src/NHibernate/Multi/LinqBatchItem.cs index fdbf9f1be2f..733b3115505 100644 --- a/src/NHibernate/Multi/LinqBatchItem.cs +++ b/src/NHibernate/Multi/LinqBatchItem.cs @@ -4,11 +4,15 @@ using System.Linq; using System.Linq.Expressions; using NHibernate.Linq; -using NHibernate.Util; using Remotion.Linq.Parsing.ExpressionVisitors; namespace NHibernate.Multi { + interface ILinqBatchItem + { + List GetTypedResults(); + } + public static class LinqBatchItem { public static LinqBatchItem Create(IQueryable query, Expression, TResult>> selector) @@ -42,9 +46,10 @@ private static LinqBatchItem GetForQuery(IQueryable query, Exp /// Create instance via methods /// /// Result type - public partial class LinqBatchItem : QueryBatchItem + public partial class LinqBatchItem : QueryBatchItem, ILinqBatchItem { private readonly Delegate _postExecuteTransformer; + private readonly System.Type _resultTypeOverride; public LinqBatchItem(IQuery query) : base(query) { @@ -53,6 +58,7 @@ public LinqBatchItem(IQuery query) : base(query) internal LinqBatchItem(IQuery query, NhLinqExpression linq) : base(query) { _postExecuteTransformer = linq.ExpressionToHqlTranslationResults.PostExecuteTransformer; + _resultTypeOverride = linq.ExpressionToHqlTranslationResults.ExecuteResultTypeOverride; } protected override IList GetResultsNonBatched() @@ -69,11 +75,10 @@ protected override List DoGetResults() { if (_postExecuteTransformer != null) { - var elementType = GetResultTypeIfChanged(); - - IList transformerList = elementType == null + IList transformerList = _resultTypeOverride == null ? base.DoGetResults() - : GetTypedResults(elementType); + //see LinqToFutureValueFixture tests that cover this scenario + : LinqBatchReflectHelper.GetTypedResults(this, _resultTypeOverride); return GetTransformedResults(transformerList); } @@ -90,27 +95,9 @@ private List GetTransformedResults(IList transformerList) }; } - private System.Type GetResultTypeIfChanged() - { - if (_postExecuteTransformer == null) - { - return null; - } - var elementType = _postExecuteTransformer.Method.GetParameters()[1].ParameterType.GetGenericArguments()[0]; - if (typeof(T).IsAssignableFrom(elementType)) - { - return null; - } - - return elementType; - } - - private IList GetTypedResults(System.Type type) + List ILinqBatchItem.GetTypedResults() { - var method = ReflectHelper.GetMethod(() => GetTypedResults()) - .GetGenericMethodDefinition(); - var generic = method.MakeGenericMethod(type); - return (IList) generic.Invoke(this, null); + return GetTypedResults(); } } } diff --git a/src/NHibernate/Multi/LinqBatchReflectHelper.cs b/src/NHibernate/Multi/LinqBatchReflectHelper.cs new file mode 100644 index 00000000000..4d1037a4e75 --- /dev/null +++ b/src/NHibernate/Multi/LinqBatchReflectHelper.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Util; + +namespace NHibernate.Multi +{ + static class LinqBatchReflectHelper + { + private static readonly ConcurrentDictionary> GetResultsForTypeDic = new ConcurrentDictionary>(); + + private static readonly MethodInfo GetTypedResultsMethod = ReflectHelper.GetMethod((ILinqBatchItem i) => i.GetTypedResults()).GetGenericMethodDefinition(); + + internal static IList GetTypedResults(ILinqBatchItem batchItem, System.Type type) + { + return GetResultsForTypeDic.GetOrAdd(type, t => CompileDelegate(t)).Invoke(batchItem); + } + + private static Func CompileDelegate(System.Type type) + { + var generic = GetTypedResultsMethod.MakeGenericMethod(type); + var instance = Expression.Parameter(typeof(ILinqBatchItem)); + var methodCall = Expression.Call(instance, generic); + return Expression.Lambda>(methodCall, instance).Compile(); + } + } +} diff --git a/src/NHibernate/Multi/QueryBatch.cs b/src/NHibernate/Multi/QueryBatch.cs index 09580939c13..068b45630de 100644 --- a/src/NHibernate/Multi/QueryBatch.cs +++ b/src/NHibernate/Multi/QueryBatch.cs @@ -151,18 +151,19 @@ protected void ExecuteBatched() } var rowCount = 0; + CacheBatcher cacheBatcher = null; try { if (resultSetsCommand.HasQueries) { + cacheBatcher = new CacheBatcher(Session); using (var reader = resultSetsCommand.GetReader(Timeout)) { - var cacheBatcher = new CacheBatcher(Session); foreach (var query in _queries) { if (query.CachingInformation != null) { - foreach (var cachingInfo in query.CachingInformation.Where(ci => ci.IsCacheable)) + foreach (var cachingInfo in query.CachingInformation) { cachingInfo.SetCacheBatcher(cacheBatcher); } @@ -170,18 +171,20 @@ protected void ExecuteBatched() rowCount += query.ProcessResultsSet(reader); } - cacheBatcher.ExecuteBatch(); } } - // Query cacheable results must be cached untransformed: the put does not need to wait for - // the ProcessResults. - PutCacheableResults(); - foreach (var query in _queries) { - query.ProcessResults(); + //TODO 6.0: Replace with query.ProcessResults(); + if (query is IQueryBatchItemWithAsyncProcessResults q) + q.ProcessResults(); + else + query.ProcessResults(); } + + cacheBatcher?.ExecuteBatch(); + PutCacheableResults(); } catch (Exception sqle) { diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 6655a88503d..5a0123650f3 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -14,13 +14,17 @@ namespace NHibernate.Multi /// /// Base class for both ICriteria and IQuery queries /// - public abstract partial class QueryBatchItemBase : IQueryBatchItem + public abstract partial class QueryBatchItemBase : IQueryBatchItem, IQueryBatchItemWithAsyncProcessResults { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(QueryBatch)); + protected ISessionImplementor Session; private List[] _subselectResultKeys; private List _queryInfos; private CacheMode? _cacheMode; private IList _finalResults; + private DbDataReader _reader; + private List[] _hydratedObjects; protected class QueryInfo : ICachingInformation, ICachingInformationWithFetches { @@ -46,7 +50,7 @@ protected class QueryInfo : ICachingInformation, ICachingInformationWithFetches public bool IsCacheable { get; } /// - public QueryKey CacheKey { get;} + public QueryKey CacheKey { get; } /// public bool CanGetFromCache { get; } @@ -176,6 +180,7 @@ public int ProcessResultsSet(DbDataReader reader) var dialect = Session.Factory.Dialect; var hydratedObjects = new List[_queryInfos.Count]; + var isDebugLog = Log.IsDebugEnabled(); using (Session.SwitchCacheMode(_cacheMode)) { @@ -217,14 +222,21 @@ public int ProcessResultsSet(DbDataReader reader) var lockModeArray = loader.GetLockModes(queryParameters.LockModes); var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); var tmpResults = new List(); - var queryCacheBuilder = new QueryCacheResultBuilder(loader); + var queryCacheBuilder = queryInfo.IsCacheable ? new QueryCacheResultBuilder(loader) : null; var cacheBatcher = queryInfo.CacheBatcher; var ownCacheBatcher = cacheBatcher == null; if (ownCacheBatcher) cacheBatcher = new CacheBatcher(Session); - for (var count = 0; count < maxRows && reader.Read(); count++) + if (isDebugLog) + Log.Debug("processing result set"); + + int count; + for (count = 0; count < maxRows && reader.Read(); count++) { + if (isDebugLog) + Log.Debug("result set row: {0}", count); + rowCount++; var o = @@ -250,6 +262,9 @@ public int ProcessResultsSet(DbDataReader reader) tmpResults.Add(o); } + if (isDebugLog) + Log.Debug("done processing result set ({0} rows)", count); + queryInfo.Result = tmpResults; if (queryInfo.CanPutToCache) queryInfo.ResultToCache = queryCacheBuilder.Result; @@ -260,17 +275,21 @@ public int ProcessResultsSet(DbDataReader reader) reader.NextResult(); } - InitializeEntitiesAndCollections(reader, hydratedObjects); - + StopLoadingCollections(reader); + _reader = reader; + _hydratedObjects = hydratedObjects; return rowCount; } } - /// + /// public void ProcessResults() { ThrowIfNotInitialized(); + using (Session.SwitchCacheMode(_cacheMode)) + InitializeEntitiesAndCollections(_reader, _hydratedObjects); + for (var i = 0; i < _queryInfos.Count; i++) { var queryInfo = _queryInfos[i]; @@ -348,6 +367,17 @@ private void InitializeEntitiesAndCollections(DbDataReader reader, List[ } } + private void StopLoadingCollections(DbDataReader reader) + { + for (var i = 0; i < _queryInfos.Count; i++) + { + var queryInfo = _queryInfos[i]; + if (queryInfo.IsResultFromCache) + continue; + queryInfo.Loader.StopLoadingCollections(Session, reader); + } + } + private void ThrowIfNotInitialized() { if (_queryInfos == null) diff --git a/src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..1c19f8c0018 --- /dev/null +++ b/src/NHibernate/MultiTenancy/AbstractMultiTenancyConnectionProvider.cs @@ -0,0 +1,61 @@ +using System; +using System.Data.Common; +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.MultiTenancy +{ + /// + /// Base implementation for multi-tenancy strategy. + /// + [Serializable] + public abstract partial class AbstractMultiTenancyConnectionProvider : IMultiTenancyConnectionProvider + { + /// + public IConnectionAccess GetConnectionAccess(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory) + { + var tenantConnectionString = GetTenantConnectionString(tenantConfiguration, sessionFactory); + if (string.IsNullOrEmpty(tenantConnectionString)) + { + throw new HibernateException($"Tenant '{tenantConfiguration.TenantIdentifier}' connection string is empty."); + } + + return new ContextualConnectionAccess(tenantConnectionString, sessionFactory); + } + + /// + /// Gets the connection string for the given tenant configuration. + /// + /// The tenant configuration. + /// The session factory. + /// The connection string for the tenant. + protected abstract string GetTenantConnectionString(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory); + + [Serializable] + partial class ContextualConnectionAccess : IConnectionAccess + { + private readonly ISessionFactoryImplementor _sessionFactory; + + public ContextualConnectionAccess(string connectionString, ISessionFactoryImplementor sessionFactory) + { + ConnectionString = connectionString; + _sessionFactory = sessionFactory; + } + + /// + public string ConnectionString { get; } + + /// + public DbConnection GetConnection() + { + return _sessionFactory.ConnectionProvider.GetConnection(ConnectionString); + } + + /// + public void CloseConnection(DbConnection connection) + { + _sessionFactory.ConnectionProvider.CloseConnection(connection); + } + } + } +} diff --git a/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs b/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs new file mode 100644 index 00000000000..045e8f0fdc0 --- /dev/null +++ b/src/NHibernate/MultiTenancy/IMultiTenancyConnectionProvider.cs @@ -0,0 +1,20 @@ +using NHibernate.Connection; +using NHibernate.Engine; + +namespace NHibernate.MultiTenancy +{ + /// + /// A specialized Connection provider contract used when the application is using multi-tenancy support requiring + /// tenant aware connections. + /// + public interface IMultiTenancyConnectionProvider + { + /// + /// Gets the tenant connection access. + /// + /// The tenant configuration. + /// The session factory. + /// The tenant connection access. + IConnectionAccess GetConnectionAccess(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory); + } +} diff --git a/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs new file mode 100644 index 00000000000..cca618ad39a --- /dev/null +++ b/src/NHibernate/MultiTenancy/MultiTenancyStrategy.cs @@ -0,0 +1,28 @@ +namespace NHibernate.MultiTenancy +{ + /// + /// Strategy for multi-tenancy + /// + /// + public enum MultiTenancyStrategy + { + /// + /// No multi-tenancy + /// + None, + + /// + /// Multi-tenancy implemented as separate database per tenant. + /// + Database, +// /// +// /// Multi-tenancy implemented by use of discriminator columns. +// /// +// Discriminator, +// +// /// +// /// Multi-tenancy implemented as separate schemas. +// /// +// Schema, + } +} diff --git a/src/NHibernate/MultiTenancy/TenantConfiguration.cs b/src/NHibernate/MultiTenancy/TenantConfiguration.cs new file mode 100644 index 00000000000..b7fee880437 --- /dev/null +++ b/src/NHibernate/MultiTenancy/TenantConfiguration.cs @@ -0,0 +1,23 @@ +using System; + +namespace NHibernate.MultiTenancy +{ + /// + /// Tenant specific configuration. + /// This class can be used as base class for user complex tenant configurations. + /// + [Serializable] + public class TenantConfiguration + { + /// + /// Tenant identifier must uniquely identify tenant + /// Note: Among other things this value is used for data separation between tenants in cache so not unique value will leak data to other tenants + /// + public string TenantIdentifier { get; } + + public TenantConfiguration(string tenantIdentifier) + { + TenantIdentifier = tenantIdentifier ?? throw new ArgumentNullException(nameof(tenantIdentifier)); + } + } +} diff --git a/src/NHibernate/NHibernate.csproj b/src/NHibernate/NHibernate.csproj index 2661cc1bde7..5c615f81804 100644 --- a/src/NHibernate/NHibernate.csproj +++ b/src/NHibernate/NHibernate.csproj @@ -12,6 +12,7 @@ NHibernate is a mature, open source object-relational mapper for the .NET framework. It is actively developed, fully featured and used in thousands of successful projects. ORM; O/RM; DataBase; DAL; ObjectRelationalMapping; NHibernate; ADO.Net; Core + 7.2 @@ -37,7 +38,7 @@ - + @@ -76,5 +77,6 @@ ./NHibernate.license.txt + diff --git a/src/NHibernate/NHibernateUtil.cs b/src/NHibernate/NHibernateUtil.cs index fcc7a894e94..226d26bf67c 100644 --- a/src/NHibernate/NHibernateUtil.cs +++ b/src/NHibernate/NHibernateUtil.cs @@ -137,6 +137,11 @@ public static IType GuessType(System.Type type) /// public static readonly DateType Date = new DateType(); + /// + /// NHibernate local date type + /// + public static readonly DateType LocalDate = new LocalDateType(); + /// /// NHibernate decimal type /// @@ -287,7 +292,6 @@ public static IType GuessType(System.Type type) /// public static readonly AnyType Object = new AnyType(); - // /// // /// NHibernate blob type // /// @@ -383,7 +387,6 @@ public static IType Custom(System.Type userTypeClass) } } - /// /// Force initialization of a proxy or persistent collection. /// @@ -408,7 +411,6 @@ public static void Initialize(object proxy) { persistent.ForceInitialization(); } - } /// @@ -557,7 +559,6 @@ public static void Close(IEnumerable enumerable) hibernateEnumerable.Dispose(); } - /// /// Check if the property is initialized. If the named property does not exist /// or is not persistent, this method always returns true. diff --git a/src/NHibernate/NonUniqueResultException.cs b/src/NHibernate/NonUniqueResultException.cs index 1dfb262a3d9..82ff46f6de5 100644 --- a/src/NHibernate/NonUniqueResultException.cs +++ b/src/NHibernate/NonUniqueResultException.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.Serialization; - namespace NHibernate { /// diff --git a/src/NHibernate/ObjectNotFoundByUniqueKeyException.cs b/src/NHibernate/ObjectNotFoundByUniqueKeyException.cs new file mode 100644 index 00000000000..f61442aaefc --- /dev/null +++ b/src/NHibernate/ObjectNotFoundByUniqueKeyException.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.Serialization; +using System.Security; +using NHibernate.Impl; + +namespace NHibernate +{ + /// + /// Thrown when entity can't be found by given unique key + /// + [Serializable] + public class ObjectNotFoundByUniqueKeyException : HibernateException + { + /// + /// Property name + /// + public string PropertyName { get; } + + /// + /// Key + /// + public object Key { get; } + + /// + /// Thrown when entity can't be found by given unique key + /// + /// Entity name + /// Property name + /// Key + public ObjectNotFoundByUniqueKeyException(string entityName, string propertyName, object key) + : base("No row with given unique key found " + MessageHelper.InfoString(entityName, propertyName, key)) + { + Key = key; + PropertyName = propertyName; + } + + #region Serialization + + protected ObjectNotFoundByUniqueKeyException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Key = info.GetValue(nameof(Key), typeof(object)); + PropertyName = info.GetString(nameof(PropertyName)); + } + + [SecurityCritical] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue(nameof(Key), Key); + info.AddValue(nameof(PropertyName), PropertyName); + } + + #endregion Serialization + } +} diff --git a/src/NHibernate/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs b/src/NHibernate/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs index 1b8f0dd14aa..67c8d550bd9 100644 --- a/src/NHibernate/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs +++ b/src/NHibernate/Param/AggregatedIndexCollectionSelectorParameterSpecifications.cs @@ -26,7 +26,6 @@ public AggregatedIndexCollectionSelectorParameterSpecifications(IList sqlQueryParametersList, QueryParameters queryParameters, ISessionImplementor session) { throw new NotImplementedException(); @@ -43,10 +42,9 @@ public IType ExpectedType set { } } - public string RenderDisplayInfo() - + public string RenderDisplayInfo() { - return "index-selector [" + CollectDisplayInfo() + "]" ; + return "index-selector [" + CollectDisplayInfo() + "]"; } public IEnumerable GetIdsForBackTrack(IMapping sessionFactory) @@ -66,4 +64,4 @@ private string CollectDisplayInfo() return buffer.ToString(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Param/NamedParameter.cs b/src/NHibernate/Param/NamedParameter.cs index b42f69925f0..a9a9b67de2b 100644 --- a/src/NHibernate/Param/NamedParameter.cs +++ b/src/NHibernate/Param/NamedParameter.cs @@ -5,16 +5,24 @@ namespace NHibernate.Param public class NamedParameter { public NamedParameter(string name, object value, IType type) + : this(name, value, type, false) + { + } + + internal NamedParameter(string name, object value, IType type, bool isCollection) { Name = name; Value = value; Type = type; + IsCollection = isCollection; } public string Name { get; private set; } public object Value { get; internal set; } public IType Type { get; internal set; } + public virtual bool IsCollection { get; } + public bool Equals(NamedParameter other) { if (ReferenceEquals(null, other)) @@ -38,4 +46,4 @@ public override int GetHashCode() return (Name != null ? Name.GetHashCode() : 0); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 4489945d400..033673f9656 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -31,7 +31,7 @@ namespace NHibernate.Persister.Collection /// Summary description for AbstractCollectionPersister. /// public abstract partial class AbstractCollectionPersister : ICollectionMetadata, ISqlLoadableCollection, - IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister + IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable { protected static readonly object NotFoundPlaceHolder = new object(); private readonly string role; @@ -158,8 +158,8 @@ public abstract partial class AbstractCollectionPersister : ICollectionMetadata, private readonly string[] spaces; - private readonly IDictionary collectionPropertyColumnAliases = new Dictionary(); - private readonly IDictionary collectionPropertyColumnNames = new Dictionary(); + private readonly Dictionary collectionPropertyColumnAliases = new Dictionary(); + private readonly Dictionary collectionPropertyColumnNames = new Dictionary(); private static readonly INHibernateLogger log = NHibernateLogger.For(typeof (ICollectionPersister)); @@ -1381,6 +1381,13 @@ public string GetManyToManyFilterFragment(string alias, IDictionary enabledFilters) + { + return IsManyToMany && (manyToManyWhereString != null || manyToManyFilterHelper.IsAffectedBy(enabledFilters)); + } + public string[] ToColumns(string alias, string propertyName) { if ("index".Equals(propertyName)) @@ -1512,17 +1519,17 @@ public string[] GetCollectionPropertyColumnAliases(string propertyName, string s public void InitCollectionPropertyMap() { - InitCollectionPropertyMap("key", keyType, keyColumnAliases, keyColumnNames); - InitCollectionPropertyMap("element", elementType, elementColumnAliases, elementColumnNames); + InitCollectionPropertyMap(CollectionPersister.PropKey, keyType, keyColumnAliases, keyColumnNames); + InitCollectionPropertyMap(CollectionPersister.PropElement, elementType, elementColumnAliases, elementColumnNames); if (hasIndex) { - InitCollectionPropertyMap("index", indexType, indexColumnAliases, indexColumnNames); + InitCollectionPropertyMap(CollectionPersister.PropIndex, indexType, indexColumnAliases, indexColumnNames); } if (hasIdentifier) { - InitCollectionPropertyMap("id", identifierType, new string[] {identifierColumnAlias}, + InitCollectionPropertyMap(CollectionPersister.PropId, identifierType, new string[] {identifierColumnAlias}, new string[] {identifierColumnName}); } } @@ -1713,9 +1720,9 @@ public object NotFoundObject public abstract SqlString WhereJoinFragment(string alias, bool innerJoin, bool includeSubclasses); - // 6.0 TODO: Remove (Replace with ISupportSelectModeJoinable.SelectFragment) + // 6.0 TODO: Remove // Since v5.2 - [Obsolete("Use overload taking includeLazyProperties parameter")] + [Obsolete("Please use overload taking EntityLoadInfo")] public virtual string SelectFragment( IJoinable rhs, string rhsAlias, @@ -1724,14 +1731,20 @@ public virtual string SelectFragment( string currentCollectionSuffix, bool includeCollectionColumns) { - return SelectFragment( - rhs, rhsAlias, lhsAlias, currentEntitySuffix, currentCollectionSuffix, includeCollectionColumns, false); + return SelectFragment(rhs, rhsAlias, lhsAlias, currentCollectionSuffix, includeCollectionColumns, new EntityLoadInfo(currentEntitySuffix)); } - // 6.0 TODO: Make abstract + // 6.0 TODO: Remove + [Obsolete("Please use overload taking EntityLoadInfo")] public virtual string SelectFragment( IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns, bool includeLazyProperties) + { + return SelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = true}); + } + + //6.0 TODO: Make abstract + public virtual string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string currentCollectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { throw new NotImplementedException("SelectFragment with fetching lazy properties option is not implemented by " + GetType().FullName); } diff --git a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs index 276ab028874..18ca77da8ac 100644 --- a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs @@ -262,7 +262,7 @@ protected override int DoUpdateRows(object id, IPersistentCollection collection, } } - public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns, bool includeLazyProperties) + public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { // we need to determine the best way to know that two joinables // represent a single many-to-many... diff --git a/src/NHibernate/Persister/Collection/CollectionPersister.cs b/src/NHibernate/Persister/Collection/CollectionPersister.cs new file mode 100644 index 00000000000..bfc1436cd73 --- /dev/null +++ b/src/NHibernate/Persister/Collection/CollectionPersister.cs @@ -0,0 +1,11 @@ +namespace NHibernate.Persister.Collection +{ + internal static class CollectionPersister + { + /// The property name of the "special" identifier property + public const string PropId = "id"; + public const string PropElement = "element"; + public const string PropKey = "key"; + public const string PropIndex = "index"; + } +} diff --git a/src/NHibernate/Persister/Collection/ICollectionPersister.cs b/src/NHibernate/Persister/Collection/ICollectionPersister.cs index 4d87ea158b3..8f813f0ce09 100644 --- a/src/NHibernate/Persister/Collection/ICollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/ICollectionPersister.cs @@ -36,7 +36,7 @@ public partial interface ICollectionPersister ICacheConcurrencyStrategy Cache { get; } /// Get the cache structure - ICacheEntryStructure CacheEntryStructure { get;} + ICacheEntryStructure CacheEntryStructure { get; } /// /// Get the associated IType @@ -74,7 +74,7 @@ public partial interface ICollectionPersister bool IsArray { get; } /// Is this a one-to-many association? - bool IsOneToMany { get;} + bool IsOneToMany { get; } /// /// Is this a many-to-many association? Note that this is mainly @@ -82,7 +82,7 @@ public partial interface ICollectionPersister /// contain all the information needed to handle a many-to-many /// itself, as internally it is looked at as two many-to-ones. /// - bool IsManyToMany { get;} + bool IsManyToMany { get; } /// /// Is this collection lazily initialized? @@ -100,7 +100,7 @@ public partial interface ICollectionPersister string Role { get; } /// Get the persister of the entity that "owns" this collection - IEntityPersister OwnerEntityPersister { get;} + IEntityPersister OwnerEntityPersister { get; } /// /// Get the surrogate key generation strategy (optional operation) @@ -121,7 +121,7 @@ public partial interface ICollectionPersister /// Is cascade delete handled by the database-level /// foreign key constraint definition? /// - bool CascadeDeleteEnabled { get;} + bool CascadeDeleteEnabled { get; } /// /// Does this collection cause version increment of the owning entity? @@ -129,10 +129,10 @@ public partial interface ICollectionPersister bool IsVersioned { get; } /// Can the elements of this collection change? - bool IsMutable { get;} + bool IsMutable { get; } ISessionFactoryImplementor Factory { get; } - bool IsExtraLazy { get;} + bool IsExtraLazy { get; } /// /// Initialize the given collection with the given key @@ -268,7 +268,7 @@ public partial interface ICollectionPersister int GetSize(object key, ISessionImplementor session); bool IndexExists(object key, object index, ISessionImplementor session); bool ElementExists(object key, object element, ISessionImplementor session); - + /// /// Try to find an element by a given index. /// @@ -278,13 +278,14 @@ public partial interface ICollectionPersister /// The owner of the collection. /// The value of the element where available; otherwise . object GetElementByIndex(object key, object index, ISessionImplementor session, object owner); - + /// /// A place-holder to inform that the data-reader was empty. /// object NotFoundObject { get; } } + //6.0 TODO: Merge into ICollectionPersister public static class CollectionPersisterExtensions { /// diff --git a/src/NHibernate/Persister/Collection/IQueryableCollection.cs b/src/NHibernate/Persister/Collection/IQueryableCollection.cs index 6004fe907f9..ba24382c62b 100644 --- a/src/NHibernate/Persister/Collection/IQueryableCollection.cs +++ b/src/NHibernate/Persister/Collection/IQueryableCollection.cs @@ -12,7 +12,7 @@ public interface IQueryableCollection : IPropertyMapping, IJoinable, ICollection /// Get the index formulas if this is an indexed collection /// (optional operation) /// - string[] IndexFormulas { get;} + string[] IndexFormulas { get; } /// /// Get the persister of the element class, if this is a diff --git a/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs b/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs index 710ef996369..70f32337b04 100644 --- a/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs +++ b/src/NHibernate/Persister/Collection/NamedQueryCollectionInitializer.cs @@ -1,4 +1,3 @@ - using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Loader.Collection; diff --git a/src/NHibernate/Persister/Collection/OneToManyPersister.cs b/src/NHibernate/Persister/Collection/OneToManyPersister.cs index 51626e9f619..a501de1b8c0 100644 --- a/src/NHibernate/Persister/Collection/OneToManyPersister.cs +++ b/src/NHibernate/Persister/Collection/OneToManyPersister.cs @@ -294,7 +294,7 @@ protected override int DoUpdateRows(object id, IPersistentCollection collection, } } - public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns, bool fetchLazyProperties) + public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { var buf = new StringBuilder(); @@ -303,15 +303,25 @@ public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhs buf.Append(SelectFragment(lhsAlias, collectionSuffix)).Append(StringHelper.CommaSpace); } - if (fetchLazyProperties) + //6.0 TODO: Remove +#pragma warning disable 618 + if (entityInfo.IncludeLazyProps) { var selectMode = ReflectHelper.CastOrThrow(ElementPersister, "fetch lazy properties"); if (selectMode != null) - return buf.Append(selectMode.SelectFragment(null, null, lhsAlias, entitySuffix, null, false, fetchLazyProperties)).ToString(); + return buf.Append(selectMode.SelectFragment(null, null, lhsAlias, entityInfo.EntitySuffix, null, false, true)).ToString(); + } +#pragma warning restore 618 + + if (entityInfo.LazyProperties?.Count > 0) + { + var selectMode = ReflectHelper.CastOrThrow(ElementPersister, "fetch lazy properties"); + if (selectMode != null) + return buf.Append(selectMode.SelectFragment(null, null, lhsAlias, null, false, entityInfo)).ToString(); } var ojl = (IOuterJoinLoadable)ElementPersister; - return buf.Append(ojl.SelectFragment(lhsAlias, entitySuffix)).ToString(); //use suffix for the entity columns + return buf.Append(ojl.SelectFragment(lhsAlias, entityInfo.EntitySuffix)).ToString(); //use suffix for the entity columns } protected override SelectFragment GenerateSelectFragment(string alias, string columnSuffix) diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 9901611ba10..2c71d037c0b 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -40,7 +40,7 @@ namespace NHibernate.Persister.Entity /// public abstract partial class AbstractEntityPersister : IOuterJoinLoadable, IQueryable, IClassMetadata, IUniqueKeyLoadable, ISqlLoadable, ILazyPropertyInitializer, IPostInsertIdentityPersister, ILockable, - ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister + ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable { #region InclusionChecker @@ -133,7 +133,7 @@ public virtual void BindValues(DbCommand ps) private readonly bool hasSubselectLoadableCollections; protected internal string rowIdName; - private readonly ISet lazyProperties; + private readonly HashSet lazyProperties; private readonly string sqlWhereString; private readonly string sqlWhereStringTemplate; @@ -552,8 +552,8 @@ protected AbstractEntityPersister(PersistentClass persistentClass, ICacheConcurr protected abstract int[] SubclassColumnTableNumberClosure { get; } protected abstract int[] SubclassFormulaTableNumberClosure { get; } - protected internal abstract int[] PropertyTableNumbersInSelect { get;} - protected internal abstract int[] PropertyTableNumbers { get;} + protected internal abstract int[] PropertyTableNumbersInSelect { get; } + protected internal abstract int[] PropertyTableNumbers { get; } public virtual string DiscriminatorColumnName { @@ -1052,13 +1052,13 @@ public int[] NaturalIdentifierProperties get { return entityMetamodel.NaturalIdentifierProperties; } } - public abstract string[][] ConstraintOrderedTableKeyColumnClosure { get;} - public abstract IType DiscriminatorType { get;} - public abstract string[] ConstraintOrderedTableNameClosure { get;} - public abstract string DiscriminatorSQLValue { get;} - public abstract object DiscriminatorValue { get;} + public abstract string[][] ConstraintOrderedTableKeyColumnClosure { get; } + public abstract IType DiscriminatorType { get; } + public abstract string[] ConstraintOrderedTableNameClosure { get; } + public abstract string DiscriminatorSQLValue { get; } + public abstract object DiscriminatorValue { get; } public abstract string[] SubclassClosure { get; } - public abstract string[] PropertySpaces { get;} + public abstract string[] PropertySpaces { get; } protected virtual void AddDiscriminatorToInsert(SqlInsertBuilder insert) { } @@ -1413,7 +1413,12 @@ public void InitializeLazyProperties( int[] lazyIndexes; if (allLazyProperties) { - lazyIndexes = indexes = lazyPropertyNumbers; + indexes = lazyPropertyNumbers; + lazyIndexes = new int[lazyPropertyNumbers.Length]; + for(var i = 0; i < lazyIndexes.Length; i++) + { + lazyIndexes[i] = i; + } } else { @@ -1641,12 +1646,12 @@ public string PropertySelectFragment(string name, string suffix, bool allPropert return PropertySelectFragment(name, suffix, null, allProperties); } - public string PropertySelectFragment(string name, string suffix, string[] fetchProperties) + public string PropertySelectFragment(string name, string suffix, ICollection fetchProperties) { return PropertySelectFragment(name, suffix, fetchProperties, false); } - private string PropertySelectFragment(string name, string suffix, string[] fetchProperties, bool allProperties) + private string PropertySelectFragment(string name, string suffix, ICollection fetchProperties, bool allProperties) { SelectFragment select = new SelectFragment(Factory.Dialect) .SetSuffix(suffix) @@ -1859,7 +1864,6 @@ protected string ConcretePropertySelectFragment(string alias, IInclusionChecker return frag.ToFragmentString(); } - protected virtual SqlString GenerateSnapshotSelectString() { //TODO: should we use SELECT .. FOR UPDATE? @@ -2149,13 +2153,18 @@ public string GenerateTableAlias(string rootAlias, int tableNumber) if (tableNumber == 0) return rootAlias; - StringBuilder buf = new StringBuilder().Append(rootAlias); - if (!rootAlias.EndsWith("_")) - { - buf.Append('_'); - } + string x = rootAlias.EndsWith('_') + ? string.Empty + : "_"; + return string.Concat(rootAlias, x, tableNumber.ToString(), "_"); + } - return buf.Append(tableNumber).Append('_').ToString(); + private string GetSubclassAliasedColumn(string rootAlias, int tableNumber, string columnName) + { + if (string.IsNullOrEmpty(rootAlias)) + return columnName; + + return GenerateTableAlias(rootAlias, tableNumber) + "." + columnName; } public string[] ToColumns(string name, int i) @@ -2983,7 +2992,7 @@ public virtual SqlString GetSelectByUniqueKeyString(string propertyName) public virtual SqlString GetSelectByUniqueKeyString(string[] suppliedPropertyNames, out IType[] parameterTypes) { var propertyNames = GetUniqueKeyPropertyNames(suppliedPropertyNames); - parameterTypes = propertyNames.Select(GetPropertyType).ToArray(); + parameterTypes = propertyNames.ToArray(p => GetPropertyType(p)); // 6.0 TODO: remove the next if block if (propertyNames.Length == 1) @@ -3014,7 +3023,7 @@ public void BindSelectByUniqueKey( string[] suppliedPropertyNames) { var propertyNames = GetUniqueKeyPropertyNames(suppliedPropertyNames); - var parameterTypes = propertyNames.Select(GetPropertyType).ToArray(); + var parameterTypes = propertyNames.ToArray(p => GetPropertyType(p)); var entity = binder.Entity; for (var i = 0; i < propertyNames.Length; i++) { @@ -3656,16 +3665,21 @@ protected void LogStaticSQL() public virtual string FilterFragment(string alias, IDictionary enabledFilters) { - StringBuilder sessionFilterFragment = new StringBuilder(); + var filterFragment = FilterFragment(alias); + if (!filterHelper.IsAffectedBy(enabledFilters)) + return filterFragment; + var sessionFilterFragment = new StringBuilder(); filterHelper.Render(sessionFilterFragment, GenerateFilterConditionAlias(alias), GetColumnsToTableAliasMap(alias), enabledFilters); - - return sessionFilterFragment.Append(FilterFragment(alias)).ToString(); + return sessionFilterFragment.Append(filterFragment).ToString(); } private IDictionary GetColumnsToTableAliasMap(string rootAlias) { - IDictionary propDictionary = new Dictionary(); + if (SubclassTableSpan < 2) + return CollectionHelper.EmptyDictionary(); + + var propDictionary = new Dictionary(); for (int i =0; i < SubclassPropertyNameClosure.Length; i++) { string property = SubclassPropertyNameClosure[i]; @@ -3678,20 +3692,11 @@ private IDictionary GetColumnsToTableAliasMap(string rootAlias) } } - IDictionary dict = new Dictionary(); + var dict = new Dictionary(); for (int i = 0; i < SubclassColumnTableNumberClosure.Length; i++ ) { - string fullColumn; string col = SubclassColumnClosure[i]; - if (!string.IsNullOrEmpty(rootAlias)) - { - string alias = GenerateTableAlias(rootAlias, SubclassColumnTableNumberClosure[i]); - fullColumn = string.Format("{0}.{1}", alias, col); - } - else - { - fullColumn = col; - } + var fullColumn = GetSubclassAliasedColumn(rootAlias, SubclassColumnTableNumberClosure[i], col); PropertyKey key = new PropertyKey(col, SubclassColumnTableNumberClosure[i]); if (propDictionary.ContainsKey(key)) @@ -3705,6 +3710,15 @@ private IDictionary GetColumnsToTableAliasMap(string rootAlias) } } + //Reversed loop to prefer root columns in case same key names for subclasses + for (int i = SubclassTableSpan - 1; i >= 0; i--) + { + foreach (var key in GetSubclassTableKeyColumns(i)) + { + dict[key] = GetSubclassAliasedColumn(rootAlias, i, key); + } + } + return dict; } @@ -3744,7 +3758,7 @@ public virtual string OneToManyFilterFragment(string alias) public virtual SqlString FromJoinFragment(string alias, bool innerJoin, bool includeSubclasses) { return SubclassTableSpan == 1 - ? new SqlString(string.Empty) // just a performance opt! + ? SqlString.Empty // just a performance opt! : CreateJoin(alias, innerJoin, includeSubclasses).ToFromFragmentString; } @@ -3765,12 +3779,10 @@ private JoinFragment CreateJoin(string name, bool innerjoin, bool includeSubclas int tableSpan = SubclassTableSpan; for (int j = 1; j < tableSpan; j++) //notice that we skip the first table; it is the driving table! { - string[] idCols = StringHelper.Qualify(name, GetJoinIdKeyColumns(j)); //some joins may be to non primary keys - - bool joinIsIncluded = IsClassOrSuperclassTable(j) || - (includeSubclasses && !IsSubclassTableSequentialSelect(j) && !IsSubclassTableLazy(j)); + var joinIsIncluded = IsJoinIncluded(includeSubclasses, j); if (joinIsIncluded) { + string[] idCols = StringHelper.Qualify(name, GetJoinIdKeyColumns(j)); //some joins may be to non primary keys join.AddJoin(GetSubclassTableName(j), GenerateTableAlias(name, j), idCols, @@ -3784,6 +3796,26 @@ private JoinFragment CreateJoin(string name, bool innerjoin, bool includeSubclas return join; } + internal bool HasSubclassJoins(bool includeSubclasses) + { + if (SubclassTableSpan == 1) + return false; + + for (int i = 1; i < SubclassTableSpan; ++i) + { + if (IsJoinIncluded(includeSubclasses, i)) + return true; + } + + return false; + } + + private bool IsJoinIncluded(bool includeSubclasses, int j) + { + return IsClassOrSuperclassTable(j) || + (includeSubclasses && !IsSubclassTableSequentialSelect(j) && !IsSubclassTableLazy(j)); + } + private JoinFragment CreateJoin(int[] tableNumbers, string drivingAlias) { string[] keyCols = StringHelper.Qualify(drivingAlias, GetSubclassTableKeyColumns(tableNumbers[0])); @@ -4143,7 +4175,6 @@ protected internal IEntityTuplizer GetTuplizer(ISessionImplementor session) return EntityTuplizer; } - public virtual bool HasCache { get { return cache != null; } @@ -4323,18 +4354,27 @@ public override string ToString() // 6.0 TODO: Remove (to inline usages) // Since v5.2 - [Obsolete("Use overload taking includeLazyProperties parameter")] + [Obsolete("Use overload taking entityInfo parameter")] public string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns) { return SelectFragment(rhs, rhsAlias, lhsAlias, entitySuffix, collectionSuffix, includeCollectionColumns, false); } + // 6.0 TODO: Remove (to inline usages) + // Since v5.3 + [Obsolete("Use overload taking entityInfo parameter")] public string SelectFragment( IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns, bool includeLazyProperties) { - return SelectFragment(lhsAlias, entitySuffix, includeLazyProperties); + return SelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = includeLazyProperties}); + } + + public string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return IdentifierSelectFragment(lhsAlias, entityInfo.EntitySuffix) + + PropertySelectFragment(lhsAlias, entityInfo.EntitySuffix, entityInfo.LazyProperties, entityInfo.IncludeLazyProps); } public bool IsInstrumented diff --git a/src/NHibernate/Persister/Entity/ILoadable.cs b/src/NHibernate/Persister/Entity/ILoadable.cs index 3b20e1baf33..66308dc02fb 100644 --- a/src/NHibernate/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Persister/Entity/ILoadable.cs @@ -65,7 +65,7 @@ public partial interface ILoadable : IEntityPersister string GetDiscriminatorAlias(string suffix); /// Does the result set contain rowids? - bool HasRowId { get;} + bool HasRowId { get; } /// /// Retrieve property values from one row of a result set diff --git a/src/NHibernate/Persister/Entity/ILockable.cs b/src/NHibernate/Persister/Entity/ILockable.cs index 370ef5d0011..0daed44a868 100644 --- a/src/NHibernate/Persister/Entity/ILockable.cs +++ b/src/NHibernate/Persister/Entity/ILockable.cs @@ -15,18 +15,18 @@ public interface ILockable : IEntityPersister /// /// Locks are always applied to the "root table". /// - string RootTableName { get;} + string RootTableName { get; } /// /// Get the names of columns on the root table used to persist the identifier. /// - string[] RootTableIdentifierColumnNames { get;} + string[] RootTableIdentifierColumnNames { get; } /// /// For versioned entities, get the name of the column (again, expected on the /// root table) used to store the version values. /// - string VersionColumnName { get;} + string VersionColumnName { get; } /// /// Get the SQL alias this persister would use for the root table @@ -42,8 +42,8 @@ public interface ILockable : IEntityPersister /// /// To build the SQL command in pessimistic lock /// - SqlType[] IdAndVersionSqlTypes { get;} + SqlType[] IdAndVersionSqlTypes { get; } #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Persister/Entity/IQueryable.cs b/src/NHibernate/Persister/Entity/IQueryable.cs index a0fe5934e8d..f2de9dd6e52 100644 --- a/src/NHibernate/Persister/Entity/IQueryable.cs +++ b/src/NHibernate/Persister/Entity/IQueryable.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NHibernate.Util; namespace NHibernate.Persister.Entity @@ -16,7 +17,7 @@ internal static class AbstractEntityPersisterExtensions /// Given a query alias and an identifying suffix, render the property select fragment. /// //6.0 TODO: Merge into IQueryable - public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, string[] fetchProperties) + public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, ICollection fetchProperties) { return ReflectHelper.CastOrThrow(query, "individual lazy property fetches") .PropertySelectFragment(alias, suffix, fetchProperties); @@ -55,14 +56,14 @@ public interface IQueryable : ILoadable, IPropertyMapping, IJoinable /// multiple tables? /// /// True if the inheritance hierarchy is spread across multiple tables; false otherwise. - bool IsMultiTable { get;} + bool IsMultiTable { get; } /// /// Get the names of all tables used in the hierarchy (up and down) ordered such /// that deletes in the given order would not cause constraint violations. /// /// The ordered array of table names. - string[] ConstraintOrderedTableNameClosure { get;} + string[] ConstraintOrderedTableNameClosure { get; } /// /// For each table specified in , get @@ -75,24 +76,24 @@ public interface IQueryable : ILoadable, IPropertyMapping, IJoinable /// The second dimension should have the same length across all the elements in /// the first dimension. If not, that'd be a problem ;) /// - string[][] ConstraintOrderedTableKeyColumnClosure { get;} + string[][] ConstraintOrderedTableKeyColumnClosure { get; } /// /// Get the name of the temporary table to be used to (potentially) store id values /// when performing bulk update/deletes. /// /// The appropriate temporary table name. - string TemporaryIdTableName { get;} + string TemporaryIdTableName { get; } /// /// Get the appropriate DDL command for generating the temporary table to /// be used to (potentially) store id values when performing bulk update/deletes. /// /// The appropriate temporary table creation command. - string TemporaryIdTableDDL { get;} + string TemporaryIdTableDDL { get; } /// Is the version property included in insert statements? - bool VersionPropertyInsertable { get;} + bool VersionPropertyInsertable { get; } /// /// Given a query alias and an identifying suffix, render the identifier select fragment. diff --git a/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs b/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs index 743711c55eb..bff0512d40f 100644 --- a/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs +++ b/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace NHibernate.Persister.Entity { // 6.0 TODO: merge into 'IJoinable'. @@ -8,11 +11,31 @@ public interface ISupportSelectModeJoinable /// string IdentifierSelectFragment(string name, string suffix); + //Since 5.3 /// /// All columns to select, when loading. /// + [Obsolete("Please use overload taking EntityLoadInfo")] string SelectFragment( IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string currentCollectionSuffix, bool includeCollectionColumns, bool includeLazyProperties); } + + public sealed class EntityLoadInfo + { + public bool IncludeLazyProps { get; set; } + public string EntitySuffix { get; } + public ISet LazyProperties { get; set; } + + public EntityLoadInfo(string entitySuffix) + { + EntitySuffix = entitySuffix; + } + } + + // 6.0 TODO: merge into 'IJoinable'. + internal interface ISupportLazyPropsJoinable + { + string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo); + } } diff --git a/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs b/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs index 47b0a7c19a7..00b03e88157 100644 --- a/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs @@ -140,8 +140,7 @@ public JoinedSubclassEntityPersister(PersistentClass persistentClass, ICacheConc tables.Add(tabname); var keyCols = new List(idColumnSpan); - var enumerableKCols = new SafetyEnumerable(key.ColumnIterator); - foreach (var kcol in enumerableKCols) + foreach (var kcol in key.ColumnIterator.OfType()) keyCols.Add(kcol.GetQuotedName(factory.Dialect)); keyColumns.Add(keyCols.ToArray()); diff --git a/src/NHibernate/Persister/Entity/NamedQueryLoader.cs b/src/NHibernate/Persister/Entity/NamedQueryLoader.cs index 8f8e6d44be1..5877ccd742d 100644 --- a/src/NHibernate/Persister/Entity/NamedQueryLoader.cs +++ b/src/NHibernate/Persister/Entity/NamedQueryLoader.cs @@ -1,4 +1,3 @@ - using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Loader.Entity; diff --git a/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs b/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs index 346d0dfad2a..c8d2c834cac 100644 --- a/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs @@ -180,7 +180,7 @@ public override Type.IType DiscriminatorType public override string DiscriminatorSQLValue { - get { return discriminatorSQLValue;} + get { return discriminatorSQLValue; } } public override object DiscriminatorValue @@ -393,4 +393,4 @@ public override string GetPropertyTableName(string propertyName) return TableName; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Properties/BackrefPropertyAccessor.cs b/src/NHibernate/Properties/BackrefPropertyAccessor.cs index 9721657eee2..71c94908536 100644 --- a/src/NHibernate/Properties/BackrefPropertyAccessor.cs +++ b/src/NHibernate/Properties/BackrefPropertyAccessor.cs @@ -66,7 +66,6 @@ public MethodInfo Method #endregion } - /// The Getter implementation for id backrefs. [Serializable] private class BackrefGetter : IGetter diff --git a/src/NHibernate/Properties/EmbeddedPropertyAccessor.cs b/src/NHibernate/Properties/EmbeddedPropertyAccessor.cs index 8e22d653bf4..cf88ef06941 100644 --- a/src/NHibernate/Properties/EmbeddedPropertyAccessor.cs +++ b/src/NHibernate/Properties/EmbeddedPropertyAccessor.cs @@ -105,6 +105,5 @@ public override string ToString() return string.Format("EmbeddedSetter({0})", clazz.FullName); } } - } } diff --git a/src/NHibernate/Properties/IPropertyAccessor.cs b/src/NHibernate/Properties/IPropertyAccessor.cs index 224cb5ad487..b54ff794dd4 100644 --- a/src/NHibernate/Properties/IPropertyAccessor.cs +++ b/src/NHibernate/Properties/IPropertyAccessor.cs @@ -39,7 +39,7 @@ public interface IPropertyAccessor /// /// Allow embedded and custom accessors to define if the ReflectionOptimizer can be used. /// - bool CanAccessThroughReflectionOptimizer { get;} + bool CanAccessThroughReflectionOptimizer { get; } #endregion } } diff --git a/src/NHibernate/Properties/PropertyAccessorFactory.cs b/src/NHibernate/Properties/PropertyAccessorFactory.cs index 4a65b5015bf..b703663ac39 100644 --- a/src/NHibernate/Properties/PropertyAccessorFactory.cs +++ b/src/NHibernate/Properties/PropertyAccessorFactory.cs @@ -275,6 +275,5 @@ public static IPropertyAccessor DynamicMapPropertyAccessor { get { return MapAccessor; } } - } } diff --git a/src/NHibernate/PropertyValueException.cs b/src/NHibernate/PropertyValueException.cs index 77c9a739636..c58d57ed75f 100644 --- a/src/NHibernate/PropertyValueException.cs +++ b/src/NHibernate/PropertyValueException.cs @@ -25,7 +25,6 @@ public PropertyValueException(string message, string entityName, string property this.propertyName = propertyName; } - public PropertyValueException(string message, string entityName, string propertyName, Exception innerException) : base(message, innerException) { @@ -33,7 +32,6 @@ public PropertyValueException(string message, string entityName, string property this.propertyName = propertyName; } - public string EntityName { get { return entityName; } diff --git a/src/NHibernate/Proxy/AbstractLazyInitializer.cs b/src/NHibernate/Proxy/AbstractLazyInitializer.cs index ddc12f5398b..5f16a96fc3c 100644 --- a/src/NHibernate/Proxy/AbstractLazyInitializer.cs +++ b/src/NHibernate/Proxy/AbstractLazyInitializer.cs @@ -123,7 +123,7 @@ public object Identifier set { _id = value; } } - public abstract System.Type PersistentClass { get;} + public abstract System.Type PersistentClass { get; } public bool IsUninitialized { diff --git a/src/NHibernate/Proxy/DynamicProxy/DefaultMethodEmitter.cs b/src/NHibernate/Proxy/DynamicProxy/DefaultMethodEmitter.cs index 1562b6f4306..30b5a4d759b 100644 --- a/src/NHibernate/Proxy/DynamicProxy/DefaultMethodEmitter.cs +++ b/src/NHibernate/Proxy/DynamicProxy/DefaultMethodEmitter.cs @@ -68,7 +68,7 @@ public void EmitMethodBody(MethodBuilder proxyMethod, MethodBuilder callbackMeth IL.MarkLabel(skipBaseCall); // Push arguments for InvocationInfo constructor. - IL.Emit(OpCodes.Ldarg_0); // 'this' pointer + IL.Emit(OpCodes.Ldarg_0); // 'this' pointer PushTargetMethodInfo(IL, proxyMethod, method); PushTargetMethodInfo(IL, callbackMethod, callbackMethod); PushStackTrace(IL); diff --git a/src/NHibernate/Proxy/DynamicProxy/ProxyFactory.cs b/src/NHibernate/Proxy/DynamicProxy/ProxyFactory.cs index ad0a79a60c3..3e9c9fd3bec 100644 --- a/src/NHibernate/Proxy/DynamicProxy/ProxyFactory.cs +++ b/src/NHibernate/Proxy/DynamicProxy/ProxyFactory.cs @@ -226,7 +226,6 @@ private static void DefineSerializationConstructor(TypeBuilder typeBuilder, Fiel constructor.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.Managed); - IL.Emit(OpCodes.Ldtoken, typeof (IInterceptor)); IL.Emit(OpCodes.Call, ReflectionCache.TypeMethods.GetTypeFromHandle); IL.Emit(OpCodes.Stloc, interceptorType); diff --git a/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs b/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs index 29bd50d4ef4..314644f121c 100644 --- a/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs +++ b/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs @@ -56,6 +56,11 @@ public static TypeInfo CreateProxyType(System.Type baseType) var name = new AssemblyName(assemblyName); var assemblyBuilder = ProxyBuilderHelper.DefineDynamicAssembly(AppDomain.CurrentDomain, name); + +#if NETFX || NETCOREAPP2_0 + if (!baseType.IsVisible) + ProxyBuilderHelper.GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyBuilder, baseType.Assembly.GetName().Name); +#endif var moduleBuilder = ProxyBuilderHelper.DefineDynamicModule(assemblyBuilder, moduleName); const TypeAttributes typeAttributes = TypeAttributes.AutoClass | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.BeforeFieldInit; diff --git a/src/NHibernate/Proxy/IEntityNotFoundDelegate.cs b/src/NHibernate/Proxy/IEntityNotFoundDelegate.cs index 1bc3eaa3b6c..2c948e26c1c 100644 --- a/src/NHibernate/Proxy/IEntityNotFoundDelegate.cs +++ b/src/NHibernate/Proxy/IEntityNotFoundDelegate.cs @@ -1,5 +1,31 @@ namespace NHibernate.Proxy { + //6.0 TODO Add to IEntityNotFoundDelegate interface + internal static class EntityNotFoundDelegateExtension + { + /// + /// Method to handle the scenario of an entity not found by unique key. + /// + /// + /// The entityName (may be the class fullname) + /// Property name + /// Key + public static void HandleEntityNotFound( + this IEntityNotFoundDelegate interceptor, + string entityName, + string propertyName, + object key) + { + if (interceptor is Impl.SessionFactoryImpl.DefaultEntityNotFoundDelegate x) + { + x.HandleEntityNotFound(entityName, propertyName, key); + return; + } + + new Impl.SessionFactoryImpl.DefaultEntityNotFoundDelegate().HandleEntityNotFound(entityName, propertyName, key); + } + } + /// /// Delegate to handle the scenario of an entity not found by a specified id. /// diff --git a/src/NHibernate/Proxy/ILazyInitializer.cs b/src/NHibernate/Proxy/ILazyInitializer.cs index df7f0a5feb8..8174d961aa8 100644 --- a/src/NHibernate/Proxy/ILazyInitializer.cs +++ b/src/NHibernate/Proxy/ILazyInitializer.cs @@ -20,7 +20,7 @@ public partial interface ILazyInitializer /// /// The entity-name of the entity our owning proxy represents. /// - string EntityName { get;} + string EntityName { get; } /// /// Get the actual class of the entity. Generally, should be used instead. @@ -100,4 +100,4 @@ public partial interface ILazyInitializer /// void UnsetSession(); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Proxy/INHibernateProxy.cs b/src/NHibernate/Proxy/INHibernateProxy.cs index e36ac9c25c0..7248d27b9b7 100644 --- a/src/NHibernate/Proxy/INHibernateProxy.cs +++ b/src/NHibernate/Proxy/INHibernateProxy.cs @@ -18,6 +18,6 @@ namespace NHibernate.Proxy public interface INHibernateProxy { /// Get the underlying lazy initialization handler. - ILazyInitializer HibernateLazyInitializer { get;} + ILazyInitializer HibernateLazyInitializer { get; } } } diff --git a/src/NHibernate/Proxy/Map/MapProxy.cs b/src/NHibernate/Proxy/Map/MapProxy.cs index 78135becd38..daec46ac686 100644 --- a/src/NHibernate/Proxy/Map/MapProxy.cs +++ b/src/NHibernate/Proxy/Map/MapProxy.cs @@ -112,7 +112,7 @@ public void Remove(object key) public object this[object key] { get { return li.Map[key]; } - set { li.Map[key] = value;} + set { li.Map[key] = value; } } public ICollection Keys diff --git a/src/NHibernate/Proxy/NHibernateProxyBuilder.cs b/src/NHibernate/Proxy/NHibernateProxyBuilder.cs index 607e548c4f0..96c530f940c 100644 --- a/src/NHibernate/Proxy/NHibernateProxyBuilder.cs +++ b/src/NHibernate/Proxy/NHibernateProxyBuilder.cs @@ -78,9 +78,10 @@ public TypeInfo CreateProxyType(System.Type baseType, IReadOnlyCollection !i.IsVisible) - .Select(i => i.Assembly.GetName().Name) - .Distinct(); + new[] {baseType} + .Concat(interfaces).Where(i => !i.IsVisible) + .Select(i => i.Assembly.GetName().Name) + .Distinct(); foreach (var a in assemblyNamesToIgnoreAccessCheck) ProxyBuilderHelper.GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyBuilder, a); #else diff --git a/src/NHibernate/Proxy/NHibernateProxyHelper.cs b/src/NHibernate/Proxy/NHibernateProxyHelper.cs index 69ffec283c3..ce1f27f6168 100644 --- a/src/NHibernate/Proxy/NHibernateProxyHelper.cs +++ b/src/NHibernate/Proxy/NHibernateProxyHelper.cs @@ -64,4 +64,3 @@ public static bool IsProxy(this object entity) } } } - diff --git a/src/NHibernate/Proxy/Poco/BasicLazyInitializer.cs b/src/NHibernate/Proxy/Poco/BasicLazyInitializer.cs index 8a6a50f892b..ee698fad583 100644 --- a/src/NHibernate/Proxy/Poco/BasicLazyInitializer.cs +++ b/src/NHibernate/Proxy/Poco/BasicLazyInitializer.cs @@ -14,8 +14,6 @@ namespace NHibernate.Proxy.Poco [Obsolete("DynamicProxy has been obsoleted, use static proxies instead (see StaticProxyFactory)")] public abstract class BasicLazyInitializer : AbstractLazyInitializer { - private static readonly IEqualityComparer IdentityEqualityComparer = new IdentityEqualityComparer(); - internal System.Type persistentClass; protected internal MethodInfo getIdentifierMethod; protected internal MethodInfo setIdentifierMethod; @@ -73,7 +71,7 @@ public virtual object Invoke(MethodInfo method, object[] args, object proxy) { if (!overridesEquals && methodName == "GetHashCode") { - return IdentityEqualityComparer.GetHashCode(proxy); + return ReferenceComparer.Instance.GetHashCode(proxy); } else if (IsEqualToIdentifierMethod(method)) { @@ -92,7 +90,7 @@ public virtual object Invoke(MethodInfo method, object[] args, object proxy) { if (!overridesEquals && methodName == "Equals") { - return IdentityEqualityComparer.Equals(args[0], proxy); + return ReferenceComparer.Instance.Equals(args[0], proxy); } else if (setIdentifierMethod!=null&&method.Equals(setIdentifierMethod)) { diff --git a/src/NHibernate/Proxy/ProxyBuilderHelper.cs b/src/NHibernate/Proxy/ProxyBuilderHelper.cs index 94af19db95d..a9868383878 100644 --- a/src/NHibernate/Proxy/ProxyBuilderHelper.cs +++ b/src/NHibernate/Proxy/ProxyBuilderHelper.cs @@ -14,6 +14,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security; +using NHibernate.Proxy.DynamicProxy; using NHibernate.Util; namespace NHibernate.Proxy @@ -101,11 +102,19 @@ internal static IEnumerable GetProxiableMethods(System.Type type) internal static IEnumerable GetProxiableMethods(System.Type type, IEnumerable interfaces) { - var proxiableMethods = - GetProxiableMethods(type) + if (type.IsInterface || type == typeof(object) || type.GetInterfaces().Length == 0) + { + return GetProxiableMethods(type) .Concat(interfaces.SelectMany(i => i.GetMethods())) .Distinct(); - + } + + var proxiableMethods = new HashSet(GetProxiableMethods(type), new MethodInfoComparer(type)); + foreach (var interfaceMethod in interfaces.SelectMany(i => i.GetMethods())) + { + proxiableMethods.Add(interfaceMethod); + } + return proxiableMethods; } @@ -136,23 +145,43 @@ internal static MethodBuilder GetObjectDataMethodBuilder(TypeBuilder typeBuilder internal static MethodBuilder GenerateMethodSignature(string name, MethodInfo method, TypeBuilder typeBuilder) { - //TODO: Should we use attributes of base method? - var methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual; + var explicitImplementation = method.DeclaringType.IsInterface; + if (explicitImplementation && + (typeBuilder.BaseType == typeof(object) || +#pragma warning disable 618 + typeBuilder.BaseType == typeof(ProxyDummy)) && +#pragma warning restore 618 + (IsEquals(method) || IsGetHashCode(method))) + { + // If we are building a proxy for an interface, and it defines an Equals or GetHashCode, they must + // be implicitly implemented for overriding object methods. + // (Ideally we should check the method actually comes from the interface declared for the proxy.) + explicitImplementation = false; + } + + var methodAttributes = explicitImplementation + ? MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | + MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual + : MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual; if (method.IsSpecialName) methodAttributes |= MethodAttributes.SpecialName; var parameters = method.GetParameters(); - - var methodBuilder = typeBuilder.DefineMethod( - name, - methodAttributes, - CallingConventions.HasThis, - method.ReturnType, - parameters.Select(param => param.ParameterType).ToArray()); + var implementationName = explicitImplementation + ? $"{method.DeclaringType.FullName}.{name}" + : name; + var methodBuilder = + typeBuilder.DefineMethod( + implementationName, + methodAttributes, + CallingConventions.HasThis, + method.ReturnType, + parameters.ToArray(param => param.ParameterType)); + if (explicitImplementation) + methodBuilder.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.IL); var typeArgs = method.GetGenericArguments(); - if (typeArgs.Length > 0) { var typeNames = GenerateTypeNames(typeArgs.Length); @@ -169,8 +198,7 @@ internal static MethodBuilder GenerateMethodSignature(string name, MethodInfo me // Copy generic parameter constraints (class and interfaces). var typeConstraints = typeArg.GetGenericParameterConstraints() - .Select(x => ResolveTypeConstraint(method, x)) - .ToArray(); + .ToArray(x => ResolveTypeConstraint(method, x)); var baseTypeConstraint = typeConstraints.SingleOrDefault(x => x.IsClass); typeArgBuilder.SetBaseTypeConstraint(baseTypeConstraint); @@ -183,6 +211,18 @@ internal static MethodBuilder GenerateMethodSignature(string name, MethodInfo me return methodBuilder; } + private static bool IsGetHashCode(MethodBase method) + { + return method.Name == "GetHashCode" && method.GetParameters().Length == 0; + } + + private static bool IsEquals(MethodBase method) + { + if (method.Name != "Equals") return false; + var parameters = method.GetParameters(); + return parameters.Length == 1 && parameters[0].ParameterType == typeof(object); + } + internal static void GenerateInstanceOfIgnoresAccessChecksToAttribute( AssemblyBuilder assemblyBuilder, string assemblyName) @@ -254,5 +294,72 @@ private static string[] GenerateTypeNames(int count) return result; } + + /// + /// Method equality for the proxy building purpose: we want to equate an interface method to a base type + /// method which implements it. This implies the base type method has the same signature and there is no + /// explicit implementation of the interface method in the base type. + /// + private class MethodInfoComparer : IEqualityComparer + { + private readonly Dictionary> _interfacesMap; + + public MethodInfoComparer(System.Type baseType) + { + _interfacesMap = BuildInterfacesMap(baseType); + } + + private static Dictionary> BuildInterfacesMap(System.Type type) + { + return type.GetInterfaces() + .Distinct() + .ToDictionary(i => i, i => ToDictionary(type.GetInterfaceMap(i))); + } + + private static Dictionary ToDictionary(InterfaceMapping interfaceMap) + { + var map = new Dictionary(interfaceMap.InterfaceMethods.Length); + for (var i = 0; i < interfaceMap.InterfaceMethods.Length; i++) + { + map.Add(interfaceMap.InterfaceMethods[i], interfaceMap.TargetMethods[i]); + } + + return map; + } + + public bool Equals(MethodInfo x, MethodInfo y) + { + if (x == y) + return true; + if (x == null || y == null) + return false; + if (x.Name != y.Name) + return false; + + // If they have the same declaring type, one cannot be the implementation of the other. + if (x.DeclaringType == y.DeclaringType) + return false; + // If they belong to two different interfaces or to two different concrete types, one cannot be the + // implementation of the other. + if (x.DeclaringType.IsInterface == y.DeclaringType.IsInterface) + return false; + + var interfaceMethod = x.DeclaringType.IsInterface ? x : y; + // If the interface is not implemented by the base type, the method cannot be implemented by the + // base type method. + if (!_interfacesMap.TryGetValue(interfaceMethod.DeclaringType, out var map)) + return false; + + var baseMethod = x.DeclaringType.IsInterface ? y : x; + return map[interfaceMethod] == baseMethod; + } + + public int GetHashCode(MethodInfo obj) + { + // Hashing by name only, putting methods with the same name in the same bucket, in order to keep + // this method fast. + return obj.Name.GetHashCode(); + } + } } } diff --git a/src/NHibernate/QueryOverExtensions.cs b/src/NHibernate/QueryOverExtensions.cs new file mode 100644 index 00000000000..ef0ab4f7673 --- /dev/null +++ b/src/NHibernate/QueryOverExtensions.cs @@ -0,0 +1,55 @@ +namespace NHibernate +{ + // 6.0 TODO: consider moving other criteria delegated methods to extension methods. + // It may allow better return typing for chaining, and it is slightly less code. + public static class QueryOverExtensions + { + /// + /// Set a timeout for the underlying ADO.NET query. + /// + /// The query on which to set the timeout. + /// The timeout in seconds. + /// (for method chaining). + public static TQueryOver SetTimeout(this TQueryOver queryOver, int timeout) where TQueryOver: IQueryOver + { + queryOver.RootCriteria.SetTimeout(timeout); + return queryOver; + } + + /// + /// Set a fetch size for the underlying ADO query. + /// + /// The query on which to set the timeout. + /// The fetch size. + /// (for method chaining). + public static TQueryOver SetFetchSize(this TQueryOver queryOver, int fetchSize) where TQueryOver: IQueryOver + { + queryOver.RootCriteria.SetFetchSize(fetchSize); + return queryOver; + } + + /// + /// Add a comment to the generated SQL. + /// + /// The query on which to set the timeout. + /// A human-readable string. + /// (for method chaining). + public static TQueryOver SetComment(this TQueryOver queryOver, string comment) where TQueryOver: IQueryOver + { + queryOver.RootCriteria.SetComment(comment); + return queryOver; + } + + /// + /// Override the current session flush mode, just for this query. + /// + /// The query on which to set the flush mode. + /// The flush mode to use for the query. + /// (for method chaining). + public static TQueryOver SetFlushMode(this TQueryOver queryOver, FlushMode flushMode) where TQueryOver: IQueryOver + { + queryOver.RootCriteria.SetFlushMode(flushMode); + return queryOver; + } + } +} diff --git a/src/NHibernate/SelectMode.cs b/src/NHibernate/SelectMode.cs index fc29874f217..b023e758829 100644 --- a/src/NHibernate/SelectMode.cs +++ b/src/NHibernate/SelectMode.cs @@ -33,8 +33,15 @@ public enum SelectMode JoinOnly, /// - /// Skips fetching for eagerly mapped association (no-op for lazy association). + /// Skips fetching for fetch="join" association (no-op for lazy association). /// Skip, + + /// + /// Fetch lazy property group. + /// Provide path to lazy property and it will be fetched along with properties that belong to the same fetch group (lazy-group) + /// Note: To fetch single property it must be mapped with unique fetch group (lazy-group) + /// + FetchLazyPropertyGroup, } } diff --git a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs index aa9421fa08e..a0c17158cb8 100644 --- a/src/NHibernate/SqlCommand/ANSIJoinFragment.cs +++ b/src/NHibernate/SqlCommand/ANSIJoinFragment.cs @@ -18,27 +18,16 @@ public override void AddJoin(string tableName, string alias, string[] fkColumns, public override void AddJoin(string tableName, string alias, string[] fkColumns, string[] pkColumns, JoinType joinType, SqlString on) { - string joinString; - switch (joinType) + var joinString = GetJoinString(joinType); + + _fromFragment.Add(joinString).Add(tableName).Add(" ").Add(alias).Add(" "); + if (joinType == JoinType.CrossJoin) { - case JoinType.InnerJoin: - joinString = " inner join "; - break; - case JoinType.LeftOuterJoin: - joinString = " left outer join "; - break; - case JoinType.RightOuterJoin: - joinString = " right outer join "; - break; - case JoinType.FullJoin: - joinString = " full outer join "; - break; - default: - throw new AssertionFailure("undefined join type"); + // Cross join does not have an 'on' statement + return; } - _fromFragment.Add(joinString + tableName + ' ' + alias + " on "); - + _fromFragment.Add("on "); if (fkColumns.Length == 0) { AddBareCondition(_fromFragment, on); @@ -57,6 +46,25 @@ public override void AddJoin(string tableName, string alias, string[] fkColumns, AddCondition(_fromFragment, on); } + internal static string GetJoinString(JoinType joinType) + { + switch (joinType) + { + case JoinType.InnerJoin: + return " inner join "; + case JoinType.LeftOuterJoin: + return " left outer join "; + case JoinType.RightOuterJoin: + return " right outer join "; + case JoinType.FullJoin: + return " full outer join "; + case JoinType.CrossJoin: + return " cross join "; + default: + throw new AssertionFailure("undefined join type"); + } + } + public override SqlString ToFromFragmentString { get { return _fromFragment.ToSqlString(); } diff --git a/src/NHibernate/SqlCommand/Alias.cs b/src/NHibernate/SqlCommand/Alias.cs index 6d1baca4912..ab623967c11 100644 --- a/src/NHibernate/SqlCommand/Alias.cs +++ b/src/NHibernate/SqlCommand/Alias.cs @@ -175,7 +175,6 @@ public string[] ToUnquotedAliasStrings(string[] sqlIdentifiers, Dialect.Dialect return aliases; } - /// /// /// @@ -193,4 +192,4 @@ public string[] ToAliasStrings(string[] sqlIdentifiers, Dialect.Dialect dialect) return aliases; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/SqlCommand/DisjunctionFragment.cs b/src/NHibernate/SqlCommand/DisjunctionFragment.cs index 7372b11500f..55cefa41bbf 100644 --- a/src/NHibernate/SqlCommand/DisjunctionFragment.cs +++ b/src/NHibernate/SqlCommand/DisjunctionFragment.cs @@ -11,14 +11,12 @@ public DisjunctionFragment() { } - public DisjunctionFragment(IEnumerable fragments) { foreach (var conditionalFragment in fragments) AddCondition(conditionalFragment); } - public DisjunctionFragment AddCondition(ConditionalFragment fragment) { if (buffer.Count > 0) @@ -38,4 +36,4 @@ public SqlString ToFragmentString() return buffer.ToSqlString(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/SqlCommand/InsertSelect.cs b/src/NHibernate/SqlCommand/InsertSelect.cs index 1ee65da3185..dc2f6b285f6 100644 --- a/src/NHibernate/SqlCommand/InsertSelect.cs +++ b/src/NHibernate/SqlCommand/InsertSelect.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; - namespace NHibernate.SqlCommand { public class InsertSelect : ISqlStringBuilder @@ -74,4 +73,4 @@ public SqlString ToSqlString() return buf.ToSqlString(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/SqlCommand/JoinFragment.cs b/src/NHibernate/SqlCommand/JoinFragment.cs index 18b249bd57f..38c5d8ce280 100644 --- a/src/NHibernate/SqlCommand/JoinFragment.cs +++ b/src/NHibernate/SqlCommand/JoinFragment.cs @@ -10,7 +10,8 @@ public enum JoinType InnerJoin = 0, FullJoin = 4, LeftOuterJoin = 1, - RightOuterJoin = 2 + RightOuterJoin = 2, + CrossJoin = 8 } /// diff --git a/src/NHibernate/SqlCommand/Parser/MsSqlSelectParser.cs b/src/NHibernate/SqlCommand/Parser/MsSqlSelectParser.cs index ced57273b9c..a3c15820882 100644 --- a/src/NHibernate/SqlCommand/Parser/MsSqlSelectParser.cs +++ b/src/NHibernate/SqlCommand/Parser/MsSqlSelectParser.cs @@ -55,7 +55,7 @@ public MsSqlSelectParser(SqlString sql) public SqlString Sql { get; } public int SelectIndex { get; } public int FromIndex { get; } - public int OrderByIndex { get;} + public int OrderByIndex { get; } public bool IsDistinct { get; } /// diff --git a/src/NHibernate/SqlCommand/QueryJoinFragment.cs b/src/NHibernate/SqlCommand/QueryJoinFragment.cs index 73eb8d61563..5a29fff4d02 100644 --- a/src/NHibernate/SqlCommand/QueryJoinFragment.cs +++ b/src/NHibernate/SqlCommand/QueryJoinFragment.cs @@ -101,7 +101,6 @@ public override bool AddCondition(string condition) return false; } - public override bool AddCondition(SqlString condition) { //TODO: this seems hackish diff --git a/src/NHibernate/SqlCommand/SelectFragment.cs b/src/NHibernate/SqlCommand/SelectFragment.cs index c07e34199d3..dfe8de0f059 100644 --- a/src/NHibernate/SqlCommand/SelectFragment.cs +++ b/src/NHibernate/SqlCommand/SelectFragment.cs @@ -12,8 +12,8 @@ namespace NHibernate.SqlCommand public class SelectFragment { private string suffix; - private IList columns = new List(); - private IList columnAliases = new List(); + private readonly List columns = new List(); + private readonly List columnAliases = new List(); private Dialect.Dialect dialect; private string[] usedAliases; private string extraSelectList; diff --git a/src/NHibernate/SqlCommand/SqlBaseBuilder.cs b/src/NHibernate/SqlCommand/SqlBaseBuilder.cs index ed43a8f6fb4..5fc68023c49 100644 --- a/src/NHibernate/SqlCommand/SqlBaseBuilder.cs +++ b/src/NHibernate/SqlCommand/SqlBaseBuilder.cs @@ -76,7 +76,7 @@ protected SqlString ToWhereString(string tableAlias, string[] columnNames, strin for (int i = 0; i < columnNames.Length; i++) { - if (string.IsNullOrEmpty(columnNames[i])) continue;// prevent empty column name + if (string.IsNullOrEmpty(columnNames[i])) continue; // prevent empty column name if (andNeeded) { @@ -108,6 +108,5 @@ protected SqlString ToWhereString(string columnName, string op) if (string.IsNullOrEmpty(columnName)) return null; return new SqlString(columnName, op, Parameter.Placeholder); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/SqlCommand/SqlInsertBuilder.cs b/src/NHibernate/SqlCommand/SqlInsertBuilder.cs index e2d1a8a3f4b..d73f98fd4d5 100644 --- a/src/NHibernate/SqlCommand/SqlInsertBuilder.cs +++ b/src/NHibernate/SqlCommand/SqlInsertBuilder.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; - +using System.Linq; using NHibernate.Engine; using NHibernate.SqlTypes; using NHibernate.Type; @@ -29,7 +28,7 @@ public SqlInsertBuilder(ISessionFactoryImplementor factory) protected internal Dialect.Dialect Dialect { - get{return factory.Dialect;} + get { return factory.Dialect; } } public virtual SqlInsertBuilder SetComment(string comment) @@ -72,7 +71,6 @@ public SqlInsertBuilder AddColumn(string columnName, object val, ILiteralType li return AddColumn(columnName, literalType.ObjectToSQLString(val, Dialect)); } - /// /// Add a column with a specific value to the INSERT sql /// @@ -221,7 +219,7 @@ public SqlCommandInfo ToSqlCommandInfo() public SqlType[] GetParametersTypeArray() { - return (new List(new SafetyEnumerable(columns.Values))).ToArray(); + return columns.Values.OfType().ToArray(); } } } diff --git a/src/NHibernate/SqlCommand/SqlSelectBuilder.cs b/src/NHibernate/SqlCommand/SqlSelectBuilder.cs index e287fb8b5c4..1859ea28263 100644 --- a/src/NHibernate/SqlCommand/SqlSelectBuilder.cs +++ b/src/NHibernate/SqlCommand/SqlSelectBuilder.cs @@ -1,4 +1,3 @@ - using System; using NHibernate.Engine; using NHibernate.Type; diff --git a/src/NHibernate/SqlCommand/SqlSimpleSelectBuilder.cs b/src/NHibernate/SqlCommand/SqlSimpleSelectBuilder.cs index b10666bc882..ceaf0907a58 100644 --- a/src/NHibernate/SqlCommand/SqlSimpleSelectBuilder.cs +++ b/src/NHibernate/SqlCommand/SqlSimpleSelectBuilder.cs @@ -13,8 +13,8 @@ public class SqlSimpleSelectBuilder : SqlBaseBuilder, ISqlStringBuilder { private string tableName; - private readonly IList columnNames = new List(); - private readonly IDictionary aliases = new Dictionary(); //key=column Name, value=column Alias + private readonly List columnNames = new List(); + private readonly Dictionary aliases = new Dictionary(); //key=column Name, value=column Alias private LockMode lockMode = LockMode.Read; private string comment; @@ -39,7 +39,6 @@ public SqlSimpleSelectBuilder SetTableName(string tableName) return this; } - /// /// Adds a columnName to the SELECT fragment. /// @@ -230,7 +229,6 @@ public SqlString ToSqlString() commaNeeded = true; } - sqlBuilder.Add(" FROM ") .Add(Dialect.AppendLockHint(lockMode, tableName)); diff --git a/src/NHibernate/SqlCommand/SqlString.cs b/src/NHibernate/SqlCommand/SqlString.cs index 71870969fa5..793453a5d6a 100644 --- a/src/NHibernate/SqlCommand/SqlString.cs +++ b/src/NHibernate/SqlCommand/SqlString.cs @@ -199,13 +199,13 @@ public SqlString(params object[] parts) : this((IEnumerable)parts) { } - private SqlString(IEnumerable parts) + internal SqlString(IEnumerable parts) { _parts = new List(); _parameters = new SortedList(); var sqlIndex = 0; - var pendingContent = new StringBuilder(); // Collect adjoining string parts (the compaction). + var pendingContent = new StringBuilder(); // Collect adjoining string parts (the compaction). foreach (var part in parts) { Add(part, pendingContent, ref sqlIndex); @@ -347,7 +347,23 @@ public SqlString Append(string text) { if (string.IsNullOrEmpty(text)) return this; if (_length == 0) return new SqlString(text); - return new SqlString(new object[] { this, text }); + return new SqlString(this, text); + } + + public SqlString Append(params object[] parts) + { + return _length == 0 + ? new SqlString(parts) + : new SqlString(GetAppendParts(parts)); + } + + private IEnumerable GetAppendParts(object[] parts) + { + yield return this; + foreach (var part in parts) + { + yield return part; + } } /// @@ -409,6 +425,11 @@ internal int IndexOfOrdinal(string text) return IndexOf(text, 0, _length, StringComparison.Ordinal); } + internal bool Contains(string text) + { + return IndexOfOrdinal(text) >= 0; + } + /// /// Returns the index of the first occurrence of , case-insensitive. /// @@ -1008,6 +1029,22 @@ public SqlString GetSubselectString() return new SubselectClauseExtractor(this).GetSqlString(); } + internal void SubstituteBogusParameters(IReadOnlyList actualParams, Parameter bogusParam) + { + int index = 0; + var keys = _parameters.Keys; + // The loop below is technically not altering the keys collection on which we iterate, but + // the underlying implementation still throws on foreach iterations over keys even if we + // have only changed the associated value. + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < keys.Count; i++) + { + var key = keys[i]; + if (ReferenceEquals(_parameters[key], bogusParam)) + _parameters[key] = actualParams[index++]; + } + } + [Serializable] private struct Part : IEquatable { diff --git a/src/NHibernate/SqlCommand/SqlStringBuilder.cs b/src/NHibernate/SqlCommand/SqlStringBuilder.cs index 87b1f03cad9..1692cf24019 100644 --- a/src/NHibernate/SqlCommand/SqlStringBuilder.cs +++ b/src/NHibernate/SqlCommand/SqlStringBuilder.cs @@ -164,7 +164,6 @@ public SqlStringBuilder Add(SqlString sqlString) return this; } - /// /// Adds an existing SqlString to this SqlStringBuilder /// @@ -312,7 +311,7 @@ public SqlStringBuilder RemoveAt(int index) /// The SqlString that was built. public SqlString ToSqlString() { - return new SqlString(sqlParts.ToArray()); + return new SqlString(sqlParts); } public override string ToString() diff --git a/src/NHibernate/SqlCommand/SqlStringHelper.cs b/src/NHibernate/SqlCommand/SqlStringHelper.cs index 591a4931207..4530881e259 100644 --- a/src/NHibernate/SqlCommand/SqlStringHelper.cs +++ b/src/NHibernate/SqlCommand/SqlStringHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,7 +11,6 @@ namespace NHibernate.SqlCommand /// public static class SqlStringHelper { - public static SqlString Join(SqlString separator, IEnumerable objects) { SqlStringBuilder buf = new SqlStringBuilder(); @@ -30,6 +30,45 @@ public static SqlString Join(SqlString separator, IEnumerable objects) return buf.ToSqlString(); } + internal static SqlString JoinParts(object separator, IList parts) + { + if (parts.Count == 0) + return SqlString.Empty; + + if (parts.Count == 1) + return parts[0] is SqlString sqlstring + ? sqlstring + : new SqlString(parts); + + var buf = new SqlStringBuilder(); + + buf.AddObject(parts[0]); + for (var index = 1; index < parts.Count; index++) + { + buf.AddObject(separator).AddObject(parts[index]); + } + + return buf.ToSqlString(); + } + + internal static SqlString Join(string separator, IList strings) + { + if (strings.Count == 0) + return SqlString.Empty; + + if (strings.Count == 1) + return strings[0]; + + var buf = new SqlStringBuilder(); + + buf.Add(strings[0]); + for (var index = 1; index < strings.Count; index++) + { + buf.Add(separator).Add(strings[index]); + } + + return buf.ToSqlString(); + } public static SqlString[] Add(SqlString[] x, string sep, SqlString[] y) { @@ -41,7 +80,12 @@ public static SqlString[] Add(SqlString[] x, string sep, SqlString[] y) return result; } - + /// + /// Removes the as someColumnAlias clause from a SqlString representing a column expression. + /// Consider using CriterionUtil.GetColumn... methods instead. + /// + /// The SqlString representing a column expression which might be aliased. + /// if it was not aliased, otherwise an un-aliased SqlString representing the column. public static SqlString RemoveAsAliasesFromSql(SqlString sql) { int index = sql.LastIndexOfCaseInsensitive(" as "); @@ -49,16 +93,75 @@ public static SqlString RemoveAsAliasesFromSql(SqlString sql) return sql.Substring(0, index); } - public static bool IsNotEmpty(SqlString str) { return !IsEmpty(str); } - public static bool IsEmpty(SqlString str) { return str == null || str.Count == 0; } + + internal static SqlString ParametersList(List parameters) + { + var parametersCount = parameters.Count; + if (parametersCount == 0) + { + return SqlString.Empty; + } + + if (parametersCount == 1) + { + return new SqlString(parameters[0]); + } + + var builder = new SqlStringBuilder(); + builder.Add("("); + + builder.Add(parameters[0]); + + for (var index = 1; index < parametersCount; index++) + { + builder.Add(", "); + builder.Add(parameters[index]); + } + + builder.Add(")"); + + return builder.ToSqlString(); + } + + internal static SqlString Repeat(SqlString placeholder, int count, string separator, bool wrapInParens) + { + if (count == 0) + return SqlString.Empty; + + if (count == 1) + return wrapInParens + ? new SqlString("(", placeholder, ")") + : placeholder; + + var builder = new SqlStringBuilder((placeholder.Count + 1) * count + 1); + + if (wrapInParens) + { + builder.Add("("); + } + + builder.Add(placeholder); + + for (int i = 1; i < count; i++) + { + builder.Add(separator).Add(placeholder); + } + + if (wrapInParens) + { + builder.Add(")"); + } + + return builder.ToSqlString(); + } } } diff --git a/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs b/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs index def92f8fe50..c9b7ccd1352 100644 --- a/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs +++ b/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs @@ -52,7 +52,6 @@ public SqlUpdateBuilder AddColumn(string columnName, object val, ILiteralType li return AddColumn(columnName, literalType.ObjectToSQLString(val, Dialect)); } - /// /// Add a column with a specific value to the UPDATE sql /// @@ -134,7 +133,7 @@ private void AddColumnWithValueOrType(string columnName, object valueOrType) public SqlUpdateBuilder AppendAssignmentFragment(SqlString fragment) { // SqlString is immutable - assignments = assignments == null ? fragment : assignments.Append(", ").Append(fragment); + assignments = assignments == null ? fragment : assignments.Append(", ", fragment); return this; } @@ -301,7 +300,6 @@ public SqlString ToSqlString() sqlBuilder.Add(StringHelper.CommaSpace); commaNeeded = true; - sqlBuilder.Add(valuePair.Key) .Add(" = "); @@ -321,7 +319,6 @@ public SqlString ToSqlString() sqlBuilder.Add(assignments); } - sqlBuilder.Add(" WHERE "); bool andNeeded = false; foreach (SqlString whereString in whereStrings) @@ -359,7 +356,8 @@ public SqlString ToSqlString() public SqlCommandInfo ToSqlCommandInfo() { SqlString text = ToSqlString(); - List parameterTypes = new List(new SafetyEnumerable(columns.Values)); + + var parameterTypes = columns.Values.OfType().ToList(); parameterTypes.AddRange(whereParameterTypes); return new SqlCommandInfo(text, parameterTypes.ToArray()); } diff --git a/src/NHibernate/SqlTypes/SqlType.cs b/src/NHibernate/SqlTypes/SqlType.cs index c32c377723c..29523e9122e 100644 --- a/src/NHibernate/SqlTypes/SqlType.cs +++ b/src/NHibernate/SqlTypes/SqlType.cs @@ -21,7 +21,7 @@ namespace NHibernate.SqlTypes ///

/// [Serializable] - public class SqlType + public class SqlType : IEquatable { private readonly DbType dbType; private readonly int length; @@ -125,24 +125,34 @@ public override bool Equals(object obj) public bool Equals(SqlType rhsSqlType) { + if (ReferenceEquals(this, rhsSqlType)) + return true; + if (rhsSqlType == null) - { return false; - } - if (LengthDefined) - { - return (DbType.Equals(rhsSqlType.DbType)) && (Length == rhsSqlType.Length); - } - if (PrecisionDefined) - { - return (DbType.Equals(rhsSqlType.DbType)) && (Precision == rhsSqlType.Precision) && (Scale == rhsSqlType.Scale); - } - if (ScaleDefined) - { - return DbType.Equals(rhsSqlType.DbType) && Scale == rhsSqlType.Scale; - } - return (DbType.Equals(rhsSqlType.DbType)); + if (DbType != rhsSqlType.DbType) + return false; + + if (LengthDefined != rhsSqlType.LengthDefined) + return false; + + if (PrecisionDefined != rhsSqlType.PrecisionDefined) + return false; + + if (ScaleDefined != rhsSqlType.ScaleDefined) + return false; + + if (Length != rhsSqlType.Length) + return false; + + if (Precision != rhsSqlType.Precision) + return false; + + if (Scale != rhsSqlType.Scale) + return false; + + return true; } public override string ToString() diff --git a/src/NHibernate/Stat/CollectionStatistics.cs b/src/NHibernate/Stat/CollectionStatistics.cs index d46dff86117..df8f7df697d 100644 --- a/src/NHibernate/Stat/CollectionStatistics.cs +++ b/src/NHibernate/Stat/CollectionStatistics.cs @@ -50,7 +50,6 @@ public override string ToString() .Append(",removeCount=").Append(removeCount) .Append(",updateCount=").Append(updateCount) .Append(']').ToString(); - } } } diff --git a/src/NHibernate/Stat/ISessionStatistics.cs b/src/NHibernate/Stat/ISessionStatistics.cs index bf8c28fc2ff..c30065ad0f8 100644 --- a/src/NHibernate/Stat/ISessionStatistics.cs +++ b/src/NHibernate/Stat/ISessionStatistics.cs @@ -9,15 +9,15 @@ namespace NHibernate.Stat public interface ISessionStatistics { /// Get the number of entity instances associated with the session - int EntityCount { get;} + int EntityCount { get; } /// Get the number of collection instances associated with the session - int CollectionCount { get;} + int CollectionCount { get; } /// Get the set of all EntityKeys. - IList EntityKeys { get;} + IList EntityKeys { get; } /// Get the set of all CollectionKeys. - IList CollectionKeys { get;} + IList CollectionKeys { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Stat/IStatistics.cs b/src/NHibernate/Stat/IStatistics.cs index 480afb54fff..c0bb7e870d9 100644 --- a/src/NHibernate/Stat/IStatistics.cs +++ b/src/NHibernate/Stat/IStatistics.cs @@ -10,110 +10,110 @@ namespace NHibernate.Stat public interface IStatistics { /// Global number of entity deletes - long EntityDeleteCount { get;} + long EntityDeleteCount { get; } /// Global number of entity inserts - long EntityInsertCount { get;} + long EntityInsertCount { get; } /// Global number of entity loads - long EntityLoadCount { get;} + long EntityLoadCount { get; } /// Global number of entity fetchs - long EntityFetchCount { get;} + long EntityFetchCount { get; } /// Global number of entity updates - long EntityUpdateCount { get;} + long EntityUpdateCount { get; } /// Global number of executed queries - long QueryExecutionCount { get;} + long QueryExecutionCount { get; } /// The of the slowest query. TimeSpan QueryExecutionMaxTime { get; } /// The query string for the slowest query. - string QueryExecutionMaxTimeQueryString { get;} + string QueryExecutionMaxTimeQueryString { get; } /// The global number of cached queries successfully retrieved from cache - long QueryCacheHitCount { get;} + long QueryCacheHitCount { get; } /// The global number of cached queries *not* found in cache - long QueryCacheMissCount { get;} + long QueryCacheMissCount { get; } /// The global number of cacheable queries put in cache - long QueryCachePutCount { get;} + long QueryCachePutCount { get; } /// Get the global number of flush executed by sessions (either implicit or explicit) - long FlushCount { get;} + long FlushCount { get; } /// /// Get the global number of connections asked by the sessions /// (the actual number of connections used may be much smaller depending /// whether you use a connection pool or not) /// - long ConnectCount { get;} + long ConnectCount { get; } /// Global number of cacheable entities/collections successfully retrieved from the cache - long SecondLevelCacheHitCount { get;} + long SecondLevelCacheHitCount { get; } /// Global number of cacheable entities/collections not found in the cache and loaded from the database. - long SecondLevelCacheMissCount { get;} + long SecondLevelCacheMissCount { get; } /// Global number of cacheable entities/collections put in the cache - long SecondLevelCachePutCount { get;} + long SecondLevelCachePutCount { get; } /// Global number of sessions closed - long SessionCloseCount { get;} + long SessionCloseCount { get; } /// Global number of sessions opened - long SessionOpenCount { get;} + long SessionOpenCount { get; } /// Global number of collections loaded - long CollectionLoadCount { get;} + long CollectionLoadCount { get; } /// Global number of collections fetched - long CollectionFetchCount { get;} + long CollectionFetchCount { get; } /// Global number of collections updated - long CollectionUpdateCount { get;} + long CollectionUpdateCount { get; } /// Global number of collections removed - long CollectionRemoveCount { get;} + long CollectionRemoveCount { get; } /// Global number of collections recreated - long CollectionRecreateCount { get;} + long CollectionRecreateCount { get; } /// Start time - DateTime StartTime { get;} + DateTime StartTime { get; } /// Enable/Disable statistics logs (this is a dynamic parameter) - bool IsStatisticsEnabled { get;set;} + bool IsStatisticsEnabled { get; set; } /// All executed query strings - string[] Queries { get;} + string[] Queries { get; } /// The names of all entities - string[] EntityNames { get;} + string[] EntityNames { get; } /// The names of all collection roles - string[] CollectionRoleNames { get;} + string[] CollectionRoleNames { get; } /// Get all second-level cache region names - string[] SecondLevelCacheRegionNames { get;} + string[] SecondLevelCacheRegionNames { get; } /// The number of transactions we know to have been successful - long SuccessfulTransactionCount { get;} + long SuccessfulTransactionCount { get; } /// The number of transactions we know to have completed - long TransactionCount { get;} + long TransactionCount { get; } /// The number of prepared statements that were acquired - long PrepareStatementCount { get;} + long PrepareStatementCount { get; } /// The number of prepared statements that were released - long CloseStatementCount { get;} + long CloseStatementCount { get; } /// The number of StaleObjectStateExceptions that occurred - long OptimisticFailureCount { get;} + long OptimisticFailureCount { get; } /// Reset all statistics void Clear(); diff --git a/src/NHibernate/Stat/QueryStatistics.cs b/src/NHibernate/Stat/QueryStatistics.cs index e6335f13be1..5943747a66f 100644 --- a/src/NHibernate/Stat/QueryStatistics.cs +++ b/src/NHibernate/Stat/QueryStatistics.cs @@ -86,7 +86,6 @@ public override string ToString() .Append(",executionMaxTime=").Append(executionMaxTime) .Append(",executionMinTime=").Append(executionMinTime) .Append(']').ToString(); - } } } diff --git a/src/NHibernate/Stat/StatisticsImpl.cs b/src/NHibernate/Stat/StatisticsImpl.cs index 2a17e3eb1eb..b7d055dc9e0 100644 --- a/src/NHibernate/Stat/StatisticsImpl.cs +++ b/src/NHibernate/Stat/StatisticsImpl.cs @@ -1,19 +1,14 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Text; -using System.Threading; - -using NHibernate.Cache; using NHibernate.Engine; -using NHibernate.Util; using System.Linq; namespace NHibernate.Stat { public class StatisticsImpl : IStatistics, IStatisticsImplementor { - private object _syncRoot; + private readonly object _syncRoot = new object(); private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(StatisticsImpl)); private readonly ISessionFactoryImplementor sessionFactory; @@ -81,17 +76,6 @@ public StatisticsImpl(ISessionFactoryImplementor sessionFactory) this.sessionFactory = sessionFactory; } - private object SyncRoot - { - get - { - if (_syncRoot == null) - Interlocked.CompareExchange(ref _syncRoot, new object(), null); - - return _syncRoot; - } - } - #region IStatistics Members public long EntityDeleteCount @@ -305,10 +289,9 @@ public long OptimisticFailureCount get { return optimisticFailureCount; } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Clear() { - lock (SyncRoot) + lock (_syncRoot) { secondLevelCacheHitCount = 0; secondLevelCacheMissCount = 0; @@ -355,10 +338,9 @@ public void Clear() } } - [MethodImpl(MethodImplOptions.Synchronized)] public EntityStatistics GetEntityStatistics(string entityName) { - lock (SyncRoot) + lock (_syncRoot) { EntityStatistics es; if (!entityStatistics.TryGetValue(entityName, out es)) @@ -370,10 +352,9 @@ public EntityStatistics GetEntityStatistics(string entityName) } } - [MethodImpl(MethodImplOptions.Synchronized)] public CollectionStatistics GetCollectionStatistics(string role) { - lock (SyncRoot) + lock (_syncRoot) { CollectionStatistics cs; if (!collectionStatistics.TryGetValue(role, out cs)) @@ -385,10 +366,9 @@ public CollectionStatistics GetCollectionStatistics(string role) } } - [MethodImpl(MethodImplOptions.Synchronized)] public SecondLevelCacheStatistics GetSecondLevelCacheStatistics(string regionName) { - lock (SyncRoot) + lock (_syncRoot) { SecondLevelCacheStatistics slcs; @@ -406,10 +386,9 @@ public SecondLevelCacheStatistics GetSecondLevelCacheStatistics(string regionNam } } - [MethodImpl(MethodImplOptions.Synchronized)] public QueryStatistics GetQueryStatistics(string queryString) { - lock (SyncRoot) + lock (_syncRoot) { QueryStatistics qs; if (!queryStatistics.TryGetValue(queryString, out qs)) @@ -460,10 +439,9 @@ public TimeSpan OperationThreshold { return operationThreshold; } - [MethodImpl(MethodImplOptions.Synchronized)] set { - lock (SyncRoot) + lock (_syncRoot) { operationThreshold = value; } @@ -474,46 +452,41 @@ public TimeSpan OperationThreshold #region IStatisticsImplementor Members - [MethodImpl(MethodImplOptions.Synchronized)] public void OpenSession() { - lock (SyncRoot) + lock (_syncRoot) { sessionOpenCount++; } } - [MethodImpl(MethodImplOptions.Synchronized)] public void CloseSession() { - lock (SyncRoot) + lock (_syncRoot) { sessionCloseCount++; } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Flush() { - lock (SyncRoot) + lock (_syncRoot) { flushCount++; } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Connect() { - lock (SyncRoot) + lock (_syncRoot) { connectCount++; } } - [MethodImpl(MethodImplOptions.Synchronized)] public void LoadEntity(string entityName, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { entityLoadCount++; GetEntityStatistics(entityName).loadCount++; @@ -524,10 +497,9 @@ public void LoadEntity(string entityName, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void FetchEntity(string entityName, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { entityFetchCount++; GetEntityStatistics(entityName).fetchCount++; @@ -538,10 +510,9 @@ public void FetchEntity(string entityName, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void UpdateEntity(string entityName, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { entityUpdateCount++; GetEntityStatistics(entityName).updateCount++; @@ -552,10 +523,9 @@ public void UpdateEntity(string entityName, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void InsertEntity(string entityName, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { entityInsertCount++; GetEntityStatistics(entityName).insertCount++; @@ -566,10 +536,9 @@ public void InsertEntity(string entityName, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void DeleteEntity(string entityName, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { entityDeleteCount++; GetEntityStatistics(entityName).deleteCount++; @@ -580,10 +549,9 @@ public void DeleteEntity(string entityName, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void LoadCollection(string role, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { collectionLoadCount++; GetCollectionStatistics(role).loadCount++; @@ -594,10 +562,9 @@ public void LoadCollection(string role, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void FetchCollection(string role, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { collectionFetchCount++; GetCollectionStatistics(role).fetchCount++; @@ -608,10 +575,9 @@ public void FetchCollection(string role, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void UpdateCollection(string role, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { collectionUpdateCount++; GetCollectionStatistics(role).updateCount++; @@ -622,10 +588,9 @@ public void UpdateCollection(string role, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void RecreateCollection(string role, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { collectionRecreateCount++; GetCollectionStatistics(role).recreateCount++; @@ -636,24 +601,22 @@ public void RecreateCollection(string role, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void RemoveCollection(string role, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { collectionRemoveCount++; GetCollectionStatistics(role).removeCount++; } if (operationThreshold < time) { - LogOperation(OperationRecreateCollection, role, time); + LogOperation(OperationRemoveCollection, role, time); } } - [MethodImpl(MethodImplOptions.Synchronized)] public void SecondLevelCachePut(string regionName) { - lock (SyncRoot) + lock (_syncRoot) { SecondLevelCacheStatistics slc = GetSecondLevelCacheStatistics(regionName); if (slc != null) @@ -664,10 +627,9 @@ public void SecondLevelCachePut(string regionName) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void SecondLevelCacheHit(string regionName) { - lock (SyncRoot) + lock (_syncRoot) { SecondLevelCacheStatistics slc = GetSecondLevelCacheStatistics(regionName); if (slc != null) @@ -678,10 +640,9 @@ public void SecondLevelCacheHit(string regionName) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void SecondLevelCacheMiss(string regionName) { - lock (SyncRoot) + lock (_syncRoot) { SecondLevelCacheStatistics slc = GetSecondLevelCacheStatistics(regionName); if (slc != null) @@ -692,10 +653,9 @@ public void SecondLevelCacheMiss(string regionName) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void QueryExecuted(string hql, int rows, TimeSpan time) { - lock (SyncRoot) + lock (_syncRoot) { queryExecutionCount++; if (queryExecutionMaxTime < time) @@ -715,10 +675,9 @@ public void QueryExecuted(string hql, int rows, TimeSpan time) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void QueryCacheHit(string hql, string regionName) { - lock (SyncRoot) + lock (_syncRoot) { queryCacheHitCount++; if (hql != null) @@ -734,10 +693,9 @@ public void QueryCacheHit(string hql, string regionName) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void QueryCacheMiss(string hql, string regionName) { - lock (SyncRoot) + lock (_syncRoot) { queryCacheMissCount++; if (hql != null) @@ -753,10 +711,9 @@ public void QueryCacheMiss(string hql, string regionName) } } - [MethodImpl(MethodImplOptions.Synchronized)] public void QueryCachePut(string hql, string regionName) { - lock (SyncRoot) + lock (_syncRoot) { queryCachePutCount++; if (hql != null) diff --git a/src/NHibernate/Tool/hbm2ddl/DatabaseMetadata.cs b/src/NHibernate/Tool/hbm2ddl/DatabaseMetadata.cs index 039ef4a12b4..aa747dd1302 100644 --- a/src/NHibernate/Tool/hbm2ddl/DatabaseMetadata.cs +++ b/src/NHibernate/Tool/hbm2ddl/DatabaseMetadata.cs @@ -14,8 +14,8 @@ public class DatabaseMetadata : IDatabaseMetadata { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof (DatabaseMetadata)); - private readonly IDictionary tables = new Dictionary(); - private readonly ISet sequences = new HashSet(); + private readonly Dictionary tables = new Dictionary(); + private readonly HashSet sequences = new HashSet(); private readonly bool extras; private readonly Dialect.Dialect dialect; private readonly IDataBaseSchema meta; @@ -27,7 +27,6 @@ public DatabaseMetadata(DbConnection connection, Dialect.Dialect dialect) { } - public DatabaseMetadata(DbConnection connection, Dialect.Dialect dialect, bool extras) { meta = dialect.GetDataBaseSchema(connection); @@ -73,7 +72,6 @@ public ITableMetadata GetTableMetadata(string name, string schema, string catalo metaInfo = meta.GetTables(catalog, schema, name, Types); } } - } DataRowCollection rows = metaInfo.Rows; diff --git a/src/NHibernate/Tool/hbm2ddl/IConnectionHelper.cs b/src/NHibernate/Tool/hbm2ddl/IConnectionHelper.cs index a6555525f84..b25081a09c0 100644 --- a/src/NHibernate/Tool/hbm2ddl/IConnectionHelper.cs +++ b/src/NHibernate/Tool/hbm2ddl/IConnectionHelper.cs @@ -15,12 +15,11 @@ public partial interface IConnectionHelper /// /// Get a reference to the connection we are using. /// - DbConnection Connection { get;} + DbConnection Connection { get; } /// /// Release any resources held by this helper. /// void Release(); } - } diff --git a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs index a7af2bf813a..1cdd27614fe 100644 --- a/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs +++ b/src/NHibernate/Tool/hbm2ddl/SchemaExport.cs @@ -7,6 +7,7 @@ using NHibernate.AdoNet.Util; using NHibernate.Cfg; using NHibernate.Connection; +using NHibernate.MultiTenancy; using NHibernate.Util; using Environment=NHibernate.Cfg.Environment; @@ -31,6 +32,7 @@ public partial class SchemaExport private IFormatter formatter; private string delimiter; private string outputFile; + private bool _requireTenantConnection; /// /// Create a schema exported for a given Configuration @@ -68,6 +70,7 @@ private void Initialize() dropSQL = cfg.GenerateDropSchemaScript(dialect); createSQL = cfg.GenerateSchemaCreationScript(dialect); formatter = (PropertiesHelper.GetBoolean(Environment.FormatSql, configProperties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter; + _requireTenantConnection = PropertiesHelper.GetEnum(Environment.MultiTenancy, configProperties, MultiTenancyStrategy.None) == MultiTenancyStrategy.Database; wasInitialized = true; } @@ -93,6 +96,7 @@ public SchemaExport SetDelimiter(string delimiter) return this; } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -104,9 +108,27 @@ public SchemaExport SetDelimiter(string delimiter) /// public void Create(bool useStdOut, bool execute) { - Execute(useStdOut, execute, false); + Create(useStdOut, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the schema creation script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public void Create(bool useStdOut, bool execute, DbConnection connection) + { + InitConnectionAndExecute(GetAction(useStdOut), execute, false, connection, null); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -118,9 +140,27 @@ public void Create(bool useStdOut, bool execute) /// public void Create(Action scriptAction, bool execute) { - Execute(scriptAction, execute, false); + Create(scriptAction, execute, null); } + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the schema creation script + /// + /// an action that will be called for each line of the generated ddl. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public void Create(Action scriptAction, bool execute, DbConnection connection) + { + InitConnectionAndExecute(scriptAction, execute, false, connection, null); + } + + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the schema creation script /// @@ -132,9 +172,27 @@ public void Create(Action scriptAction, bool execute) /// public void Create(TextWriter exportOutput, bool execute) { - Execute(null, execute, false, exportOutput); + Create(exportOutput, execute, null); + } + + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the schema creation script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to false. + /// + public void Create(TextWriter exportOutput, bool execute, DbConnection connection) + { + InitConnectionAndExecute(null, execute, false, connection, exportOutput); } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -146,9 +204,27 @@ public void Create(TextWriter exportOutput, bool execute) /// public void Drop(bool useStdOut, bool execute) { - Execute(useStdOut, execute, true); + Drop(useStdOut, execute, null); + } + + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the drop schema script + /// + /// if the ddl should be outputted in the Console. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public void Drop(bool useStdOut, bool execute, DbConnection connection) + { + InitConnectionAndExecute(GetAction(useStdOut), execute, true, connection, null); } + //TODO 6.0: Remove (replaced by method with optional connection parameter) /// /// Run the drop schema script /// @@ -160,7 +236,24 @@ public void Drop(bool useStdOut, bool execute) /// public void Drop(TextWriter exportOutput, bool execute) { - Execute(null, execute, true, exportOutput); + Drop(exportOutput, execute, null); + } + + //TODO 6.0: Make connection parameter optional: DbConnection connection = null + /// + /// Run the drop schema script + /// + /// if non-null, the ddl will be written to this TextWriter. + /// if the ddl should be executed against the Database. + /// Optional explicit connection. Required for multi-tenancy. + /// Must be an opened connection. The method doesn't close the connection. + /// + /// This is a convenience method that calls and sets + /// the justDrop parameter to true. + /// + public void Drop(TextWriter exportOutput, bool execute, DbConnection connection) + { + InitConnectionAndExecute(null, execute, true, connection, exportOutput); } private void ExecuteInitialized(Action scriptAction, bool execute, bool throwOnError, TextWriter exportOutput, @@ -237,14 +330,7 @@ private void ExecuteSql(DbCommand cmd, string sql) public void Execute(bool useStdOut, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput) { - if (useStdOut) - { - Execute(Console.WriteLine, execute, justDrop, connection, exportOutput); - } - else - { - Execute(null, execute, justDrop, connection, exportOutput); - } + Execute(GetAction(useStdOut), execute, justDrop, connection, exportOutput); } public void Execute(Action scriptAction, bool execute, bool justDrop, DbConnection connection, @@ -315,27 +401,22 @@ public void Execute(Action scriptAction, bool execute, bool justDrop, Db /// public void Execute(bool useStdOut, bool execute, bool justDrop) { - if (useStdOut) - { - Execute(Console.WriteLine, execute, justDrop); - } - else - { - Execute(null, execute, justDrop); - } + InitConnectionAndExecute(GetAction(useStdOut), execute, justDrop, null, null); } - public void Execute(Action scriptAction, bool execute, bool justDrop) { Execute(scriptAction, execute, justDrop, null); } - public void Execute(Action scriptAction, bool execute, bool justDrop, TextWriter exportOutput) + { + InitConnectionAndExecute(scriptAction, execute, justDrop, null, exportOutput); + } + + private void InitConnectionAndExecute(Action scriptAction, bool execute, bool justDrop, DbConnection connection, TextWriter exportOutput) { Initialize(); - DbConnection connection = null; TextWriter fileOutput = exportOutput; IConnectionProvider connectionProvider = null; @@ -346,8 +427,13 @@ public void Execute(Action scriptAction, bool execute, bool justDrop, Te fileOutput = new StreamWriter(outputFile); } - if (execute) + if (execute && connection == null) { + if (_requireTenantConnection) + { + throw new ArgumentException("When Database multi-tenancy is enabled you need to provide explicit connection. Please use overload with connection parameter."); + } + var props = new Dictionary(); foreach (var de in dialect.DefaultProperties) { @@ -380,12 +466,17 @@ public void Execute(Action scriptAction, bool execute, bool justDrop, Te } finally { - if (connection != null) + if (connectionProvider != null) { connectionProvider.CloseConnection(connection); connectionProvider.Dispose(); } } } + + private static Action GetAction(bool useStdOut) + { + return useStdOut ? Console.WriteLine : (Action) null; + } } } diff --git a/src/NHibernate/Tool/hbm2ddl/SchemaMetadataUpdater.cs b/src/NHibernate/Tool/hbm2ddl/SchemaMetadataUpdater.cs index 89b3fec7bd2..6b60b06cb60 100644 --- a/src/NHibernate/Tool/hbm2ddl/SchemaMetadataUpdater.cs +++ b/src/NHibernate/Tool/hbm2ddl/SchemaMetadataUpdater.cs @@ -3,6 +3,7 @@ using NHibernate.Engine; using NHibernate.Mapping; using System.Collections.Generic; +using System.Linq; namespace NHibernate.Tool.hbm2ddl { @@ -54,6 +55,8 @@ public static void QuoteTableAndColumns(Configuration configuration) public static void QuoteTableAndColumns(Configuration configuration, Dialect.Dialect dialect) { + // We have to build the mappings in order to quote collection mappings that use a second pass command to be fully initialized + configuration.BuildMappings(); foreach (var cm in configuration.ClassMappings) { QuoteTable(cm.Table, dialect); @@ -61,6 +64,8 @@ public static void QuoteTableAndColumns(Configuration configuration, Dialect.Dia foreach (var cm in configuration.CollectionMappings) { QuoteTable(cm.Table, dialect); + QuoteColumns(cm.Key, dialect); + QuoteColumns(cm.Element, dialect); } } @@ -70,7 +75,20 @@ private static void QuoteTable(Table table, Dialect.Dialect dialect) { table.IsQuoted = true; } - foreach (var column in table.ColumnIterator) + + QuoteColumns(table.ColumnIterator, dialect); + } + + private static void QuoteColumns(IValue value, Dialect.Dialect dialect) + { + if (value == null) + return; + QuoteColumns(value.ColumnIterator.OfType(), dialect); + } + + private static void QuoteColumns(IEnumerable columns, Dialect.Dialect dialect) + { + foreach (var column in columns) { if (!column.IsQuoted && dialect.IsKeyword(column.Name)) { diff --git a/src/NHibernate/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs b/src/NHibernate/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs index 6814531f9e7..d463da73cbb 100644 --- a/src/NHibernate/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs +++ b/src/NHibernate/Tool/hbm2ddl/SuppliedConnectionProviderConnectionHelper.cs @@ -3,7 +3,6 @@ namespace NHibernate.Tool.hbm2ddl { - /// /// A implementation based on a provided /// . Essentially, ensures that the connection diff --git a/src/NHibernate/Transaction/AdoNetTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetTransactionFactory.cs index e1d485aeecb..5fe50f06757 100644 --- a/src/NHibernate/Transaction/AdoNetTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetTransactionFactory.cs @@ -6,7 +6,6 @@ using NHibernate.Engine; using NHibernate.Engine.Transaction; using NHibernate.Exceptions; -using NHibernate.Impl; namespace NHibernate.Transaction { @@ -16,7 +15,7 @@ namespace NHibernate.Transaction /// public partial class AdoNetTransactionFactory : ITransactionFactory { - private readonly INHibernateLogger isolaterLog = NHibernateLogger.For(typeof(ITransactionFactory)); + private static readonly INHibernateLogger _isolatorLog = NHibernateLogger.For(typeof(ITransactionFactory)); /// public virtual ITransaction CreateTransaction(ISessionImplementor session) @@ -52,25 +51,17 @@ public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolate DbConnection connection = null; DbTransaction trans = null; - // bool wasAutoCommit = false; try { // We make an exception for SQLite and use the session's connection, // since SQLite only allows one connection to the database. - if (session.Factory.Dialect is SQLiteDialect) - connection = session.Connection; - else - connection = session.Factory.ConnectionProvider.GetConnection(); + connection = session.Factory.Dialect is SQLiteDialect + ? session.Connection + : session.Factory.ConnectionProvider.GetConnection(); if (transacted) { trans = connection.BeginTransaction(); - // TODO NH: a way to read the autocommit state is needed - //if (TransactionManager.GetAutoCommit(connection)) - //{ - // wasAutoCommit = true; - // TransactionManager.SetAutoCommit(connection, false); - //} } work.DoWork(connection, trans); @@ -78,7 +69,6 @@ public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolate if (transacted) { trans.Commit(); - //TransactionManager.Commit(connection); } } catch (Exception t) @@ -94,49 +84,33 @@ public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolate } catch (Exception ignore) { - isolaterLog.Debug(ignore, "Unable to rollback transaction"); + _isolatorLog.Debug(ignore, "Unable to rollback transaction"); } - if (t is HibernateException) + switch (t) { - throw; - } - else if (t is DbException) - { - throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, - "error performing isolated work"); - } - else - { - throw new HibernateException("error performing isolated work", t); + case HibernateException _: + throw; + case DbException _: + throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, + "error performing isolated work"); + default: + throw new HibernateException("error performing isolated work", t); } } } finally { - //if (transacted && wasAutoCommit) - //{ - // try - // { - // // TODO NH: reset autocommit - // // TransactionManager.SetAutoCommit(connection, true); - // } - // catch (Exception) - // { - // log.Debug("was unable to reset connection back to auto-commit"); - // } - //} - try { trans?.Dispose(); } catch (Exception ignore) { - isolaterLog.Warn(ignore, "Unable to dispose transaction"); + _isolatorLog.Warn(ignore, "Unable to dispose transaction"); } - if (session.Factory.Dialect is SQLiteDialect == false) + if (connection != null && session.Factory.Dialect is SQLiteDialect == false) session.Factory.ConnectionProvider.CloseConnection(connection); } } diff --git a/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs index 3e0bedc3ed1..7f74bc7218c 100644 --- a/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs @@ -6,7 +6,6 @@ using NHibernate.AdoNet; using NHibernate.Engine; using NHibernate.Engine.Transaction; -using NHibernate.Impl; using NHibernate.Util; namespace NHibernate.Transaction @@ -47,14 +46,20 @@ public override void EnlistInSystemTransactionIfNeeded(ISessionImplementor sessi if (session == null) throw new ArgumentNullException(nameof(session)); - if (!session.ConnectionManager.ShouldAutoJoinTransaction) - { + if (!ShouldAutoJoinSystemTransaction(session)) return; - } JoinSystemTransaction(session, System.Transactions.Transaction.Current); } + private static bool ShouldAutoJoinSystemTransaction(ISessionImplementor session) + { + var connectionManager = session.ConnectionManager; + return connectionManager.ShouldAutoJoinTransaction && + // Shortcut for avoiding accessing Transaction.Current, which is costly. + (session.TransactionContext == null || connectionManager.ProcessingFromSystemTransaction); + } + /// public override void ExplicitJoinSystemTransaction(ISessionImplementor session) { @@ -186,6 +191,7 @@ public class SystemTransactionContext : ITransactionContext, IEnlistmentNotifica private readonly System.Transactions.Transaction _originalTransaction; private readonly ManualResetEventSlim _lock = new ManualResetEventSlim(true); private volatile bool _needCompletionLocking = true; + private bool _preparing; // Required for not locking the completion phase itself when locking session usages from concurrent threads. private static readonly AsyncLocal _bypassLock = new AsyncLocal(); private readonly int _systemTransactionCompletionLockTimeout; @@ -236,7 +242,9 @@ public virtual void Wait() // Remove the block then throw. Unlock(); throw new HibernateException( - "Synchronization timeout for transaction completion. Either raise {Cfg.Environment.SystemTransactionCompletionLockTimeout}, or this may be a bug in NHibernate."); + $"Synchronization timeout for transaction completion. Either raise" + + $"{Cfg.Environment.SystemTransactionCompletionLockTimeout}, or check all scopes are properly" + + $"disposed and/or all direct System.Transaction.Current changes are properly managed."); } catch (HibernateException) { @@ -285,16 +293,44 @@ protected virtual void Unlock() { // Cloned transaction is not disposed "unexpectedly", its status is accessible till context disposal. var status = EnlistedTransaction.TransactionInformation.Status; - if (status != TransactionStatus.Active) + if (status != TransactionStatus.Active || _preparing) return status; - // The clone status can be out of date when active, check the original one (which could be disposed if - // the clone is out of date). + // The clone status can be out of date when active and not in prepare phase, in case of rollback or + // dependent clone usage. + // In such case the original transaction is already disposed, and trying to check its status will + // trigger a dispose exception. return _originalTransaction.TransactionInformation.Status; } catch (ObjectDisposedException ode) { - _logger.Warn(ode, "Enlisted transaction status was wrongly active, original transaction being already disposed. Will assume neither active nor committed."); + // For ruling out the dependent clone case when possible, we check if the current transaction is + // equal to the context one (System.Transactions.Transaction does override equality for this), and + // in such case, we check the state of the current transaction instead. (The state of the current + // transaction if equal can only be the same, but it will be inaccessible in case of rollback, due + // to the current transaction being already disposed.) + // The current transaction may not be reachable during 2PC phases and transaction completion events, + // but in such cases the context transaction is either no more active or in prepare phase, which is + // already covered by _preparing test. + try + { + var currentTransaction = System.Transactions.Transaction.Current; + if (!ReferenceEquals(currentTransaction, _originalTransaction) && + currentTransaction == EnlistedTransaction) + return currentTransaction.TransactionInformation.Status; + } + catch (ObjectDisposedException) + { + // Just ignore that one, no use to log two dispose exceptions which are indeed the same. + } + catch (InvalidOperationException ioe) + { + _logger.Warn(ioe, "Attempting to dodge a disposed transaction trouble, current" + + "transaction was unreachable."); + } + + _logger.Warn(ode, "Enlisted transaction status is maybe wrongly active, original " + + "transaction being already disposed. Will assume neither active nor committed."); return null; } } @@ -310,6 +346,7 @@ protected virtual void Unlock() /// The object for notifying the prepare phase outcome. public virtual void Prepare(PreparingEnlistment preparingEnlistment) { + _preparing = true; using (_session.BeginContext()) { try @@ -343,10 +380,12 @@ public virtual void Prepare(PreparingEnlistment preparingEnlistment) Lock(); _logger.Debug("Prepared for system transaction"); + _preparing = false; preparingEnlistment.Prepared(); } catch (Exception exception) { + _preparing = false; _logger.Error(exception, "System transaction prepare phase failed"); try { @@ -404,7 +443,7 @@ protected virtual void ProcessSecondPhase(Enlistment enlistment, bool? success) /// /// Handle the transaction completion. Notify of the end of the /// transaction. Notify end of transaction to the session and to - /// if any. Close sessions requiring it then cleanup transaction contextes and then blocked + /// if any. Close sessions requiring it then cleanup transaction contexts and then blocked /// threads. /// /// if the transaction is committed, diff --git a/src/NHibernate/Transaction/AdoTransaction.cs b/src/NHibernate/Transaction/AdoTransaction.cs index 5f7f8fc7c35..fe6d8b2b61c 100644 --- a/src/NHibernate/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Transaction/AdoTransaction.cs @@ -23,8 +23,8 @@ public partial class AdoTransaction : ITransaction private bool commitFailed; // Since v5.2 [Obsolete] - private IList synchronizations; - private IList _completionSynchronizations; + private List synchronizations; + private List _completionSynchronizations; /// /// Initializes a new instance of the class. @@ -370,6 +370,7 @@ protected virtual void Dispose(bool isDisposing) // don't dispose of multiple times. return; } + _isAlreadyDisposed = true; // free managed resources that are being managed by the AdoTransaction if we // know this call came through Dispose() @@ -387,13 +388,11 @@ protected virtual void Dispose(bool isDisposing) // Assume we are rolled back AfterTransactionCompletion(false); } + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here - - _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } } diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index cabbfb97ca0..7e2cd704974 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -109,6 +109,11 @@ public override IList TransformList(IList collection) return collection; } + protected virtual void OnPropertyNotFound(string propertyName) + { + throw new PropertyNotFoundException(_resultClass.GetType(), propertyName, "setter"); + } + #region Setter resolution /// @@ -136,7 +141,7 @@ private void SetProperty(string alias, object value, object resultObj) if (TrySet(alias, value, resultObj, _fieldsByNameCaseInsensitive)) return; - throw new PropertyNotFoundException(resultObj.GetType(), alias, "setter"); + OnPropertyNotFound(alias); } private bool TrySet(string alias, object value, object resultObj, Dictionary> fieldsMap) diff --git a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs index 0497d292a51..e6a7c07baad 100644 --- a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs @@ -29,7 +29,6 @@ public override IList TransformList(IList collection) return collection; } - public override bool IsTransformedValueATupleElement(string[] aliases, int tupleLength) { return false; diff --git a/src/NHibernate/Transform/AliasedTupleSubsetResultTransformer.cs b/src/NHibernate/Transform/AliasedTupleSubsetResultTransformer.cs index e4e320546db..90fc3d6ad93 100644 --- a/src/NHibernate/Transform/AliasedTupleSubsetResultTransformer.cs +++ b/src/NHibernate/Transform/AliasedTupleSubsetResultTransformer.cs @@ -11,7 +11,6 @@ namespace NHibernate.Transform [Serializable] public abstract class AliasedTupleSubsetResultTransformer : ITupleSubsetResultTransformer { - public abstract bool IsTransformedValueATupleElement(string[] aliases, int tupleLength); public bool[] IncludeInTransform(string[] aliases, int tupleLength) diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index 1174f5d9478..caed8a31221 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -24,6 +24,7 @@ public class CacheableResultTransformer : IResultTransformer public bool AutoDiscoverTypes { get; } private readonly SqlString _autoDiscoveredQuery; + private readonly bool _skipTransformer; private int _tupleLength; private int _tupleSubsetLength; @@ -60,7 +61,7 @@ public class CacheableResultTransformer : IResultTransformer /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. // Since v5.1 - [Obsolete("Please use overload with autoDiscoverTypes parameter.")] + [Obsolete("Please use overload with skipTransformer parameter.")] public static CacheableResultTransformer Create(IResultTransformer transformer, string[] aliases, bool[] includeInTuple) @@ -68,6 +69,17 @@ public static CacheableResultTransformer Create(IResultTransformer transformer, return Create(transformer, aliases, includeInTuple, false, null); } + // Since 5.2 + [Obsolete("Please use overload with skipTransformer parameter.")] + public static CacheableResultTransformer Create( + IResultTransformer transformer, string[] aliases, bool[] includeInTuple, bool autoDiscoverTypes, + SqlString autoDiscoveredQuery) + { + return autoDiscoverTypes + ? Create(autoDiscoveredQuery) + : Create(includeInTuple, GetIncludeInTransform(transformer, aliases, includeInTuple), false); + } + /// /// Returns a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. @@ -84,15 +96,25 @@ public static CacheableResultTransformer Create(IResultTransformer transformer, /// Indicates if types auto-discovery is enabled. /// If , the query for which they /// will be autodiscovered. + /// If true cache results untransformed. /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. public static CacheableResultTransformer Create( - IResultTransformer transformer, string[] aliases, bool[] includeInTuple, bool autoDiscoverTypes, - SqlString autoDiscoveredQuery) + IResultTransformer transformer, + string[] aliases, + bool[] includeInTuple, + bool autoDiscoverTypes, + SqlString autoDiscoveredQuery, + bool skipTransformer) { return autoDiscoverTypes ? Create(autoDiscoveredQuery) - : Create(includeInTuple, GetIncludeInTransform(transformer, aliases, includeInTuple)); + : Create( + includeInTuple, + skipTransformer + ? null + : GetIncludeInTransform(transformer, aliases, includeInTuple), + skipTransformer); } /// @@ -106,11 +128,12 @@ public static CacheableResultTransformer Create( /// must be non-null /// Indexes that are included in the transformation. /// null if all elements in the tuple are included. + /// /// a CacheableResultTransformer that is used to transform /// tuples to a value(s) that can be cached. - private static CacheableResultTransformer Create(bool[] includeInTuple, bool[] includeInTransform) + private static CacheableResultTransformer Create(bool[] includeInTuple, bool[] includeInTransform, bool skipTransformer) { - return new CacheableResultTransformer(includeInTuple, includeInTransform); + return new CacheableResultTransformer(includeInTuple, includeInTransform, skipTransformer); } private static CacheableResultTransformer Create(SqlString autoDiscoveredQuery) @@ -136,8 +159,9 @@ private static bool[] GetIncludeInTransform(IResultTransformer transformer, stri return resultTransformer.IncludeInTransform(aliases, tupleLength); } - private CacheableResultTransformer(bool[] includeInTuple, bool[] includeInTransform) + private CacheableResultTransformer(bool[] includeInTuple, bool[] includeInTransform, bool skipTransformer) { + _skipTransformer = skipTransformer; InitializeTransformer(includeInTuple, includeInTransform); } @@ -212,7 +236,7 @@ public IList RetransformResults(IList transformedResults, if (_includeInTuple == null) throw new InvalidOperationException("This transformer is not initialized"); - if (!HasSameParameters(Create(transformer, aliases, includeInTuple, false, null))) + if (!HasSameParameters(Create(transformer, aliases, includeInTuple, false, null, _skipTransformer))) { throw new InvalidOperationException( "this CacheableResultTransformer is inconsistent with specified arguments; cannot re-transform" @@ -220,7 +244,11 @@ public IList RetransformResults(IList transformedResults, } bool requiresRetransform = true; string[] aliasesToUse = aliases == null ? null : Index(aliases); - if (transformer.Equals(_actualTransformer)) + + if (_skipTransformer) + { + } + else if (transformer.Equals(_actualTransformer)) { requiresRetransform = false; } @@ -245,7 +273,6 @@ public IList RetransformResults(IList transformedResults, return transformedResults; } - /// /// Untransforms, if necessary, a List of values previously /// transformed by this (or an equivalent) CacheableResultTransformer. @@ -283,13 +310,11 @@ public IList UntransformToTuples(IList results) results[i], _tupleSubsetLength == 1); results[i] = Unindex(tuple); } - } return results; } - /// /// Returns the result types for the transformed value. /// @@ -302,13 +327,11 @@ public IType[] GetCachedResultTypes(IType[] tupleResultTypes) : tupleResultTypes; } - public IList TransformList(IList list) { return list; } - /// /// "Compact" the given array by picking only the elements identified by /// the _includeInTransformIndex array. The picked elements are returned @@ -330,7 +353,6 @@ private T[] Index(T[] objects) return objectsIndexed; } - /// /// Expand the given array by putting each of its elements at the /// position identified by the _includeInTransformIndex array. The diff --git a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs index a6d3e014c82..0837f0aaadc 100644 --- a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs +++ b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; - +using NHibernate.Util; namespace NHibernate.Transform { @@ -13,19 +13,6 @@ public class DistinctRootEntityResultTransformer : IResultTransformer, ITupleSub private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(DistinctRootEntityResultTransformer)); internal static readonly DistinctRootEntityResultTransformer Instance = new DistinctRootEntityResultTransformer(); - sealed class IdentityComparer : IEqualityComparer - { - public bool Equals(T x, T y) - { - return ReferenceEquals(x, y); - } - - public int GetHashCode(T obj) - { - return RuntimeHelpers.GetHashCode(obj); - } - } - public object TransformTuple(object[] tuple, string[] aliases) { return tuple[tuple.Length - 1]; @@ -37,7 +24,7 @@ public IList TransformList(IList list) return list; IList result = (IList) Activator.CreateInstance(list.GetType()); - var distinct = new HashSet(new IdentityComparer()); + var distinct = new HashSet(ReferenceComparer.Instance); for (int i = 0; i < list.Count; i++) { @@ -55,9 +42,9 @@ public IList TransformList(IList list) return result; } - internal static List TransformList(IEnumerable list) + internal static List TransformList(IEnumerable list) where T: class { - var result = list.Distinct(new IdentityComparer()).ToList(); + var result = list.Distinct(ReferenceComparer.Instance).ToList(); if (log.IsDebugEnabled()) { log.Debug("transformed: {0} rows to: {1} distinct results", list.Count(), result.Count); diff --git a/src/NHibernate/Transform/ITupleSubsetResultTransformer.cs b/src/NHibernate/Transform/ITupleSubsetResultTransformer.cs index 71f07d689df..a2a1f2c58cd 100644 --- a/src/NHibernate/Transform/ITupleSubsetResultTransformer.cs +++ b/src/NHibernate/Transform/ITupleSubsetResultTransformer.cs @@ -1,5 +1,4 @@ - -namespace NHibernate.Transform +namespace NHibernate.Transform { /// /// A ResultTransformer that operates on "well-defined" and consistent @@ -48,7 +47,6 @@ public interface ITupleSubsetResultTransformer : IResultTransformer /// false, otherwise. bool IsTransformedValueATupleElement(string[] aliases, int tupleLength); - /// /// Returns an array with the i-th element indicating whether the i-th /// element of the tuple is included in the transformed value. diff --git a/src/NHibernate/Transform/PassThroughResultTransformer.cs b/src/NHibernate/Transform/PassThroughResultTransformer.cs index 72454e2883a..0ed0be9e710 100644 --- a/src/NHibernate/Transform/PassThroughResultTransformer.cs +++ b/src/NHibernate/Transform/PassThroughResultTransformer.cs @@ -23,13 +23,11 @@ public IList TransformList(IList collection) #endregion - public bool IsTransformedValueATupleElement(string[] aliases, int tupleLength) { return tupleLength == 1; } - public bool[] IncludeInTransform(string[] aliases, int tupleLength) { bool[] includeInTransformedResult = new bool[tupleLength]; @@ -37,7 +35,6 @@ public bool[] IncludeInTransform(string[] aliases, int tupleLength) return includeInTransformedResult; } - internal IList UntransformToTuples(IList results, bool isSingleResult) { // untransform only if necessary; if transformed, do it in place; @@ -52,7 +49,6 @@ internal IList UntransformToTuples(IList results, bool isSingleResult) return results; } - internal object[] UntransformToTuple(object transformed, bool isSingleResult) { return isSingleResult ? new[] {transformed} : (object[]) transformed; diff --git a/src/NHibernate/Tuple/Component/AbstractComponentTuplizer.cs b/src/NHibernate/Tuple/Component/AbstractComponentTuplizer.cs index 8152da34511..2f8bd7c8d2b 100644 --- a/src/NHibernate/Tuple/Component/AbstractComponentTuplizer.cs +++ b/src/NHibernate/Tuple/Component/AbstractComponentTuplizer.cs @@ -121,6 +121,5 @@ public virtual bool IsInstance(object obj) protected internal abstract IInstantiator BuildInstantiator(Mapping.Component component); protected internal abstract IGetter BuildGetter(Mapping.Component component, Mapping.Property prop); protected internal abstract ISetter BuildSetter(Mapping.Component component, Mapping.Property prop); - } } diff --git a/src/NHibernate/Tuple/Component/IComponentTuplizer.cs b/src/NHibernate/Tuple/Component/IComponentTuplizer.cs index 8936c61fd93..eb6ac529956 100644 --- a/src/NHibernate/Tuple/Component/IComponentTuplizer.cs +++ b/src/NHibernate/Tuple/Component/IComponentTuplizer.cs @@ -27,6 +27,6 @@ public interface IComponentTuplizer : ITuplizer /// Does the component managed by this tuuplizer contain a parent property? /// True if the component does contain a parent property; false otherwise. - bool HasParentProperty { get;} + bool HasParentProperty { get; } } } diff --git a/src/NHibernate/Tuple/Component/PocoComponentTuplizer.cs b/src/NHibernate/Tuple/Component/PocoComponentTuplizer.cs index c7b93ca21a1..88fd793bc35 100644 --- a/src/NHibernate/Tuple/Component/PocoComponentTuplizer.cs +++ b/src/NHibernate/Tuple/Component/PocoComponentTuplizer.cs @@ -22,7 +22,6 @@ public class PocoComponentTuplizer : AbstractComponentTuplizer [NonSerialized] private bool isBytecodeProviderImpl; // 6.0 TODO: remove - [OnDeserialized] internal void OnDeserialized(StreamingContext context) { diff --git a/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs index d6af0c552e0..120db5ae19a 100644 --- a/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs +++ b/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs @@ -324,7 +324,7 @@ protected virtual void SetIdentifierPropertyValue(object entity, object value) } /// Return the entity-mode handled by this tuplizer instance. - public abstract EntityMode EntityMode { get;} + public abstract EntityMode EntityMode { get; } protected virtual IInstantiator Instantiator { get; set; } @@ -420,6 +420,5 @@ protected EntityMetamodel EntityMetamodel { get { return entityMetamodel; } } - } } diff --git a/src/NHibernate/Tuple/Entity/BytecodeEnhancementMetadataPocoImpl.cs b/src/NHibernate/Tuple/Entity/BytecodeEnhancementMetadataPocoImpl.cs index 988e8767b63..e0adb041feb 100644 --- a/src/NHibernate/Tuple/Entity/BytecodeEnhancementMetadataPocoImpl.cs +++ b/src/NHibernate/Tuple/Entity/BytecodeEnhancementMetadataPocoImpl.cs @@ -132,7 +132,7 @@ public IFieldInterceptor InjectInterceptor(object entity, ISessionImplementor se return null; // Can happen when a saved entity is refreshed within the same session NH2860 } - if (entity.GetType().BaseType != _entityType) + if (!_entityType.IsInstanceOfType(entity)) { throw new ArgumentException( $"Passed entity instance [{entity}] is not of expected type [{EntityName}]"); @@ -170,7 +170,7 @@ public IFieldInterceptor ExtractInterceptor(object entity) return null; } - if (_entityType != interceptor.MappedClass) + if (!_entityType.IsAssignableFrom(interceptor.MappedClass)) { throw new ArgumentException( $"Passed entity instance [{entity}] is not of expected type [{EntityName}]"); diff --git a/src/NHibernate/Tuple/Entity/EntityMetamodel.cs b/src/NHibernate/Tuple/Entity/EntityMetamodel.cs index 7fb104dac86..2a02711b2bf 100644 --- a/src/NHibernate/Tuple/Entity/EntityMetamodel.cs +++ b/src/NHibernate/Tuple/Entity/EntityMetamodel.cs @@ -49,12 +49,13 @@ public class EntityMetamodel private readonly bool[] propertyVersionability; private readonly CascadeStyle[] cascadeStyles; - private readonly IDictionary propertyIndexes = new Dictionary(); + private readonly Dictionary propertyIndexes = new Dictionary(); + private readonly IDictionary _identifierPropertyTypes = new Dictionary(); + private readonly IDictionary _propertyTypes = new Dictionary(); private readonly bool hasCollections; private readonly bool hasMutableProperties; private readonly bool hasLazyProperties; - private readonly int[] naturalIdPropertyNumbers; private bool lazy; @@ -83,16 +84,17 @@ public EntityMetamodel(PersistentClass persistentClass, ISessionFactoryImplement { this.sessionFactory = sessionFactory; - name = persistentClass.EntityName; rootName = persistentClass.RootClazz.EntityName; entityType = TypeFactory.ManyToOne(name); type = persistentClass.MappedClass; rootType = persistentClass.RootClazz.MappedClass; rootTypeAssemblyQualifiedName = rootType == null ? null : rootType.AssemblyQualifiedName; + OverridesEquals = type != null && ReflectHelper.OverridesEquals(type); // type will be null for dynamic entities identifierProperty = PropertyFactory.BuildIdentifierProperty(persistentClass, sessionFactory.GetIdentifierGenerator(rootName)); + MapIdentifierPropertyTypes(identifierProperty); versioned = persistentClass.IsVersioned; @@ -411,13 +413,44 @@ private bool HasPartialUpdateComponentGeneration(Mapping.Component component) private void MapPropertyToIndex(Mapping.Property prop, int i) { - propertyIndexes[prop.Name] = i; - Mapping.Component comp = prop.Value as Mapping.Component; - if (comp != null) + MapPropertyToIndex(null, prop, i); + } + + private void MapPropertyToIndex(string path, Mapping.Property prop, int i) + { + var propPath = !string.IsNullOrEmpty(path) ? $"{path}.{prop.Name}" : prop.Name; + propertyIndexes[propPath] = i; + _propertyTypes[propPath] = prop.Type; + if (!(prop.Value is Mapping.Component comp)) + { + return; + } + + foreach (var subprop in comp.PropertyIterator) + { + MapPropertyToIndex(propPath, subprop, i); + } + } + + private void MapIdentifierPropertyTypes(IdentifierProperty identifier) + { + MapIdentifierPropertyTypes(identifier.Name, identifier.Type); + } + + private void MapIdentifierPropertyTypes(string path, IType propertyType) + { + if (!string.IsNullOrEmpty(path)) { - foreach (Mapping.Property subprop in comp.PropertyIterator) + _identifierPropertyTypes[path] = propertyType; + } + + if (propertyType is IAbstractComponentType componentType) + { + for (var i = 0; i < componentType.PropertyNames.Length; i++) { - propertyIndexes[prop.Name + '.' + subprop.Name] = i; + MapIdentifierPropertyTypes( + !string.IsNullOrEmpty(path) ? $"{path}.{componentType.PropertyNames[i]}" : componentType.PropertyNames[i], + componentType.Subtypes[i]); } } } @@ -517,6 +550,8 @@ public StandardProperty[] Properties get { return properties; } } + internal bool OverridesEquals { get; private set; } + public int GetPropertyIndex(string propertyName) { int? index = GetPropertyIndexOrNull(propertyName); @@ -536,6 +571,18 @@ public int GetPropertyIndex(string propertyName) return null; } + internal IType GetIdentifierPropertyType(string memberPath) + { + return _identifierPropertyTypes.TryGetValue(memberPath, out var propertyType) ? propertyType : null; + } + + internal IType GetPropertyType(string memberPath) + { + return _propertyTypes.TryGetValue(memberPath, out var propertyType) + ? propertyType + : GetIdentifierPropertyType(memberPath); + } + public bool HasCollections { get { return hasCollections; } diff --git a/src/NHibernate/Tuple/Entity/IEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/IEntityTuplizer.cs index 75ba17dbc0c..f3ee8e7e28a 100644 --- a/src/NHibernate/Tuple/Entity/IEntityTuplizer.cs +++ b/src/NHibernate/Tuple/Entity/IEntityTuplizer.cs @@ -108,7 +108,7 @@ public interface IEntityTuplizer : ITuplizer /// Does this entity, for this mode, present a possibility for proxying? /// True if this tuplizer can generate proxies for this entity. - bool HasProxy { get;} + bool HasProxy { get; } /// /// Generates an appropriate proxy representation of this entity for this entity-mode. diff --git a/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs index aaf9140290e..3913d678032 100644 --- a/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs +++ b/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs @@ -15,7 +15,6 @@ namespace NHibernate.Tuple.Entity { - /// An specific to the POCO entity mode. public class PocoEntityTuplizer : AbstractEntityTuplizer { diff --git a/src/NHibernate/Tuple/ITuplizer.cs b/src/NHibernate/Tuple/ITuplizer.cs index f337dff99e5..153ea7eaa26 100644 --- a/src/NHibernate/Tuple/ITuplizer.cs +++ b/src/NHibernate/Tuple/ITuplizer.cs @@ -43,7 +43,7 @@ public interface ITuplizer /// Need to determine how to best handle this for the Tuplizers for EntityModes /// other than POCO. /// - System.Type MappedClass { get;} + System.Type MappedClass { get; } /// /// Extract the current values contained on the given entity. @@ -76,4 +76,4 @@ public interface ITuplizer /// True if the object is considered as an instance of this entity within the given mode. bool IsInstance(object obj); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Tuple/IdentifierProperty.cs b/src/NHibernate/Tuple/IdentifierProperty.cs index 345ed50551f..b932e33b455 100644 --- a/src/NHibernate/Tuple/IdentifierProperty.cs +++ b/src/NHibernate/Tuple/IdentifierProperty.cs @@ -22,7 +22,6 @@ public class IdentifierProperty : Property private readonly bool identifierAssignedByInsert; private readonly bool hasIdentifierMapper; - /// /// Construct a non-virtual identifier property. /// @@ -99,4 +98,4 @@ public bool HasIdentifierMapper get { return hasIdentifierMapper; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Tuple/PocoInstantiator.cs b/src/NHibernate/Tuple/PocoInstantiator.cs index 94429acea44..6af0682e6c6 100644 --- a/src/NHibernate/Tuple/PocoInstantiator.cs +++ b/src/NHibernate/Tuple/PocoInstantiator.cs @@ -20,7 +20,7 @@ public class PocoInstantiator : IInstantiator, IDeserializationCallback [NonSerialized] private IInstantiationOptimizer optimizer; - private readonly IProxyFactory proxyFactory; // 6.0 TODO: remove + private readonly IProxyFactory proxyFactory; // 6.0 TODO: remove private readonly bool generateFieldInterceptionProxy; // 6.0 TODO: remove diff --git a/src/NHibernate/Type/AbstractEnumType.cs b/src/NHibernate/Type/AbstractEnumType.cs index 5454b680884..e37ab923d15 100644 --- a/src/NHibernate/Type/AbstractEnumType.cs +++ b/src/NHibernate/Type/AbstractEnumType.cs @@ -3,7 +3,6 @@ namespace NHibernate.Type { - /// /// Base class for enum types. /// diff --git a/src/NHibernate/Type/AnyType.cs b/src/NHibernate/Type/AnyType.cs index 9390c91d1fa..d32ac2ff2e3 100644 --- a/src/NHibernate/Type/AnyType.cs +++ b/src/NHibernate/Type/AnyType.cs @@ -412,6 +412,5 @@ public override bool[] ToColumnNullness(object value, IMapping mapping) ArrayHelper.Fill(result, true); return result; } - } } diff --git a/src/NHibernate/Type/ByteType.cs b/src/NHibernate/Type/ByteType.cs index 6c77d87325a..7c90bd6738a 100644 --- a/src/NHibernate/Type/ByteType.cs +++ b/src/NHibernate/Type/ByteType.cs @@ -45,8 +45,8 @@ public override void Set(DbCommand cmd, object value, int index, ISessionImpleme { var dp = cmd.Parameters[index]; dp.Value = dp.DbType == DbType.Int16 ? Convert.ToInt16(value) : Convert.ToByte(value); - } + public override string Name { get { return "Byte"; } diff --git a/src/NHibernate/Type/CollectionType.cs b/src/NHibernate/Type/CollectionType.cs index ba76596c512..408e03d94ba 100644 --- a/src/NHibernate/Type/CollectionType.cs +++ b/src/NHibernate/Type/CollectionType.cs @@ -160,6 +160,11 @@ public override object Disassemble(object value, ISessionImplementor session, ob public override void BeforeAssemble(object oid, ISessionImplementor session) { + if (oid == null) + { + return; + } + var queryCacheQueue = session.PersistenceContext.BatchFetchQueue.QueryCacheQueue; if (queryCacheQueue == null) { @@ -433,7 +438,6 @@ public override string ToString() return GetType().FullName + '(' + Role + ')'; } - #region Methods added in NH protected virtual void Clear(object collection) @@ -662,12 +666,12 @@ public virtual IEnumerable GetElementsIterator(object collection) public virtual bool HasHolder() { - return false;// entityMode == EntityMode.DOM4J; + return false; // entityMode == EntityMode.DOM4J; } protected internal virtual bool InitializeImmediately() { - return false;// entityMode == EntityMode.DOM4J; + return false; // entityMode == EntityMode.DOM4J; } public virtual object IndexOf(object collection, object element) diff --git a/src/NHibernate/Type/CompositeCustomType.cs b/src/NHibernate/Type/CompositeCustomType.cs index 1588ab4cbd7..583f35abc5f 100644 --- a/src/NHibernate/Type/CompositeCustomType.cs +++ b/src/NHibernate/Type/CompositeCustomType.cs @@ -7,6 +7,7 @@ using NHibernate.SqlTypes; using NHibernate.UserTypes; using System.Collections.Generic; +using NHibernate.Util; namespace NHibernate.Type { @@ -179,7 +180,7 @@ public override void NullSafeSet(DbCommand st, object value, int index, bool[] s public override void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) { - bool[] settable = Enumerable.Repeat(true, GetColumnSpan(session.Factory)).ToArray(); + bool[] settable = ArrayHelper.Fill(true, GetColumnSpan(session.Factory)); userType.NullSafeSet(cmd, value, index, settable, session); } @@ -260,6 +261,5 @@ public override bool[] ToColumnNullness(object value, IMapping mapping) } return result; } - } } diff --git a/src/NHibernate/Type/DateType.cs b/src/NHibernate/Type/DateType.cs index 08e4097a7a7..76d3fbb99e9 100644 --- a/src/NHibernate/Type/DateType.cs +++ b/src/NHibernate/Type/DateType.cs @@ -35,7 +35,7 @@ public DateType() : base(SqlTypeFactory.Date) /// protected override DateTime AdjustDateTime(DateTime dateValue) => - dateValue.Date; + Kind == DateTimeKind.Unspecified ? dateValue.Date : DateTime.SpecifyKind(dateValue.Date, Kind); /// public override bool IsEqual(object x, object y) diff --git a/src/NHibernate/Type/EmbeddedComponentType.cs b/src/NHibernate/Type/EmbeddedComponentType.cs index 0964217419d..5fe81b36a76 100644 --- a/src/NHibernate/Type/EmbeddedComponentType.cs +++ b/src/NHibernate/Type/EmbeddedComponentType.cs @@ -19,9 +19,12 @@ public override bool IsEmbedded public override object Instantiate(object parent, ISessionImplementor session) { - bool useParent= false; - // NH Different implementation : since we are not sure about why H3.2 use the "parent" - //useParent = parent != null && base.ReturnedClass.IsInstanceOfType(parent); + var useParent = + parent != null && + //TODO: Yuck! This is not quite good enough, it's a quick + //hack around the problem of having a to-one association + //that refers to an embedded component: + ReturnedClass.IsInstanceOfType(parent); return useParent ? parent : base.Instantiate(parent, session); } diff --git a/src/NHibernate/Type/EntityType.cs b/src/NHibernate/Type/EntityType.cs index c11fb22726e..b45502fc914 100644 --- a/src/NHibernate/Type/EntityType.cs +++ b/src/NHibernate/Type/EntityType.cs @@ -291,6 +291,10 @@ public bool IsUniqueKeyReference get { return uniqueKeyPropertyName != null; } } + /// + /// True if not null entity key can represent null entity + /// (e.g. entity mapped with not-found="ignore" or not constrained one-to-one mapping) + /// public abstract bool IsNullable { get; } /// Retrieves the {@link Joinable} defining the associated entity. @@ -530,6 +534,10 @@ public object LoadByUniqueKey(string entityName, string uniqueKeyPropertyName, o if (result == null) { result = persister.LoadByUniqueKey(uniqueKeyPropertyName, key, session); + if (result == null && !IsNullable) + { + factory.EntityNotFoundDelegate.HandleEntityNotFound(entityName, uniqueKeyPropertyName, key); + } } return result == null ? null : persistenceContext.ProxyFor(result); } diff --git a/src/NHibernate/Type/EnumCharType.cs b/src/NHibernate/Type/EnumCharType.cs index 0fd8d3cf23c..ece3684ff6f 100644 --- a/src/NHibernate/Type/EnumCharType.cs +++ b/src/NHibernate/Type/EnumCharType.cs @@ -8,7 +8,6 @@ namespace NHibernate.Type [Serializable] public partial class EnumCharType : AbstractEnumType { - public EnumCharType() : base(new StringFixedLengthSqlType(1),typeof(T)) { } @@ -94,7 +93,6 @@ public virtual object GetValue(object instance) } } - public override void Set(DbCommand cmd, object value, int index, ISessionImplementor session) { var par = cmd.Parameters[index]; diff --git a/src/NHibernate/Type/GenericIdentifierBagType.cs b/src/NHibernate/Type/GenericIdentifierBagType.cs index 0d9938e4711..85888c268ed 100644 --- a/src/NHibernate/Type/GenericIdentifierBagType.cs +++ b/src/NHibernate/Type/GenericIdentifierBagType.cs @@ -75,7 +75,6 @@ public override object Instantiate(int anticipatedSize) return anticipatedSize <= 0 ? new List() : new List(anticipatedSize + 1); } - protected override void Clear(object collection) { ((IList)collection).Clear(); diff --git a/src/NHibernate/Type/GenericMapType.cs b/src/NHibernate/Type/GenericMapType.cs index d7cda3f36b3..b7c4114906c 100644 --- a/src/NHibernate/Type/GenericMapType.cs +++ b/src/NHibernate/Type/GenericMapType.cs @@ -46,7 +46,6 @@ public override System.Type ReturnedClass get { return typeof(IDictionary); } } - public override IEnumerable GetElementsIterator(object collection) { return ((IDictionary) collection).Values; diff --git a/src/NHibernate/Type/IAbstractComponentType.cs b/src/NHibernate/Type/IAbstractComponentType.cs index ee1721093c3..1c8f0f1138a 100644 --- a/src/NHibernate/Type/IAbstractComponentType.cs +++ b/src/NHibernate/Type/IAbstractComponentType.cs @@ -42,8 +42,8 @@ public partial interface IAbstractComponentType : IType FetchMode GetFetchMode(int i); - bool IsEmbedded { get;} + bool IsEmbedded { get; } bool IsMethodOf(MethodBase method); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Type/IType.cs b/src/NHibernate/Type/IType.cs index 595084d9a67..675cab147d9 100644 --- a/src/NHibernate/Type/IType.cs +++ b/src/NHibernate/Type/IType.cs @@ -14,7 +14,7 @@ public static int GetOwnerColumnSpan(this IType type, IMapping sessionFactory) : type.GetColumnSpan(sessionFactory); } } - + /// /// Defines a mapping from a .NET to a SQL data-type. /// This interface is intended to be implemented by applications that need custom types. diff --git a/src/NHibernate/Type/ImmutableType.cs b/src/NHibernate/Type/ImmutableType.cs index 18f047a68de..a2f0d5bf5bf 100644 --- a/src/NHibernate/Type/ImmutableType.cs +++ b/src/NHibernate/Type/ImmutableType.cs @@ -43,6 +43,5 @@ public override object DeepCopy(object value, ISessionFactoryImplementor factory { return value; } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Type/LocalDateType.cs b/src/NHibernate/Type/LocalDateType.cs new file mode 100644 index 00000000000..d6fadaec31b --- /dev/null +++ b/src/NHibernate/Type/LocalDateType.cs @@ -0,0 +1,17 @@ +using System; +using System.Data; + +namespace NHibernate.Type +{ + /// + /// Maps the Year, Month, and Day of a Property to a + /// column. Specify when reading + /// dates from . + /// + [Serializable] + public class LocalDateType : DateType + { + /// + protected override DateTimeKind Kind => DateTimeKind.Local; + } +} diff --git a/src/NHibernate/Type/ManyToOneType.cs b/src/NHibernate/Type/ManyToOneType.cs index 8e3a97dfc41..b38fb35eba8 100644 --- a/src/NHibernate/Type/ManyToOneType.cs +++ b/src/NHibernate/Type/ManyToOneType.cs @@ -34,10 +34,8 @@ public ManyToOneType(string className, bool lazy) public ManyToOneType(string entityName, string uniqueKeyPropertyName, bool lazy, bool unwrapProxy, bool ignoreNotFound, bool isLogicalOneToOne) : this(entityName, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne, null) { - } - public ManyToOneType(string entityName, string uniqueKeyPropertyName, bool lazy, bool unwrapProxy, bool ignoreNotFound, bool isLogicalOneToOne, string propertyName) : base(entityName, uniqueKeyPropertyName, !lazy, unwrapProxy) { @@ -236,7 +234,7 @@ public override bool IsDirty(object old, object current, bool[] checkable, ISess return IsDirtyManyToOne(old, current, IsAlwaysDirtyChecked ? null : checkable, session); } - + /// public override bool IsNullable { get { return ignoreNotFound; } diff --git a/src/NHibernate/Type/MutableType.cs b/src/NHibernate/Type/MutableType.cs index 2322eb1dc81..862871f4cf7 100644 --- a/src/NHibernate/Type/MutableType.cs +++ b/src/NHibernate/Type/MutableType.cs @@ -47,6 +47,5 @@ public override object DeepCopy(object value, ISessionFactoryImplementor factory { return (value == null) ? null : DeepCopyNotNull(value); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Type/NullableType.cs b/src/NHibernate/Type/NullableType.cs index e84b6df48da..883894ee971 100644 --- a/src/NHibernate/Type/NullableType.cs +++ b/src/NHibernate/Type/NullableType.cs @@ -377,6 +377,15 @@ public override int GetHashCode() return (SqlType.GetHashCode() / 2) + (Name.GetHashCode() / 2); } + /// + /// Provides a more descriptive string representation by reporting the properties that are important for equality. + /// Useful in error messages. + /// + public override string ToString() + { + return $"{base.ToString()} (SqlType: {SqlType})"; + } + #endregion } } diff --git a/src/NHibernate/Type/OneToOneType.cs b/src/NHibernate/Type/OneToOneType.cs index f55ec0c2ac3..0837ea3b98a 100644 --- a/src/NHibernate/Type/OneToOneType.cs +++ b/src/NHibernate/Type/OneToOneType.cs @@ -122,6 +122,7 @@ public override object Hydrate(DbDataReader rs, string[] names, ISessionImplemen return identifier; } + /// public override bool IsNullable { get { return foreignKeyDirection.Equals(ForeignKeyDirection.ForeignKeyToParent); } diff --git a/src/NHibernate/Type/PersistentEnumType.cs b/src/NHibernate/Type/PersistentEnumType.cs index 9fca2648cc8..51c10a4313f 100644 --- a/src/NHibernate/Type/PersistentEnumType.cs +++ b/src/NHibernate/Type/PersistentEnumType.cs @@ -221,7 +221,6 @@ public virtual object GetValue(object code) return converter.ToEnumValue(code); } - public override void Set(DbCommand cmd, object value, int index, ISessionImplementor session) { cmd.Parameters[index].Value = value != null ? GetValue(value) : DBNull.Value; diff --git a/src/NHibernate/Type/PrimitiveType.cs b/src/NHibernate/Type/PrimitiveType.cs index 879d6b39715..7b452db3481 100644 --- a/src/NHibernate/Type/PrimitiveType.cs +++ b/src/NHibernate/Type/PrimitiveType.cs @@ -17,9 +17,9 @@ public abstract class PrimitiveType : ImmutableType, ILiteralType protected PrimitiveType(SqlType sqlType) : base(sqlType) {} - public abstract System.Type PrimitiveClass { get;} + public abstract System.Type PrimitiveClass { get; } - public abstract object DefaultValue { get;} + public abstract object DefaultValue { get; } #region ILiteralType Members diff --git a/src/NHibernate/Type/SingleType.cs b/src/NHibernate/Type/SingleType.cs index bf719e3832b..70ca434e04d 100644 --- a/src/NHibernate/Type/SingleType.cs +++ b/src/NHibernate/Type/SingleType.cs @@ -62,7 +62,7 @@ public override System.Type ReturnedClass public override void Set(DbCommand rs, object value, int index, ISessionImplementor session) { - rs.Parameters[index].Value = value; + rs.Parameters[index].Value = Convert.ToSingle(value); } // Since 5.2 diff --git a/src/NHibernate/Type/TimeAsTimeSpanType.cs b/src/NHibernate/Type/TimeAsTimeSpanType.cs index 51fa6745b57..e525ecfa555 100644 --- a/src/NHibernate/Type/TimeAsTimeSpanType.cs +++ b/src/NHibernate/Type/TimeAsTimeSpanType.cs @@ -43,10 +43,13 @@ public override object Get(DbDataReader rs, int index, ISessionImplementor sessi try { var value = rs[index]; - if(value is TimeSpan time) //For those dialects where DbType.Time means TimeSpan. + if (value is TimeSpan time) //For those dialects where DbType.Time means TimeSpan. return time; - - return ((DateTime)value).TimeOfDay; + + // Todo: investigate if this convert should be made culture invariant, here and in other NHibernate types, + // such as AbstractDateTimeType and TimeType, or even in all other places doing such converts in NHibernate. + var dbValue = Convert.ToDateTime(value); + return dbValue.TimeOfDay; } catch (Exception ex) { diff --git a/src/NHibernate/Type/TypeFactory.cs b/src/NHibernate/Type/TypeFactory.cs index 3ce5f04001f..271b85ff4ea 100644 --- a/src/NHibernate/Type/TypeFactory.cs +++ b/src/NHibernate/Type/TypeFactory.cs @@ -91,9 +91,9 @@ private enum TypeClassification private static readonly ConcurrentDictionary getTypeDelegatesWithPrecision = new ConcurrentDictionary(); - private delegate NullableType GetNullableTypeWithLengthOrScale(int lengthOrScale); // Func + public delegate NullableType GetNullableTypeWithLengthOrScale(int lengthOrScale); // Func - private delegate NullableType GetNullableTypeWithPrecision(byte precision, byte scale); + public delegate NullableType GetNullableTypeWithPrecision(byte precision, byte scale); private delegate NullableType NullableTypeCreatorDelegate(SqlType sqlType); @@ -113,22 +113,66 @@ public static void RegisterType(System.Type systemType, IType nhibernateType, IE RegisterType(nhibernateType, typeAliases); } - private static void RegisterType(System.Type systemType, IType nhibernateType, - IEnumerable aliases, GetNullableTypeWithLengthOrScale ctorLengthOrScale) + /// + /// Defines which NHibernate type should be chosen by default for handling a given .Net type. + /// This must be done before any operation on NHibernate, including building its + /// and building session factory. Otherwise the behavior will be undefined. + /// + /// The .Net type. + /// The NHibernate type. + /// The additional aliases to map to the type. Use if none. + /// The factory method to create the NHibernate type using length or scale. + public static void RegisterType( + System.Type systemType, + IType nhibernateType, + IEnumerable aliases, + GetNullableTypeWithLengthOrScale ctorLengthOrScale) + { + RegisterType(systemType, nhibernateType, aliases, ctorLengthOrScale, true); + } + + private static void RegisterType( + System.Type systemType, + IType nhibernateType, + IEnumerable aliases, + GetNullableTypeWithLengthOrScale ctorLengthOrScale, + bool @override) { var typeAliases = new List(aliases); typeAliases.AddRange(GetClrTypeAliases(systemType)); - RegisterType(nhibernateType, typeAliases, ctorLengthOrScale); + RegisterType(nhibernateType, typeAliases, ctorLengthOrScale, @override); } - private static void RegisterType(System.Type systemType, IType nhibernateType, - IEnumerable aliases, GetNullableTypeWithPrecision ctorPrecision) + /// + /// Defines which NHibernate type should be chosen by default for handling a given .Net type. + /// This must be done before any operation on NHibernate, including building its + /// and building session factory. Otherwise the behavior will be undefined. + /// + /// The .Net type. + /// The NHibernate type. + /// The additional aliases to map to the type. Use if none. + /// The factory method to create the NHibernate type using precision. + public static void RegisterType( + System.Type systemType, + IType nhibernateType, + IEnumerable aliases, + GetNullableTypeWithPrecision ctorPrecision) + { + RegisterType(systemType, nhibernateType, aliases, ctorPrecision, true); + } + + private static void RegisterType( + System.Type systemType, + IType nhibernateType, + IEnumerable aliases, + GetNullableTypeWithPrecision ctorPrecision, + bool @override) { var typeAliases = new List(aliases); typeAliases.AddRange(GetClrTypeAliases(systemType)); - RegisterType(nhibernateType, typeAliases, ctorPrecision); + RegisterType(nhibernateType, typeAliases, ctorPrecision, @override); } private static IEnumerable GetClrTypeAliases(System.Type systemType) @@ -158,28 +202,38 @@ private static void RegisterType(IType nhibernateType, IEnumerable alias } } - private static void RegisterType(IType nhibernateType, IEnumerable aliases, GetNullableTypeWithLengthOrScale ctorLengthOrScale) + private static void RegisterType(IType nhibernateType, IEnumerable aliases, GetNullableTypeWithLengthOrScale ctorLengthOrScale, bool @override = false) { var typeAliases = new List(aliases) { nhibernateType.Name }; foreach (var alias in typeAliases) { RegisterTypeAlias(nhibernateType, alias); - if (!_getTypeDelegatesWithLengthOrScale.TryAdd(alias, ctorLengthOrScale)) + if (@override) + { + _getTypeDelegatesWithLengthOrScale[alias] = ctorLengthOrScale; + } + else if (!_getTypeDelegatesWithLengthOrScale.TryAdd(alias, ctorLengthOrScale)) { - throw new HibernateException("An item with the same key has already been added to getTypeDelegatesWithLength."); + throw new HibernateException( + "An item with the same key has already been added to getTypeDelegatesWithLength."); } } } - private static void RegisterType(IType nhibernateType, IEnumerable aliases, GetNullableTypeWithPrecision ctorPrecision) + private static void RegisterType(IType nhibernateType, IEnumerable aliases, GetNullableTypeWithPrecision ctorPrecision, bool @override = false) { var typeAliases = new List(aliases) { nhibernateType.Name }; foreach (var alias in typeAliases) { RegisterTypeAlias(nhibernateType, alias); - if (!getTypeDelegatesWithPrecision.TryAdd(alias, ctorPrecision)) + if (@override) { - throw new HibernateException("An item with the same key has already been added to getTypeDelegatesWithPrecision."); + getTypeDelegatesWithPrecision[alias] = ctorPrecision; + } + else if (!getTypeDelegatesWithPrecision.TryAdd(alias, ctorPrecision)) + { + throw new HibernateException( + "An item with the same key has already been added to getTypeDelegatesWithPrecision."); } } } @@ -204,13 +258,31 @@ private static void RegisterTypeAlias(IType nhibernateType, string alias) /// static TypeFactory() { - // set up the mappings of .NET Classes/Structs to their NHibernate types. + RegisterTypes(); + } + + private static void RegisterTypes() + { + // set up the mappings of .NET Classes/Structs to their NHibernate types. RegisterDefaultNetTypes(); // add the mappings of the NHibernate specific names that are used in type="" RegisterBuiltInTypes(); } + /// + /// Clears all custom type registrations and re-register all default NHibernate types + /// + public static void ClearCustomRegistrations() + { + typeByTypeOfName.Clear(); + _obsoleteMessageByAlias.Clear(); + _getTypeDelegatesWithLengthOrScale.Clear(); + getTypeDelegatesWithPrecision.Clear(); + + RegisterTypes(); + } + /// /// Register other Default .NET type /// @@ -221,39 +293,47 @@ private static void RegisterDefaultNetTypes() { // NOTE: each .NET type should appear only one time RegisterType(typeof (Byte[]), NHibernateUtil.Binary, new[] {"binary"}, - l => GetType(NHibernateUtil.Binary, l, len => new BinaryType(SqlTypeFactory.GetBinary(len)))); + l => GetType(NHibernateUtil.Binary, l, len => new BinaryType(SqlTypeFactory.GetBinary(len))), + false); - RegisterType(typeof(Boolean), NHibernateUtil.Boolean, new[] { "boolean", "bool" }); + RegisterType(typeof (Boolean), NHibernateUtil.Boolean, new[] { "boolean", "bool" }); RegisterType(typeof (Byte), NHibernateUtil.Byte, new[]{ "byte"}); RegisterType(typeof (Char), NHibernateUtil.Character, new[] {"character", "char"}); RegisterType(typeof (CultureInfo), NHibernateUtil.CultureInfo, new[]{ "locale"}); - RegisterType(typeof(DateTime), NHibernateUtil.DateTime, new[] { "datetime" }, - s => GetType(NHibernateUtil.DateTime, s, scale => new DateTimeType(SqlTypeFactory.GetDateTime((byte)scale)))); + RegisterType(typeof (DateTime), NHibernateUtil.DateTime, new[] { "datetime" }, + s => GetType(NHibernateUtil.DateTime, s, scale => new DateTimeType(SqlTypeFactory.GetDateTime((byte)scale))), + false); RegisterType(typeof (DateTimeOffset), NHibernateUtil.DateTimeOffset, new[]{ "datetimeoffset"}, - s => GetType(NHibernateUtil.DateTimeOffset, s, scale => new DateTimeOffsetType(SqlTypeFactory.GetDateTimeOffset((byte)scale)))); + s => GetType(NHibernateUtil.DateTimeOffset, s, scale => new DateTimeOffsetType(SqlTypeFactory.GetDateTimeOffset((byte)scale))), + false); RegisterType(typeof (Decimal), NHibernateUtil.Decimal, new[] {"big_decimal", "decimal"}, - (p, s) => GetType(NHibernateUtil.Decimal, p, s, st => new DecimalType(st))); + (p, s) => GetType(NHibernateUtil.Decimal, p, s, st => new DecimalType(st)), + false); RegisterType(typeof (Double), NHibernateUtil.Double, new[] {"double"}, - (p, s) => GetType(NHibernateUtil.Double, p, s, st => new DoubleType(st))); + (p, s) => GetType(NHibernateUtil.Double, p, s, st => new DoubleType(st)), + false); RegisterType(typeof (Guid), NHibernateUtil.Guid, new[]{ "guid"}); RegisterType(typeof (Int16), NHibernateUtil.Int16, new[]{ "short"}); RegisterType(typeof (Int32), NHibernateUtil.Int32, new[] {"integer", "int"}); RegisterType(typeof (Int64), NHibernateUtil.Int64, new[]{ "long"}); - RegisterType(typeof(SByte), NHibernateUtil.SByte, EmptyAliases); + RegisterType(typeof (SByte), NHibernateUtil.SByte, EmptyAliases); RegisterType(typeof (Single), NHibernateUtil.Single, new[] {"float", "single"}, - (p, s) => GetType(NHibernateUtil.Single, p, s, st => new SingleType(st))); + (p, s) => GetType(NHibernateUtil.Single, p, s, st => new SingleType(st)), + false); RegisterType(typeof (String), NHibernateUtil.String, new[] {"string"}, - l => GetType(NHibernateUtil.String, l, len => new StringType(SqlTypeFactory.GetString(len)))); + l => GetType(NHibernateUtil.String, l, len => new StringType(SqlTypeFactory.GetString(len))), + false); RegisterType(typeof (TimeSpan), NHibernateUtil.TimeSpan, new[] {"timespan"}); RegisterType(typeof (System.Type), NHibernateUtil.Class, new[] {"class"}, - l => GetType(NHibernateUtil.Class, l, len => new TypeType(SqlTypeFactory.GetString(len)))); + l => GetType(NHibernateUtil.Class, l, len => new TypeType(SqlTypeFactory.GetString(len))), + false); RegisterType(typeof (UInt16), NHibernateUtil.UInt16, new[] {"ushort"}); RegisterType(typeof (UInt32), NHibernateUtil.UInt32, new[] {"uint"}); @@ -263,7 +343,7 @@ private static void RegisterDefaultNetTypes() RegisterType(typeof (Uri), NHibernateUtil.Uri, new[] {"uri", "url"}); - RegisterType(typeof(XDocument), NHibernateUtil.XDoc, new[] { "xdoc", "xdocument" }); + RegisterType(typeof (XDocument), NHibernateUtil.XDoc, new[] { "xdoc", "xdocument" }); // object needs to have both class and serializable setup before it can // be created. @@ -449,7 +529,6 @@ public static IType Basic(string name, IDictionary parameters) returnType = BuiltInType(typeName, length); } - else { // it is not in the basicNameMap and typeByTypeOfName @@ -574,7 +653,7 @@ public static IType HeuristicType(string typeName, IDictionary p if (typeClassification == TypeClassification.LengthOrScale) { parsedTypeName = typeName.Split(LengthSplit); - if (!int.TryParse(parsedTypeName[1], out int parsedLength)) + if (!Int32.TryParse(parsedTypeName[1], out int parsedLength)) { throw new MappingException($"Could not parse length value '{parsedTypeName[1]}' as int for type '{typeName}'"); } @@ -897,7 +976,6 @@ public static CollectionType Array(string role, string propertyRef, System.Type return CollectionTypeFactory.Array(role, propertyRef, elementClass); } - public static CollectionType GenericBag(string role, string propertyRef, System.Type elementClass) { MethodInfo mi = BagDefinition.MakeGenericMethod(elementClass); @@ -992,4 +1070,4 @@ public static void InjectParameters(Object type, IDictionary par } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/ADOExceptionReporter.cs b/src/NHibernate/Util/ADOExceptionReporter.cs index 3e2b787c6b2..61eca2c9caf 100644 --- a/src/NHibernate/Util/ADOExceptionReporter.cs +++ b/src/NHibernate/Util/ADOExceptionReporter.cs @@ -1,6 +1,5 @@ using System; - namespace NHibernate.Util { public class ADOExceptionReporter diff --git a/src/NHibernate/Util/ArrayHelper.cs b/src/NHibernate/Util/ArrayHelper.cs index 00da5847f46..f078cfa8607 100644 --- a/src/NHibernate/Util/ArrayHelper.cs +++ b/src/NHibernate/Util/ArrayHelper.cs @@ -97,7 +97,6 @@ public static string ToString(object[] array) return sb.ToString(); } - /// /// Append all elements in the 'from' list to the 'to' list. /// @@ -181,6 +180,15 @@ public static int CountTrue(bool[] array) return array.Count(t => t); } + internal static IEnumerable IndexesOf(T[] array, T value) + { + for (int i = 0; i < array.Length; i++) + { + if (EqualityComparer.Default.Equals(array[i], value)) + yield return i; + } + } + public static bool ArrayEquals(T[] a, T[] b) { return ArrayComparer.Default.Equals(a, b); @@ -204,7 +212,6 @@ public static int ArrayGetHashCode(T[] array) return ArrayComparer.Default.GetHashCode(array); } - /// /// Append a value to an array. /// diff --git a/src/NHibernate/Util/AsyncLock.cs b/src/NHibernate/Util/AsyncLock.cs index f322d48f175..8a6f00bc95f 100644 --- a/src/NHibernate/Util/AsyncLock.cs +++ b/src/NHibernate/Util/AsyncLock.cs @@ -8,24 +8,32 @@ namespace NHibernate.Util public sealed class AsyncLock { private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - private readonly Task _releaser; + private readonly IDisposable _releaser; + private readonly Task _releaserTask; public AsyncLock() { - _releaser = Task.FromResult((IDisposable)new Releaser(this)); + _releaser = new Releaser(this); + _releaserTask = Task.FromResult(_releaser); } public Task LockAsync() { var wait = _semaphore.WaitAsync(); return wait.IsCompleted ? - _releaser : + _releaserTask : wait.ContinueWith( (_, state) => (IDisposable)state, - _releaser.Result, CancellationToken.None, + _releaser, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } + public IDisposable Lock() + { + _semaphore.Wait(); + return _releaser; + } + private sealed class Releaser : IDisposable { private readonly AsyncLock _toRelease; @@ -33,4 +41,4 @@ private sealed class Releaser : IDisposable public void Dispose() { _toRelease._semaphore.Release(); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/AsyncReaderWriterLock.cs b/src/NHibernate/Util/AsyncReaderWriterLock.cs new file mode 100644 index 00000000000..bd533bc4172 --- /dev/null +++ b/src/NHibernate/Util/AsyncReaderWriterLock.cs @@ -0,0 +1,254 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NHibernate.Util +{ + // Idea from: + // https://github.com/kpreisser/AsyncReaderWriterLockSlim + // https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-7-asyncreaderwriterlock/ + internal class AsyncReaderWriterLock : IDisposable + { + private readonly SemaphoreSlim _writeLockSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _readLockSemaphore = new SemaphoreSlim(0, 1); + private readonly Releaser _writerReleaser; + private readonly Releaser _readerReleaser; + private readonly Task _readerReleaserTask; + private SemaphoreSlim _waitingReadLockSemaphore; + private SemaphoreSlim _waitingDisposalSemaphore; + private int _readersWaiting; + private int _currentReaders; + private int _writersWaiting; + private bool _disposed; + + public AsyncReaderWriterLock() + { + _writerReleaser = new Releaser(this, true); + _readerReleaser = new Releaser(this, false); + _readerReleaserTask = Task.FromResult(_readerReleaser); + } + + internal int CurrentReaders => _currentReaders; + + internal int WritersWaiting => _writersWaiting; + + internal int ReadersWaiting => _readersWaiting; + + internal bool Writing => _currentReaders == 0 && _writeLockSemaphore.CurrentCount == 0; + + internal bool AcquiredWriteLock => _writeLockSemaphore.CurrentCount == 0; + + public Releaser WriteLock() + { + if (!CanEnterWriteLock(out var waitForReadLocks)) + { + _writeLockSemaphore.Wait(); + lock (_writeLockSemaphore) + { + _writersWaiting--; + } + } + + if (waitForReadLocks) + { + _readLockSemaphore.Wait(); + } + + DisposeWaitingSemaphore(); + + return _writerReleaser; + } + + public async Task WriteLockAsync() + { + if (!CanEnterWriteLock(out var waitForReadLocks)) + { + await _writeLockSemaphore.WaitAsync().ConfigureAwait(false); + lock (_writeLockSemaphore) + { + _writersWaiting--; + } + } + + if (waitForReadLocks) + { + await _readLockSemaphore.WaitAsync().ConfigureAwait(false); + } + + DisposeWaitingSemaphore(); + + return _writerReleaser; + } + + public Releaser ReadLock() + { + if (CanEnterReadLock(out var waitingReadLockSemaphore)) + { + return _readerReleaser; + } + + waitingReadLockSemaphore.Wait(); + + return _readerReleaser; + } + + public Task ReadLockAsync() + { + return CanEnterReadLock(out var waitingReadLockSemaphore) ? _readerReleaserTask : ReadLockInternalAsync(); + + async Task ReadLockInternalAsync() + { + await waitingReadLockSemaphore.WaitAsync().ConfigureAwait(false); + + return _readerReleaser; + } + } + + public void Dispose() + { + lock (_writeLockSemaphore) + { + _writeLockSemaphore.Dispose(); + _readLockSemaphore.Dispose(); + _waitingReadLockSemaphore?.Dispose(); + _waitingDisposalSemaphore?.Dispose(); + _disposed = true; + } + } + + private bool CanEnterWriteLock(out bool waitForReadLocks) + { + waitForReadLocks = false; + lock (_writeLockSemaphore) + { + AssertNotDisposed(); + if (_writeLockSemaphore.CurrentCount > 0 && _writeLockSemaphore.Wait(0)) + { + waitForReadLocks = _currentReaders > 0; + return true; + } + + _writersWaiting++; + } + + return false; + } + + private void ExitWriteLock() + { + lock (_writeLockSemaphore) + { + AssertNotDisposed(); + if (_writeLockSemaphore.CurrentCount == 1) + { + throw new InvalidOperationException(); + } + + // Writers have the highest priority even if they came last + if (_writersWaiting > 0 || _waitingReadLockSemaphore == null) + { + _writeLockSemaphore.Release(); + return; + } + + if (_readersWaiting > 0) + { + _currentReaders += _readersWaiting; + _waitingReadLockSemaphore.Release(_readersWaiting); + _readersWaiting = 0; + // We have to dispose the waiting read lock only after all readers finished using it + _waitingDisposalSemaphore = _waitingReadLockSemaphore; + _waitingReadLockSemaphore = null; + } + + _writeLockSemaphore.Release(); + } + } + + private bool CanEnterReadLock(out SemaphoreSlim waitingReadLockSemaphore) + { + lock (_writeLockSemaphore) + { + AssertNotDisposed(); + if (_writersWaiting == 0 && _writeLockSemaphore.CurrentCount > 0) + { + _currentReaders++; + waitingReadLockSemaphore = null; + + return true; + } + + if (_waitingReadLockSemaphore == null) + { + _waitingReadLockSemaphore = new SemaphoreSlim(0); + } + + _readersWaiting++; + waitingReadLockSemaphore = _waitingReadLockSemaphore; + + return false; + } + } + + private void ExitReadLock() + { + lock (_writeLockSemaphore) + { + AssertNotDisposed(); + if (_currentReaders == 0) + { + throw new InvalidOperationException(); + } + + _currentReaders--; + if (_currentReaders == 0 && _writeLockSemaphore.CurrentCount == 0) + { + _readLockSemaphore.Release(); + } + } + } + + private void DisposeWaitingSemaphore() + { + _waitingDisposalSemaphore?.Dispose(); + _waitingDisposalSemaphore = null; + } + + private void AssertNotDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(AsyncReaderWriterLock)); + } + } + + public struct Releaser : IDisposable + { + private readonly AsyncReaderWriterLock _toRelease; + private readonly bool _writer; + + internal Releaser(AsyncReaderWriterLock toRelease, bool writer) + { + _toRelease = toRelease; + _writer = writer; + } + + public void Dispose() + { + if (_toRelease == null) + { + return; + } + + if (_writer) + { + _toRelease.ExitWriteLock(); + } + else + { + _toRelease.ExitReadLock(); + } + } + } + } +} diff --git a/src/NHibernate/Util/CollectionHelper.cs b/src/NHibernate/Util/CollectionHelper.cs index 52e1ef8b12a..e6ed9842065 100644 --- a/src/NHibernate/Util/CollectionHelper.cs +++ b/src/NHibernate/Util/CollectionHelper.cs @@ -733,6 +733,16 @@ public static bool DictionaryEquals(IDictionary m1, IDictionary.Default) : m1.All(kv => m2.TryGetValue(kv.Key, out var value) && comparer.Equals(kv.Value, value))); + //It's added to make use of optimized .NET Core Dictionary.Remove(key, out value) method + internal static bool Remove(this Dictionary dic, TKey key, out TValue value) + { + if (!dic.TryGetValue(key, out value)) + return false; + + dic.Remove(key); + return true; + } + private static bool? FastCheckEquality(IEnumerable c1, IEnumerable c2) { if (c1 == c2) diff --git a/src/NHibernate/Util/CollectionPrinter.cs b/src/NHibernate/Util/CollectionPrinter.cs index 7e82d222a3f..b845d503062 100644 --- a/src/NHibernate/Util/CollectionPrinter.cs +++ b/src/NHibernate/Util/CollectionPrinter.cs @@ -92,6 +92,5 @@ public static string ToString(IEnumerable elements) result.Append("]"); return result.ToString(); } - } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/DbTypeExtensions.cs b/src/NHibernate/Util/DbTypeExtensions.cs new file mode 100644 index 00000000000..5f56a0d56c8 --- /dev/null +++ b/src/NHibernate/Util/DbTypeExtensions.cs @@ -0,0 +1,14 @@ +using System.Data; + +namespace NHibernate.Util +{ + internal static class DbTypeExtensions + { + /// + /// Checks whether the type is a , , or + /// + /// + /// + public static bool IsStringType(this DbType dbType) => dbType == DbType.String || dbType == DbType.AnsiString || dbType == DbType.StringFixedLength || dbType == DbType.AnsiStringFixedLength; + } +} diff --git a/src/NHibernate/Util/EnumerableExtensions.cs b/src/NHibernate/Util/EnumerableExtensions.cs index 505fb0e7f62..6ec596ffe97 100644 --- a/src/NHibernate/Util/EnumerableExtensions.cs +++ b/src/NHibernate/Util/EnumerableExtensions.cs @@ -5,8 +5,7 @@ namespace NHibernate.Util { - //Since v5.1 - [Obsolete("This class has no more usages and will be removed in next major version.")] + //TODO 6.0: Make internal public static class EnumerableExtensions { //Since v5.1 @@ -39,5 +38,84 @@ public static void ForEach(this IEnumerable query, Action method) method(item); } } + + internal static TOutput[] ToArray(this ICollection input, Func converter) + { + var results = new TOutput[input.Count]; + + int i = 0; + foreach (var value in input) + { + results[i++] = converter(value); + } + + return results; + } + + internal static TOutput[] ToArray(this List input, Func converter) + { + var results = new TOutput[input.Count]; + + for (var i = 0; i < input.Count; i++) + { + results[i] = converter(input[i]); + } + + return results; + } + + internal static TOutput[] ToArray(this TInput[] input, Converter converter) + { + return Array.ConvertAll(input, converter); + } + + internal static List ToList(this ICollection input, Func converter) + { + var results = new List(input.Count); + + foreach (var value in input) + { + results.Add(converter(value)); + } + + return results; + } + + internal static List ToList(this TInput[] input, Func converter) + { + var results = new List(input.Length); + + foreach (var value in input) + { + results.Add(converter(value)); + } + + return results; + } + + internal static List ToList(this List input, Converter converter) + { + return input.ConvertAll(converter); + } + + internal static IList ToIList(this IEnumerable list) + { + return list as IList ?? list.ToList(); + } + + internal static IReadOnlyList EmptyIfNull(this IReadOnlyList list) + { + return list ?? Array.Empty(); + } + + internal static IEnumerable CastOrDefault(this IEnumerable list) + { + foreach (var obj in list) + { + yield return obj == null + ? default(T) + : (T) obj; + } + } } } diff --git a/src/NHibernate/Util/ExpressionsHelper.cs b/src/NHibernate/Util/ExpressionsHelper.cs index 6fa3a44615f..6f9f290aa72 100644 --- a/src/NHibernate/Util/ExpressionsHelper.cs +++ b/src/NHibernate/Util/ExpressionsHelper.cs @@ -1,6 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System; +using System.Runtime.CompilerServices; +using NHibernate.Engine; +using NHibernate.Linq; +using NHibernate.Linq.Clauses; +using NHibernate.Linq.Expressions; +using NHibernate.Linq.Functions; +using NHibernate.Linq.Visitors; +using NHibernate.Persister.Collection; +using NHibernate.Persister.Entity; +using NHibernate.Type; +using Remotion.Linq.Clauses; +using Remotion.Linq.Clauses.Expressions; +using Remotion.Linq.Parsing; namespace NHibernate.Util { @@ -15,5 +31,874 @@ public static MemberInfo DecodeMemberAccessExpression(Expressi } return ((MemberExpression)expression.Body).Member; } + +#if NETCOREAPP2_0 + /// + /// Try to retrieve from a reduced expression. + /// + /// The reduced dynamic expression. + /// The out binder parameter. + /// Whether the binder was found. + internal static bool TryGetDynamicMemberBinder(InvocationExpression expression, out GetMemberBinder memberBinder) + { + // This is an ugly workaround for dynamic expressions in .NET Core. In .NET Core a dynamic expression is reduced + // when first visited by a expression visitor that is not a DynamicExpressionVisitor, where in .NET Framework it is never reduced. + // As RelinqExpressionVisitor does not extend DynamicExpressionVisitor, we will always have a reduced dynamic expression in .NET Core. + // Unfortunately we can not tap into the expression tree earlier to intercept the dynamic expression + if (expression.Arguments.Count == 2 && + expression.Arguments[0] is ConstantExpression constant && + constant.Value is CallSite site && + site.Binder is GetMemberBinder binder) + { + memberBinder = binder; + return true; + } + + memberBinder = null; + return false; + } +#endif + + /// + /// Check whether the given expression represent a variable. + /// + /// The expression to check. + /// The path of the variable. + /// The closure context where the variable is stored. + /// Whether the expression represents a variable. + internal static bool IsVariable(Expression expression, out string path, out object closureContext) + { + Expression childExpression; + string currentPath; + switch (expression) + { + case MemberExpression memberExpression: + childExpression = memberExpression.Expression; + currentPath = memberExpression.Member.Name; + break; + case ConstantExpression constantExpression: + path = null; + if (constantExpression.Type.Attributes.HasFlag(TypeAttributes.NestedPrivate) && + Attribute.IsDefined(constantExpression.Type, typeof(CompilerGeneratedAttribute), inherit: true)) + { + closureContext = constantExpression.Value; + return true; + } + + closureContext = null; + return false; + default: + path = null; + closureContext = null; + return false; + } + + if (!IsVariable(childExpression, out path, out closureContext)) + { + return false; + } + + path = path != null ? $"{currentPath}_{path}" : currentPath; + return true; + } + + /// + /// Get the mapped type for the given expression. + /// + /// The query parameters. + /// The expression. + /// The mapped type of the expression or when the mapped type was not + /// found and the type is . + internal static IType GetType(VisitorParameters parameters, Expression expression) + { + if (expression is ConstantExpression constantExpression && + parameters.ConstantToParameterMap.TryGetValue(constantExpression, out var param)) + { + return param.Type; + } + + if (TryGetMappedType(parameters.SessionFactory, expression, out var type, out _, out _, out _)) + { + return type; + } + + return expression.Type == typeof(object) ? null : TypeFactory.HeuristicType(expression.Type); + } + + /// + /// Try to get the mapped nullability from the given expression. + /// + /// The session factory. + /// The expression to evaluate. + /// Output parameter that represents whether the is nullable. + /// Whether the mapped nullability was found. + internal static bool TryGetMappedNullability( + ISessionFactoryImplementor sessionFactory, + Expression expression, + out bool nullable) + { + if (!TryGetMappedType( + sessionFactory, + expression, + out _, + out var entityPersister, + out var componentType, + out var memberPath)) + { + nullable = false; + return false; + } + + // The source entity is always not null, as it gets translated to the entity identifier + if (memberPath == null) + { + nullable = false; + return true; + } + + int index; + if (componentType != null) + { + index = Array.IndexOf( + componentType.PropertyNames, + memberPath.Substring(memberPath.LastIndexOf('.') + 1)); + nullable = componentType.PropertyNullability[index]; + return true; + } + + if (entityPersister.EntityMetamodel.GetIdentifierPropertyType(memberPath) != null) + { + nullable = false; // Identifier is always not-null + return true; + } + + index = entityPersister.EntityMetamodel.GetPropertyIndex(memberPath); + nullable = entityPersister.PropertyNullability[index]; + return true; + } + + /// + /// Try to get the mapped type from the given expression. When the type is + /// , the will be set based on the expression type + /// only when the mapping for was found, otherwise + /// will be returned. + /// + /// The session factory to retrieve types. + /// The expression to evaluate. + /// Output parameter that represents the mapped type of . + /// + /// Output parameter that represents the entity persister of the entity where is defined. + /// This parameter will not be set when represents a property in a collection composite element. + /// + /// + /// Output parameter that represents the component type where is defined. + /// This parameter will not be set when does not represent a property in a component. + /// + /// + /// Output parameter that represents the path of the mapped member, which in most cases is the member name. In case + /// when the mapped member is defined inside a component the path will be prefixed with the name of the component member and a dot. + /// (e.g. Component.Property). + /// Whether the mapped type was found. + /// + /// When the contains an expression of type , the + /// result may not be correct when casting to an entity that is mapped with multiple entity names. + /// When the is polymorphic, the first implementor will be returned. + /// When the contains a , the first found entity name + /// will be returned from or . + /// When the contains a expression, the first found entity name + /// will be returned from or . + /// + internal static bool TryGetMappedType( + ISessionFactoryImplementor sessionFactory, + Expression expression, + out IType mappedType, + out IEntityPersister entityPersister, + out IAbstractComponentType component, + out string memberPath) + { + // In order to get the correct entity name from the expression we first have to find the constant expression that contains the + // IEntityNameProvider instance, from which we can retrieve the starting entity name. Once we have it, we have to traverse all + // expressions that we had to traverse in order to find the IEntityNameProvider instance, but in reverse order (bottom to top) + // and keep tracking the entity name until we reach to top. + + memberPath = null; + mappedType = null; + entityPersister = null; + component = null; + // Try to retrieve the starting entity name with all members that were traversed in that process. + if (!MemberMetadataExtractor.TryGetAllMemberMetadata(expression, out var metadataResults)) + { + // Failed to find the starting entity name, due to: + // - Unsupported expression + // - The expression didn't contain the IEntityNameProvider instance + return false; + } + + // Due to coalesce and conditional expressions we can have multiple paths to traverse, in that case find the first path + // for which we are able to determine the mapped type. + foreach (var metadataResult in metadataResults) + { + if (ProcessMembersMetadataResult( + metadataResult, + sessionFactory, + out mappedType, + out entityPersister, + out component, + out memberPath)) + { + return true; + } + } + + return false; + } + + private static bool ProcessMembersMetadataResult( + MemberMetadataResult metadataResult, + ISessionFactoryImplementor sessionFactory, + out IType mappedType, + out IEntityPersister entityPersister, + out IAbstractComponentType component, + out string memberPath) + { + if (!TryGetEntityPersister(metadataResult.EntityName, null, sessionFactory, out var currentEntityPersister)) + { + // Failed to find the starting entity name, due to: + // - Querying a type that is not related to any entity e.g. s.Query().Where(a => a.Type == "A") + memberPath = null; + mappedType = null; + entityPersister = null; + component = null; + return false; + } + + if (metadataResult.MemberPaths.Count == 0) // The expression do not contain any member expressions + { + if (metadataResult.ConvertType != null) + { + mappedType = TryGetEntityPersister( + currentEntityPersister, + metadataResult.ConvertType, + sessionFactory, + out var convertPersister) + ? convertPersister.EntityMetamodel.EntityType // ((Subclass)q) + : TypeFactory.GetDefaultTypeFor(metadataResult.ConvertType); // ((NotMapped)q) + } + else + { + mappedType = currentEntityPersister.EntityMetamodel.EntityType; // q + } + + memberPath = null; + component = null; + entityPersister = currentEntityPersister; + return mappedType != null; + } + + // If there was a cast right after the constant expression that contains the IEntityNameProvider instance, we have + // to update the entity persister according to it, otherwise use the value returned by TryGetAllMemberMetadata method. + if (metadataResult.ConvertType != null) + { + if (!TryGetEntityPersister( + currentEntityPersister, + metadataResult.ConvertType, + sessionFactory, + out var convertPersister)) // ((NotMapped)q).Id + { + memberPath = null; + mappedType = null; + entityPersister = null; + component = null; + return false; + } + + currentEntityPersister = convertPersister; // ((Subclass)q).Id + } + + return TraverseMembers( + sessionFactory, + metadataResult.MemberPaths, + currentEntityPersister, + out mappedType, + out entityPersister, + out component, + out memberPath); + } + + private static bool TraverseMembers( + ISessionFactoryImplementor sessionFactory, + Stack memberPaths, + IEntityPersister currentEntityPersister, + out IType mappedType, + out IEntityPersister entityPersister, + out IAbstractComponentType component, + out string memberPath) + { + // Traverse the members that were traversed by the TryGetAllMemberMetadata method in the reverse order and try to keep + // tracking the entity persister until all members are traversed. + var member = memberPaths.Pop(); + var currentType = currentEntityPersister.EntityMetamodel.GetPropertyType(member.Path); + IAbstractComponentType currentComponentType = null; + while (memberPaths.Count > 0 && currentType != null) + { + memberPath = member.Path; + var convertType = member.ConvertType; + member = memberPaths.Pop(); + + switch (currentType) + { + case IAssociationType associationType: + ProcessAssociationType( + associationType, + sessionFactory, + member, + convertType, + out currentType, + out currentEntityPersister, + out currentComponentType); + break; + case IAbstractComponentType componentType: + currentComponentType = componentType; + if (currentEntityPersister == null) + { + // When persister is not available (q.OneToManyCompositeElement[0].Prop), try to get the type from the component + currentType = TryGetComponentPropertyType(componentType, member.Path); + } + else + { + // Concatenate the component property path in order to be able to use EntityMetamodel.GetPropertyType to retrieve the type. + // As GetPropertyType supports only components, do not concatenate when dealing with collection composite elements or elements. + // q.Component.Prop + member = new MemberMetadata( + $"{memberPath}.{member.Path}", + member.ConvertType, + member.HasIndexer); + + // q.Component.Prop + currentType = currentEntityPersister.EntityMetamodel.GetPropertyType(member.Path); + } + + break; + default: + // q.Prop.NotMappedProp + currentType = null; + currentEntityPersister = null; + currentComponentType = null; + break; + } + } + + // When traversed to the top of the expression, return the current tracking values + if (memberPaths.Count == 0) + { + memberPath = currentEntityPersister != null || currentComponentType != null ? member.Path : null; + mappedType = GetType(currentEntityPersister, currentType, member, sessionFactory); + entityPersister = currentEntityPersister; + component = currentComponentType; + return mappedType != null; + } + + // Member not mapped + memberPath = null; + mappedType = null; + entityPersister = null; + component = null; + return false; + } + + private static IType TryGetComponentPropertyType(IAbstractComponentType componentType, string memberPath) + { + var index = Array.IndexOf(componentType.PropertyNames, memberPath); + return index < 0 + ? null // q.OneToManyCompositeElement[0].NotMappedProp + : componentType.Subtypes[index]; // q.OneToManyCompositeElement[0].Prop + } + + private static void ProcessAssociationType( + IAssociationType associationType, + ISessionFactoryImplementor sessionFactory, + MemberMetadata member, + System.Type convertType, + out IType memberType, + out IEntityPersister memberPersister, + out IAbstractComponentType memberComponent) + { + if (associationType.IsCollectionType) + { + // Check manually for entity association as GetAssociatedEntityName throws when there is none. + var queryableCollection = + (IQueryableCollection) associationType.GetAssociatedJoinable(sessionFactory); + if (!queryableCollection.ElementType.IsEntityType) // q.OneToManyCompositeElement[0].Member, q.OneToManyElement[0].Member + { + memberPersister = null; + // Can be or + switch (queryableCollection.ElementType) + { + case IAbstractComponentType componentType: // q.OneToManyCompositeElement[0].Member + memberComponent = componentType; + memberType = TryGetComponentPropertyType(componentType, member.Path); + return; + default: // q.OneToManyElement[0].Member + memberType = null; + memberComponent = null; + return; + } + } + + // q.OneToMany[0].Member + TryGetEntityPersister( + associationType.GetAssociatedEntityName(sessionFactory), + convertType, + sessionFactory, + out memberPersister); + } + else if (associationType.IsAnyType) + { + // ((Address)q.AnyType).Member, q.AnyType.Member + // Unfortunately we cannot detect the exact entity name as cast does not provide it, + // so the only option is to guess it. + TryGetEntityPersister(convertType, sessionFactory, out memberPersister); + } + else // q.ManyToOne.Member + { + TryGetEntityPersister( + associationType.GetAssociatedEntityName(sessionFactory), + convertType, + sessionFactory, + out memberPersister); + } + + memberComponent = null; + memberType = memberPersister != null + ? memberPersister.EntityMetamodel.GetPropertyType(member.Path) + : null; // q.AnyType.Member, ((NotMappedClass)q.ManyToOne) + } + + private static bool TryGetEntityPersister( + string currentEntityName, + System.Type convertedType, + ISessionFactoryImplementor sessionFactory, + out IEntityPersister persister) + { + var currentEntityPersister = sessionFactory.TryGetEntityPersister(currentEntityName); + if (currentEntityPersister == null) + { + // When dealing with a polymorphic query it is not important which entity name we pick + // as they all need to have the same mapped types for members of the type that is queried. + // If one of the entites has a different type mapped (e.g. enum mapped as string instead of numeric), + // the query will fail to execute as currently the ParameterMetadata is bound to IQueryPlan and not to IQueryTranslator + // (e.g. s.Query().Where(a => a.MyEnum == MyEnum.Option)). + currentEntityName = sessionFactory.GetImplementors(currentEntityName).FirstOrDefault(); + if (currentEntityName == null) + { + persister = null; + return false; + } + + currentEntityPersister = sessionFactory.GetEntityPersister(currentEntityName); + } + + return TryGetEntityPersister(currentEntityPersister, convertedType, sessionFactory, out persister); + } + + private static bool TryGetEntityPersister( + IEntityPersister currentEntityPersister, + System.Type convertedType, + ISessionFactoryImplementor sessionFactory, + out IEntityPersister persister) + { + if (convertedType == null) + { + persister = currentEntityPersister; + return true; + } + + if (currentEntityPersister.EntityMetamodel.HasSubclasses) + { + // When a class is casted to a subclass e.g. ((PizzaOrder)c.Order).PizzaName, we + // can only guess the entity name of it, as there can be many entity names mapped + // to the same subclass. + persister = currentEntityPersister.EntityMetamodel.SubclassEntityNames + .Select(sessionFactory.GetEntityPersister) + .FirstOrDefault(p => p.MappedClass == convertedType); + + return persister != null; + } + + return TryGetEntityPersister(convertedType, sessionFactory, out persister); + } + + private static bool TryGetEntityPersister( + System.Type convertedType, + ISessionFactoryImplementor sessionFactory, + out IEntityPersister persister) + { + if (convertedType == null) + { + persister = null; + return false; + } + + var entityName = sessionFactory.TryGetGuessEntityName(convertedType); + if (entityName == null) + { + persister = null; + return false; + } + + persister = sessionFactory.GetEntityPersister(entityName); + return true; + } + + private static IType GetType( + IEntityPersister currentEntityPersister, + IType currentType, + MemberMetadata member, + ISessionFactoryImplementor sessionFactory) + { + // Not mapped + if (currentType == null) + { + return null; + } + + IEntityPersister persister; + if (!member.HasIndexer || currentEntityPersister == null) + { + if (member.ConvertType == null) + { + return currentType; // q.Prop, q.OneToManyCompositeElement[0].Prop + } + + return TryGetEntityPersister(member.ConvertType, sessionFactory, out persister) + ? persister.EntityMetamodel.EntityType // (Entity)q.Prop, (Entity)q.OneToManyCompositeElement[0].Prop + : TypeFactory.GetDefaultTypeFor(member.ConvertType); // (long)q.Prop, (long)q.OneToManyCompositeElement[0].Prop + } + + if (!(currentType is IAssociationType associationType)) + { + // q.Prop[0] + return null; + } + + var queryableCollection = (IQueryableCollection) associationType.GetAssociatedJoinable(sessionFactory); + if (member.ConvertType == null) + { + // q.OneToMany[0] + return queryableCollection.ElementType; + } + + return TryGetEntityPersister(member.ConvertType, sessionFactory, out persister) + ? persister.EntityMetamodel.EntityType // (Entity)q.OneToMany[0] + : TypeFactory.GetDefaultTypeFor(member.ConvertType); // (long)q.OneToMany[0] + } + + private class MemberMetadataExtractor : NhExpressionVisitor + { + private readonly List _childrenResults = new List(); + private readonly Stack _memberPaths; + private System.Type _convertType; + private bool _hasIndexer; + private string _entityName; + + private MemberMetadataExtractor(Stack memberPaths, System.Type convertType, bool hasIndexer) + { + _memberPaths = memberPaths; + _convertType = convertType; + _hasIndexer = hasIndexer; + } + + /// + /// Traverses the expression from top to bottom until the first containing an IEntityNameProvider + /// instance is found. + /// + /// The expression to traverse. + /// Output parameter that represents a collection, where each item contains information about all + /// that were traversed until the first containing an + /// instance is found. The number of items depends on how many different paths exist + /// in the that contains a instance. When + /// is not found or one of the expressions is not supported the parameter will be set to . + /// Whether was populated. + public static bool TryGetAllMemberMetadata(Expression expression, out List results) + { + if (TryGetAllMemberMetadata(expression, new Stack(), null, false, out var result)) + { + results = result.GetAllResults().ToList(); + return true; + } + + results = null; + return false; + } + + private static bool TryGetAllMemberMetadata( + Expression expression, + Stack memberPaths, + System.Type convertType, + bool hasIndexer, + out MemberMetadataResult results) + { + var extractor = new MemberMetadataExtractor(memberPaths, convertType, hasIndexer); + extractor.Accept(expression); + results = extractor._entityName != null || extractor._childrenResults.Count > 0 + ? new MemberMetadataResult( + extractor._childrenResults, + extractor._memberPaths, + extractor._entityName, + extractor._convertType) + : null; + + return results != null; + } + + private void Accept(Expression expression) + { + base.Visit(expression); + } + + protected override Expression VisitMember(MemberExpression node) + { + _memberPaths.Push(new MemberMetadata(node.Member.Name, _convertType, _hasIndexer)); + _convertType = null; + _hasIndexer = false; + return base.Visit(node.Expression); + } + +#if NETCOREAPP2_0 + protected override Expression VisitInvocation(InvocationExpression node) + { + if (TryGetDynamicMemberBinder(node, out var binder)) + { + _memberPaths.Push(new MemberMetadata(binder.Name, _convertType, _hasIndexer)); + _convertType = null; + _hasIndexer = false; + return base.Visit(node.Arguments[1]); + } + + return base.VisitInvocation(node); + } +#endif + + protected override Expression VisitDynamic(DynamicExpression node) + { + if (node.Binder is GetMemberBinder binder) + { + _memberPaths.Push(new MemberMetadata(binder.Name, _convertType, _hasIndexer)); + _convertType = null; + _hasIndexer = false; + return base.Visit(node.Arguments[0]); + } + + return Visit(node); + } + + protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression node) + { + if (node.ReferencedQuerySource is IFromClause fromClause) + { + return base.Visit(fromClause.FromExpression); + } + + if (node.ReferencedQuerySource is JoinClause joinClause) + { + return base.Visit(joinClause.InnerSequence); + } + + if (node.ReferencedQuerySource is NhOuterJoinClause outerJoinClause) + { + return base.Visit(outerJoinClause.JoinClause.InnerSequence); + } + + // Not supported expression + _entityName = null; + return node; + } + + protected override Expression VisitSubQuery(SubQueryExpression expression) + { + base.Visit(expression.QueryModel.SelectClause.Selector); + return expression; + } + + protected override Expression VisitUnary(UnaryExpression node) + { + // Store only the outermost cast, when there are multiple casts for the same member + if (_convertType == null) + { + _convertType = node.Type; + } + + return base.Visit(node.Operand); + } + + protected internal override Expression VisitNhNominated(NhNominatedExpression node) + { + return base.Visit(node.Expression); + } + + protected override Expression VisitConstant(ConstantExpression node) + { + _entityName = node.Value is IEntityNameProvider entityNameProvider + ? entityNameProvider.EntityName + : null; // Not a NhQueryable + + return node; + } + + protected override Expression VisitBinary(BinaryExpression node) + { + if (node.NodeType == ExpressionType.ArrayIndex) + { + _hasIndexer = true; + return base.Visit(node.Left); + } + + if (node.NodeType == ExpressionType.Coalesce && + (TryGetMembersMetadata(node.Left) | TryGetMembersMetadata(node.Right))) + { + return node; + } + + return Visit(node); + } + + protected override Expression VisitConditional(ConditionalExpression node) + { + if (TryGetMembersMetadata(node.IfTrue) | TryGetMembersMetadata(node.IfFalse)) + { + return node; + } + + return Visit(node); + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (ListIndexerGenerator.IsMethodSupported(node.Method)) + { + _hasIndexer = true; + return base.Visit( + node.Object == null + ? Enumerable.First(node.Arguments) // q.Children.ElementAt(0) + : node.Object // q.Children[0] + ); + } + + if (VisitorUtil.TryGetPotentialDynamicComponentDictionaryMember(node, out var memberName)) + { + _memberPaths.Push(new MemberMetadata(memberName, _convertType, _hasIndexer)); + _convertType = null; + _hasIndexer = false; + return base.Visit(node.Object); + } + + return Visit(node); + } + + public override Expression Visit(Expression node) + { + // Not supported expression + _entityName = null; + return node; + } + + private bool TryGetMembersMetadata(Expression expression) + { + if (TryGetAllMemberMetadata(expression, Clone(_memberPaths), _convertType, _hasIndexer, out var result)) + { + _childrenResults.Add(result); + return true; + } + + return false; + } + + private static Stack Clone(Stack original) + { + var arr = new T[original.Count]; + original.CopyTo(arr, 0); + Array.Reverse(arr); + return new Stack(arr); + } + } + + private struct MemberMetadata + { + public MemberMetadata(string path, System.Type convertType, bool hasIndexer) + { + Path = path; + ConvertType = convertType; + HasIndexer = hasIndexer; + } + + public string Path { get; } + + public System.Type ConvertType { get; } + + public bool HasIndexer { get; } + } + + private class MemberMetadataResult + { + public MemberMetadataResult( + List childrenResults, + Stack memberPaths, + string entityName, + System.Type convertType) + { + ChildrenResults = childrenResults; + MemberPaths = memberPaths; + EntityName = entityName; + ConvertType = convertType; + } + + /// + /// Metadata about all that were traversed. + /// + public Stack MemberPaths { get; } + + /// + /// type that was used on a containing + /// an . + /// + public System.Type ConvertType { get; } + + /// + /// The entity name from . + /// + public string EntityName { get; } + + /// + /// Direct children of the current metadata result. + /// + public List ChildrenResults { get; } + + /// + /// Gets all leaf (bottom) children that have the entity name set. + /// + /// + public IEnumerable GetAllResults() + { + return GetAllResults(this); + } + + private static IEnumerable GetAllResults(MemberMetadataResult result) + { + if (result.ChildrenResults.Count == 0) + { + yield return result; + } + else + { + foreach (var childResult in result.ChildrenResults) + { + foreach (var childChildrenResult in GetAllResults(childResult)) + { + yield return childChildrenResult; + } + } + } + } + } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/FilterHelper.cs b/src/NHibernate/Util/FilterHelper.cs index 82015537f9c..7eaedaed329 100644 --- a/src/NHibernate/Util/FilterHelper.cs +++ b/src/NHibernate/Util/FilterHelper.cs @@ -55,27 +55,20 @@ public string Render(String alias, IDictionary enabledFilters) public void Render(StringBuilder buffer, string alias, IDictionary enabledFilters) { - Render(buffer, alias, new Dictionary(), enabledFilters); + Render(buffer, alias, CollectionHelper.EmptyDictionary(), enabledFilters); } public void Render(StringBuilder buffer, string defaultAlias, IDictionary propMap, IDictionary enabledFilters) { - if (filterNames != null) + for (int i = 0; i < filterNames.Length; i++) { - int max = filterNames.Length; - if (max > 0) + if (enabledFilters.ContainsKey(filterNames[i])) { - for (int i = 0; i < max; i++) + string condition = filterConditions[i]; + if (!string.IsNullOrEmpty(condition)) { - if (enabledFilters.ContainsKey(filterNames[i])) - { - string condition = filterConditions[i]; - if (StringHelper.IsNotEmpty(condition)) - { - buffer.Append(" and "); - AddFilterString(buffer, defaultAlias, propMap, condition); - } - } + buffer.Append(" and "); + AddFilterString(buffer, defaultAlias, propMap, condition); } } } @@ -83,22 +76,23 @@ public void Render(StringBuilder buffer, string defaultAlias, IDictionary propMap, string condition) { - int i = condition.IndexOf(FilterImpl.MARKER); + int i; int upTo = 0; - while (i > -1 && upTo < condition.Length) + while ((i = condition.IndexOf(FilterImpl.MARKER, upTo, StringComparison.Ordinal)) >= 0 && upTo < condition.Length) { - buffer.Append(condition.Substring(upTo, i - upTo)); + buffer.Append(condition, upTo, i - upTo); int startOfProperty = i + FilterImpl.MARKER.Length + 1; - upTo = condition.IndexOf(" ", startOfProperty); + upTo = condition.IndexOf(' ', startOfProperty); upTo = upTo >= 0 ? upTo : condition.Length; string property = condition.Substring(startOfProperty, upTo - startOfProperty); - string fullColumn = propMap.ContainsKey(property) ? propMap[property] : string.Format("{0}.{1}", defaultAlias, property); + if (!propMap.TryGetValue(property, out var fullColumn)) + fullColumn = string.IsNullOrEmpty(defaultAlias) + ? property + : defaultAlias + "." + property; buffer.Append(fullColumn); - - i = condition.IndexOf(FilterImpl.MARKER, upTo); } buffer.Append(condition.Substring(upTo)); } diff --git a/src/NHibernate/Util/IdentityMap.cs b/src/NHibernate/Util/IdentityMap.cs index 3b6667a2ea3..686c51365b3 100644 --- a/src/NHibernate/Util/IdentityMap.cs +++ b/src/NHibernate/Util/IdentityMap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Runtime.Serialization; - namespace NHibernate.Util { /// @@ -40,7 +39,7 @@ public sealed class IdentityMap : IDictionary, IDeserializationCallback /// A new IdentityMap based on a Hashtable. public static IDictionary Instantiate(int size) { - return new IdentityMap(new Hashtable(size, new IdentityEqualityComparer())); + return new IdentityMap(new Hashtable(size, ReferenceComparer.Instance)); } /// @@ -51,7 +50,7 @@ public static IDictionary Instantiate(int size) /// A new IdentityMap based on ListDictionary. public static IDictionary InstantiateSequenced(int size) { - return new IdentityMap(new SequencedHashMap(size, new IdentityEqualityComparer())); + return new IdentityMap(new SequencedHashMap(size, ReferenceComparer.Instance)); } /// @@ -204,7 +203,6 @@ public ICollection Values get { return map.Values; } } - /// /// /// @@ -276,4 +274,4 @@ public void OnDeserialization(object sender) ((IDeserializationCallback)map).OnDeserialization(sender); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/IdentitySet.cs b/src/NHibernate/Util/IdentitySet.cs index 8d32568746a..d3db1d17758 100644 --- a/src/NHibernate/Util/IdentitySet.cs +++ b/src/NHibernate/Util/IdentitySet.cs @@ -1,13 +1,14 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; namespace NHibernate.Util { /// /// Set implementation that use reference equals instead of Equals() as its comparison mechanism. /// + // Since 5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] public class IdentitySet : ISet { private IDictionary map; @@ -25,7 +26,6 @@ public IdentitySet(IEnumerable members) Add(member); } - #region Implementation of ICollection void ICollection.Add(object item) @@ -35,7 +35,6 @@ void ICollection.Add(object item) #endregion - public bool Add(object o) { object tempObject = map[o]; @@ -43,7 +42,6 @@ public bool Add(object o) return tempObject == null; } - public void Clear() { map.Clear(); @@ -86,7 +84,6 @@ public bool IsReadOnly get { return false; } } - #region Implementation of ISet public void UnionWith(IEnumerable other) @@ -154,6 +151,5 @@ public bool SetEquals(IEnumerable other) } #endregion - } } diff --git a/src/NHibernate/Util/JoinedEnumerable.cs b/src/NHibernate/Util/JoinedEnumerable.cs index 32178006e13..2e9d3ab6b50 100644 --- a/src/NHibernate/Util/JoinedEnumerable.cs +++ b/src/NHibernate/Util/JoinedEnumerable.cs @@ -34,7 +34,6 @@ public JoinedEnumerable(IEnumerable first, IEnumerable second) { } - #region System.Collections.IEnumerable Members /// @@ -46,8 +45,6 @@ public IEnumerator GetEnumerator() #endregion - - #region Nested class JoinedEnumerator private class JoinedEnumerator : IEnumerator, IDisposable @@ -61,7 +58,6 @@ public JoinedEnumerator(IEnumerable enumerators) _current = 0; } - #region System.Collections.IEnumerator Members public bool MoveNext() @@ -86,7 +82,6 @@ public bool MoveNext() return false; } - public void Reset() { foreach (var enumerator in _enumerators) @@ -94,7 +89,6 @@ public void Reset() _current = 0; } - public object Current { get { return _enumerators[_current].Current; } @@ -127,7 +121,6 @@ public void Dispose() Dispose(true); } - /// /// Takes care of freeing the managed and unmanaged resources that /// this class is responsible for. @@ -159,13 +152,13 @@ private void Dispose(bool isDisposing) currentDisposable.Dispose(); } } + // nothing for Finalizer to do - so tell the GC to ignore it + GC.SuppressFinalize(this); } // free unmanaged resources here _isAlreadyDisposed = true; - // nothing for Finalizer to do - so tell the GC to ignore it - GC.SuppressFinalize(this); } #endregion @@ -174,9 +167,8 @@ private void Dispose(bool isDisposing) #endregion } - - - + // Since 5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] public class JoinedEnumerable : IEnumerable { private readonly IEnumerable[] enumerables; @@ -241,7 +233,6 @@ T IEnumerator.Current public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } private void Dispose(bool disposing) @@ -249,9 +240,11 @@ private void Dispose(bool disposing) if (!disposed) { if (disposing) + { for (; currentEnumIdx < enumerators.Length; currentEnumIdx++) enumerators[currentEnumIdx].Dispose(); - GC.SuppressFinalize(this); + GC.SuppressFinalize(this); + } disposed = true; } } diff --git a/src/NHibernate/Util/LRUMap.cs b/src/NHibernate/Util/LRUMap.cs index a0c5f947148..fb278c4769d 100644 --- a/src/NHibernate/Util/LRUMap.cs +++ b/src/NHibernate/Util/LRUMap.cs @@ -105,6 +105,5 @@ public int MaximumSize RemoveLRU(); } } - } } diff --git a/src/NHibernate/Util/LinkedHashMap.cs b/src/NHibernate/Util/LinkedHashMap.cs index 772a8819960..5487204deee 100644 --- a/src/NHibernate/Util/LinkedHashMap.cs +++ b/src/NHibernate/Util/LinkedHashMap.cs @@ -349,15 +349,12 @@ private Entry Last private bool RemoveImpl(TKey key) { - Entry e; - bool result = false; - if (entries.TryGetValue(key, out e)) - { - result = entries.Remove(key); - version++; - RemoveEntry(e); - } - return result; + if (!entries.Remove(key, out var e)) + return false; + + version++; + RemoveEntry(e); + return true; } void IDeserializationCallback.OnDeserialization(object sender) @@ -691,6 +688,5 @@ object IEnumerator.Current #endregion } - } } diff --git a/src/NHibernate/Util/ObjectHelpers.cs b/src/NHibernate/Util/ObjectHelpers.cs index 79ccbe0cc1d..95c292f6c0e 100644 --- a/src/NHibernate/Util/ObjectHelpers.cs +++ b/src/NHibernate/Util/ObjectHelpers.cs @@ -1,4 +1,3 @@ - namespace NHibernate.Util { /// diff --git a/src/NHibernate/Util/ParameterHelper.cs b/src/NHibernate/Util/ParameterHelper.cs new file mode 100644 index 00000000000..d0b6bd14625 --- /dev/null +++ b/src/NHibernate/Util/ParameterHelper.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; +using System.Linq; +using NHibernate.Engine; +using NHibernate.Proxy; +using NHibernate.Type; + +namespace NHibernate.Util +{ + internal static class ParameterHelper + { + /// + /// Guesses the from the param's value. + /// + /// The object to guess the of. + /// The session factory to search for entity persister. + /// Whether is a collection. + /// An for the object. + /// + /// Thrown when the param is null because the + /// can't be guess from a null value. + /// + public static IType TryGuessType(object param, ISessionFactoryImplementor sessionFactory, bool isCollection) + { + if (param == null) + { + return null; + } + + if (param is IEnumerable enumerable && isCollection) + { + var firstValue = enumerable.Cast().FirstOrDefault(); + return firstValue == null + ? TryGuessType(enumerable.GetCollectionElementType(), sessionFactory) + : TryGuessType(firstValue, sessionFactory, false); + } + + var clazz = NHibernateProxyHelper.GetClassWithoutInitializingProxy(param); + return TryGuessType(clazz, sessionFactory); + } + + /// + /// Guesses the from the param's value. + /// + /// The object to guess the of. + /// The session factory to search for entity persister. + /// An for the object. + /// + /// Thrown when the param is null because the + /// can't be guess from a null value. + /// + public static IType GuessType(object param, ISessionFactoryImplementor sessionFactory) + { + if (param == null) + { + throw new ArgumentNullException(nameof(param), "The IType can not be guessed for a null value."); + } + + System.Type clazz = NHibernateProxyHelper.GetClassWithoutInitializingProxy(param); + return GuessType(clazz, sessionFactory); + } + + /// + /// Guesses the from the . + /// + /// The to guess the of. + /// The session factory to search for entity persister. + /// Whether is a collection. + /// An for the . + /// + /// Thrown when the clazz is null because the + /// can't be guess from a null type. + /// + public static IType TryGuessType(System.Type clazz, ISessionFactoryImplementor sessionFactory, bool isCollection) + { + if (clazz == null) + { + return null; + } + + if (isCollection) + { + return TryGuessType(ReflectHelper.GetCollectionElementType(clazz), sessionFactory, false); + } + + return TryGuessType(clazz, sessionFactory); + } + + /// + /// Guesses the from the . + /// + /// The to guess the of. + /// The session factory to search for entity persister. + /// An for the . + /// + /// Thrown when the clazz is null because the + /// can't be guess from a null type. + /// + public static IType GuessType(System.Type clazz, ISessionFactoryImplementor sessionFactory) + { + if (clazz == null) + { + throw new ArgumentNullException(nameof(clazz), "The IType can not be guessed for a null value."); + } + + return TryGuessType(clazz, sessionFactory) ?? + throw new HibernateException("Could not determine a type for class: " + clazz.AssemblyQualifiedName); + } + + /// + /// Guesses the from the . + /// + /// The to guess the of. + /// The session factory to search for entity persister. + /// An for the . + /// + /// Thrown when the clazz is null because the + /// can't be guess from a null type. + /// + public static IType TryGuessType(System.Type clazz, ISessionFactoryImplementor sessionFactory) + { + if (clazz == null) + { + return null; + } + + var type = TypeFactory.HeuristicType(clazz); + if (type == null || type is SerializableType) + { + if (sessionFactory.TryGetEntityPersister(clazz.FullName) != null) + { + return NHibernateUtil.Entity(clazz); + } + } + + return type; + } + } +} diff --git a/src/NHibernate/Util/PropertiesHelper.cs b/src/NHibernate/Util/PropertiesHelper.cs index da80f5b0860..fcd7f350741 100644 --- a/src/NHibernate/Util/PropertiesHelper.cs +++ b/src/NHibernate/Util/PropertiesHelper.cs @@ -55,6 +55,17 @@ public static string GetString(string property, IDictionary prop return value ?? defaultValue; } + public static TEnum GetEnum(string property, IDictionary properties, TEnum defaultValue) where TEnum : struct + { + var enumValue = GetString(property, properties, null); + if (enumValue == null) + { + return defaultValue; + } + + return (TEnum) Enum.Parse(typeof(TEnum), enumValue, false); + } + public static IDictionary ToDictionary(string property, string delim, IDictionary properties) { IDictionary map = new Dictionary(); diff --git a/src/NHibernate/Util/ReferenceComparer.cs b/src/NHibernate/Util/ReferenceComparer.cs new file mode 100644 index 00000000000..3a20761984a --- /dev/null +++ b/src/NHibernate/Util/ReferenceComparer.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Util +{ + /// + /// Compares objects by reference equality + /// + /// + [Serializable] + class ReferenceComparer : IEqualityComparer, IEqualityComparer where T : class + { + private ReferenceComparer() + { + } + + public bool Equals(T x, T y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(T obj) + { + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + + bool IEqualityComparer.Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + int IEqualityComparer.GetHashCode(object obj) + { + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + + public static readonly ReferenceComparer Instance = new ReferenceComparer(); + } +} diff --git a/src/NHibernate/Util/ReflectHelper.cs b/src/NHibernate/Util/ReflectHelper.cs index 7de13090ff7..06352101745 100644 --- a/src/NHibernate/Util/ReflectHelper.cs +++ b/src/NHibernate/Util/ReflectHelper.cs @@ -104,6 +104,95 @@ public static MethodInfo GetMethod(Expression method) return ((MethodCallExpression)method.Body).Method; } + /// Get a from a method group + /// A method group + internal static MethodInfo FastGetMethod(System.Func func) + { + return func.Method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + internal static MethodInfo FastGetMethod(System.Func func, T a) + { + return func.Method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + /// A dummy parameter + internal static MethodInfo FastGetMethod(System.Func func, T1 a1, T2 a2) + { + return func.Method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + internal static MethodInfo FastGetMethod(System.Func func, T1 a1, T2 a2, T3 a3) + { + return func.Method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + internal static MethodInfo FastGetMethodDefinition(System.Func func, T a) + { + var method = func.Method; + return method.IsGenericMethod ? method.GetGenericMethodDefinition() : method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + /// A dummy parameter + internal static MethodInfo FastGetMethodDefinition(System.Func func, T1 a1, T2 a2) + { + var method = func.Method; + return method.IsGenericMethod ? method.GetGenericMethodDefinition() : method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + internal static MethodInfo FastGetMethodDefinition(System.Func func, T1 a1, T2 a2, T3 a3) + { + var method = func.Method; + return method.IsGenericMethod ? method.GetGenericMethodDefinition() : method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + internal static MethodInfo FastGetMethodDefinition(System.Func func, T1 a1, T2 a2, T3 a3, T4 a4) + { + var method = func.Method; + return method.IsGenericMethod ? method.GetGenericMethodDefinition() : method; + } + + /// Get a from a method group + /// A method group + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + /// A dummy parameter + internal static MethodInfo FastGetMethodDefinition(System.Func func, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) + { + var method = func.Method; + return method.IsGenericMethod ? method.GetGenericMethodDefinition() : method; + } + /// /// Get the for a public overload of a given method if the method does not match /// given parameter types, otherwise directly yield the given method. @@ -149,6 +238,21 @@ public static MemberInfo GetProperty(Expression + /// Gets the static field or property to be accessed. + /// + /// The type of the property. + /// The expression representing the property getter. + /// The of the property. + public static MemberInfo GetProperty(Expression> property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + return ((MemberExpression)property.Body).Member; + } + internal static bool ParameterTypesMatch(ParameterInfo[] parameters, System.Type[] types) { if (parameters.Length != types.Length) @@ -350,7 +454,7 @@ public static System.Type ClassForName(string name) /// the method try to find the System.Type scanning all Assemblies of the . /// /// If no System.Type was found for . - public static System.Type ClassForFullName(string classFullName) + public static System.Type ClassForFullName(string classFullName) { var result = ClassForFullNameOrNull(classFullName); if (result == null) diff --git a/src/NHibernate/Util/ReflectionCache.cs b/src/NHibernate/Util/ReflectionCache.cs index 33ecdc16183..47fde15950d 100644 --- a/src/NHibernate/Util/ReflectionCache.cs +++ b/src/NHibernate/Util/ReflectionCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -21,190 +22,215 @@ internal static class ReflectionCache internal static class EnumerableMethods { internal static readonly MethodInfo AggregateDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Aggregate(null, null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.Aggregate, default(IEnumerable), default(Func)); internal static readonly MethodInfo AggregateWithSeedDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Aggregate(null, null, null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.Aggregate, default(IEnumerable), default(object), default(Func)); internal static readonly MethodInfo AggregateWithSeedAndResultSelectorDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Aggregate(null, null, null, null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.Aggregate, default(IEnumerable), default(object), default(Func), default(Func)); internal static readonly MethodInfo AllDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.All(null, null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.All, default(IEnumerable), default(Func)); internal static readonly MethodInfo CastDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Cast(null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.Cast, default(IEnumerable)); - internal static readonly MethodInfo GroupByWithElementSelectorDefinition = ReflectHelper.GetMethodDefinition( - () => Enumerable.GroupBy(null, null, default(Func))); + internal static readonly MethodInfo GroupByWithElementSelectorDefinition = + ReflectHelper.FastGetMethodDefinition(Enumerable.GroupBy, default(IEnumerable), default(Func), default(Func)); internal static readonly MethodInfo MaxDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Max(null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.Max, default(IEnumerable)); internal static readonly MethodInfo MinDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Min(null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.Min, default(IEnumerable)); internal static readonly MethodInfo SelectDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.Select(null, default(Func))); + ReflectHelper.FastGetMethodDefinition(Enumerable.Select, default(IEnumerable), default(Func)); internal static readonly MethodInfo SumOnInt = - ReflectHelper.GetMethod(() => Enumerable.Sum(default(IEnumerable))); + ReflectHelper.FastGetMethod(Enumerable.Sum, default(IEnumerable)); internal static readonly MethodInfo ToArrayDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.ToArray(null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.ToArray, default(IEnumerable)); internal static readonly MethodInfo ToListDefinition = - ReflectHelper.GetMethodDefinition(() => Enumerable.ToList(null)); + ReflectHelper.FastGetMethodDefinition(Enumerable.ToList, default(IEnumerable)); + + internal static readonly MethodInfo SkipDefinition = + ReflectHelper.FastGetMethodDefinition(Enumerable.Skip, default(IEnumerable), default(int)); + internal static readonly MethodInfo TakeDefinition = + ReflectHelper.FastGetMethodDefinition(Enumerable.Take, default(IEnumerable), default(int)); } internal static class MethodBaseMethods { internal static readonly MethodInfo GetMethodFromHandle = - ReflectHelper.GetMethod(() => MethodBase.GetMethodFromHandle(default(RuntimeMethodHandle))); + ReflectHelper.FastGetMethod(MethodBase.GetMethodFromHandle, default(RuntimeMethodHandle)); internal static readonly MethodInfo GetMethodFromHandleWithDeclaringType = - ReflectHelper.GetMethod(() => MethodBase.GetMethodFromHandle(default(RuntimeMethodHandle), default(RuntimeTypeHandle))); + ReflectHelper.FastGetMethod(MethodBase.GetMethodFromHandle, default(RuntimeMethodHandle), default(RuntimeTypeHandle)); } internal static class QueryableMethods { internal static readonly MethodInfo SelectDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Select(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Select, default(IQueryable), default(Expression>)); + internal static readonly MethodInfo SelectManyDefinition = + ReflectHelper.FastGetMethodDefinition( + Queryable.SelectMany, + default(IQueryable), + default(Expression>>), + default(Expression>)); + + internal static readonly MethodInfo GroupJoinDefinition = + ReflectHelper.FastGetMethodDefinition( + Queryable.GroupJoin, + default(IQueryable), + default(IEnumerable), + default(Expression>), + default(Expression>), + default(Expression, object>>)); internal static readonly MethodInfo CountDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Count(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Count, default(IQueryable)); internal static readonly MethodInfo CountWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Count(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Count, default(IQueryable), default(Expression>)); internal static readonly MethodInfo LongCountDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.LongCount(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.LongCount, default(IQueryable)); internal static readonly MethodInfo LongCountWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.LongCount(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.LongCount, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AnyDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Any(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Any, default(IQueryable)); internal static readonly MethodInfo AnyWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Any(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Any, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AllDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.All(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.All, default(IQueryable), default(Expression>)); internal static readonly MethodInfo FirstDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.First(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.First, default(IQueryable)); internal static readonly MethodInfo FirstWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.First(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.First, default(IQueryable), default(Expression>)); internal static readonly MethodInfo FirstOrDefaultDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.FirstOrDefault(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.FirstOrDefault, default(IQueryable)); internal static readonly MethodInfo FirstOrDefaultWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.FirstOrDefault(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.FirstOrDefault, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SingleDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Single(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Single, default(IQueryable)); internal static readonly MethodInfo SingleWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Single(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Single, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SingleOrDefaultDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.SingleOrDefault(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.SingleOrDefault, default(IQueryable)); internal static readonly MethodInfo SingleOrDefaultWithPredicateDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.SingleOrDefault(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.SingleOrDefault, default(IQueryable), default(Expression>)); internal static readonly MethodInfo MinDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Min(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Min, default(IQueryable)); internal static readonly MethodInfo MinWithSelectorDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Min(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Min, default(IQueryable), default(Expression>)); internal static readonly MethodInfo MaxDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Max(null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Max, default(IQueryable)); internal static readonly MethodInfo MaxWithSelectorDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Max(null, null)); + ReflectHelper.FastGetMethodDefinition(Queryable.Max, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumOfInt = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfNullableInt = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfLong = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfNullableLong = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfFloat = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfNullableFloat = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfDouble = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfNullableDouble = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfDecimal = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumOfNullableDecimal = - ReflectHelper.GetMethod(() => Queryable.Sum(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Sum, default(IQueryable)); internal static readonly MethodInfo SumWithSelectorOfIntDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfNullableIntDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfLongDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfNullableLongDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfFloatDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfNullableFloatDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfDoubleDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfNullableDoubleDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfDecimalDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo SumWithSelectorOfNullableDecimalDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Sum(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Sum, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageOfInt = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfNullableInt = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfLong = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfNullableLong = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfFloat = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfNullableFloat = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfDouble = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfNullableDouble = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfDecimal = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageOfNullableDecimal = - ReflectHelper.GetMethod(() => Queryable.Average(default(IQueryable))); + ReflectHelper.FastGetMethod(Queryable.Average, default(IQueryable)); internal static readonly MethodInfo AverageWithSelectorOfIntDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfNullableIntDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfLongDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfNullableLongDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfFloatDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfNullableFloatDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfDoubleDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfNullableDoubleDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfDecimalDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); internal static readonly MethodInfo AverageWithSelectorOfNullableDecimalDefinition = - ReflectHelper.GetMethodDefinition(() => Queryable.Average(null, default(Expression>))); + ReflectHelper.FastGetMethodDefinition(Queryable.Average, default(IQueryable), default(Expression>)); + + internal static readonly MethodInfo SkipDefinition = + ReflectHelper.FastGetMethodDefinition(Queryable.Skip, default(IQueryable), default(int)); + internal static readonly MethodInfo TakeDefinition = + ReflectHelper.FastGetMethodDefinition(Queryable.Take, default(IQueryable), default(int)); } internal static class TypeMethods { internal static readonly MethodInfo GetTypeFromHandle = - ReflectHelper.GetMethod(() => System.Type.GetTypeFromHandle(default(RuntimeTypeHandle))); + ReflectHelper.FastGetMethod(System.Type.GetTypeFromHandle, default(RuntimeTypeHandle)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/SafetyEnumerable.cs b/src/NHibernate/Util/SafetyEnumerable.cs index c6656d58e7a..f3177400aa7 100644 --- a/src/NHibernate/Util/SafetyEnumerable.cs +++ b/src/NHibernate/Util/SafetyEnumerable.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; @@ -7,6 +8,8 @@ namespace NHibernate.Util /// Used to ensure a collection filtering a given IEnumerable by a certain type. /// /// The type used like filter. + // Since v5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] public class SafetyEnumerable : IEnumerable { /* @@ -25,8 +28,8 @@ public IEnumerator GetEnumerator() { if (element == null) yield return default(T); - else if (element is T) - yield return (T) element; + else if (element is T elem) + yield return elem; } } diff --git a/src/NHibernate/Util/SequencedHashMap.cs b/src/NHibernate/Util/SequencedHashMap.cs index 1f9a539c0ac..3ab0d612bb9 100644 --- a/src/NHibernate/Util/SequencedHashMap.cs +++ b/src/NHibernate/Util/SequencedHashMap.cs @@ -144,7 +144,6 @@ public override string ToString() #endregion } - /// /// Construct an empty sentinel used to hold the head (sentinel.next) and the tail (sentinal.prev) /// of the list. The sentinal has a key and value @@ -447,7 +446,6 @@ public virtual bool ContainsValue(object value) return false; } - private Entry First { get { return (IsEmpty) ? null : _sentinel.Next; } @@ -577,7 +575,6 @@ public bool Contains(object o) } } - private class ValuesCollection : ICollection { private SequencedHashMap _parent; @@ -629,7 +626,6 @@ public bool Contains(object o) } } - private enum ReturnType { /// @@ -648,7 +644,6 @@ private enum ReturnType ReturnEntry } - private class OrderedEnumerator : IDictionaryEnumerator { private SequencedHashMap _parent; @@ -675,7 +670,6 @@ public object Current throw new InvalidOperationException("Enumerator was modified"); } - switch (_returnType) { case ReturnType.ReturnKey: diff --git a/src/NHibernate/Util/SerializationHelper.Surrogates.cs b/src/NHibernate/Util/SerializationHelper.Surrogates.cs index 16a0aa179c4..6769450e0e6 100644 --- a/src/NHibernate/Util/SerializationHelper.Surrogates.cs +++ b/src/NHibernate/Util/SerializationHelper.Surrogates.cs @@ -84,8 +84,8 @@ public MemberInfoReference(MemberInfo member) _bindingFlags = GetBindingFlags(member); if (member is MethodBase method) { - _genericArguments = method.IsGenericMethod ? method.GetGenericArguments().ToArray() : System.Type.EmptyTypes; - _parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); + _genericArguments = method.IsGenericMethod ? method.GetGenericArguments() : System.Type.EmptyTypes; + _parameterTypes = method.GetParameters().ToArray(p => p.ParameterType); } } @@ -306,4 +306,4 @@ public override void RemoveSurrogate(System.Type type, StreamingContext context) } } } -#endif \ No newline at end of file +#endif diff --git a/src/NHibernate/Util/SimpleMRUCache.cs b/src/NHibernate/Util/SimpleMRUCache.cs index 06c66679d9f..b6ad7beb413 100644 --- a/src/NHibernate/Util/SimpleMRUCache.cs +++ b/src/NHibernate/Util/SimpleMRUCache.cs @@ -1,7 +1,5 @@ using System; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using System.Threading; namespace NHibernate.Util { @@ -19,7 +17,7 @@ public class SimpleMRUCache : IDeserializationCallback { private const int DefaultStrongRefCount = 128; - private object _syncRoot; + private readonly object _syncRoot = new object(); private readonly int strongReferenceCount; @@ -35,17 +33,6 @@ public SimpleMRUCache(int strongReferenceCount) cache = new LRUMap(strongReferenceCount); } - private object SyncRoot - { - get - { - if (_syncRoot == null) - Interlocked.CompareExchange(ref _syncRoot, new object(), null); - - return _syncRoot; - } - } - #region IDeserializationCallback Members void IDeserializationCallback.OnDeserialization(object sender) @@ -57,20 +44,18 @@ void IDeserializationCallback.OnDeserialization(object sender) public object this[object key] { - [MethodImpl(MethodImplOptions.Synchronized)] get { - lock (SyncRoot) + lock (_syncRoot) { return cache[key]; } } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Put(object key, object value) { - lock (SyncRoot) + lock (_syncRoot) { cache.Add(key, value); } @@ -78,20 +63,18 @@ public void Put(object key, object value) public int Count { - [MethodImpl(MethodImplOptions.Synchronized)] get { - lock (SyncRoot) + lock (_syncRoot) { return cache.Count; } } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Clear() { - lock (SyncRoot) + lock (_syncRoot) { cache.Clear(); } diff --git a/src/NHibernate/Util/SingletonEnumerable.cs b/src/NHibernate/Util/SingletonEnumerable.cs index 155aff136e4..73feb1a074a 100644 --- a/src/NHibernate/Util/SingletonEnumerable.cs +++ b/src/NHibernate/Util/SingletonEnumerable.cs @@ -1,8 +1,11 @@ +using System; using System.Collections; using System.Collections.Generic; namespace NHibernate.Util { + // Since 5.3 + [Obsolete("This class has no more usages and will be removed in a future version")] public sealed class SingletonEnumerable : IEnumerable { private readonly T value; @@ -78,4 +81,4 @@ object IEnumerator.Current #endregion } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/SoftLimitMRUCache.cs b/src/NHibernate/Util/SoftLimitMRUCache.cs index bd232103246..e69c3f2916e 100644 --- a/src/NHibernate/Util/SoftLimitMRUCache.cs +++ b/src/NHibernate/Util/SoftLimitMRUCache.cs @@ -1,8 +1,6 @@ using System; using System.Collections; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using System.Threading; namespace NHibernate.Util { @@ -25,7 +23,7 @@ namespace NHibernate.Util public class SoftLimitMRUCache : IDeserializationCallback { private const int DefaultStrongRefCount = 128; - private object _syncRoot; + private readonly object _syncRoot = new object(); private readonly int strongReferenceCount; @@ -50,17 +48,6 @@ public SoftLimitMRUCache(int strongReferenceCount) public SoftLimitMRUCache() : this(DefaultStrongRefCount) {} - private object SyncRoot - { - get - { - if (_syncRoot == null) - Interlocked.CompareExchange(ref _syncRoot, new object(), null); - - return _syncRoot; - } - } - #region IDeserializationCallback Members void IDeserializationCallback.OnDeserialization(object sender) @@ -72,10 +59,9 @@ void IDeserializationCallback.OnDeserialization(object sender) public object this[object key] { - [MethodImpl(MethodImplOptions.Synchronized)] get { - lock (SyncRoot) + lock (_syncRoot) { object result = softReferenceCache[key]; if (result != null) @@ -87,10 +73,9 @@ public object this[object key] } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Put(object key, object value) { - lock (SyncRoot) + lock (_syncRoot) { softReferenceCache[key] = value; strongReferenceCache[key] = value; @@ -99,10 +84,9 @@ public void Put(object key, object value) public int Count { - [MethodImpl(MethodImplOptions.Synchronized)] get { - lock (SyncRoot) + lock (_syncRoot) { return strongReferenceCache.Count; } @@ -111,20 +95,18 @@ public int Count public int SoftCount { - [MethodImpl(MethodImplOptions.Synchronized)] get { - lock (SyncRoot) + lock (_syncRoot) { return softReferenceCache.Count; } } } - [MethodImpl(MethodImplOptions.Synchronized)] public void Clear() { - lock (SyncRoot) + lock (_syncRoot) { strongReferenceCache.Clear(); softReferenceCache.Clear(); diff --git a/src/NHibernate/Util/StringHelper.cs b/src/NHibernate/Util/StringHelper.cs index 34fce731762..7372d79f639 100644 --- a/src/NHibernate/Util/StringHelper.cs +++ b/src/NHibernate/Util/StringHelper.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Text; - namespace NHibernate.Util { /// @@ -379,6 +378,24 @@ internal static bool IsNotRoot(string qualifiedName, out string root, out string return true; } + /// + /// Returns true if supplied fullPath has non empty pathToProperty + /// "alias.Entity.Value" -> pathToProperty = "alias.Entity", propertyName = "Value" + /// + internal static bool ParsePathAndPropertyName(string fullPath, out string pathToProperty, out string propertyName) + { + propertyName = fullPath; + pathToProperty = string.Empty; + int loc = fullPath.LastIndexOf('.'); + if (loc < 0) + return false; + + propertyName = fullPath.Substring(loc + 1); + pathToProperty = fullPath.Substring(0, loc); + return true; + } + + /// /// Converts a in the format of "true", "t", "false", or "f" to /// a . @@ -529,7 +546,6 @@ public static bool IsNotEmpty(string str) return !IsEmpty(str); } - /// /// /// @@ -810,7 +826,6 @@ public static string[] ParseFilterParameterName(string filterParameterName) return new[] { filterName, parameterName }; } - /// /// Return the index of the next line separator, starting at startIndex. If will match /// the first CRLF or LF line separator. If there is no match, -1 will be returned. When @@ -833,7 +848,6 @@ public static int IndexOfAnyNewLine(this string str, int startIndex, out int new return matchStartIdx; } - /// /// Check if the given index points to a line separator in the string. Both CRLF and LF /// line separators are handled. When returning, newLineLength will be set to the number diff --git a/src/NHibernate/Util/StringTokenizer.cs b/src/NHibernate/Util/StringTokenizer.cs index 105e659bcf5..a53c6ab2d38 100644 --- a/src/NHibernate/Util/StringTokenizer.cs +++ b/src/NHibernate/Util/StringTokenizer.cs @@ -67,7 +67,6 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() #endregion - private class StringTokenizerEnumerator : IEnumerator { private StringTokenizer _stokenizer; @@ -149,4 +148,4 @@ private string GetNext() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Util/TypeExtensions.cs b/src/NHibernate/Util/TypeExtensions.cs index 71bf854957f..e03b58a0890 100644 --- a/src/NHibernate/Util/TypeExtensions.cs +++ b/src/NHibernate/Util/TypeExtensions.cs @@ -6,6 +6,8 @@ namespace NHibernate.Util { public static class TypeExtensions { + //Since 5.4 + [Obsolete("This method has no more usages and will be removed in a future version.")] public static bool IsEnumerableOfT(this System.Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); @@ -48,5 +50,30 @@ internal static System.Type UnwrapIfNullable(this System.Type type) return type; } + + internal static bool IsIntegralNumberType(this System.Type type) + { + var code = System.Type.GetTypeCode(type); + if (code == TypeCode.SByte || code == TypeCode.Byte || + code == TypeCode.Int16 || code == TypeCode.UInt16 || + code == TypeCode.Int32 || code == TypeCode.UInt32 || + code == TypeCode.Int64 || code == TypeCode.UInt64) + { + return true; + } + + return false; + } + + internal static bool IsRealNumberType(this System.Type type) + { + var code = System.Type.GetTypeCode(type); + if (code == TypeCode.Decimal || code == TypeCode.Single || code == TypeCode.Double) + { + return true; + } + + return false; + } } } diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index 7c0a870156f..178be1bfe37 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -92,7 +92,14 @@ - + + + + The class name of a custom ITransactionFactory implementation. + Defaults to the built-in AdoNetWithSystemTransactionFactory. + + + @@ -117,6 +124,14 @@ + + + + + Whether to throw or not on schema auto-update failures. false by default. + + + @@ -144,7 +159,48 @@ + + + + The pre-transformer registrar used to register custom expression transformers. + + + + + + + Whether to use the legacy pre-evaluation or not in Linq queries. true by default. + + Legacy pre-evaluation is causing special properties or functions like DateTime.Now or Guid.NewGuid() + to be always evaluated with the .Net runtime and replaced in the query by parameter values. + + The new pre-evaluation allows them to be converted to HQL function calls which will be run on the db + side. This allows for example to retrieve the server time instead of the client time, or to generate + UUIDs for each row instead of an unique one for all rows. + + The new pre-evaluation will likely be enabled by default in the next major version (6.0). + + + + + + + When the new pre-evaluation is enabled, should methods which translation is not supported by the current + dialect fallback to pre-evaluation? false by default. + + When this fallback option is enabled while legacy pre-evaluation is disabled, properties or functions + like DateTime.Now or Guid.NewGuid() used in Linq expressions will not fail when the dialect does not + support them, but will instead be pre-evaluated. + + When this fallback option is disabled while legacy pre-evaluation is disabled, properties or functions + like DateTime.Now or Guid.NewGuid() used in Linq expressions will fail when the dialect does not + support them. + + This option has no effect if the legacy pre-evaluation is enabled. + + + @@ -174,6 +230,17 @@ + + + + Should sessions check on every operation whether there is an ongoing system transaction or not, and enlist + into it if any? Default is true. It can also be controlled at session opening, with + ISessionFactory.WithOptions. A session can also be instructed to explicitly join the current + transaction by calling ISession.JoinTransaction. This setting has no effect when using a + transaction factory that is not system transactions aware. + + + @@ -188,6 +255,18 @@ + + + + Oracle 10g introduced BINARY_DOUBLE and BINARY_FLOAT types which are compatible with .NET + double and float types, where FLOAT and DOUBLE are not. Oracle FLOAT and DOUBLE types do + not conform to the IEEE standard as they are internally implemented as NUMBER type, which + makes them an exact numeric type. + False by default. + See https://docs.oracle.com/database/121/TTSQL/types.htm#TTSQL126 + + + @@ -200,6 +279,18 @@ + + + + SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid + connection string parameter (default is 'true'). The BinaryGuid setting will affect + how to cast GUID to string in SQL. NHibernate will attempt to detect this + setting automatically from the connection string, but if the connection + or connection string is being handled by the application instead of by NHibernate, + you can use the 'sqlite.binaryguid' NHibernate setting to override the behavior. + + + @@ -245,6 +336,20 @@ + + + + Strategy for multi-tenancy. Supported Values: Database, None. Corresponds to MultiTenancyStrategy enum. + + + + + + + Connection provider for given multi-tenancy strategy. Class name implementing IMultiTenancyConnectionProvider. + + +