├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── Neo4jClient.Extension.sln ├── README.md ├── SolutionItems └── Properties │ └── AssemblyInfoGlobal.cs ├── build.common.ps1 ├── build.ps1 ├── docs └── images │ └── TestDataDiagram.png ├── nuget.config ├── src ├── Neo4jClient.Extension.Attributes │ ├── CypherExtensionAttribute.cs │ ├── CypherLabelAttribute.cs │ ├── CypherMatchAttribute.cs │ ├── CypherMergeAttribute.cs │ ├── CypherMergeOnCreateAttribute.cs │ ├── CypherMergeOnMatchAttribute.cs │ ├── Neo4jClient.Extension.Attributes.csproj │ ├── Neo4jClient.Extension.Attributes.nuspec │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config └── Neo4jClient.Extension │ ├── Cypher │ ├── BaseRelationship.cs │ ├── CypherExtensionContext.cs │ ├── CypherProperty.cs │ ├── CypherTypeItem.cs │ ├── CypherTypeItemHelper.cs │ ├── Extension │ │ ├── CreateDynamicOptions.cs │ │ ├── CypherExtension.CqlBuilders.cs │ │ ├── CypherExtension.Dynamics.cs │ │ ├── CypherExtension.Entity.cs │ │ ├── CypherExtension.Fluent.cs │ │ └── CypherExtension.Main.cs │ ├── FluentConfig.cs │ └── MergeOptions.cs │ ├── Neo4jClient.Extension.csproj │ ├── Neo4jClient.Extension.nuspec │ ├── Options │ ├── CreateOptions.cs │ ├── IOptions.cs │ ├── MatchOptions.cs │ └── MatchRelationshipOptions.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── packages.config └── test ├── Neo4jClient.Extension.IntegrationTest ├── App.config ├── IntegrationTest.cs ├── Neo4jClient.Extension.IntegrationTest.csproj ├── Properties │ └── AssemblyInfo.cs ├── Tests │ ├── CreateTests.cs │ └── MergeTests.cs └── packages.config ├── Neo4jClient.Extension.Test.Common ├── Domain │ ├── Address.cs │ ├── Person.cs │ └── Weapon.cs ├── DomainModelDiagram.cd ├── Neo │ ├── NeoConfig.cs │ └── Relationships │ │ ├── CheckedOutRelationship.cs │ │ ├── HomeAddressRelationship.cs │ │ └── WorkAddressRelationship.cs ├── Neo4jClient.Extension.Test.Common.csproj ├── Properties │ └── AssemblyInfo.cs ├── SampleDataFactory.cs └── packages.config └── Neo4jClient.Extension.UnitTest ├── CustomConverters ├── AreaConverterFixture.cs └── AreaJsonConverter.cs ├── Cypher ├── CypherExtensionTests.cs ├── CypherLabelAttributeTests.cs ├── CypherTypeItemHelperTests.cs ├── FluentConfigBaseTest.cs ├── FluentConfigCreateTests.cs ├── FluentConfigMatchTests.cs ├── FluentConfigMergeTests.cs └── FluentConfigUpdateTests.cs ├── Neo4jClient.Extension.UnitTest.csproj ├── Properties └── AssemblyInfo.cs └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | .vs/config/applicationhost.config 185 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonpinn/Neo4jClient.Extension/b2bd16891e6daf20c9a4d392b3081cc448aaae7e/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 simonpinn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Neo4jClient.Extension.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{12FEE675-BAC9-48E5-9C28-28ED67FFC9AE}" 7 | ProjectSection(SolutionItems) = preProject 8 | build.common.ps1 = build.common.ps1 9 | build.ps1 = build.ps1 10 | nuget.config = nuget.config 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Properties", "Properties", "{4DF33B7D-5D06-4EBC-8569-2A1008683E2A}" 15 | ProjectSection(SolutionItems) = preProject 16 | SolutionItems\Properties\AssemblyInfoGlobal.cs = SolutionItems\Properties\AssemblyInfoGlobal.cs 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{9CE869B3-2DAC-4EBF-88B6-F70F74198B9B}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Extension", "src\Neo4jClient.Extension\Neo4jClient.Extension.csproj", "{41C65BED-56A6-4942-95D2-10E62F607C7F}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Extension.Attributes", "src\Neo4jClient.Extension.Attributes\Neo4jClient.Extension.Attributes.csproj", "{6D2502F8-F491-45E6-ABD8-2F7407926F5A}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Extension.IntegrationTest", "test\Neo4jClient.Extension.IntegrationTest\Neo4jClient.Extension.IntegrationTest.csproj", "{8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Extension.Test.Common", "test\Neo4jClient.Extension.Test.Common\Neo4jClient.Extension.Test.Common.csproj", "{B7C14349-6BEC-44D1-AB33-B82AD85899AA}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Extension.UnitTest", "test\Neo4jClient.Extension.UnitTest\Neo4jClient.Extension.UnitTest.csproj", "{066A5EBD-C612-40E2-8065-160FA9853503}" 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {41C65BED-56A6-4942-95D2-10E62F607C7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {41C65BED-56A6-4942-95D2-10E62F607C7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {41C65BED-56A6-4942-95D2-10E62F607C7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {41C65BED-56A6-4942-95D2-10E62F607C7F}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {6D2502F8-F491-45E6-ABD8-2F7407926F5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {6D2502F8-F491-45E6-ABD8-2F7407926F5A}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {6D2502F8-F491-45E6-ABD8-2F7407926F5A}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {6D2502F8-F491-45E6-ABD8-2F7407926F5A}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {B7C14349-6BEC-44D1-AB33-B82AD85899AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {B7C14349-6BEC-44D1-AB33-B82AD85899AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {B7C14349-6BEC-44D1-AB33-B82AD85899AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {B7C14349-6BEC-44D1-AB33-B82AD85899AA}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {066A5EBD-C612-40E2-8065-160FA9853503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {066A5EBD-C612-40E2-8065-160FA9853503}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {066A5EBD-C612-40E2-8065-160FA9853503}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {066A5EBD-C612-40E2-8065-160FA9853503}.Release|Any CPU.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(NestedProjects) = preSolution 62 | {4DF33B7D-5D06-4EBC-8569-2A1008683E2A} = {12FEE675-BAC9-48E5-9C28-28ED67FFC9AE} 63 | {8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B} = {9CE869B3-2DAC-4EBF-88B6-F70F74198B9B} 64 | {B7C14349-6BEC-44D1-AB33-B82AD85899AA} = {9CE869B3-2DAC-4EBF-88B6-F70F74198B9B} 65 | {066A5EBD-C612-40E2-8065-160FA9853503} = {9CE869B3-2DAC-4EBF-88B6-F70F74198B9B} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neo4jClient.Extension # 2 | 3 | Extending the awesome of [Neo4jClient](https://github.com/Readify/Neo4jClient) 4 | 5 | ![Version](https://img.shields.io/nuget/v/Neo4jClient.Extension.svg) 6 | 7 | Merge, match and create nodes or relationships using objects instead of typing pseudo Cypher. 8 | 9 | Reduces mistakes and simplifies composition of queries. As well as some more advanced features, the following key extension methods are provided: 10 | 11 | * `CreateEntity` 12 | * `CreateRelationship` 13 | * `MergeEntity` 14 | * `MergeRelationship` 15 | 16 | Any object can be provided to these methods. 17 | 18 | ##Fluent Config Setup## 19 | 20 | To allow unobtrusive usage the extension library with domain model projects which don't want a reference to Neo4j, a fluent config interface has been included to construct the model. Given a domain model like below: 21 | 22 | ![Person, Address domain entities](https://raw.githubusercontent.com/simonpinn/Neo4jClient.Extension/master/docs/images/TestDataDiagram.png) 23 | 24 | The person entity would be configured once per application lifetime scope like this: 25 | 26 | FluentConfig.Config() 27 | .With("SecretAgent") 28 | .Match(x => x.Id) 29 | .Merge(x => x.Id) 30 | .MergeOnCreate(p => p.Id) 31 | .MergeOnCreate(p => p.DateCreated) 32 | .MergeOnMatchOrCreate(p => p.Title) 33 | .MergeOnMatchOrCreate(p => p.Name) 34 | .MergeOnMatchOrCreate(p => p.IsOperative) 35 | .MergeOnMatchOrCreate(p => p.Sex) 36 | .MergeOnMatchOrCreate(p => p.SerialNumber) 37 | .MergeOnMatchOrCreate(p => p.SpendingAuthorisation) 38 | .Set(); 39 | 40 | Note how we only set DateCreated when creating, not updating. 41 | 42 | A relationship might be setup like this: 43 | 44 | FluentConfig.Config() 45 | .With() 46 | .MergeOnMatchOrCreate(hr => hr.DateEffective) 47 | .Set(); 48 | 49 | The address entity undergoes a similar setup - see the [unit tests](https://github.com/simonpinn/Neo4jClient.Extension/blob/master/test/Neo4jClient.Extension.Test.Common/Neo/NeoConfig.cs) for the complete setup. 50 | 51 | ##Fluent Config Usage## 52 | Now that our model is configured, creating a weapon is as simple as: 53 | 54 | var weapon = SampleDataFactory.GetWellKnownWeapon(1); 55 | var q = GetFluentQuery() 56 | .CreateEntity(weapon, "w"); 57 | 58 | Creating a person, their two addresses and setting the relationships between the three nodes: 59 | 60 | var agent = SampleDataFactory.GetWellKnownPerson(7); 61 | 62 | var q = GetFluentQuery() 63 | .CreateEntity(agent, "a") 64 | .CreateEntity(agent.HomeAddress, "ha") 65 | .CreateEntity(agent.WorkAddress, "wa") 66 | .CreateRelationship(new HomeAddressRelationship("a", "ha")) 67 | .CreateRelationship(new WorkAddressRelationship("a", "wa")); 68 | .ExecuteWithoutResults(); 69 | 70 | Easy. Here is some merge syntax just to show off: 71 | 72 | var person = SampleDataFactory.GetWellKnownPerson(7); 73 | 74 | var homeAddressRelationship = new HomeAddressRelationship("person", "address"); 75 | 76 | homeAddressRelationship.DateEffective = DateTime.Parse("2011-01-10T08:00:00+10:00"); 77 | 78 | var q = GetFluentQuery() 79 | .MergeEntity(person) 80 | .MergeEntity(person.HomeAddress) 81 | .MergeRelationship(homeAddressRelationship); 82 | .ExecuteWithoutResults(); 83 | 84 | ## Attribute Config ## 85 | Before Fluent Config there was Attribute Config. If you insist on decorating your models with attributes, you may use the following attributes on a domain model to control the generated query 86 | 87 | * `CypherLabel` Placed at class level, controls the node `label`, if unspecified then the class name is used 88 | * `CypherMatch` Specifies that a property will be used in a `MATCH` statement 89 | * `CypherMerge` Specifies that a property will be used in a `MERGE` statement 90 | * `CypherMergeOnCreate` Specifies that a property will be used in the `ON CREATE SET` portion of a`MERGE` statement 91 | * `CypherMergeOnMatch` Specifies that a property will be used in the `ON MATCH SET` portion of a `MERGE` statement 92 | 93 | Below is an example model decorated with the above attributes 94 | 95 | public class CypherModel 96 | { 97 | public CypherModel() 98 | { 99 | id = Guid.NewGuid(); 100 | } 101 | 102 | [CypherMerge] 103 | public Guid id { get; set; } 104 | 105 | [CypherMergeOnCreate] 106 | [CypherMatch] 107 | public string firstName { get; set; } 108 | 109 | [CypherMergeOnCreate] 110 | public DateTimeOffset dateOfBirth { get; set; } 111 | 112 | [CypherMergeOnCreate] 113 | [CypherMergeOnMatch] 114 | public bool isLegend { get; set; } 115 | 116 | [CypherMergeOnCreate] 117 | public int answerToTheMeaningOfLifeAndEverything { get; set; } 118 | } 119 | 120 | Yes, we think you should use the Fluent Config too. 121 | 122 | A full list of examples can be found in the unit tests within the solution. 123 | 124 | 125 | ## Packaging ## 126 | `build.ps1` is designed for [myget](http://www.myget.org/) compatibility. 127 | 128 | The script can be run locally via `powershell -f build.ps1`. By default, it expects an environment variable named `packageVersion`. 129 | 130 | Some default parameters may be overridden, for example: 131 | `powershell -f build.ps1 -configuration debug -sourceUrl https://github.com/your-username/Neo4jClient.Extension -packageVersion 5.0.0.1` 132 | 133 | Nuget packages are written to `./_output` -------------------------------------------------------------------------------- /SolutionItems/Properties/AssemblyInfoGlobal.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonpinn/Neo4jClient.Extension/b2bd16891e6daf20c9a4d392b3081cc448aaae7e/SolutionItems/Properties/AssemblyInfoGlobal.cs -------------------------------------------------------------------------------- /build.common.ps1: -------------------------------------------------------------------------------- 1 | $Script:UseWriteHost = $true 2 | 3 | if(!$Global:ColorScheme) { 4 | $Global:ColorScheme = @{ 5 | "Banner"=[ConsoleColor]::Cyan 6 | "RuntimeName"=[ConsoleColor]::Yellow 7 | "Help_Header"=[ConsoleColor]::Yellow 8 | "Help_Switch"=[ConsoleColor]::Green 9 | "Help_Argument"=[ConsoleColor]::Cyan 10 | "Help_Optional"=[ConsoleColor]::Gray 11 | "Help_Command"=[ConsoleColor]::DarkYellow 12 | "Help_Executable"=[ConsoleColor]::DarkYellow 13 | "ParameterName"=[ConsoleColor]::Cyan 14 | "Warning" = [ConsoleColor]::Yellow 15 | } 16 | } 17 | 18 | function _WriteOut { 19 | param( 20 | [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true)][string]$msg, 21 | [Parameter(Mandatory=$false)][ConsoleColor]$ForegroundColor, 22 | [Parameter(Mandatory=$false)][ConsoleColor]$BackgroundColor, 23 | [Parameter(Mandatory=$false)][switch]$NoNewLine) 24 | 25 | if($__TestWriteTo) { 26 | $cur = Get-Variable -Name $__TestWriteTo -ValueOnly -Scope Global -ErrorAction SilentlyContinue 27 | $val = $cur + "$msg" 28 | if(!$NoNewLine) { 29 | $val += [Environment]::NewLine 30 | } 31 | Set-Variable -Name $__TestWriteTo -Value $val -Scope Global -Force 32 | return 33 | } 34 | 35 | if(!$Script:UseWriteHost) { 36 | if(!$msg) { 37 | $msg = "" 38 | } 39 | if($NoNewLine) { 40 | [Console]::Write($msg) 41 | } else { 42 | [Console]::WriteLine($msg) 43 | } 44 | } 45 | else { 46 | try { 47 | if(!$ForegroundColor) { 48 | $ForegroundColor = $host.UI.RawUI.ForegroundColor 49 | } 50 | if(!$BackgroundColor) { 51 | $BackgroundColor = $host.UI.RawUI.BackgroundColor 52 | } 53 | 54 | Write-Host $msg -ForegroundColor:$ForegroundColor -BackgroundColor:$BackgroundColor -NoNewLine:$NoNewLine 55 | } catch { 56 | $Script:UseWriteHost = $false 57 | _WriteOut $msg 58 | } 59 | } 60 | } 61 | 62 | function _WriteConfig{ 63 | param( 64 | [Parameter(Mandatory=$true,Position=0)]$name, 65 | [Parameter(Mandatory=$true,Position=1)]$value) 66 | 67 | _WriteOut -NoNewline -ForegroundColor $Global:ColorScheme.ParameterName "${name}: " 68 | _WriteOut "$value" 69 | 70 | } 71 | 72 | function _DownloadNuget{ 73 | param([Parameter(Mandatory=$true,Position=0)]$rootPath) 74 | 75 | $sourceNugetExe = "http://nuget.org/nuget.exe" 76 | $targetNugetExe = "$rootPath\nuget.exe" 77 | 78 | if(!(Test-Path $targetNugetExe )){ 79 | _WriteOut "Downloading nuget to $targetNugetExe" 80 | Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe 81 | } 82 | else{ 83 | # _WriteOut "nuget.exe is already present" 84 | } 85 | 86 | Set-Alias nuget $targetNugetExe -Scope Global 87 | } 88 | 89 | function checkExitCode{ 90 | if ($lastExitCode -ne 0) 91 | { 92 | Write-Host "##myget[buildProblem description='lastExitCode was not zero']" 93 | exit $lastExitCode 94 | } 95 | } -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$configuration = "Release", 3 | [string]$packageVersion = $null, 4 | [string]$sourceUrlOverride = $null 5 | ) 6 | 7 | . ".\build.common.ps1" 8 | 9 | $solutionName = "Neo4jClient.Extension" 10 | $sourceUrl = "https://github.com/simonpinn/Neo4jClient.Extension" 11 | 12 | function init { 13 | # Initialization 14 | $global:rootFolder = Split-Path -parent $script:MyInvocation.MyCommand.Path 15 | $global:rootFolder = Join-Path $rootFolder . 16 | $global:packagesFolder = Join-Path $rootFolder packages 17 | $global:outputFolder = Join-Path $rootFolder _output 18 | $global:msbuild = "C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" 19 | 20 | # Override via script parameter 21 | if($packageVersion){ 22 | $env:PackageVersion = $packageVersion 23 | } 24 | 25 | # default if none specified 26 | if(!(Test-Path Env:\PackageVersion )){ 27 | $env:PackageVersion = "0.1.2.3" 28 | } 29 | 30 | # Override via environment variable 31 | if(Test-Path Env:\sourceUrlOverride ){ 32 | $sourceUrl = $env:sourceUrlOverride 33 | } 34 | 35 | # Override via script parameter 36 | if($sourceUrlOverride){ 37 | $sourceUrl = $sourceUrlOverride 38 | } 39 | 40 | _WriteOut -ForegroundColor $ColorScheme.Banner "-= $solutionName Build =-" 41 | _WriteConfig "rootFolder" $rootFolder 42 | _WriteConfig "packageVersion" $env:PackageVersion 43 | _WriteConfig "configuration" $configuration 44 | _WriteConfig "sourceUrl" $sourceUrl 45 | } 46 | 47 | function restorePackages{ 48 | _WriteOut -ForegroundColor $ColorScheme.Banner "nuget, gitlink restore" 49 | 50 | New-Item -Force -ItemType directory -Path $packagesFolder 51 | _DownloadNuget $packagesFolder 52 | nuget restore 53 | nuget install gitlink -SolutionDir "$rootFolder" -ExcludeVersion 54 | } 55 | 56 | function nugetPack{ 57 | _WriteOut -ForegroundColor $ColorScheme.Banner "Nuget pack" 58 | 59 | New-Item -Force -ItemType directory -Path $outputFolder 60 | 61 | if(!(Test-Path Env:\nuget )){ 62 | $env:nuget = nuget 63 | } 64 | 65 | nuget pack $rootFolder\src\Neo4jClient.Extension.Attributes\Neo4jClient.Extension.Attributes.csproj -o $outputFolder -IncludeReferencedProjects -p Configuration=$configuration -Version $env:PackageVersion 66 | nuget pack $rootFolder\src\Neo4jClient.Extension\Neo4jClient.Extension.csproj -o $outputFolder -IncludeReferencedProjects -p Configuration=$configuration -Version $env:PackageVersion 67 | } 68 | 69 | function nugetPublish{ 70 | 71 | if(Test-Path Env:\nugetapikey ){ 72 | _WriteOut -ForegroundColor $ColorScheme.Banner "Nuget publish" 73 | &$env:nuget push .\_output\* $env:nugetapikey 74 | } 75 | else{ 76 | _WriteOut -ForegroundColor Yellow "nugetapikey environment variable not detected. Skipping nuget publish" 77 | } 78 | } 79 | 80 | function buildSolution{ 81 | 82 | _WriteOut -ForegroundColor $ColorScheme.Banner "Build Solution" 83 | & $msbuild "$rootFolder\$solutionName.sln" /p:Configuration=$configuration 84 | 85 | &"$rootFolder\packages\gitlink\lib\net45\GitLink.exe" $rootFolder -u $sourceUrl 86 | 87 | checkExitCode 88 | } 89 | 90 | function executeTests{ 91 | 92 | Write-Host "Execute Tests" 93 | $nunitConsole = "$rootFolder\packages\NUnit.Runners.2.6.3\tools\nunit-console.exe" 94 | & $nunitConsole "$rootFolder\test\Neo4jClient.Extension.UnitTest\bin\$configuration\Neo4jClient.Extension.Test.dll" 95 | 96 | checkExitCode 97 | } 98 | 99 | init 100 | 101 | restorePackages 102 | 103 | buildSolution 104 | 105 | executeTests 106 | 107 | nugetPack 108 | 109 | nugetPublish 110 | 111 | Write-Host "Build $env:PackageVersion complete" -------------------------------------------------------------------------------- /docs/images/TestDataDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonpinn/Neo4jClient.Extension/b2bd16891e6daf20c9a4d392b3081cc448aaae7e/docs/images/TestDataDiagram.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/CypherExtensionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Neo4jClient.Extension.Cypher.Attributes 4 | { 5 | public abstract class CypherExtensionAttribute:Attribute 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/CypherLabelAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher.Attributes 2 | { 3 | public class CypherLabelAttribute : CypherExtensionAttribute 4 | { 5 | public string Name { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/CypherMatchAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher.Attributes 2 | { 3 | public class CypherMatchAttribute : CypherExtensionAttribute 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/CypherMergeAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher.Attributes 2 | { 3 | public class CypherMergeAttribute : CypherExtensionAttribute 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/CypherMergeOnCreateAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher.Attributes 2 | { 3 | public class CypherMergeOnCreateAttribute : CypherExtensionAttribute 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/CypherMergeOnMatchAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher.Attributes 2 | { 3 | public class CypherMergeOnMatchAttribute : CypherExtensionAttribute 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/Neo4jClient.Extension.Attributes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6D2502F8-F491-45E6-ABD8-2F7407926F5A} 8 | Library 9 | Properties 10 | Neo4jClient.Extension.Attributes 11 | Neo4jClient.Extension.Attributes 12 | v4.5 13 | 512 14 | ..\..\ 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\..\packages\Neo4jClient.1.1.0.1\lib\net45\Neo4jClient.dll 36 | True 37 | 38 | 39 | False 40 | ..\..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Properties\AssemblyInfoGlobal.cs 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/Neo4jClient.Extension.Attributes.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Neo4jClient.Extension.Attributes 5 | $version$ 6 | Neo4jClient.Extension.Attributes 7 | Simon Pinn 8 | Simon Pinn 9 | https://github.com/simonpinn/Neo4jClient.Extension/blob/master/LICENSE 10 | https://github.com/simonpinn/Neo4jClient.Extension 11 | false 12 | Extending the awesome Neo4jClient, provides just the attributes required by Neo4jClient.Extension to allow class libraries to remove dependency on Neo4jClient 13 | Copyright 2015 14 | Neo4j Neo4jClient GraphDb GraphData 15 | Simplify and reduce magic strings when using the Neo4jClient, allows any object to be a data POCO 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("Neo4jClient.Extension.Attribute")] 4 | [assembly: AssemblyProduct("Neo4jClient.Extension.Attribute")] 5 | [assembly: AssemblyDescription("Extending the awesome Neo4jClient")] 6 | 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension.Attributes/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/BaseRelationship.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher 2 | { 3 | public abstract class BaseRelationship 4 | { 5 | protected BaseRelationship(string key) : this(key,null,null){} 6 | protected BaseRelationship(string fromKey, string toKey) : this(fromKey+toKey, fromKey,toKey){} 7 | protected BaseRelationship(string key, string fromKey, string toKey) 8 | { 9 | FromKey = fromKey; 10 | ToKey = toKey; 11 | Key = key; 12 | } 13 | 14 | public string FromKey { get; set; } 15 | public string Key { get; set; } 16 | public string ToKey { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/CypherExtensionContext.cs: -------------------------------------------------------------------------------- 1 | using Neo4jClient.Cypher; 2 | using Newtonsoft.Json.Serialization; 3 | 4 | namespace Neo4jClient.Extension.Cypher 5 | { 6 | public interface ICypherExtensionContext 7 | { 8 | IContractResolver JsonContractResolver { get; set; } 9 | } 10 | 11 | public class CypherExtensionContext : ICypherExtensionContext 12 | { 13 | public static CypherExtensionContext Create(ICypherFluentQuery query) 14 | { 15 | return new CypherExtensionContext 16 | { 17 | JsonContractResolver = query.Query.JsonContractResolver 18 | }; 19 | } 20 | 21 | public CypherExtensionContext() 22 | { 23 | JsonContractResolver = new CamelCasePropertyNamesContractResolver(); 24 | } 25 | 26 | public IContractResolver JsonContractResolver { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/CypherProperty.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Neo4jClient.Extension.Cypher 4 | { 5 | public class CypherProperty 6 | { 7 | public string TypeName { get; set; } 8 | public string JsonName { get; set; } 9 | 10 | public override string ToString() 11 | { 12 | return string.Format("TypeName={0}, JsonName={1}", TypeName, JsonName); 13 | } 14 | } 15 | 16 | public class CypherPropertyComparer : IEqualityComparer 17 | { 18 | public bool Equals(CypherProperty x, CypherProperty y) 19 | { 20 | return x.JsonName == y.JsonName; 21 | } 22 | 23 | public int GetHashCode(CypherProperty obj) 24 | { 25 | return obj.JsonName.GetHashCode(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/CypherTypeItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Neo4jClient.Extension.Cypher 4 | { 5 | public class CypherTypeItem : IEquatable 6 | { 7 | public Type Type { get; set; } 8 | public Type AttributeType { get; set; } 9 | 10 | public bool Equals(CypherTypeItem other) 11 | { 12 | return other.Type == Type && other.AttributeType == AttributeType; 13 | } 14 | 15 | bool IEquatable.Equals(CypherTypeItem other) 16 | { 17 | return Equals(other); 18 | } 19 | 20 | public override int GetHashCode() 21 | { 22 | return Type.GetHashCode() ^ AttributeType.GetHashCode(); 23 | } 24 | 25 | public override string ToString() 26 | { 27 | return string.Format("Type={0}, AttributeType={1}", Type.Name, AttributeType.Name); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/CypherTypeItemHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Neo4jClient.Extension.Cypher.Attributes; 5 | 6 | namespace Neo4jClient.Extension.Cypher 7 | { 8 | public class CypherTypeItemHelper 9 | { 10 | private readonly ConcurrentDictionary> _typeProperties = new ConcurrentDictionary>(); 11 | 12 | public CypherTypeItem AddKeyAttribute(ICypherExtensionContext context, TEntity entity) 13 | where TAttr : CypherExtensionAttribute 14 | where TEntity : class 15 | { 16 | var type = entity.GetType(); 17 | var key = new CypherTypeItem { Type = type, AttributeType = typeof(TAttr) }; 18 | //check cache 19 | if (!_typeProperties.ContainsKey(key)) 20 | { 21 | //strip off properties create map for usage 22 | _typeProperties.AddOrUpdate(key, type.GetProperties().Where(x => x.GetCustomAttributes(typeof(TAttr),true).Any()) 23 | .Select(x => new CypherProperty {TypeName = x.Name, JsonName = x.Name.ApplyCasing(context)}) 24 | .ToList(), (k, e) => e); 25 | } 26 | return key; 27 | } 28 | 29 | public List PropertiesForPurpose(ICypherExtensionContext context, TEntity entity) 30 | where TEntity : class 31 | where TAttr : CypherExtensionAttribute 32 | { 33 | var key = AddKeyAttribute(context, entity); 34 | return _typeProperties[key]; 35 | } 36 | 37 | public List PropertiesForPurpose(TEntity entity) 38 | where TEntity : class 39 | where TAttr : CypherExtensionAttribute 40 | { 41 | return PropertiesForPurpose(CypherExtension.DefaultExtensionContext,entity); 42 | } 43 | 44 | public void AddPropertyUsage(CypherTypeItem type, List properties) 45 | { 46 | _typeProperties.AddOrUpdate(type, properties, (item, list) => properties); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/Extension/CreateDynamicOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Cypher 2 | { 3 | class CreateDynamicOptions 4 | { 5 | public bool IgnoreNulls { get; set; } 6 | 7 | public override string ToString() 8 | { 9 | return string.Format("IgnoreNulls={0}", IgnoreNulls); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Neo4jClient.Extension.Cypher.Attributes; 7 | using Newtonsoft.Json.Serialization; 8 | 9 | namespace Neo4jClient.Extension.Cypher 10 | { 11 | public static partial class CypherExtension 12 | { 13 | private static string GetMatchWithParam(string key, string label, string paramName) 14 | { 15 | return GetMatchCypher(key, label, string.IsNullOrEmpty(paramName) ? "" : AsWrappedVariable(paramName)); 16 | } 17 | 18 | private static string GetMatchCypher(string key, string label, string variable) 19 | { 20 | var cypher = string.Format("{0} {1}", GetAliasLabelCql(key, label), variable).TrimEnd(); 21 | return cypher; 22 | } 23 | 24 | private static string GetAliasLabelCql(string alias, string label) 25 | { 26 | return string.Format("{0}:{1}", alias, label); 27 | } 28 | 29 | private static string AsWrappedVariable(string input) 30 | { 31 | var output = string.Format("{{{0}}}", input); 32 | return output; 33 | } 34 | private static string WithPrePostWrap(string innerCypher, IOptionsBase options) 35 | { 36 | var output = string.Format("{0}({1}){2}", options.PreCql, innerCypher, options.PostCql); 37 | return output; 38 | } 39 | 40 | private static string GetSetWithParamCql(string alias, string paramName) 41 | { 42 | var cql = string.Format("{0} = {{{1}}}", alias, paramName); 43 | return cql; 44 | } 45 | 46 | private static string GetSetWithParamCql(string alias, string property, string paramName) 47 | { 48 | var cql = GetSetWithParamCql(alias + "." + property, paramName); 49 | return cql; 50 | } 51 | 52 | private static string GetMatchParamName(string key) 53 | { 54 | return key + "MatchKey"; 55 | } 56 | 57 | private static string GetRelationshipCql(string identifierFrom, string relationshipSegment, string identifierTo) 58 | { 59 | var cql = string.Format("({0})-[{1}]->({2})" 60 | , identifierFrom 61 | , relationshipSegment 62 | , identifierTo); 63 | 64 | return cql; 65 | } 66 | 67 | internal static string GetMatchCypher(this TEntity entity 68 | , ICypherExtensionContext context 69 | , List useProperties 70 | , string paramKey) 71 | where TEntity : class 72 | { 73 | var label = entity.EntityLabel(); 74 | paramKey = entity.EntityParamKey(paramKey); 75 | 76 | var matchProperties = useProperties 77 | .Select(x => string.Format("{0}:{{{1}}}.{0}", x.JsonName, GetMatchParamName(paramKey))) 78 | .ToList(); 79 | 80 | var jsonProperties = string.Join(",", matchProperties); 81 | 82 | var parameterCypher = matchProperties.Count == 0 ? string.Empty : AsWrappedVariable(jsonProperties); 83 | 84 | var cypher = GetMatchCypher(paramKey, label, parameterCypher); 85 | 86 | return cypher; 87 | } 88 | 89 | internal static string ToCypherString(this TEntity entity, ICypherExtensionContext context, string paramKey = null, List useProperties = null) 90 | where TAttr : CypherExtensionAttribute 91 | where TEntity : class 92 | { 93 | var properties = useProperties ?? CypherTypeItemHelper.PropertiesForPurpose(entity); 94 | 95 | return entity.GetMatchCypher(context, properties, paramKey); 96 | } 97 | internal static string ApplyCasing(this string value, ICypherExtensionContext context) 98 | { 99 | var useCamelCase = (context.JsonContractResolver is CamelCasePropertyNamesContractResolver); 100 | if (useCamelCase) 101 | { 102 | return string.Format( 103 | "{0}{1}" 104 | , value.Substring(0, 1).ToLowerInvariant() 105 | , value.Length > 1 ? value.Substring(1, value.Length - 1) : string.Empty); 106 | } 107 | return value; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Dynamics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Neo4jClient.Extension.Cypher 8 | { 9 | public static partial class CypherExtension 10 | { 11 | private static Dictionary CreateDynamic( 12 | this TEntity entity 13 | , List properties 14 | , CreateDynamicOptions options = null) where TEntity : class 15 | { 16 | if (options == null) 17 | { 18 | options = new CreateDynamicOptions(); 19 | } 20 | 21 | var type = entity.GetType(); 22 | var propertiesForDict = properties.Select( 23 | prop => new 24 | { 25 | Key = prop.JsonName 26 | , 27 | Value = GetValue(entity, prop, type) 28 | } 29 | ).ToList(); 30 | 31 | if (options.IgnoreNulls) 32 | { 33 | propertiesForDict.RemoveAll(p => p.Value == null); 34 | } 35 | 36 | return propertiesForDict.ToDictionary(x => x.Key, x => x.Value); 37 | } 38 | 39 | private static object GetValue(TEntity entity, CypherProperty property, Type entityTypeCache = null) 40 | { 41 | var entityType = entityTypeCache ?? entity.GetType(); 42 | var value = entityType.GetProperty(property.TypeName).GetValue(entity, null); 43 | return value; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Entity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Neo4jClient.Extension.Cypher.Attributes; 8 | 9 | namespace Neo4jClient.Extension.Cypher 10 | { 11 | /// 12 | /// Entity extension methods 13 | /// 14 | public static partial class CypherExtension 15 | { 16 | private static readonly object _syncRoot = new object(); 17 | 18 | internal static string EntityLabel(this T entity) 19 | { 20 | var entityType = entity.GetType(); 21 | 22 | // http://stackoverflow.com/questions/157933 23 | lock (_syncRoot) 24 | { 25 | if (!EntityLabelCache.ContainsKey(entityType)) 26 | { 27 | var label = entityType.GetCustomAttributes(typeof (CypherLabelAttribute), true).FirstOrDefault() as CypherLabelAttribute; 28 | 29 | try 30 | { 31 | EntityLabelCache.Add(entityType, label == null ? entityType.Name : label.Name); 32 | } 33 | catch (ArgumentException e) 34 | { 35 | var moreInfoException = new ArgumentException($"Failed to cache label '{label}' for type='{typeof(T).Name}'", e); 36 | throw moreInfoException; 37 | } 38 | } 39 | } 40 | 41 | var output = EntityLabelCache[entityType]; 42 | return output; 43 | } 44 | 45 | internal static string EntityParamKey(this T entity, string paramKey = null) 46 | { 47 | return paramKey ?? entity.GetType().Name.ToLowerInvariant(); 48 | } 49 | 50 | 51 | public static List UseProperties(this T entity, params Expression>[] properties) 52 | where T : class 53 | { 54 | return entity.UseProperties(DefaultExtensionContext, properties); 55 | } 56 | 57 | internal static List UseProperties(this T entity, CypherExtensionContext context, params Expression>[] properties) 58 | where T : class 59 | { 60 | //Cache the T entity properties into a dictionary of strings 61 | if (properties != null) 62 | { 63 | return properties.ToList().Where(x => x != null).Select(x => 64 | { 65 | var memberExpression = x.Body as MemberExpression ?? ((UnaryExpression)x.Body).Operand as MemberExpression; 66 | return memberExpression == null ? null : memberExpression.Member.Name; 67 | }).Select(x => new CypherProperty { TypeName = x, JsonName = x.ApplyCasing(context) }).ToList(); 68 | } 69 | return new List(); 70 | } 71 | 72 | private static List GetCreateProperties(T entity, List onCreateOverride = null) where T : class 73 | { 74 | var properties = onCreateOverride ?? CypherTypeItemHelper.PropertiesForPurpose(entity); 75 | return properties; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Fluent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Neo4jClient.Extension.Cypher 8 | { 9 | public static partial class CypherExtension 10 | { 11 | internal static void AddConfigProperties(CypherTypeItem type, List properties) 12 | { 13 | CypherTypeItemHelper.AddPropertyUsage(type, properties); 14 | } 15 | internal static void SetConfigLabel(Type type, string label) 16 | { 17 | if (EntityLabelCache.ContainsKey(type)) 18 | { 19 | EntityLabelCache[type] = label; 20 | } 21 | else 22 | { 23 | EntityLabelCache.Add(type, label); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text.RegularExpressions; 6 | using Neo4jClient.Cypher; 7 | using Neo4jClient.Extension.Cypher.Attributes; 8 | using Newtonsoft.Json.Serialization; 9 | 10 | namespace Neo4jClient.Extension.Cypher 11 | { 12 | public static partial class CypherExtension 13 | { 14 | private static readonly CypherTypeItemHelper CypherTypeItemHelper = new CypherTypeItemHelper(); 15 | public static CypherExtensionContext DefaultExtensionContext = new CypherExtensionContext(); 16 | private static readonly Dictionary EntityLabelCache = new Dictionary(); 17 | 18 | public static ICypherFluentQuery MatchEntity(this ICypherFluentQuery query, T entity, string identifier = null, string preCql = "", string postCql = "", List propertyOverride = null) where T : class 19 | { 20 | var options = new MatchOptions 21 | { 22 | Identifier = identifier, 23 | PreCql = preCql, 24 | PostCql = postCql, 25 | MatchOverride = propertyOverride 26 | }; 27 | return MatchEntity(query, entity, options); 28 | } 29 | 30 | public static ICypherFluentQuery MatchEntity(this ICypherFluentQuery query, T entity, MatchOptions options) 31 | where T : class 32 | { 33 | return MatchWorker(query, entity, options, (q, s) => q.Match(s)); 34 | } 35 | 36 | public static ICypherFluentQuery OptionalMatchEntity(this ICypherFluentQuery query, T entity, MatchOptions options= null) 37 | where T : class 38 | { 39 | if (options == null) 40 | { 41 | options = new MatchOptions(); 42 | } 43 | return MatchWorker(query, entity, options, (q, s) => q.OptionalMatch(s)); 44 | } 45 | 46 | private static ICypherFluentQuery MatchWorker(this ICypherFluentQuery query, T entity, MatchOptions options, Func matchFunction) where T : class 47 | { 48 | var identifier = entity.EntityParamKey(options.Identifier); 49 | var matchCypher = entity.ToCypherString(CypherExtensionContext.Create(query), identifier, options.MatchOverride); 50 | var cql = string.Format("{0}({1}){2}", options.PreCql, matchCypher, options.PostCql); 51 | dynamic cutdown = entity.CreateDynamic(options.MatchOverride ?? CypherTypeItemHelper.PropertiesForPurpose(entity)); 52 | 53 | var matchKey = GetMatchParamName(identifier); 54 | 55 | return matchFunction(query,cql) 56 | .WithParam(matchKey, cutdown); 57 | } 58 | 59 | public static ICypherFluentQuery CreateEntity(this ICypherFluentQuery query, T entity, string identifier = null, List onCreateOverride = null, string preCql = "", string postCql = "") where T : class 60 | { 61 | var options = new CreateOptions(); 62 | 63 | options.PostCql = postCql; 64 | options.PreCql = preCql; 65 | options.Identifier = identifier; 66 | options.CreateOverride = onCreateOverride; 67 | 68 | return CreateEntity(query, entity, options); 69 | } 70 | 71 | public static ICypherFluentQuery CreateEntity(this ICypherFluentQuery query, T entity, CreateOptions options) where T : class 72 | { 73 | Func getFinalCql = intermediateCql => WithPrePostWrap(intermediateCql, options); 74 | 75 | query = CommonCreate(query, entity, options, getFinalCql); 76 | 77 | return query; 78 | } 79 | 80 | public static ICypherFluentQuery CreateRelationship(this ICypherFluentQuery query, T entity, CreateOptions options = null) where T : BaseRelationship 81 | { 82 | Func getFinalCql = intermediateCql => GetRelationshipCql(entity.FromKey, intermediateCql, entity.ToKey); 83 | 84 | if (options == null) 85 | { 86 | options = new CreateOptions(); 87 | options.Identifier = entity.Key; 88 | } 89 | 90 | query = CommonCreate(query, entity, options, getFinalCql); 91 | 92 | return query; 93 | } 94 | 95 | private static ICypherFluentQuery CommonCreate( 96 | this ICypherFluentQuery query 97 | , T entity 98 | , CreateOptions options 99 | , Func getFinalCql) where T : class 100 | { 101 | if (options == null) 102 | { 103 | options = new CreateOptions(); 104 | } 105 | 106 | var createProperties = GetCreateProperties(entity); 107 | var identifier = entity.EntityParamKey(options.Identifier); 108 | var intermediateCreateCql = GetMatchWithParam(identifier, entity.EntityLabel(), createProperties.Count > 0 ? identifier : ""); 109 | 110 | var createCql = getFinalCql(intermediateCreateCql); 111 | 112 | var dynamicOptions = new CreateDynamicOptions { IgnoreNulls = true }; // working around some buug where null properties are blowing up. don't care on create. 113 | var cutdownEntity = entity.CreateDynamic(createProperties, dynamicOptions); 114 | 115 | query = query.Create(createCql); 116 | 117 | if (createProperties.Count > 0) 118 | { 119 | query = query.WithParam(identifier, cutdownEntity); 120 | } 121 | 122 | return query; 123 | } 124 | 125 | public static ICypherFluentQuery MergeEntity(this ICypherFluentQuery query, T entity, string paramKey = null, List mergeOverride = null, List onMatchOverride = null, List onCreateOverride = null, string preCql = "", string postCql = "") where T : class 126 | { 127 | paramKey = entity.EntityParamKey(paramKey); 128 | var context = CypherExtensionContext.Create(query); 129 | var cypher1 = entity.ToCypherString(context, paramKey, mergeOverride); 130 | var cql = string.Format("{0}({1}){2}", preCql, cypher1, postCql); 131 | return query.CommonMerge(entity, paramKey, cql, mergeOverride, onMatchOverride, onCreateOverride); 132 | } 133 | 134 | public static ICypherFluentQuery MergeEntity(this ICypherFluentQuery query, T entity, MergeOptions options) where T : class 135 | { 136 | var context = CypherExtensionContext.Create(query); 137 | string pattern; 138 | 139 | if (options.MergeViaRelationship != null) 140 | { 141 | var relationshipSegment = GetAliasLabelCql(string.Empty, options.MergeViaRelationship.EntityLabel()); 142 | 143 | pattern = GetRelationshipCql( 144 | options.MergeViaRelationship.FromKey 145 | , relationshipSegment 146 | , GetAliasLabelCql(options.MergeViaRelationship.ToKey, entity.EntityLabel())); 147 | } 148 | else 149 | { 150 | pattern = entity.ToCypherString(context, options.Identifier, options.MergeOverride); 151 | } 152 | var wrappedPattern = string.Format("{0}({1}){2}", options.PreCql, pattern, options.PostCql); 153 | return query.CommonMerge(entity, options.Identifier, wrappedPattern, options.MergeOverride, options.OnMatchOverride, options.OnCreateOverride); 154 | } 155 | 156 | public static ICypherFluentQuery MergeRelationship(this ICypherFluentQuery query, T entity, List mergeOverride = null, List onMatchOverride = null, List onCreateOverride = null) where T : BaseRelationship 157 | { 158 | //Eaxctly the same as a merge entity except the cql is different 159 | var cql = GetRelationshipCql( 160 | entity.FromKey 161 | , entity.ToCypherString(CypherExtensionContext.Create(query), entity.Key, mergeOverride) 162 | , entity.ToKey); 163 | 164 | return query.CommonMerge(entity, entity.Key, cql, mergeOverride, onMatchOverride, onCreateOverride); 165 | } 166 | 167 | public static ICypherFluentQuery MatchRelationship( 168 | this ICypherFluentQuery query 169 | , T relationship 170 | , MatchRelationshipOptions options) where T : BaseRelationship 171 | { 172 | return MatchRelationshipWorker(query, relationship, options, (fluentQuery, s) => fluentQuery.Match(s)); 173 | } 174 | 175 | public static ICypherFluentQuery OptionalMatchRelationship( 176 | this ICypherFluentQuery query 177 | , T relationship 178 | , MatchRelationshipOptions options = null) where T : BaseRelationship 179 | { 180 | if (options == null) 181 | { 182 | options = new MatchRelationshipOptions(); 183 | } 184 | return MatchRelationshipWorker(query, relationship, options, (fluentQuery, s) => fluentQuery.OptionalMatch(s)); 185 | } 186 | 187 | private static ICypherFluentQuery MatchRelationshipWorker( 188 | this ICypherFluentQuery query 189 | , T relationship 190 | , MatchRelationshipOptions options 191 | , Func matchFunction) where T : BaseRelationship 192 | { 193 | var matchProperties = options.MatchOverride ?? CypherTypeItemHelper.PropertiesForPurpose(relationship); 194 | var cql = GetRelationshipCql( 195 | relationship.FromKey 196 | , relationship.ToCypherString(CypherExtensionContext.Create(query), relationship.Key, matchProperties) 197 | , relationship.ToKey); 198 | 199 | return matchFunction(query,cql); 200 | } 201 | 202 | public static ICypherFluentQuery MatchRelationship(this ICypherFluentQuery query, T relationship, List matchOverride = null) where T : BaseRelationship 203 | { 204 | var options = new MatchRelationshipOptions(); 205 | options.MatchOverride = matchOverride; 206 | return MatchRelationship(query, relationship, options); 207 | } 208 | 209 | private static ICypherFluentQuery CommonMerge( 210 | this ICypherFluentQuery query 211 | , T entity 212 | , string key 213 | , string mergeCql 214 | , List mergeOverride = null 215 | , List onMatchOverride = null 216 | , List onCreateOverride = null) where T : class 217 | { 218 | //A merge requires the properties of both merge, create and match in the cutdown object 219 | var mergeProperties = mergeOverride ?? CypherTypeItemHelper.PropertiesForPurpose(entity); 220 | var createProperties = GetCreateProperties(entity, onCreateOverride); 221 | var matchProperties = onMatchOverride ?? CypherTypeItemHelper.PropertiesForPurpose(entity); 222 | 223 | dynamic mergeObjectParam = entity.CreateDynamic(mergeProperties); 224 | var matchParamName = GetMatchParamName(key); 225 | 226 | query = query.Merge(mergeCql); 227 | query = query.WithParam(matchParamName, mergeObjectParam); 228 | 229 | if (matchProperties.Count > 0) 230 | { 231 | var entityType = entity.GetType(); 232 | foreach (var matchProperty in matchProperties) 233 | { 234 | var propertyParam = key + matchProperty.JsonName; 235 | var propertyValue = GetValue(entity, matchProperty, entityType); 236 | query = query.OnMatch().Set(GetSetWithParamCql(key, matchProperty.JsonName, propertyParam)); 237 | query = query.WithParam(propertyParam, propertyValue); 238 | } 239 | } 240 | 241 | if (createProperties.Count > 0) 242 | { 243 | var createParamName = key + "OnCreate"; 244 | dynamic createObjectParam = entity.CreateDynamic(createProperties); 245 | query = query.OnCreate().Set(GetSetWithParamCql(key, createParamName)); 246 | query = query.WithParam(createParamName, createObjectParam); 247 | } 248 | 249 | return query; 250 | } 251 | 252 | public static string GetFormattedDebugText(this ICypherFluentQuery query) 253 | { 254 | return GetFormattedCypher(query.Query.DebugQueryText); 255 | } 256 | 257 | public static string GetFormattedCypher(string cypherText) 258 | { 259 | var regex = new Regex("\\\"([^(\\\")\"]+)\\\":", RegexOptions.Multiline); 260 | var s = regex.Replace(cypherText, "$1:"); 261 | s = s.Replace("ON MATCH\r\nSET", "ON MATCH SET"); // this is more readable 262 | s = s.Replace("ON CREATE\r\nSET", "ON CREATE SET"); 263 | return s; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/FluentConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using Neo4jClient.Extension.Cypher.Attributes; 12 | 13 | namespace Neo4jClient.Extension.Cypher 14 | { 15 | public class FluentConfig 16 | { 17 | private readonly CypherExtensionContext _context; 18 | 19 | private FluentConfig(CypherExtensionContext context) 20 | { 21 | _context = context; 22 | } 23 | 24 | public static FluentConfig Config(CypherExtensionContext context = null) 25 | { 26 | return new FluentConfig(context??new CypherExtensionContext()); 27 | } 28 | 29 | public ConfigWith With(string label = null) 30 | { 31 | return new ConfigWith(_context, label); 32 | } 33 | } 34 | 35 | public class ConfigWith 36 | { 37 | private readonly ConcurrentBag> _properties = new ConcurrentBag>(); 38 | private readonly CypherExtensionContext _context; 39 | private readonly string _label; 40 | 41 | public ConfigWith(CypherExtensionContext context, string label = null) 42 | { 43 | _label = label; 44 | _context = context; 45 | } 46 | 47 | private ConfigWith Config(Expression> property, Type attribute) 48 | { 49 | var memberExpression = property.Body as MemberExpression ?? ((UnaryExpression) property.Body).Operand as MemberExpression; 50 | var name = memberExpression == null ? null : memberExpression.Member.Name; 51 | _properties.Add(new Tuple(attribute, new CypherProperty {TypeName = name, JsonName = name.ApplyCasing(_context)})); 52 | return this; 53 | } 54 | 55 | public ConfigWith Match(Expression> property) 56 | { 57 | return Config(property, typeof(CypherMatchAttribute)); 58 | } 59 | 60 | public ConfigWith Merge(Expression> property) 61 | { 62 | return Config(property, typeof (CypherMergeAttribute)); 63 | } 64 | 65 | public ConfigWith MergeOnCreate(Expression> property) 66 | { 67 | return Config(property, typeof(CypherMergeOnCreateAttribute)); 68 | } 69 | 70 | public ConfigWith MergeOnMatch(Expression> property) 71 | { 72 | return Config(property, typeof(CypherMergeOnMatchAttribute)); 73 | } 74 | 75 | public ConfigWith MergeOnMatchOrCreate(Expression> property) 76 | { 77 | return MergeOnMatch(property).MergeOnCreate(property); 78 | } 79 | 80 | public List>> Set() 81 | { 82 | //set the properties 83 | var returnValue = _properties.GroupBy(x => x.Item1) 84 | .Select(x => new Tuple>(new CypherTypeItem() 85 | { 86 | AttributeType = x.Key, 87 | Type = typeof (T) 88 | }, x.Select(y => y.Item2).Distinct(new CypherPropertyComparer()).ToList())).ToList(); 89 | 90 | returnValue.ForEach(x => CypherExtension.AddConfigProperties(x.Item1, x.Item2)); 91 | //set the label 92 | if (!string.IsNullOrWhiteSpace(_label)) 93 | { 94 | CypherExtension.SetConfigLabel(typeof (T), _label); 95 | } 96 | return returnValue; 97 | } 98 | 99 | public ConfigWith With(string label=null) 100 | { 101 | Set(); 102 | return new ConfigWith(_context, label); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Cypher/MergeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Neo4jClient.Extension.Cypher 8 | { 9 | public class MergeOptions 10 | { 11 | public string Identifier { get; set; } 12 | 13 | public List MergeOverride { get; set; } 14 | 15 | public List OnMatchOverride { get; set; } 16 | 17 | public List OnCreateOverride { get; set; } 18 | 19 | public string PreCql { get; set; } 20 | 21 | public string PostCql { get; set; } 22 | 23 | /// 24 | /// Merge the entity via a relationship 25 | /// 26 | public BaseRelationship MergeViaRelationship { get; set; } 27 | 28 | public MergeOptions() 29 | { 30 | MergeOverride = null; 31 | OnMatchOverride = null; 32 | OnCreateOverride = null; 33 | } 34 | 35 | /// 36 | /// For overriding the default identifier configured via FluentConfig 37 | /// 38 | public static MergeOptions WithIdentifier(string identifier) 39 | { 40 | return new MergeOptions { Identifier = identifier }; 41 | } 42 | 43 | /// 44 | /// For when merging against a node that is matched via a relationsip 45 | /// 46 | public static MergeOptions ViaRelationship(BaseRelationship relationship) 47 | { 48 | var options = new MergeOptions(); 49 | options.Identifier = relationship.ToKey; 50 | options.MergeViaRelationship = relationship; 51 | return options; 52 | } 53 | } 54 | 55 | public static class MergeOptionExtensions 56 | { 57 | public static MergeOptions WithMergeProperties(this MergeOptions target, List mergeOverride) 58 | { 59 | target.MergeOverride = mergeOverride; 60 | return target; 61 | } 62 | 63 | public static MergeOptions WithNoMergeProperties(this MergeOptions target) 64 | { 65 | return WithMergeProperties(target, new List()); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Neo4jClient.Extension.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {41C65BED-56A6-4942-95D2-10E62F607C7F} 8 | Library 9 | Properties 10 | Neo4jClient.Extension 11 | Neo4jClient.Extension 12 | v4.5 13 | 512 14 | ..\..\ 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\..\packages\Neo4jClient.1.1.0.1\lib\net45\Neo4jClient.dll 37 | True 38 | 39 | 40 | False 41 | ..\..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 42 | 43 | 44 | 45 | 46 | 47 | False 48 | ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll 49 | 50 | 51 | False 52 | ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Properties\AssemblyInfoGlobal.cs 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Designer 87 | 88 | 89 | 90 | 91 | {6d2502f8-f491-45e6-abd8-2f7407926f5a} 92 | Neo4jClient.Extension.Attributes 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 108 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Neo4jClient.Extension.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Neo4jClient.Extension 5 | $version$ 6 | Neo4jClient.Extension 7 | Simon Pinn 8 | Simon Pinn 9 | https://github.com/simonpinn/Neo4jClient.Extension/blob/master/LICENSE 10 | https://github.com/simonpinn/Neo4jClient.Extension 11 | false 12 | $description$ 13 | Copyright 2014 14 | Neo4j Neo4jClient GraphDb GraphData 15 | Simplify and reduce magic strings when using the Neo4jClient, allows any object to be a data POCO 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Options/CreateOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Neo4jClient.Extension.Cypher 8 | { 9 | public class CreateOptions : IOptionsBase 10 | { 11 | public string Identifier { get; set; } 12 | public string PreCql { get; set; } 13 | public string PostCql { get; set; } 14 | public List CreateOverride { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Options/IOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Neo4jClient.Extension.Cypher 8 | { 9 | public interface IOptionsBase 10 | { 11 | string PreCql { get; set; } 12 | string PostCql { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Options/MatchOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Neo4jClient.Extension.Cypher 8 | { 9 | public class MatchOptions 10 | { 11 | public string Identifier { get; set; } 12 | 13 | public string PreCql { get; set; } 14 | 15 | public string PostCql { get; set; } 16 | 17 | public List MatchOverride { get; set; } 18 | 19 | public MatchOptions() 20 | { 21 | MatchOverride = null; 22 | } 23 | 24 | public static MatchOptions Create(string identifier) 25 | { 26 | return new MatchOptions {Identifier = identifier}; 27 | } 28 | } 29 | 30 | public static class MatchOptionExtensions 31 | { 32 | public static MatchOptions WithProperties(this MatchOptions target, List propertyOverride) 33 | { 34 | target.MatchOverride = propertyOverride; 35 | return target; 36 | } 37 | 38 | public static MatchOptions WithNoProperties(this MatchOptions target) 39 | { 40 | return WithProperties(target, new List()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Options/MatchRelationshipOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Neo4jClient.Extension.Cypher 4 | { 5 | public class MatchRelationshipOptions 6 | { 7 | public List MatchOverride { get; set; } 8 | 9 | public static MatchRelationshipOptions Create() 10 | { 11 | return new MatchRelationshipOptions(); 12 | } 13 | } 14 | 15 | public static class MatchRelationshipOptionsExtensions 16 | { 17 | public static MatchRelationshipOptions WithProperties(this MatchRelationshipOptions target, List propertyOverride) 18 | { 19 | target.MatchOverride = propertyOverride; 20 | return target; 21 | } 22 | 23 | public static MatchRelationshipOptions WithNoProperties(this MatchRelationshipOptions target) 24 | { 25 | return WithProperties(target, new List()); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("Neo4jClient.Extension")] 4 | [assembly: AssemblyProduct("Neo4jClient.Extension")] 5 | [assembly: AssemblyDescription("Extending the awesome Neo4jClient, provides just the attributes required by Neo4jClient.Extension to allow class libraries to remove dependency on Neo4jClient")] 6 | 7 | -------------------------------------------------------------------------------- /src/Neo4jClient.Extension/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/IntegrationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Neo4jClient.Cypher; 8 | using Neo4jClient.Extension.Test.CustomConverters; 9 | using Neo4jClient.Extension.Test.Data; 10 | using Neo4jClient.Transactions; 11 | using NUnit.Framework; 12 | 13 | namespace Neo4jClient.Extension.Test.Integration 14 | { 15 | 16 | public class IntegrationTest 17 | { 18 | protected static ITransactionalGraphClient GraphClient { get; private set; } 19 | 20 | protected ICypherFluentQuery CypherQuery { get { return GraphClient.Cypher; } } 21 | 22 | [SetUp] 23 | public void Setup() 24 | { 25 | CypherQuery.Match("(n)") 26 | .OptionalMatch("(n)-[r]-()") 27 | .Delete("n, r") 28 | .ExecuteWithoutResults(); 29 | } 30 | 31 | protected Func RealQueryFactory 32 | { 33 | get { return () => CypherQuery; } 34 | } 35 | 36 | static IntegrationTest() 37 | { 38 | var connectionString = ConfigurationManager.AppSettings["Neo4jConnectionString"]; 39 | GraphClient =new GraphClient(new Uri(connectionString)); 40 | 41 | GraphClient.JsonConverters.Add(new AreaJsonConverter()); 42 | 43 | GraphClient.Connect(); 44 | 45 | NeoConfig.ConfigureModel(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8F1FA0BF-C481-4D1D-A8ED-F9B4CB17E98B} 8 | Library 9 | Properties 10 | Neo4jClient.Extension.Test.Integration 11 | Neo4jClient.Extension.Test.Integration 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\..\packages\Neo4jClient.1.1.0.1\lib\net45\Neo4jClient.dll 36 | True 37 | 38 | 39 | ..\..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 40 | True 41 | 42 | 43 | ..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll 44 | True 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ..\..\packages\UnitsNet.3.14.0\lib\net35\UnitsNet.dll 56 | True 57 | 58 | 59 | 60 | 61 | Properties\AssemblyInfoGlobal.cs 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {6d2502f8-f491-45e6-abd8-2f7407926f5a} 75 | Neo4jClient.Extension.Attributes 76 | 77 | 78 | {41c65bed-56a6-4942-95d2-10e62f607c7f} 79 | Neo4jClient.Extension 80 | 81 | 82 | {b7c14349-6bec-44d1-ab33-b82ad85899aa} 83 | Neo4jClient.Extension.Test.Common 84 | 85 | 86 | {066a5ebd-c612-40e2-8065-160fa9853503} 87 | Neo4jClient.Extension.UnitTest 88 | 89 | 90 | 91 | 92 | 99 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("Neo4jClient.Extension.IntegrationTest")] 4 | [assembly: AssemblyProduct("Neo4jClient.Extension")] 5 | [assembly: AssemblyDescription("Integration tests to see if generated code really does work")] 6 | 7 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/Tests/CreateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Neo4jClient.Extension.Cypher; 7 | using Neo4jClient.Extension.Test.Cypher; 8 | using NUnit.Framework; 9 | 10 | namespace Neo4jClient.Extension.Test.Integration.Tests 11 | { 12 | public class CreateTests : IntegrationTest 13 | { 14 | [Test] 15 | public void CreateWithUnusualType() 16 | { 17 | new FluentConfigCreateTests(RealQueryFactory) 18 | .CreateWithUnusualTypeAct() 19 | .ExecuteWithoutResults(); 20 | } 21 | 22 | [Test] 23 | public void CreateComplex() 24 | { 25 | new FluentConfigCreateTests(RealQueryFactory) 26 | .CreateComplexAct() 27 | .ExecuteWithoutResults(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/Tests/MergeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Neo4jClient.Extension.Cypher; 7 | using Neo4jClient.Extension.Test.Cypher; 8 | using NUnit.Framework; 9 | 10 | namespace Neo4jClient.Extension.Test.Integration.Tests 11 | { 12 | public class MergeTests : IntegrationTest 13 | { 14 | [Test] 15 | public void OneDeep() 16 | { 17 | // create 18 | new FluentConfigMergeTests(RealQueryFactory) 19 | .OneDeepAct() 20 | .ExecuteWithoutResults(); 21 | 22 | // merge 23 | new FluentConfigMergeTests(RealQueryFactory) 24 | .OneDeepAct() 25 | .ExecuteWithoutResults(); 26 | } 27 | 28 | [Test] 29 | public void TwoDeep() 30 | { 31 | // create 32 | new FluentConfigMergeTests(RealQueryFactory) 33 | .TwoDeepAct() 34 | .ExecuteWithoutResults(); 35 | 36 | // merge 37 | new FluentConfigMergeTests(RealQueryFactory) 38 | .TwoDeepAct() 39 | .ExecuteWithoutResults(); 40 | } 41 | 42 | [Test] 43 | public void OneDeepMergeByRelationship() 44 | { 45 | new FluentConfigMergeTests(RealQueryFactory) 46 | .OneDeepMergeByRelationshipAct() 47 | .ExecuteWithoutResults(); 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.IntegrationTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Domain/Address.cs: -------------------------------------------------------------------------------- 1 | namespace Neo4jClient.Extension.Test.Cypher 2 | { 3 | public class Address 4 | { 5 | public string Street { get; set; } 6 | 7 | public string Suburb { get; set; } 8 | 9 | public override string ToString() 10 | { 11 | return string.Format("Street='{0}', Suburb='{1}'", Street, Suburb); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Domain/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Neo4jClient.Extension.Test.Cypher 4 | { 5 | public enum Gender 6 | { 7 | Unspecified = 0, 8 | Male, 9 | Female 10 | } 11 | 12 | /// 13 | /// Contains value types and one complex type 14 | /// 15 | public class Person 16 | { 17 | /// 18 | /// Primary key seeded from else where 19 | /// 20 | public int Id { get; set; } 21 | 22 | public string Name { get; set; } 23 | 24 | public Gender Sex { get; set; } 25 | 26 | public string Title { get; set; } 27 | 28 | public Address HomeAddress { get; set; } 29 | 30 | public Address WorkAddress { get; set; } 31 | 32 | public bool IsOperative { get; set; } 33 | 34 | public int SerialNumber { get; set; } 35 | 36 | public Decimal SpendingAuthorisation { get; set; } 37 | 38 | public DateTimeOffset DateCreated { get; set; } 39 | 40 | public Person() 41 | { 42 | HomeAddress = new Address(); 43 | WorkAddress = new Address(); 44 | } 45 | 46 | public override string ToString() 47 | { 48 | return string.Format("Id={0}, Name={1}", Id, Name); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Domain/Weapon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using UnitsNet; 7 | 8 | namespace Neo4jClient.Extension.Test.TestData.Entities 9 | { 10 | public class Weapon 11 | { 12 | public int Id { get; set; } 13 | 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// Test unusal types 18 | /// 19 | public Area? BlastRadius { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/DomainModelDiagram.cd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 9 | 10 | AIACAAAAAAAABEAEAAAAAAQAIAEAEAAAAQAEAAAAAAA= 11 | TestData\Entities\Person.cs 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | AAAAAAAAAAAAAAgEAAAAAAAAAAAAAAAAAAIAAAAAAAA= 25 | TestData\Entities\Address.cs 26 | 27 | 28 | 29 | 30 | 31 | AAACAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAA= 32 | TestData\Entities\Weapon.cs 33 | 34 | 35 | 36 | 37 | 38 | BAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAA= 39 | TestData\Entities\Person.cs 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Neo/NeoConfig.cs: -------------------------------------------------------------------------------- 1 | using Neo4jClient.Extension.Cypher; 2 | using Neo4jClient.Extension.Test.Cypher; 3 | using Neo4jClient.Extension.Test.TestData.Entities; 4 | using Neo4jClient.Extension.Test.TestEntities.Relationships; 5 | 6 | namespace Neo4jClient.Extension.Test.Data 7 | { 8 | public class NeoConfig 9 | { 10 | public static void ConfigureModel() 11 | { 12 | FluentConfig.Config() 13 | .With("SecretAgent") 14 | .Match(x => x.Id) 15 | .Merge(x => x.Id) 16 | .MergeOnCreate(p => p.Id) 17 | .MergeOnCreate(p => p.DateCreated) 18 | .MergeOnMatchOrCreate(p => p.Title) 19 | .MergeOnMatchOrCreate(p => p.Name) 20 | .MergeOnMatchOrCreate(p => p.IsOperative) 21 | .MergeOnMatchOrCreate(p => p.Sex) 22 | .MergeOnMatchOrCreate(p => p.SerialNumber) 23 | .MergeOnMatchOrCreate(p => p.SpendingAuthorisation) 24 | .Set(); 25 | 26 | FluentConfig.Config() 27 | .With
() 28 | .MergeOnMatchOrCreate(a => a.Street) 29 | .MergeOnMatchOrCreate(a => a.Suburb) 30 | .Set(); 31 | 32 | FluentConfig.Config() 33 | .With() 34 | .Match(x => x.Id) 35 | .Merge(x => x.Id) 36 | .MergeOnMatchOrCreate(w => w.Name) 37 | .MergeOnMatchOrCreate(w => w.BlastRadius) 38 | .Set(); 39 | 40 | FluentConfig.Config() 41 | .With() 42 | .Match(ha => ha.DateEffective) 43 | .MergeOnMatchOrCreate(hr => hr.DateEffective) 44 | .Set(); 45 | 46 | FluentConfig.Config() 47 | .With() 48 | .Set(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Neo/Relationships/CheckedOutRelationship.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Neo4jClient.Extension.Cypher; 7 | using Neo4jClient.Extension.Cypher.Attributes; 8 | 9 | namespace Neo4jClient.Extension.Test.TestData.Relationships 10 | { 11 | [CypherLabel(Name = "HAS_CHECKED_OUT")] 12 | public class CheckedOutRelationship : BaseRelationship 13 | { 14 | public CheckedOutRelationship() : base ("agent", "weapon") 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Neo/Relationships/HomeAddressRelationship.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Neo4jClient.Extension.Cypher; 3 | using Neo4jClient.Extension.Cypher.Attributes; 4 | 5 | namespace Neo4jClient.Extension.Test.TestEntities.Relationships 6 | { 7 | [CypherLabel(Name = LabelName)] 8 | public class HomeAddressRelationship : BaseRelationship 9 | { 10 | public const string LabelName = "HOME_ADDRESS"; 11 | public HomeAddressRelationship(DateTimeOffset effective, string from = "agent", string to = "address") 12 | : base(from, to) 13 | { 14 | DateEffective = effective; 15 | } 16 | 17 | public HomeAddressRelationship(string from = "person", string to = "address") 18 | : base(from, to) 19 | { 20 | } 21 | 22 | public HomeAddressRelationship(string relationshipIdentifier, string from, string to) 23 | : base(relationshipIdentifier, from, to) 24 | { 25 | } 26 | 27 | public DateTimeOffset DateEffective { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Neo/Relationships/WorkAddressRelationship.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Neo4jClient.Extension.Cypher; 6 | using Neo4jClient.Extension.Cypher.Attributes; 7 | using Neo4jClient.Extension.Test.Cypher; 8 | 9 | namespace Neo4jClient.Extension.Test.TestEntities.Relationships 10 | { 11 | [CypherLabel(Name = LabelName)] 12 | public class WorkAddressRelationship : BaseRelationship 13 | { 14 | public const string LabelName = "WORK_ADDRESS"; 15 | public WorkAddressRelationship(string from = null, string to = null) 16 | : base(from, to) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Neo4jClient.Extension.Test.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B7C14349-6BEC-44D1-AB33-B82AD85899AA} 8 | Library 9 | Properties 10 | Neo4jClient.Extension.Test.Data 11 | Neo4jClient.Extension.Test.Data 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll 36 | True 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ..\..\packages\UnitsNet.3.14.0\lib\net35\UnitsNet.dll 48 | True 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {6d2502f8-f491-45e6-abd8-2f7407926f5a} 69 | Neo4jClient.Extension.Attributes 70 | 71 | 72 | {41c65bed-56a6-4942-95d2-10e62f607c7f} 73 | Neo4jClient.Extension 74 | 75 | 76 | 77 | 84 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Neo4jClient.Extension.Test.Data")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Neo4jClient.Extension.Test.Data")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b7c14349-6bec-44d1-ab33-b82ad85899aa")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/SampleDataFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Neo4jClient.Extension.Test.TestData.Entities; 3 | using UnitsNet; 4 | 5 | namespace Neo4jClient.Extension.Test.Cypher 6 | { 7 | public class SampleDataFactory 8 | { 9 | public static Person GetWellKnownPerson(int n) 10 | { 11 | var archer = new Person 12 | { 13 | Id=n 14 | ,Name = "Sterling Archer" 15 | ,Sex= Gender.Male 16 | ,HomeAddress = GetWellKnownAddress(200) 17 | ,WorkAddress = GetWellKnownAddress(59) 18 | ,IsOperative =true 19 | ,SerialNumber = 123456 20 | ,SpendingAuthorisation = 100.23m 21 | ,DateCreated = DateTimeOffset.Parse("2015-07-11T08:00:00+10:00") 22 | }; 23 | 24 | return archer; 25 | } 26 | 27 | public static Address GetWellKnownAddress(int n) 28 | { 29 | var address = new Address {Street = n + " Isis Street", Suburb = "Fakeville"}; 30 | return address; 31 | } 32 | 33 | public static Weapon GetWellKnownWeapon(int n) 34 | { 35 | var weapon = new Weapon(); 36 | weapon.Id = n; 37 | weapon.Name = "Grenade Launcher"; 38 | weapon.BlastRadius = Area.FromSquareMeters(20); 39 | return weapon; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.Test.Common/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaConverterFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | using NUnit.Framework; 8 | using UnitsNet; 9 | 10 | namespace Neo4jClient.Extension.Test.CustomConverters 11 | { 12 | [TestFixture] 13 | public class AreaConverterFixture 14 | { 15 | [Test] 16 | public void Converts() 17 | { 18 | Area? area1 = Area.FromSquareMeters(20); 19 | Area? area2 = null; 20 | TestConversion(area1, Area.FromSquareMeters(20)); 21 | TestConversion(area2, null); 22 | } 23 | 24 | private void TestConversion(Area? input, Area? expected) 25 | { 26 | var settings = new JsonSerializerSettings(); 27 | settings.Converters.Add(new AreaJsonConverter()); 28 | settings.Formatting = Formatting.Indented; 29 | 30 | var json = JsonConvert.SerializeObject(input, settings); 31 | 32 | Console.WriteLine(json); 33 | 34 | var areaReloaded = JsonConvert.DeserializeObject(json, settings); 35 | 36 | Assert.AreEqual(expected, areaReloaded); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | using UnitsNet; 8 | 9 | namespace Neo4jClient.Extension.Test.CustomConverters 10 | { 11 | /// 12 | /// http://stackoverflow.com/questions/27063475/custom-jsonconverter-that-can-convert-decimal-minvalue-to-empty-string-and-back 13 | /// 14 | public class AreaJsonConverter : JsonConverter 15 | { 16 | public override bool CanConvert(Type objectType) 17 | { 18 | return (objectType == typeof(Area?) || objectType == typeof(Area)); 19 | } 20 | 21 | public override object ReadJson(JsonReader reader, Type objectType, 22 | object existingValue, JsonSerializer serializer) 23 | { 24 | if (reader.TokenType == JsonToken.String) 25 | { 26 | if ((string)reader.Value == string.Empty) 27 | { 28 | return null; 29 | } 30 | } 31 | else if (reader.TokenType == JsonToken.Float || 32 | reader.TokenType == JsonToken.Integer) 33 | { 34 | return Area.FromSquareMeters((double) reader.Value); 35 | } 36 | 37 | return null; 38 | } 39 | 40 | public override void WriteJson(JsonWriter writer, object value, 41 | JsonSerializer serializer) 42 | { 43 | var area = (Area?)value; 44 | if (!area.HasValue) 45 | { 46 | writer.WriteValue((float?) null); 47 | } 48 | else 49 | { 50 | writer.WriteValue(area.Value.SquareMeters); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/CypherExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Moq; 4 | using Neo4jClient.Cypher; 5 | using Neo4jClient.Extension.Cypher; 6 | using Neo4jClient.Extension.Cypher.Attributes; 7 | using Newtonsoft.Json.Serialization; 8 | using NUnit.Framework; 9 | 10 | namespace Neo4jClient.Extension.Test.Cypher 11 | { 12 | public class CypherExtensionTestHelper 13 | { 14 | 15 | public Mock GraphClient { get; private set; } 16 | public CypherExtensionContext CypherExtensionContext { get; private set; } 17 | public CypherFluentQuery Query { get; private set; } 18 | 19 | public CypherExtensionTestHelper() 20 | { 21 | CypherExtensionContext = new CypherExtensionContext(); 22 | } 23 | 24 | public CypherExtensionTestHelper SetupGraphClient() 25 | { 26 | GraphClient = new Mock(); 27 | GraphClient.Setup(x => x.JsonContractResolver).Returns(new DefaultContractResolver()); 28 | Query = new CypherFluentQuery(GraphClient.Object); 29 | return this; 30 | } 31 | } 32 | 33 | [TestFixture] 34 | public class CypherExtensionTests 35 | { 36 | [Test] 37 | public void ToCypherStringMergeTest() 38 | { 39 | //setup 40 | var model = CreateModel(); 41 | var helper = new CypherExtensionTestHelper(); 42 | 43 | //act 44 | var result = model.ToCypherString(helper.CypherExtensionContext); 45 | var result2 = model.ToCypherString(helper.CypherExtensionContext); 46 | 47 | //assert 48 | Assert.AreEqual("cyphermodel:CypherModel {id:{cyphermodelMatchKey}.id}", result); 49 | Assert.AreEqual(result,result2); 50 | } 51 | 52 | [Test] 53 | public void MatchEntityTest() 54 | { 55 | //setup 56 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 57 | 58 | var model = CreateModel(); 59 | model.id = Guid.Parse("9aa1343f-18a4-41a6-a414-34b7df62c919"); 60 | //act 61 | var q = helper.Query.MatchEntity(model).Return(cyphermodel => cyphermodel.As()); 62 | 63 | Console.WriteLine(q.Query.QueryText); 64 | 65 | //assert 66 | Assert.AreEqual(@"MATCH (cyphermodel:CypherModel {id:{cyphermodelMatchKey}.id}) 67 | RETURN cyphermodel", q.Query.QueryText); 68 | } 69 | 70 | [Test] 71 | public void MatchEntityOverrideTest() 72 | { 73 | //setup 74 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 75 | 76 | var model = CreateModel(); 77 | 78 | //act 79 | var q = helper.Query 80 | .MatchEntity(model, propertyOverride: model.UseProperties(x => x.firstName, x => x.isLegend)) 81 | .Return(cyphermodel => cyphermodel.As()); 82 | 83 | Console.WriteLine(q.GetFormattedDebugText()); 84 | 85 | //assert 86 | Assert.AreEqual(@"MATCH (cyphermodel:CypherModel {firstName:{ 87 | firstName: ""Foo"", 88 | isLegend: false 89 | }.firstName,isLegend:{ 90 | firstName: ""Foo"", 91 | isLegend: false 92 | }.isLegend}) 93 | RETURN cyphermodel", q.GetFormattedDebugText()); 94 | } 95 | 96 | [Test] 97 | public void MatchEntityKeyTest() 98 | { 99 | //setup 100 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 101 | 102 | var model = CreateModel(); 103 | 104 | //act 105 | var q = helper.Query.MatchEntity(model,"key").Return(cyphermodel => cyphermodel.As()); 106 | 107 | Console.WriteLine(q.Query.DebugQueryText); 108 | 109 | //assert 110 | Assert.AreEqual(@"MATCH (key:CypherModel {id:{ 111 | ""id"": ""b00b7355-ce53-49f2-a421-deadb655673d"" 112 | }.id}) 113 | RETURN cyphermodel", q.Query.DebugQueryText); 114 | } 115 | 116 | [Test] 117 | public void MatchEntityPreTest() 118 | { 119 | //setup 120 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 121 | 122 | var model = CreateModel(); 123 | 124 | 125 | //act 126 | var q = helper.Query.MatchEntity(model, preCql: "(a:Node)-->").Return(cyphermodel => cyphermodel.As()); 127 | 128 | Console.WriteLine(q.GetFormattedDebugText()); 129 | 130 | //assert 131 | Assert.AreEqual(@"MATCH (a:Node)-->(cyphermodel:CypherModel {id:{ 132 | id: ""b00b7355-ce53-49f2-a421-deadb655673d"" 133 | }.id}) 134 | RETURN cyphermodel", q.GetFormattedDebugText()); 135 | } 136 | 137 | [Test] 138 | public void MatchEntityPostTest() 139 | { 140 | //setup 141 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 142 | 143 | var model = CreateModel(); 144 | 145 | //act 146 | var q = helper.Query.MatchEntity(model, postCql: "<--(a:Node)").Return(cyphermodel => cyphermodel.As()); 147 | 148 | Console.WriteLine(q.GetFormattedDebugText()); 149 | 150 | //assert 151 | Assert.AreEqual("MATCH (cyphermodel:CypherModel {id:{cyphermodelMatchKey}.id})<--(a:Node)\r\nRETURN cyphermodel", q.Query.QueryText); 152 | } 153 | 154 | [Test] 155 | public void MatchEntityPrePostKeyOverrideTest() 156 | { 157 | //setup 158 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 159 | 160 | var model = CreateModel(); 161 | 162 | //act 163 | var q = helper.Query 164 | .MatchEntity(model, "key", "(a:Node)-->", "<--(b:Node)", new List()) 165 | .Return(cyphermodel => cyphermodel.As()); 166 | 167 | Console.WriteLine(q.GetFormattedDebugText()); 168 | 169 | //assert 170 | Assert.AreEqual(@"MATCH (a:Node)-->(key:CypherModel)<--(b:Node) 171 | RETURN cyphermodel", q.GetFormattedDebugText()); 172 | } 173 | 174 | [Test] 175 | public void MatchAllTest() 176 | { 177 | //setup 178 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 179 | 180 | //act 181 | var result = helper.Query.MatchEntity(new CypherModel(), propertyOverride: new List()); 182 | 183 | //assert 184 | Assert.AreEqual("MATCH (cyphermodel:CypherModel)", result.GetFormattedDebugText()); 185 | } 186 | 187 | [Test] 188 | public void MergeEntityTest() 189 | { 190 | //setup 191 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 192 | var model = CreateModel(); 193 | 194 | //act 195 | var q = helper.Query.MergeEntity(model); 196 | 197 | Console.WriteLine(q.GetFormattedDebugText()); 198 | 199 | //assert 200 | Assert.AreEqual(@"MERGE (cyphermodel:CypherModel {id:{ 201 | id: ""b00b7355-ce53-49f2-a421-deadb655673d"" 202 | }.id}) 203 | ON MATCH SET cyphermodel.isLegend = false 204 | ON MATCH SET cyphermodel.answerToTheMeaningOfLifeAndEverything = 42 205 | ON CREATE SET cyphermodel = { 206 | firstName: ""Foo"", 207 | dateOfBirth: ""1981-04-01T00:00:00+00:00"", 208 | isLegend: false, 209 | answerToTheMeaningOfLifeAndEverything: 42 210 | }", q.GetFormattedDebugText()); 211 | } 212 | 213 | [Test] 214 | public void MergeEntityKeyTest() 215 | { 216 | //setup 217 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 218 | var model = CreateModel(); 219 | 220 | //act 221 | var q = helper.Query.MergeEntity(model,"key"); 222 | 223 | Console.WriteLine(q.GetFormattedDebugText()); 224 | 225 | //assert 226 | Assert.AreEqual(@"MERGE (key:CypherModel {id:{ 227 | id: ""b00b7355-ce53-49f2-a421-deadb655673d"" 228 | }.id}) 229 | ON MATCH SET key.isLegend = false 230 | ON MATCH SET key.answerToTheMeaningOfLifeAndEverything = 42 231 | ON CREATE SET key = { 232 | firstName: ""Foo"", 233 | dateOfBirth: ""1981-04-01T00:00:00+00:00"", 234 | isLegend: false, 235 | answerToTheMeaningOfLifeAndEverything: 42 236 | }", q.GetFormattedDebugText()); 237 | } 238 | 239 | [Test] 240 | public void MergeEntityOverrideMergeTest() 241 | { 242 | //setup 243 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 244 | var model = CreateModel(); 245 | 246 | //act 247 | var q = helper.Query.MergeEntity(model, mergeOverride:model.UseProperties(x => x.firstName)); 248 | 249 | Console.WriteLine(q.GetFormattedDebugText()); 250 | 251 | //assert 252 | Assert.AreEqual(@"MERGE (cyphermodel:CypherModel {firstName:{ 253 | firstName: ""Foo"" 254 | }.firstName}) 255 | ON MATCH SET cyphermodel.isLegend = false 256 | ON MATCH SET cyphermodel.answerToTheMeaningOfLifeAndEverything = 42 257 | ON CREATE SET cyphermodel = { 258 | firstName: ""Foo"", 259 | dateOfBirth: ""1981-04-01T00:00:00+00:00"", 260 | isLegend: false, 261 | answerToTheMeaningOfLifeAndEverything: 42 262 | }", q.GetFormattedDebugText()); 263 | } 264 | 265 | [Test] 266 | public void MergeEntityOverrideOnMatchTest() 267 | { 268 | //setup 269 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 270 | var model = CreateModel(); 271 | 272 | //act 273 | var q = helper.Query.MergeEntity(model, onMatchOverride: model.UseProperties(x => x.firstName)); 274 | 275 | Console.WriteLine(q.Query.QueryText); 276 | 277 | //assert 278 | Assert.AreEqual(@"MERGE (cyphermodel:CypherModel {id:{cyphermodelMatchKey}.id}) 279 | ON MATCH 280 | SET cyphermodel.firstName = {cyphermodelfirstName} 281 | ON CREATE 282 | SET cyphermodel = {cyphermodelOnCreate}", q.Query.QueryText); 283 | } 284 | 285 | [Test] 286 | public void MergeEntityOverrideOnCreateTest() 287 | { 288 | //setup 289 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 290 | var model = CreateModel(); 291 | 292 | //act 293 | var q = helper.Query.MergeEntity(model, onCreateOverride: model.UseProperties(x => x.firstName)); 294 | 295 | Console.WriteLine(q.GetFormattedDebugText()); 296 | 297 | //assert 298 | Assert.AreEqual(@"MERGE (cyphermodel:CypherModel {id:{ 299 | id: ""b00b7355-ce53-49f2-a421-deadb655673d"" 300 | }.id}) 301 | ON MATCH SET cyphermodel.isLegend = false 302 | ON MATCH SET cyphermodel.answerToTheMeaningOfLifeAndEverything = 42 303 | ON CREATE SET cyphermodel = { 304 | firstName: ""Foo"" 305 | }", q.GetFormattedDebugText()); 306 | } 307 | 308 | [Test] 309 | public void MergeEntityAllArgsTest() 310 | { 311 | //setup 312 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 313 | var model = CreateModel(); 314 | 315 | //act 316 | var q = helper.Query.MergeEntity(model,"key", new List(),new List(), new List(), "(a:Node)-->","<--(b:Node)"); 317 | 318 | Console.WriteLine(q.GetFormattedDebugText()); 319 | 320 | //assert 321 | Assert.AreEqual("MERGE (a:Node)-->(key:CypherModel)<--(b:Node)", q.GetFormattedDebugText()); 322 | } 323 | 324 | 325 | [Test] 326 | public void MergeRelationshipTest() 327 | { 328 | //setup 329 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 330 | 331 | var model = new ComponentOf("from", "to"); 332 | 333 | //act 334 | var q = helper.Query.MergeRelationship(model); 335 | 336 | Console.WriteLine(q.GetFormattedDebugText()); 337 | 338 | //assert 339 | Assert.AreEqual(@"MERGE (from)-[fromto:COMPONENT_OF {quantity:{ 340 | quantity: 0.0, 341 | unitOfMeasure: ""Gram"", 342 | factor: 0, 343 | instructionText: """" 344 | }.quantity,unitOfMeasure:{ 345 | quantity: 0.0, 346 | unitOfMeasure: ""Gram"", 347 | factor: 0, 348 | instructionText: """" 349 | }.unitOfMeasure,factor:{ 350 | quantity: 0.0, 351 | unitOfMeasure: ""Gram"", 352 | factor: 0, 353 | instructionText: """" 354 | }.factor,instructionText:{ 355 | quantity: 0.0, 356 | unitOfMeasure: ""Gram"", 357 | factor: 0, 358 | instructionText: """" 359 | }.instructionText}]->(to) 360 | ON MATCH SET fromto.quantity = 0.0 361 | ON MATCH SET fromto.unitOfMeasure = ""Gram"" 362 | ON MATCH SET fromto.factor = 0 363 | ON MATCH SET fromto.instructionText = """"", q.GetFormattedDebugText()); 364 | } 365 | 366 | [Test] 367 | public void MergeRelationshipDownCastTest() 368 | { 369 | //setup 370 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 371 | 372 | var model = (BaseRelationship) new ComponentOf("from", "to"); 373 | 374 | //act 375 | var q = helper.Query.MergeRelationship(model); 376 | 377 | Console.WriteLine(q.GetFormattedDebugText()); 378 | 379 | //assert 380 | Assert.AreEqual(@"MERGE (from)-[fromto:COMPONENT_OF {quantity:{ 381 | quantity: 0.0, 382 | unitOfMeasure: ""Gram"", 383 | factor: 0, 384 | instructionText: """" 385 | }.quantity,unitOfMeasure:{ 386 | quantity: 0.0, 387 | unitOfMeasure: ""Gram"", 388 | factor: 0, 389 | instructionText: """" 390 | }.unitOfMeasure,factor:{ 391 | quantity: 0.0, 392 | unitOfMeasure: ""Gram"", 393 | factor: 0, 394 | instructionText: """" 395 | }.factor,instructionText:{ 396 | quantity: 0.0, 397 | unitOfMeasure: ""Gram"", 398 | factor: 0, 399 | instructionText: """" 400 | }.instructionText}]->(to) 401 | ON MATCH SET fromto.quantity = 0.0 402 | ON MATCH SET fromto.unitOfMeasure = ""Gram"" 403 | ON MATCH SET fromto.factor = 0 404 | ON MATCH SET fromto.instructionText = """"", q.GetFormattedDebugText()); 405 | } 406 | 407 | [Test] 408 | public void MergeRelationshipMergeOverrideTest() 409 | { 410 | //setup 411 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 412 | 413 | var model = new ComponentOf("from", "to"); 414 | 415 | //act 416 | var q = helper.Query.MergeRelationship(model, model.UseProperties(x => x.quantity)); 417 | 418 | Console.WriteLine(q.GetFormattedDebugText()); 419 | 420 | //assert 421 | Assert.AreEqual(@"MERGE (from)-[fromto:COMPONENT_OF {quantity:{ 422 | quantity: 0.0 423 | }.quantity}]->(to) 424 | ON MATCH SET fromto.quantity = 0.0 425 | ON MATCH SET fromto.unitOfMeasure = ""Gram"" 426 | ON MATCH SET fromto.factor = 0 427 | ON MATCH SET fromto.instructionText = """"", q.GetFormattedDebugText()); 428 | } 429 | 430 | [Test] 431 | public void MergeRelationshipOnMatchOverrideTest() 432 | { 433 | //setup 434 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 435 | 436 | var model = new ComponentOf("from", "to"); 437 | 438 | //act 439 | var q = helper.Query.MergeRelationship(model,onMatchOverride:model.UseProperties(x => x.quantity)); 440 | 441 | Console.WriteLine(q.GetFormattedDebugText()); 442 | 443 | //assert 444 | Assert.AreEqual(@"MERGE (from)-[fromto:COMPONENT_OF {quantity:{ 445 | quantity: 0.0, 446 | unitOfMeasure: ""Gram"", 447 | factor: 0, 448 | instructionText: """" 449 | }.quantity,unitOfMeasure:{ 450 | quantity: 0.0, 451 | unitOfMeasure: ""Gram"", 452 | factor: 0, 453 | instructionText: """" 454 | }.unitOfMeasure,factor:{ 455 | quantity: 0.0, 456 | unitOfMeasure: ""Gram"", 457 | factor: 0, 458 | instructionText: """" 459 | }.factor,instructionText:{ 460 | quantity: 0.0, 461 | unitOfMeasure: ""Gram"", 462 | factor: 0, 463 | instructionText: """" 464 | }.instructionText}]->(to) 465 | ON MATCH SET fromto.quantity = 0.0", q.GetFormattedDebugText()); 466 | } 467 | 468 | [Test] 469 | public void MergeRelationshipOnCreateOverrideTest() 470 | { 471 | //setup 472 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 473 | 474 | var model = new ComponentOf("from", "to"); 475 | 476 | //act 477 | var q = helper.Query.MergeRelationship(model, onCreateOverride: model.UseProperties(x => x.quantity)); 478 | 479 | Console.WriteLine(q.GetFormattedDebugText()); 480 | 481 | //assert 482 | Assert.AreEqual(@"MERGE (from)-[fromto:COMPONENT_OF {quantity:{ 483 | quantity: 0.0, 484 | unitOfMeasure: ""Gram"", 485 | factor: 0, 486 | instructionText: """" 487 | }.quantity,unitOfMeasure:{ 488 | quantity: 0.0, 489 | unitOfMeasure: ""Gram"", 490 | factor: 0, 491 | instructionText: """" 492 | }.unitOfMeasure,factor:{ 493 | quantity: 0.0, 494 | unitOfMeasure: ""Gram"", 495 | factor: 0, 496 | instructionText: """" 497 | }.factor,instructionText:{ 498 | quantity: 0.0, 499 | unitOfMeasure: ""Gram"", 500 | factor: 0, 501 | instructionText: """" 502 | }.instructionText}]->(to) 503 | ON MATCH SET fromto.quantity = 0.0 504 | ON MATCH SET fromto.unitOfMeasure = ""Gram"" 505 | ON MATCH SET fromto.factor = 0 506 | ON MATCH SET fromto.instructionText = """" 507 | ON CREATE SET fromto = { 508 | quantity: 0.0 509 | }", q.GetFormattedDebugText()); 510 | } 511 | 512 | [Test] 513 | public void MergeRelationshipAllArgsTest() 514 | { 515 | //setup 516 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 517 | 518 | var model = new ComponentOf("from", "to"); 519 | 520 | //act 521 | var q = helper.Query.MergeRelationship(model, new List(), new List(), new List()); 522 | 523 | Console.WriteLine(q.GetFormattedDebugText()); 524 | 525 | //assert 526 | Assert.AreEqual("MERGE (from)-[fromto:COMPONENT_OF]->(to)", q.GetFormattedDebugText()); 527 | } 528 | 529 | [Test] 530 | public void EntityLabelWithoutAttrTest() 531 | { 532 | //setup 533 | var model = CreateModel(); 534 | 535 | //act 536 | var result = model.EntityLabel(); 537 | 538 | //assert 539 | Assert.AreEqual("CypherModel", result); 540 | } 541 | 542 | [Test] 543 | public void EntityLabelWithTest() 544 | { 545 | //setup 546 | var model = new LabelledModel(); 547 | 548 | //act 549 | var result = model.EntityLabel(); 550 | 551 | //assert 552 | Assert.AreEqual("MyName", result); 553 | } 554 | 555 | private CypherModel CreateModel() 556 | { 557 | var model = new CypherModel 558 | { 559 | dateOfBirth = new DateTimeOffset(1981, 4, 1, 0, 0 , 0, TimeSpan.Zero), 560 | answerToTheMeaningOfLifeAndEverything = 42, 561 | firstName = "Foo", 562 | isLegend = false 563 | }; 564 | 565 | model.id = Guid.Parse("b00b7355-ce53-49f2-a421-deadb655673d"); 566 | 567 | return model; 568 | } 569 | 570 | public enum UnitsOfMeasure 571 | { 572 | Gram, 573 | Millimeter, 574 | Cup, 575 | TableSpoon, 576 | TeaSpoon, 577 | Unit 578 | } 579 | 580 | [CypherLabel(Name = "COMPONENT_OF")] 581 | public class ComponentOf : BaseRelationship 582 | { 583 | public ComponentOf(string from = null, string to = null): base(from, to) 584 | { 585 | instructionText = string.Empty; 586 | } 587 | [CypherMerge] 588 | [CypherMergeOnMatch] 589 | public double quantity { get; set; } 590 | [CypherMerge] 591 | [CypherMergeOnMatch] 592 | public UnitsOfMeasure unitOfMeasure { get; set; } 593 | [CypherMerge] 594 | [CypherMergeOnMatch] 595 | public int factor { get; set; } 596 | [CypherMerge] 597 | [CypherMergeOnMatch] 598 | public string instructionText { get; set; } 599 | } 600 | } 601 | [CypherLabel(Name = "MyName")] 602 | public class LabelledModel { } 603 | 604 | public class CypherModel 605 | { 606 | public CypherModel() 607 | { 608 | id = Guid.NewGuid(); 609 | } 610 | 611 | [CypherMatch] 612 | [CypherMerge] 613 | public Guid id { get; set; } 614 | 615 | [CypherMergeOnCreate] 616 | public string firstName { get; set; } 617 | 618 | [CypherMergeOnCreate] 619 | public DateTimeOffset dateOfBirth { get; set; } 620 | 621 | [CypherMergeOnCreate] 622 | [CypherMergeOnMatch] 623 | public bool isLegend { get; set; } 624 | 625 | [CypherMergeOnCreate] 626 | [CypherMergeOnMatch] 627 | public int answerToTheMeaningOfLifeAndEverything { get; set; } 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/CypherLabelAttributeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Neo4jClient.Extension.Test.Cypher 4 | { 5 | using Neo4jClient.Extension.Cypher; 6 | using Neo4jClient.Extension.Cypher.Attributes; 7 | using NUnit.Framework; 8 | 9 | [TestFixture] 10 | public class CypherLabelAttributeTests 11 | { 12 | [Test] 13 | public void UsesClassName_WhenMultipleLabelsAreSpecified() 14 | { 15 | var model = new MultiLabel { Id = 1 }; 16 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 17 | 18 | var q = helper.Query.MergeEntity(model); 19 | 20 | Console.WriteLine(q.Query.QueryText); 21 | Assert.AreEqual("MERGE (multilabel:Multi:Label {id:{multilabelMatchKey}.id})", q.Query.QueryText); 22 | } 23 | 24 | [Test] 25 | public void UsesSuppliedParamName_WhenMultipleLabelsAreSpecified() 26 | { 27 | var model = new MultiLabel { Id = 1 }; 28 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 29 | 30 | var q = helper.Query.MergeEntity(model, "n"); 31 | 32 | Console.WriteLine(q.Query.QueryText); 33 | Assert.AreEqual("MERGE (n:Multi:Label {id:{nMatchKey}.id})", q.Query.QueryText); 34 | } 35 | 36 | [Test] 37 | public void HandlesLabelsWithSpaces() 38 | { 39 | var model = new MultiLabelWithSpace { Id = 1 }; 40 | var helper = new CypherExtensionTestHelper().SetupGraphClient(); 41 | 42 | var q = helper.Query.MergeEntity(model); 43 | 44 | var text = q.Query.QueryText; 45 | Console.WriteLine(text); 46 | Assert.AreEqual("MERGE (multilabelwithspace:Multi:`Space Label` {id:{multilabelwithspaceMatchKey}.id})", text); 47 | } 48 | 49 | public abstract class MultiBase 50 | { 51 | [CypherMerge] 52 | public int Id { get; set; } 53 | } 54 | 55 | [CypherLabel(Name = "Multi:`Space Label`")] 56 | public class MultiLabelWithSpace : MultiBase {} 57 | 58 | [CypherLabel(Name = "Multi:Label")] 59 | public class MultiLabel : MultiBase {} 60 | } 61 | } -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/CypherTypeItemHelperTests.cs: -------------------------------------------------------------------------------- 1 | using Neo4jClient.Extension.Cypher; 2 | using Neo4jClient.Extension.Cypher.Attributes; 3 | using NUnit.Framework; 4 | 5 | namespace Neo4jClient.Extension.Test.Cypher 6 | { 7 | [TestFixture] 8 | public class CypherTypeItemHelperTests 9 | { 10 | [Test] 11 | public void AddKeyAttributeTest() 12 | { 13 | //setup 14 | var helper = new CypherTypeItemHelper(); 15 | 16 | //act 17 | var key = helper.AddKeyAttribute(CypherExtension.DefaultExtensionContext, new CypherModel()); 18 | 19 | //assert 20 | Assert.AreEqual(new CypherTypeItem(){ Type = typeof(CypherModel), AttributeType = typeof(CypherMatchAttribute)}, key); 21 | } 22 | 23 | [Test] 24 | public void PropertyForUsageTest() 25 | { 26 | //setup 27 | var helper = new CypherTypeItemHelper(); 28 | 29 | //act 30 | var result = helper.PropertiesForPurpose(new CypherModel()); 31 | 32 | //assert 33 | Assert.AreEqual("id",result[0].TypeName); 34 | Assert.AreEqual("id", result[0].JsonName); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigBaseTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Neo4jClient.Cypher; 8 | using Neo4jClient.Extension.Cypher; 9 | using Neo4jClient.Extension.Test.CustomConverters; 10 | using Neo4jClient.Extension.Test.Data; 11 | using Neo4jClient.Extension.Test.TestData.Entities; 12 | using Neo4jClient.Extension.Test.TestEntities.Relationships; 13 | using Newtonsoft.Json; 14 | using NUnit.Framework; 15 | 16 | namespace Neo4jClient.Extension.Test.Cypher 17 | { 18 | [TestFixture] 19 | public abstract class FluentConfigBaseTest 20 | { 21 | protected List JsonConverters { get; private set; } 22 | 23 | private Func _seedQueryFactory; 24 | 25 | protected FluentConfigBaseTest() 26 | { 27 | UseMockQueryFactory(); 28 | } 29 | 30 | protected void UseQueryFactory(Func queryFactory) 31 | { 32 | _seedQueryFactory = queryFactory; 33 | } 34 | 35 | [SetUp] 36 | public void TestSetup() 37 | { 38 | JsonConverters = GraphClient.DefaultJsonConverters.ToList(); 39 | JsonConverters.Add(new AreaJsonConverter()); 40 | 41 | NeoConfig.ConfigureModel(); 42 | } 43 | 44 | protected IGraphClient GetMockCypherClient() 45 | { 46 | var moqGraphClient = new Mock(); 47 | var mockRawClient = moqGraphClient.As(); 48 | 49 | moqGraphClient.Setup(c => c.JsonConverters).Returns(JsonConverters); 50 | moqGraphClient.Setup(c => c.JsonContractResolver).Returns(GraphClient.DefaultJsonContractResolver); 51 | 52 | return mockRawClient.Object; 53 | } 54 | 55 | protected ICypherFluentQuery GetFluentQuery() 56 | { 57 | return _seedQueryFactory(); 58 | } 59 | 60 | private void UseMockQueryFactory() 61 | { 62 | _seedQueryFactory = () => 63 | { 64 | var cypherClient = GetMockCypherClient(); 65 | return new CypherFluentQuery(cypherClient); 66 | }; 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigCreateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Neo4jClient.Cypher; 7 | using Neo4jClient.Extension.Cypher; 8 | using Neo4jClient.Extension.Cypher.Attributes; 9 | using Neo4jClient.Extension.Test.TestEntities.Relationships; 10 | using NUnit.Framework; 11 | 12 | namespace Neo4jClient.Extension.Test.Cypher 13 | { 14 | public class FluentConfigCreateTests : FluentConfigBaseTest 15 | { 16 | public FluentConfigCreateTests() 17 | { 18 | 19 | } 20 | 21 | /// 22 | /// Ctor for Integration tests to use 23 | /// 24 | public FluentConfigCreateTests(Func seedQueryFactory) 25 | { 26 | UseQueryFactory(seedQueryFactory); 27 | } 28 | 29 | 30 | public ICypherFluentQuery CreateWithUnusualTypeAct() 31 | { 32 | var weapon = SampleDataFactory.GetWellKnownWeapon(1); 33 | 34 | var q = GetFluentQuery() 35 | .CreateEntity(weapon, "w"); 36 | 37 | return q; 38 | } 39 | 40 | [Test] 41 | public void CreateWithUnusualType() 42 | { 43 | var q = CreateWithUnusualTypeAct(); 44 | var text = q.GetFormattedDebugText(); 45 | Console.WriteLine(text); 46 | // GetFormattedDebugText isn't honouring JsonConverter 47 | } 48 | 49 | /// 50 | /// work around exception somewhere in neo4jclent when creating null values even though cypher syntax is valid 51 | /// 52 | [Test] 53 | public void CreateWithNullValuesSkipsTheNulls() 54 | { 55 | var agent = SampleDataFactory.GetWellKnownPerson(7); 56 | 57 | agent.HomeAddress.Suburb = null; 58 | 59 | var q = GetFluentQuery() 60 | .CreateEntity(agent.HomeAddress); 61 | 62 | var text = q.GetFormattedDebugText(); 63 | Assert.AreEqual(@"CREATE (address:Address { 64 | street: ""200 Isis Street"" 65 | })", text); 66 | } 67 | 68 | [Test] 69 | public void CreateRelationshipWithNoIdentifier() 70 | { 71 | var homeRelationship = new HomeAddressRelationship(string.Empty, "a", "ha"); 72 | 73 | var q = GetFluentQuery() 74 | .CreateRelationship(homeRelationship); 75 | 76 | var text = q.GetFormattedDebugText(); 77 | Console.WriteLine(text); 78 | 79 | Assert.AreEqual("CREATE (a)-[:HOME_ADDRESS]->(ha)", text); 80 | } 81 | 82 | 83 | [Test] 84 | public void CreateComplex() 85 | { 86 | var q = CreateComplexAct(); 87 | 88 | var text = q.GetFormattedDebugText(); 89 | Console.WriteLine(text); 90 | 91 | Assert.AreEqual(@"CREATE (a:SecretAgent { 92 | spendingAuthorisation: 100.23, 93 | serialNumber: 123456, 94 | sex: ""Male"", 95 | isOperative: true, 96 | name: ""Sterling Archer"", 97 | dateCreated: ""2015-07-11T08:00:00+10:00"", 98 | id: 7 99 | }) 100 | CREATE (ha:Address { 101 | suburb: ""Fakeville"", 102 | street: ""200 Isis Street"" 103 | }) 104 | CREATE (wa:Address { 105 | suburb: ""Fakeville"", 106 | street: ""59 Isis Street"" 107 | }) 108 | CREATE (a)-[myHomeRelationshipIdentifier:HOME_ADDRESS { 109 | dateEffective: ""2015-08-05T12:00:00+00:00"" 110 | }]->(ha) 111 | CREATE (a)-[awa:WORK_ADDRESS]->(wa)", text); 112 | } 113 | 114 | public ICypherFluentQuery CreateComplexAct() 115 | { 116 | var agent = SampleDataFactory.GetWellKnownPerson(7); 117 | var homeRelationship = new HomeAddressRelationship("myHomeRelationshipIdentifier", "a", "ha"); 118 | homeRelationship.DateEffective = DateTimeOffset.Parse("2015-08-05 12:00+00:00"); 119 | 120 | var q = GetFluentQuery() 121 | .CreateEntity(agent, "a") 122 | .CreateEntity(agent.HomeAddress, "ha") 123 | .CreateEntity(agent.WorkAddress, "wa") 124 | .CreateRelationship(homeRelationship) 125 | .CreateRelationship(new WorkAddressRelationship("a", "wa")); 126 | 127 | return q; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMatchTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Neo4jClient.Cypher; 8 | using Neo4jClient.Extension.Cypher; 9 | using Neo4jClient.Extension.Cypher.Attributes; 10 | using Neo4jClient.Extension.Test.TestData.Relationships; 11 | using Neo4jClient.Extension.Test.TestEntities.Relationships; 12 | using NUnit.Framework; 13 | 14 | namespace Neo4jClient.Extension.Test.Cypher 15 | { 16 | public class FluentConfigMatchTests : FluentConfigBaseTest 17 | { 18 | 19 | [Test] 20 | public void MatchEntity() 21 | { 22 | var person = SampleDataFactory.GetWellKnownPerson(7); 23 | var q = GetFluentQuery() 24 | .MatchEntity(person); 25 | var text = q.GetFormattedDebugText(); 26 | Console.WriteLine(text); 27 | 28 | Assert.AreEqual(@"MATCH (person:SecretAgent {id:{ 29 | id: 7 30 | }.id})", text); 31 | } 32 | 33 | [Test] 34 | public void OptionalMatchEntity() 35 | { 36 | var person = SampleDataFactory.GetWellKnownPerson(7); 37 | var q = GetFluentQuery() 38 | .MatchEntity(person) 39 | .OptionalMatchEntity(person.HomeAddress, MatchOptions.Create("ha").WithNoProperties()); 40 | var text = q.GetFormattedDebugText(); 41 | Console.WriteLine(text); 42 | 43 | Assert.AreEqual(@"MATCH (person:SecretAgent {id:{ 44 | id: 7 45 | }.id}) 46 | OPTIONAL MATCH (ha:Address)", text); 47 | } 48 | 49 | [Test] 50 | public void OptionalMatchRelationship() 51 | { 52 | var person = SampleDataFactory.GetWellKnownPerson(7); 53 | var homeAddressRelationship = new HomeAddressRelationship(); 54 | var q = GetFluentQuery() 55 | .MatchEntity(person) 56 | .OptionalMatchRelationship(homeAddressRelationship, MatchRelationshipOptions.Create().WithNoProperties()); 57 | var text = q.GetFormattedDebugText(); 58 | Console.WriteLine(text); 59 | 60 | Assert.AreEqual(@"MATCH (person:SecretAgent {id:{ 61 | id: 7 62 | }.id}) 63 | OPTIONAL MATCH (person)-[personaddress:HOME_ADDRESS]->(address)", text); 64 | } 65 | 66 | [Test] 67 | public void MatchRelationshipSimple() 68 | { 69 | var addressRelationship = new CheckedOutRelationship(); 70 | var q = GetFluentQuery() 71 | .MatchRelationship(addressRelationship); 72 | var text = q.GetFormattedDebugText(); 73 | 74 | Console.WriteLine(text); 75 | 76 | Assert.AreEqual(@"MATCH (agent)-[agentweapon:HAS_CHECKED_OUT]->(weapon)", text); 77 | } 78 | 79 | [Test] 80 | public void MatchRelationshipWithProperty() 81 | { 82 | var addressRelationship = new HomeAddressRelationship(DateTimeOffset.Parse("2015-08-05 12:00"), "agent", "homeAddress"); 83 | var q = GetFluentQuery() 84 | .MatchRelationship(addressRelationship); 85 | var text = q.GetFormattedDebugText(); 86 | 87 | Console.WriteLine(text); 88 | 89 | Assert.AreEqual(@"MATCH (agent)-[agenthomeAddress:HOME_ADDRESS {dateEffective:{agenthomeAddressMatchKey}.dateEffective}]->(homeAddress)", text); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMergeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Neo4jClient.Cypher; 8 | using Neo4jClient.Extension.Cypher; 9 | using Neo4jClient.Extension.Cypher.Attributes; 10 | using Neo4jClient.Extension.Test.TestEntities.Relationships; 11 | using NUnit.Framework; 12 | 13 | namespace Neo4jClient.Extension.Test.Cypher 14 | { 15 | public class FluentConfigMergeTests : FluentConfigBaseTest 16 | { 17 | public FluentConfigMergeTests() 18 | { 19 | } 20 | 21 | /// 22 | /// Ctor for Integration tests to use 23 | /// 24 | public FluentConfigMergeTests(Func seedQueryFactory) 25 | { 26 | UseQueryFactory(seedQueryFactory); 27 | } 28 | 29 | [Test] 30 | public void OneDeep() 31 | { 32 | var q = OneDeepAct(); 33 | var text = q.GetFormattedDebugText(); 34 | Console.WriteLine(text); 35 | 36 | Assert.AreEqual(@"MERGE (person:SecretAgent {id:{ 37 | id: 7 38 | }.id}) 39 | ON MATCH SET person.spendingAuthorisation = 100.23 40 | ON MATCH SET person.serialNumber = 123456 41 | ON MATCH SET person.sex = ""Male"" 42 | ON MATCH SET person.isOperative = true 43 | ON MATCH SET person.name = ""Sterling Archer"" 44 | ON MATCH SET person.title = null 45 | ON CREATE SET person = { 46 | spendingAuthorisation: 100.23, 47 | serialNumber: 123456, 48 | sex: ""Male"", 49 | isOperative: true, 50 | name: ""Sterling Archer"", 51 | title: null, 52 | dateCreated: ""2015-07-11T08:00:00+10:00"", 53 | id: 7 54 | }", text); 55 | } 56 | 57 | public ICypherFluentQuery OneDeepAct() 58 | { 59 | var person = SampleDataFactory.GetWellKnownPerson(7); 60 | var q = GetFluentQuery() 61 | .MergeEntity(person); 62 | return q; 63 | } 64 | 65 | [Test] 66 | public void TwoDeep() 67 | { 68 | var q = TwoDeepAct(); 69 | var text = q.GetFormattedDebugText(); 70 | Console.WriteLine(text); 71 | 72 | // assert 73 | Assert.AreEqual(@"MERGE (person:SecretAgent {id:{ 74 | id: 7 75 | }.id}) 76 | ON MATCH SET person.spendingAuthorisation = 100.23 77 | ON MATCH SET person.serialNumber = 123456 78 | ON MATCH SET person.sex = ""Male"" 79 | ON MATCH SET person.isOperative = true 80 | ON MATCH SET person.name = ""Sterling Archer"" 81 | ON MATCH SET person.title = null 82 | ON CREATE SET person = { 83 | spendingAuthorisation: 100.23, 84 | serialNumber: 123456, 85 | sex: ""Male"", 86 | isOperative: true, 87 | name: ""Sterling Archer"", 88 | title: null, 89 | dateCreated: ""2015-07-11T08:00:00+10:00"", 90 | id: 7 91 | } 92 | MERGE ((person)-[:HOME_ADDRESS]->(address:Address)) 93 | ON MATCH SET address.suburb = ""Fakeville"" 94 | ON MATCH SET address.street = ""200 Isis Street"" 95 | ON CREATE SET address = { 96 | suburb: ""Fakeville"", 97 | street: ""200 Isis Street"" 98 | } 99 | MERGE (person)-[personaddress:HOME_ADDRESS]->(address) 100 | ON MATCH SET personaddress.dateEffective = ""2011-01-10T08:00:00+03:00"" 101 | ON CREATE SET personaddress = { 102 | dateEffective: ""2011-01-10T08:00:00+03:00"" 103 | }", text); 104 | } 105 | 106 | 107 | public ICypherFluentQuery TwoDeepAct() 108 | { 109 | //setup 110 | var testPerson = SampleDataFactory.GetWellKnownPerson(7); 111 | 112 | var homeAddressRelationship = new HomeAddressRelationship(); 113 | 114 | // perhaps this would be modelled on the address node but serves to show how to attach relationship property 115 | homeAddressRelationship.DateEffective = DateTimeOffset.Parse("2011-01-10T08:00:00+03:00"); 116 | 117 | //act 118 | var q = GetFluentQuery() 119 | .MergeEntity(testPerson) 120 | .MergeEntity(testPerson.HomeAddress, MergeOptions.ViaRelationship(homeAddressRelationship)) 121 | .MergeRelationship(homeAddressRelationship); 122 | 123 | return q; 124 | } 125 | 126 | [Test] 127 | public void OneDeepMergeByRelationship() 128 | { 129 | var q = OneDeepMergeByRelationshipAct(); 130 | var text = q.GetFormattedDebugText(); 131 | Console.WriteLine(text); 132 | 133 | Assert.AreEqual(@"MERGE (person:SecretAgent {id:{ 134 | id: 7 135 | }.id}) 136 | ON MATCH SET person.spendingAuthorisation = 100.23 137 | ON MATCH SET person.serialNumber = 123456 138 | ON MATCH SET person.sex = ""Male"" 139 | ON MATCH SET person.isOperative = true 140 | ON MATCH SET person.name = ""Sterling Archer"" 141 | ON MATCH SET person.title = null 142 | ON CREATE SET person = { 143 | spendingAuthorisation: 100.23, 144 | serialNumber: 123456, 145 | sex: ""Male"", 146 | isOperative: true, 147 | name: ""Sterling Archer"", 148 | title: null, 149 | dateCreated: ""2015-07-11T08:00:00+10:00"", 150 | id: 7 151 | } 152 | MERGE ((person)-[:HOME_ADDRESS]->(homeAddress:Address)) 153 | ON MATCH SET homeAddress.suburb = ""Fakeville"" 154 | ON MATCH SET homeAddress.street = ""200 Isis Street"" 155 | ON CREATE SET homeAddress = { 156 | suburb: ""Fakeville"", 157 | street: ""200 Isis Street"" 158 | } 159 | MERGE ((person)-[:WORK_ADDRESS]->(workAddress:Address)) 160 | ON MATCH SET workAddress.suburb = ""Fakeville"" 161 | ON MATCH SET workAddress.street = ""59 Isis Street"" 162 | ON CREATE SET workAddress = { 163 | suburb: ""Fakeville"", 164 | street: ""59 Isis Street"" 165 | }", text); 166 | 167 | } 168 | 169 | public ICypherFluentQuery OneDeepMergeByRelationshipAct() 170 | { 171 | //setup 172 | var testPerson = SampleDataFactory.GetWellKnownPerson(7); 173 | 174 | var homeAddressRelationship = new HomeAddressRelationship("person", "homeAddress"); 175 | var workAddressRelationship = new WorkAddressRelationship("person", "workAddress"); 176 | 177 | // perhaps this would be modelled on the address node but serves to show how to attach relationship property 178 | homeAddressRelationship.DateEffective = DateTime.Parse("2011-01-10T08:00:00+10:00"); 179 | 180 | //act 181 | var q = GetFluentQuery() 182 | .MergeEntity(testPerson) 183 | .MergeEntity(testPerson.HomeAddress, MergeOptions.ViaRelationship(homeAddressRelationship)) 184 | .MergeEntity(testPerson.WorkAddress, MergeOptions.ViaRelationship(workAddressRelationship)); 185 | 186 | return q; 187 | } 188 | 189 | [Test] 190 | public void MatchCypher() 191 | { 192 | var testPerson = SampleDataFactory.GetWellKnownPerson(7); 193 | 194 | // act 195 | var cypherKey = testPerson.ToCypherString(new CypherExtensionContext(), "pkey"); 196 | Console.WriteLine(cypherKey); 197 | 198 | // assert 199 | Assert.AreEqual("pkey:SecretAgent {id:{pkeyMatchKey}.id}", cypherKey); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigUpdateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Neo4jClient.Extension.Cypher; 7 | using NUnit.Framework; 8 | 9 | namespace Neo4jClient.Extension.Test.Cypher 10 | { 11 | public class FluentConfigUpdateTests : FluentConfigBaseTest 12 | { 13 | /// 14 | /// Demonstrates 15 | /// 1) we dont have expression tree support for SET 16 | /// 2) Bug in neo4jclient(?) where Id property is not lower case in generated cypher so match fails. When bug fixed, this test will start failing 17 | /// 18 | [Test] 19 | public void IncrementAValue_ExpressionTreeNotAvailable() 20 | { 21 | var cypherText = GetFluentQuery() 22 | .Match("(p:SecretAgent)") 23 | .Where((Person p) => p.Id == 7) 24 | .Set("p.serialNumber = p.serialNumber + 1") 25 | .GetFormattedDebugText(); 26 | 27 | Console.WriteLine(cypherText); 28 | 29 | Assert.AreEqual(@"MATCH (p:SecretAgent) 30 | WHERE (p.Id = 7) 31 | SET p.serialNumber = p.serialNumber + 1", cypherText); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {066A5EBD-C612-40E2-8065-160FA9853503} 8 | Library 9 | Properties 10 | Neo4jClient.Extension.Test 11 | Neo4jClient.Extension.Test 12 | v4.5 13 | 512 14 | ..\..\ 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll 37 | True 38 | 39 | 40 | ..\..\packages\Neo4jClient.1.1.0.1\lib\net45\Neo4jClient.dll 41 | True 42 | 43 | 44 | ..\..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 45 | True 46 | 47 | 48 | ..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll 49 | True 50 | 51 | 52 | 53 | 54 | 55 | ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll 56 | True 57 | 58 | 59 | ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll 60 | True 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ..\..\packages\UnitsNet.3.14.0\lib\net35\UnitsNet.dll 70 | True 71 | 72 | 73 | 74 | 75 | Properties\AssemblyInfoGlobal.cs 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | {6d2502f8-f491-45e6-abd8-2f7407926f5a} 92 | Neo4jClient.Extension.Attributes 93 | 94 | 95 | {41c65bed-56a6-4942-95d2-10e62f607c7f} 96 | Neo4jClient.Extension 97 | 98 | 99 | {b7c14349-6bec-44d1-ab33-b82ad85899aa} 100 | Neo4jClient.Extension.Test.Data 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 119 | -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("Neo4jClient.Extension.Test")] 4 | [assembly: AssemblyProduct("Neo4jClient.Extension.Test")] 5 | [assembly: AssemblyDescription("Unit tests")] -------------------------------------------------------------------------------- /test/Neo4jClient.Extension.UnitTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------