├── .gitignore ├── .nuget └── packages.config ├── .travis.yml ├── Elephanet.Console ├── App.config ├── Elephanet.Console.csproj ├── Elephanet.Console.sln ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── Elephanet.Tests ├── App.config ├── CorrectEscapingTests.cs ├── DirtySpeedTests.cs ├── DocumentStoreBaseFixture.cs ├── Elephanet.Tests.csproj ├── Entities │ ├── AnotherEntity.cs │ ├── AnotherEntityWithDateTime.cs │ ├── BaseEntity.cs │ ├── Bike.cs │ ├── Car.cs │ ├── EntityForCorrectEscapingTests.cs │ ├── EntityForGetByIdTests.cs │ ├── EntityForLinqOrderByTests.cs │ ├── EntityForSchemaConventionsTest.cs │ ├── EntityForSessionAndStoreTests.cs │ ├── EntityForWhereLinqTests.cs │ ├── EntityWithDateTime.cs │ └── SomeTestClassThatNoOneWillEverUse.cs ├── GetAllTests.cs ├── GetByIdTests.cs ├── JsonConverterTests.cs ├── LinqOrderByTests.cs ├── LinqTests.cs ├── Properties │ └── AssemblyInfo.cs ├── SessionAndStoreTests.cs ├── StoreConventionsTests.cs ├── StoreInfoTests.cs ├── TableInfoTests.cs ├── TestStore.cs └── packages.config ├── Elephanet.sln ├── Elephanet ├── App.config ├── ConcurrentHashSet.cs ├── Conventions │ └── EntityNotFoundBehavior.cs ├── DocumentSession.cs ├── Elephanet.csproj ├── Elephanet.nuspec ├── EntityException.cs ├── EntityNotFoundException.cs ├── Expressions │ ├── ExpressionEvaluator.cs │ ├── JsonbExpression.cs │ ├── JsonbExpressionType.cs │ ├── JsonbExpressionVisitor.cs │ ├── JsonbTable.cs │ ├── SelectExpression.cs │ ├── SqlExpression.cs │ └── SqlExpressionType.cs ├── Extensions │ └── QueryExtension.cs ├── IDocumentSession.cs ├── IStore.cs ├── IStoreConventions.cs ├── IStoreInfo.cs ├── ITableInfo.cs ├── IdentityFactory.cs ├── JsonbQueryProvider.cs ├── NuGet.exe ├── Properties │ └── AssemblyInfo.cs ├── QueryTranslator.cs ├── Queryable.cs ├── Serialization │ ├── IJsonConverter.cs │ └── JilJsonConverter.cs ├── Sql.cs ├── Store.cs ├── StoreConventions.cs ├── StoreInfo.cs ├── StringHelpers.cs ├── TableInfo.cs ├── TypeHelper.cs └── packages.config ├── LICENSE ├── create_store.bat ├── create_store.sql ├── deploy.sh ├── readme.md └── release-notes.md /.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/ -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | mono: 3 | - alpha 4 | env: VERSION=0.3.10 5 | solution: Elephanet.sln 6 | addons: 7 | postgresql: "9.4" 8 | 9 | before_script: 10 | - psql -c "CREATE USER store_user with PASSWORD 'my super secret password';" -U postgres 11 | - psql -c "CREATE DATABASE store;" -U postgres 12 | - psql -c "GRANT ALL PRIVILEGES ON DATABASE store to store_user;" -U postgres 13 | 14 | script: 15 | - xbuild /p:Configuration=Release Elephanet.sln 16 | - mono ./packages/xunit.runner.console.2.0.0/tools/xunit.console.exe ./Elephanet.Tests/bin/Release/Elephanet.Tests.dll -noshadow 17 | - chmod 755 ./packages/ILRepack.1.25.0/tools/ILRepack.exe 18 | - ./packages/ILRepack.1.25.0/tools/ILRepack.exe Elephanet/bin/Release/Elephanet.dll Elephanet/bin/Release/Sigil.dll Elephanet/bin/Release/Jil.dll Elephanet/bin/Release/Mono.Security.dll Elephanet/bin/Release/Npgsql.dll --internalize --out:Elephanet/Elephanet.dll 19 | 20 | deploy: 21 | provider: script 22 | skip_cleanup: true 23 | script: 24 | - cd Elephanet && nuget pack Elephanet.nuspec -Version $VERSION -IncludeReferencedProjects -Prop Configuration=Release && nuget push *.nupkg $NUGET_API_KEY -verbosity detailed 25 | on: 26 | branch: master 27 | -------------------------------------------------------------------------------- /Elephanet.Console/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Elephanet.Console/Elephanet.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69} 8 | Exe 9 | Properties 10 | Elephanet.Console 11 | Elephanet.Console 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\AutoFixture.3.20.1\lib\net40\Ploeh.AutoFixture.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {1156667a-85f7-493c-bf33-f22284134559} 57 | Elephanet.Tests 58 | 59 | 60 | {f6f93a0a-0eb1-47a5-9089-db6f56369f98} 61 | Elephanet 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /Elephanet.Console/Elephanet.Console.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | # SharpDevelop 5.0 5 | VisualStudioVersion = 12.0.20827.3 6 | MinimumVisualStudioVersion = 10.0.40219.1 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elephanet.Console", "Elephanet.Console.csproj", "{7B67E4BB-55E4-4976-AA8E-72448CDE9E69}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Release|Any CPU.Build.0 = Release|Any CPU 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Elephanet.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Elephanet.Tests; 2 | using Elephanet.Tests.Entities; 3 | using Ploeh.AutoFixture; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System; 7 | using System.Diagnostics; 8 | 9 | namespace Elephanet.Benchmark 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | const int records = 10000; 16 | var watch = new Stopwatch(); 17 | Console.WriteLine("Creating {0} records", records); 18 | DocumentStore store = new DocumentStore("Server=127.0.0.1;Port=5432;User id=store_user;password=my super secret password;database=store;"); 19 | var cars = GenerateCars(records); 20 | Console.WriteLine("Generated {0} records", records); 21 | Console.WriteLine("Saving {0} records to database", records); 22 | 23 | watch.Start(); 24 | using (var session = store.OpenSession()) 25 | { 26 | foreach(var car in cars) 27 | { 28 | session.Store(car); 29 | } 30 | session.SaveChanges(); 31 | } 32 | watch.Stop(); 33 | var rate = records / watch.Elapsed.TotalSeconds; 34 | 35 | Console.WriteLine("Saved {0} records to datatbase in {1}s @ {2} p/s rate", records, watch.Elapsed.TotalSeconds, rate); 36 | Console.WriteLine("Press any key to do query test...."); 37 | Console.ReadLine(); 38 | 39 | List fords; 40 | using (var session = store.OpenSession()) 41 | { 42 | watch.Reset(); 43 | watch.Start(); 44 | fords = session.Query().Where(c => c.Make == "Ford").ToList(); 45 | watch.Stop(); 46 | } 47 | foreach (var ford in fords) 48 | { 49 | Console.WriteLine("Id: {0}, Make: {1}", ford.Id, ford.Make); 50 | } 51 | Console.WriteLine("Query took {0}ms", watch.Elapsed.TotalMilliseconds); 52 | 53 | List holdens; 54 | using (var session = store.OpenSession()) 55 | { 56 | watch.Reset(); 57 | watch.Start(); 58 | holdens = session.Query().Where(c => c.Make == "Holden").ToList(); 59 | watch.Stop(); 60 | } 61 | foreach (var holden in holdens) 62 | { 63 | Console.WriteLine("Id: {0}, Make: {1}", holden.Id, holden.Make); 64 | } 65 | Console.WriteLine("Query took {0}ms", watch.Elapsed.TotalMilliseconds); 66 | 67 | //cleanup 68 | using (var session = store.OpenSession()) 69 | { 70 | session.DeleteAll(); 71 | } 72 | Console.ReadLine(); 73 | } 74 | 75 | public static List GenerateCars(int numberOfCars) 76 | { 77 | var dummyCars = new Fixture().Build() 78 | .With(x => x.Make, "Subaru") 79 | .CreateMany(numberOfCars).ToList(); 80 | //spice it up 81 | dummyCars[100].Make = "Ford"; 82 | dummyCars[1000].Make = "Ford"; 83 | dummyCars[2948].Make = "Ford"; 84 | dummyCars[256].Make = "Holden"; 85 | dummyCars[269].Make = "Holden"; 86 | dummyCars[452].Make = "Holden"; 87 | 88 | return dummyCars.ToList(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Elephanet.Console/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("Elephanet.Console")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Elephanet.Console")] 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("0d6a9984-40b3-47f6-a67f-1a792c97de8c")] 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 | -------------------------------------------------------------------------------- /Elephanet.Console/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Elephanet.Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Elephanet.Tests/CorrectEscapingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Elephanet.Tests.Entities; 3 | using Xunit; 4 | using Shouldly; 5 | 6 | namespace Elephanet.Tests 7 | { 8 | public class CorrectEscapingTests : IClassFixture, IDisposable 9 | { 10 | private readonly TestStore _store; 11 | private EntityForCorrectEscapingTests _entity; 12 | 13 | public CorrectEscapingTests(DocumentStoreBaseFixture data) 14 | { 15 | _store = data.TestStore; 16 | } 17 | 18 | [Fact] 19 | public void SingleQuotes_ShouldBe_EscapedWhenSaving() 20 | { 21 | _entity = new EntityForCorrectEscapingTests() {Id = Guid.NewGuid(), PropertyOne = "Kia", PropertyTwo = "Cee'd"}; 22 | 23 | 24 | //save the car 25 | using (var session = _store.OpenSession()) 26 | { 27 | session.Store(_entity); 28 | session.SaveChanges(); 29 | } 30 | 31 | //retrieve and check the make and model are correct 32 | using (var session = _store.OpenSession()) 33 | { 34 | var savedCar = session.GetById(_entity.Id); 35 | savedCar.PropertyTwo.ShouldBe("Cee'd"); 36 | savedCar.PropertyOne.ShouldBe("Kia"); 37 | } 38 | } 39 | 40 | public void Dispose() 41 | { 42 | using (var session = _store.OpenSession()) 43 | { 44 | session.Delete(_entity.Id); 45 | session.SaveChanges(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Elephanet.Tests/DirtySpeedTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Elephanet.Tests.Entities; 6 | using Ploeh.AutoFixture; 7 | using System.Diagnostics; 8 | using Xunit; 9 | 10 | namespace Elephanet.Tests 11 | { 12 | public class DirtySpeedTests : IDisposable 13 | { 14 | readonly DocumentStore _store; 15 | const int WriteCount = 50000; 16 | readonly Stopwatch _watch; 17 | 18 | public DirtySpeedTests() 19 | { 20 | _store = new TestStore(); 21 | _watch = new Stopwatch(); 22 | } 23 | 24 | 25 | public List GenerateCars(int numberOfCars) 26 | { 27 | var dummyCars = new Fixture().Build() 28 | .With(x => x.Make, "Subaru") 29 | .CreateMany(numberOfCars); 30 | return dummyCars.ToList(); 31 | } 32 | 33 | [Fact(Skip="Uncomment for a speed test")] 34 | public void InsertSpeedTest() 35 | { 36 | var cars = GenerateCars(WriteCount); 37 | _watch.Reset(); 38 | _watch.Start(); 39 | using (var session = _store.OpenSession()) 40 | { 41 | foreach (Car car in cars) 42 | { 43 | session.Store(car); 44 | } 45 | 46 | session.SaveChanges(); 47 | } 48 | _watch.Stop(); 49 | var rate = WriteCount / _watch.Elapsed.TotalSeconds; 50 | Console.WriteLine("{0} writes p/s", rate); 51 | Console.WriteLine("{0} writes in {1} seconds", WriteCount, _watch.Elapsed.TotalSeconds); 52 | } 53 | 54 | [Fact(Skip="Uncomment for a speed test")] 55 | public void LinqWhereQuerySpeedTest() 56 | { 57 | const int seedNumber = 20000; //that should be enough to see if we are hitting the index nicely 58 | var cars = GenerateCars(seedNumber); 59 | 60 | //replace with a few other makes in there 61 | cars[1000].Make = "Ford"; 62 | cars[1201].Make = "Ford"; 63 | cars[2005].Make = "Ford"; 64 | 65 | //save to the database 66 | using (var session = _store.OpenSession()) 67 | { 68 | foreach (Car car in cars) 69 | { 70 | session.Store(car); 71 | } 72 | 73 | session.SaveChanges(); 74 | } 75 | 76 | using (var session = _store.OpenSession()) 77 | { 78 | _watch.Start(); 79 | var query = session.Query().Where(c => c.Make == "Ford"); 80 | //force the query 81 | var fords = query.ToList(); 82 | _watch.Stop(); 83 | } 84 | 85 | Console.WriteLine("{0} ms for reading 3 records from {1} total (initial query)", _watch.Elapsed.TotalMilliseconds, seedNumber); 86 | 87 | } 88 | 89 | public void Dispose() 90 | { 91 | using (var session = _store.OpenSession()) 92 | { 93 | session.DeleteAll(); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Elephanet.Tests/DocumentStoreBaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Tests 4 | { 5 | /// 6 | /// Use this as an IClassFixture to inject the TestStore into other test fixtures 7 | /// 8 | public class DocumentStoreBaseFixture : IDisposable 9 | { 10 | public DocumentStoreBaseFixture() 11 | { 12 | TestStore = new TestStore(); 13 | } 14 | 15 | public TestStore TestStore { get; private set; } 16 | 17 | public void Dispose() 18 | { 19 | TestStore.Destroy(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Elephanet.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {1156667A-85F7-493C-BF33-F22284134559} 10 | Library 11 | Properties 12 | Elephanet.Tests 13 | Elephanet.Tests 14 | v4.5 15 | 512 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\NSubstitute.1.7.2.0\lib\NET45\NSubstitute.dll 39 | 40 | 41 | ..\packages\AutoFixture.3.20.1\lib\net40\Ploeh.AutoFixture.dll 42 | 43 | 44 | ..\packages\Shouldly.2.1.1\lib\net40\Shouldly.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 55 | True 56 | 57 | 58 | ..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll 59 | True 60 | 61 | 62 | ..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll 63 | True 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | {f6f93a0a-0eb1-47a5-9089-db6f56369f98} 105 | Elephanet 106 | 107 | 108 | 109 | 110 | 111 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 112 | 113 | 114 | 115 | 116 | 123 | -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/AnotherEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class AnotherEntity : BaseEntity 4 | { 5 | public string Description { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/AnotherEntityWithDateTime.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class AnotherEntityWithDateTime : BaseEntity 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Tests.Entities 4 | { 5 | public class BaseEntity 6 | { 7 | public Guid Id { get; set; } 8 | public string PropertyOne { get; set; } 9 | public string PropertyTwo { get; set; } 10 | public string PropertyThree { get; set; } 11 | public string PropertyFour { get; set; } 12 | public DateTime CreatedAt { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/Bike.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Tests.Entities 4 | { 5 | public class Bike 6 | { 7 | public Guid Id { get; set; } 8 | public double WheelSize { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/Car.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Tests.Entities 4 | { 5 | public class Car 6 | { 7 | public Guid Id { get; set; } 8 | public string Make { get; set; } 9 | public string Model { get; set; } 10 | public string ImageUrl { get; set; } 11 | public string NumberPlate { get; set; } 12 | public DateTime CreatedAt { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityForCorrectEscapingTests.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class EntityForCorrectEscapingTests : BaseEntity 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityForGetByIdTests.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class EntityForGetByIdTests : BaseEntity 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityForLinqOrderByTests.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class EntityForLinqOrderByTests : BaseEntity 4 | { 5 | } 6 | 7 | 8 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityForSchemaConventionsTest.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class EntityForSchemaConventionsTest : BaseEntity 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityForSessionAndStoreTests.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class EntityForSessionAndStoreTests : BaseEntity 4 | { 5 | 6 | } 7 | 8 | public class SecondEntityForSessionAndStoreTest : EntityForSessionAndStoreTests 9 | { 10 | public int WheelSize { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityForWhereLinqTests.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Tests.Entities 2 | { 3 | public class EntityForWhereLinqTests : BaseEntity 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/EntityWithDateTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Tests.Entities 4 | { 5 | public class EntityWithDateTime 6 | { 7 | public DateTime CreatedAt { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Elephanet.Tests/Entities/SomeTestClassThatNoOneWillEverUse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Tests.Entities 4 | { 5 | public class SomeTestClassThatNoOneWillEverUse 6 | { 7 | public Guid Id { get; set; } 8 | public string Something { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Elephanet.Tests/GetAllTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Elephanet.Tests.Entities; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace Elephanet.Tests 7 | { 8 | public class GetAllTests : IClassFixture 9 | { 10 | private readonly TestStore _store; 11 | 12 | public GetAllTests(DocumentStoreBaseFixture data) 13 | { 14 | _store = data.TestStore; 15 | } 16 | 17 | [Fact] 18 | public void GetAllCreatesTheTableIfItDoesNotExist() 19 | { 20 | using (var session = _store.OpenSession()) 21 | { 22 | var things = session.GetAll().ToList(); 23 | things.Count.ShouldBe(0); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Elephanet.Tests/GetByIdTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Elephanet.Tests.Entities; 4 | using Ploeh.AutoFixture; 5 | using Shouldly; 6 | using Xunit; 7 | using System; 8 | 9 | namespace Elephanet.Tests 10 | { 11 | public class GetByIdTests : IClassFixture, IDisposable 12 | { 13 | private readonly TestStore _store; 14 | private readonly List _dummyCars; 15 | 16 | public GetByIdTests(DocumentStoreBaseFixture data) 17 | { 18 | _store = data.TestStore; 19 | _dummyCars = new Fixture().CreateMany().ToList(); 20 | using (var session = _store.OpenSession()) 21 | { 22 | foreach (var car in _dummyCars) 23 | { 24 | session.Store(car); 25 | } 26 | 27 | session.SaveChanges(); 28 | } 29 | } 30 | 31 | [Fact] 32 | public void GetById_Should_NotByNull() 33 | { 34 | using (var session = _store.OpenSession()) 35 | { 36 | var first = session.GetById(_dummyCars[0].Id); 37 | first.ShouldNotBe(null); 38 | } 39 | } 40 | 41 | [Fact] 42 | public void GetByIds_Should_NotByNull() 43 | { 44 | using (var session = _store.OpenSession()) 45 | { 46 | var cars = session.GetByIds(_dummyCars.Select(c => c.Id).ToList()).ToList(); 47 | cars[0].Id.ShouldBe(_dummyCars[0].Id); 48 | cars[2].Id.ShouldBe(_dummyCars[2].Id); 49 | } 50 | } 51 | 52 | [Fact] 53 | public void GetById_Should_Throw_EntityNotFoundException() 54 | { 55 | using (var session = _store.OpenSession()) 56 | { 57 | Should.Throw(() => 58 | { 59 | session.GetById(Guid.NewGuid()); 60 | }); 61 | } 62 | } 63 | 64 | [Fact] 65 | public void GetById_Should_Return_Null_When_ReturnNull_Convention_Set() 66 | { 67 | var store = TestStore.CreateStoreWithEntityNotFoundBehaviorReturnNull(); 68 | using (var session = store.OpenSession()) 69 | { 70 | session.GetById(Guid.NewGuid()) 71 | .ShouldBe(null); 72 | } 73 | } 74 | 75 | [Fact] 76 | public void GetAll_ShouldGetAll() 77 | { 78 | using (var session = _store.OpenSession()) 79 | { 80 | var cars = session.GetAll().ToList(); 81 | cars.Count.ShouldBe(3); 82 | } 83 | } 84 | 85 | public void Dispose() 86 | { 87 | using (var session = _store.OpenSession()) 88 | { 89 | session.DeleteAll(); 90 | } 91 | } 92 | 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Elephanet.Tests/JsonConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Elephanet.Serialization; 3 | using Elephanet.Tests.Entities; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace Elephanet.Tests 8 | { 9 | public class JsonConverterTests 10 | { 11 | private readonly JilJsonConverter _converter; 12 | private Guid _id; 13 | private readonly string _description; 14 | private readonly AnotherEntity _entityToSerialize; 15 | 16 | public JsonConverterTests() 17 | { 18 | _converter = new JilJsonConverter(); 19 | _id = Guid.NewGuid(); 20 | _description = "xxx"; 21 | _entityToSerialize = new AnotherEntity {Id = _id,Description = _description}; 22 | } 23 | 24 | [Fact] 25 | public void JsonConverter_GivenAnInheritedObject_ShouldSerialize() 26 | { 27 | string json = _converter.Serialize(_entityToSerialize); 28 | json.ShouldContain(_id.ToString()); 29 | json.ShouldContain(_description); 30 | } 31 | 32 | [Fact] 33 | public void JsonConverter_GivenAnInheritedObject_ShouldDeserialize() 34 | { 35 | string json = _converter.Serialize(_entityToSerialize); 36 | var entity = _converter.Deserialize(json); 37 | entity.Id.ShouldBe(_id); 38 | entity.Description.ShouldBe(_description); 39 | } 40 | 41 | [Fact] 42 | public void JsonConverter_GivenAnEntityWithDateTime_ShouldSerializeAndDeserialize() 43 | { 44 | var entity = new EntityWithDateTime(); 45 | var now = DateTime.UtcNow; 46 | entity.CreatedAt = now; 47 | string json = _converter.Serialize(entity); 48 | 49 | var deSerializedEntity = _converter.Deserialize(json); 50 | deSerializedEntity.CreatedAt.ToString().ShouldBe(now.ToString()); 51 | } 52 | 53 | [Fact] 54 | public void JsonConverter_GivenAnInheritedEntityWithDateTime_ShouldSerializeAndDeserialize() 55 | { 56 | var entity = new AnotherEntityWithDateTime(); 57 | var now = DateTime.UtcNow; 58 | entity.CreatedAt = now; 59 | string json = _converter.Serialize(entity); 60 | 61 | var deSerializedEntity = _converter.Deserialize(json); 62 | deSerializedEntity.CreatedAt.ToString().ShouldBe(now.ToString()); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Elephanet.Tests/LinqOrderByTests.cs: -------------------------------------------------------------------------------- 1 | using Elephanet.Tests.Entities; 2 | using Ploeh.AutoFixture; 3 | using Xunit; 4 | using Shouldly; 5 | using System.Linq; 6 | using System; 7 | 8 | namespace Elephanet.Tests 9 | { 10 | public class LinqOrderByTests : IClassFixture, IDisposable 11 | { 12 | private readonly TestStore _store; 13 | 14 | public LinqOrderByTests(DocumentStoreBaseFixture data) 15 | { 16 | _store = data.TestStore; 17 | 18 | var carA = new Fixture().Build() 19 | .With(x => x.PropertyOne, "Mazda") 20 | .With(y => y.PropertyTwo, "A") 21 | .Create(); 22 | 23 | var carB = new Fixture().Build() 24 | .With(x => x.PropertyOne, "Mazda") 25 | .With(y => y.PropertyTwo, "B") 26 | .Create(); 27 | 28 | using (var session = _store.OpenSession()) 29 | { 30 | session.Store(carA); 31 | session.Store(carB); 32 | session.SaveChanges(); 33 | } 34 | } 35 | 36 | [Fact] 37 | public void IQueryable_Should_ImplementOrderBy() 38 | { 39 | using (var session = _store.OpenSession()) 40 | { 41 | var cars = session.Query().Where(c => c.PropertyOne == "Mazda").OrderBy(o => o.PropertyTwo).ToList(); 42 | cars[0].PropertyTwo.ShouldBe("A"); 43 | cars[1].PropertyTwo.ShouldBe("B"); 44 | } 45 | } 46 | 47 | [Fact] 48 | public void IQueryable_Should_ImplementOrderByDescending() 49 | { 50 | using (var session = _store.OpenSession()) 51 | { 52 | var cars = session.Query().Where(c => c.PropertyOne == "Mazda").OrderByDescending(o => o.PropertyTwo).ToList(); 53 | cars[0].PropertyTwo.ShouldBe("B"); 54 | cars[1].PropertyTwo.ShouldBe("A"); 55 | } 56 | } 57 | 58 | public void Dispose() 59 | { 60 | using (var session = _store.OpenSession()) 61 | { 62 | session.DeleteAll(); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Elephanet.Tests/LinqTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Elephanet.Tests.Entities; 5 | using Xunit; 6 | using Shouldly; 7 | using Ploeh.AutoFixture; 8 | 9 | 10 | namespace Elephanet.Tests 11 | { 12 | public class LinqTests : IClassFixture, IDisposable 13 | { 14 | private readonly IDocumentStore _store; 15 | 16 | public LinqTests(DocumentStoreBaseFixture data) 17 | { 18 | _store = data.TestStore; 19 | CreateDummyCar(); 20 | } 21 | 22 | public void CreateDummyCar() 23 | { 24 | 25 | var dummyEntity = new Fixture().Build() 26 | .With(x => x.PropertyOne, "Subaru") 27 | .CreateMany(); 28 | 29 | var lowercasePropertyOneEntity = new Fixture().Build() 30 | .With(x => x.PropertyOne, "SAAB") 31 | .CreateMany(); 32 | 33 | using (var session = _store.OpenSession()) 34 | { 35 | foreach (var entity in dummyEntity) 36 | { 37 | session.Store(entity); 38 | } 39 | 40 | foreach (var entity in lowercasePropertyOneEntity) 41 | { 42 | session.Store(entity); 43 | } 44 | session.SaveChanges(); 45 | } 46 | 47 | } 48 | 49 | [Fact] 50 | public void WherePredicate_Should_Build() 51 | { 52 | using (var session = _store.OpenSession()) 53 | { 54 | var results = session.Query().Where(x => x.PropertyOne == "Subaru").ToList(); 55 | results.ShouldNotBeEmpty(); 56 | } 57 | } 58 | 59 | [Fact] 60 | public void WhereExpression_Should_ReturnWhereQuery() 61 | { 62 | using (var session = _store.OpenSession()) 63 | { 64 | var results = session.Query().Where(c => c.PropertyOne == "Subaru"); 65 | var car = results.ToList(); 66 | car.Count.ShouldBe(3); 67 | car.ShouldBeOfType>(); 68 | car.ForEach(c => c.PropertyOne.ShouldBe("Subaru")); 69 | 70 | } 71 | } 72 | 73 | [Fact] 74 | public void WhereExpression_Should_HandleExpressionSubtrees() 75 | { 76 | const string propertyOne = "Subaru"; 77 | using (var session = _store.OpenSession()) 78 | { 79 | var results = session.Query().Where(c => c.PropertyOne == propertyOne); 80 | var car = results.ToList(); 81 | car.Count.ShouldBe(3); 82 | car.ShouldBeOfType>(); 83 | car.ForEach(c => c.PropertyOne.ShouldBe("Subaru")); 84 | } 85 | } 86 | 87 | [Fact] 88 | public void WhereExpression_Should_HandleExtensionMethodsInSubtrees() 89 | { 90 | using (var session = _store.OpenSession()) 91 | { 92 | var results = session.Query().Where(c => c.PropertyOne == "saab".ToUpper()); 93 | var car = results.ToList(); 94 | car.Count.ShouldBe(3); 95 | car.ShouldBeOfType>(); 96 | car.ForEach(c => c.PropertyOne.ShouldBe("SAAB")); 97 | } 98 | 99 | } 100 | 101 | [Fact] 102 | public void IQueryable_Should_ImplementTakeMethod() 103 | { 104 | using (var session = _store.OpenSession()) 105 | { 106 | var results = session.Query().Where(c => c.PropertyOne == "SAAB").Take(2); 107 | var car = results.ToList(); 108 | car.Count.ShouldBe(2); 109 | car.ShouldBeOfType>(); 110 | } 111 | } 112 | 113 | [Fact] 114 | public void IQueryable_Should_ImplementSkipMethod() 115 | { 116 | using (var session = _store.OpenSession()) 117 | { 118 | var results = session.Query().Where(c => c.PropertyOne == "SAAB").Skip(2); 119 | var car = results.ToList(); 120 | car.Count.ShouldBe(1); 121 | car.ShouldBeOfType>(); 122 | } 123 | 124 | } 125 | 126 | public void Dispose() 127 | { 128 | using (var session = _store.OpenSession()) 129 | { 130 | session.DeleteAll(); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Elephanet.Tests/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("Elephanet.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Elephanet.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 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("88924a5d-1ddc-461a-bcb3-643e9c75ede2")] 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 | -------------------------------------------------------------------------------- /Elephanet.Tests/SessionAndStoreTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Elephanet.Tests.Entities; 4 | using Ploeh.AutoFixture; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace Elephanet.Tests 9 | { 10 | public class SessionAndStoreTests : IClassFixture, IDisposable 11 | { 12 | private readonly TestStore _store; 13 | 14 | public SessionAndStoreTests(DocumentStoreBaseFixture data) 15 | { 16 | _store = data.TestStore; 17 | } 18 | 19 | [Fact] 20 | public void WillPass() 21 | { 22 | true.ShouldBe(true); 23 | } 24 | 25 | [Fact] 26 | public void NewStore_ConnectionStringShouldBe_SameAsCtor() 27 | { 28 | _store.ConnectionString.ShouldBe("Server=127.0.0.1;Port=5432;User id=store_user;password=my super secret password;database=store;"); 29 | } 30 | 31 | [Fact] 32 | public void Store_Should_OpenSession() 33 | { 34 | using (var session = _store.OpenSession()) 35 | { 36 | //not really doing anything 37 | } 38 | } 39 | 40 | [Fact] 41 | public void SessionStore_Should_NotThrow() 42 | { 43 | using (var session = _store.OpenSession()) 44 | { 45 | var car = new EntityForSessionAndStoreTests 46 | { 47 | Id = Guid.NewGuid(), 48 | PropertyOne = "Subaru", 49 | PropertyTwo = "Impreza", 50 | PropertyThree = "http://www.carsfotodb.com/uploads/subaru/subaru-impreza/subaru-impreza-08.jpg", 51 | PropertyFour = "NRM1003" 52 | }; 53 | 54 | Should.NotThrow(() => session.Store(car)); 55 | } 56 | 57 | } 58 | 59 | [Fact] 60 | public void SessionSaveChanges_Should_SaveChanges() 61 | { 62 | using (var session = _store.OpenSession()) 63 | { 64 | var car = new EntityForSessionAndStoreTests 65 | { 66 | Id = Guid.NewGuid(), 67 | PropertyOne = "Subaru", 68 | PropertyTwo = "Impreza", 69 | PropertyThree = "http://www.carsfotodb.com/uploads/subaru/subaru-impreza/subaru-impreza-08.jpg", 70 | PropertyFour = "NRM1003" 71 | }; 72 | 73 | session.Store(car); 74 | session.SaveChanges(); 75 | } 76 | 77 | } 78 | 79 | [Fact] 80 | public void StoringAnEnitityAndFetchingWithinSession_Should_ReturnEntity() 81 | { 82 | 83 | var dummyCar = new Fixture().Create(); 84 | 85 | using (var session = _store.OpenSession()) 86 | { 87 | session.Store(dummyCar); 88 | var result = session.GetById(dummyCar.Id); 89 | result.Id.ShouldBe(dummyCar.Id); 90 | result.PropertyOne.ShouldBe(dummyCar.PropertyOne); 91 | result.PropertyTwo.ShouldBe(dummyCar.PropertyTwo); 92 | result.PropertyThree.ShouldBe(dummyCar.PropertyThree); 93 | result.PropertyFour.ShouldBe(dummyCar.PropertyFour); 94 | 95 | } 96 | } 97 | 98 | [Fact] 99 | public void NewStoreWithNoCtors_Should_SetConventions() 100 | { 101 | _store.StoreInfo.ShouldNotBe(null); 102 | _store.Conventions.ShouldNotBe(null); 103 | } 104 | 105 | [Fact] 106 | public void SaveSessionThenLoading_Should_ReturnEntity() 107 | { 108 | 109 | var dummyCar = new Fixture().Create(); 110 | 111 | using (var session = _store.OpenSession()) 112 | { 113 | session.Store(dummyCar); 114 | session.SaveChanges(); 115 | } 116 | 117 | using (var session = _store.OpenSession()) 118 | { 119 | var car = session.GetById(dummyCar.Id); 120 | car.Id.ShouldBe(dummyCar.Id); 121 | car.PropertyOne.ShouldBe(dummyCar.PropertyOne); 122 | car.PropertyTwo.ShouldBe(dummyCar.PropertyTwo); 123 | car.PropertyThree.ShouldBe(dummyCar.PropertyThree); 124 | car.PropertyFour.ShouldBe(dummyCar.PropertyFour); 125 | } 126 | } 127 | 128 | [Fact] 129 | public void SaveChangesWithMultipleDifferentClasses_Should_Save() 130 | { 131 | 132 | var firstDummyEntity = new Fixture().Create(); 133 | var secondDummyEntity = new Fixture().Create(); 134 | 135 | using (var session = _store.OpenSession()) 136 | { 137 | session.Store(firstDummyEntity); 138 | session.Store(secondDummyEntity); 139 | session.SaveChanges(); 140 | } 141 | 142 | using (var session = _store.OpenSession()) 143 | { 144 | session.GetById(secondDummyEntity.Id).ShouldNotBe(null); 145 | session.GetById(firstDummyEntity.Id).ShouldNotBe(null); 146 | } 147 | } 148 | 149 | 150 | [Fact] 151 | public void SessionCanDeleteById() 152 | { 153 | 154 | var dummyCars = new Fixture().Build() 155 | .With(x => x.PropertyOne, "Subaru") 156 | .CreateMany().ToList(); 157 | //setup 158 | using (var session = _store.OpenSession()) 159 | { 160 | foreach (var car in dummyCars) 161 | { 162 | session.Store(car); 163 | } 164 | session.SaveChanges(); 165 | } 166 | //delete 167 | using (var session = _store.OpenSession()) 168 | { 169 | foreach (var car in dummyCars) 170 | { 171 | session.Delete(car.Id); 172 | session.SaveChanges(); 173 | } 174 | } 175 | //check 176 | using (var session = _store.OpenSession()) 177 | { 178 | var records = session.Query().Where(c => c.PropertyOne == "Subaru").ToList(); 179 | records.Count.ShouldBe(0); 180 | } 181 | } 182 | 183 | [Fact] 184 | public void SaveChangesWithUpdates_Should_Persist() 185 | { 186 | 187 | var firstDummyEntity = new Fixture().Create(); 188 | var secondDummyEntity = new Fixture().Create(); 189 | 190 | using (var session = _store.OpenSession()) 191 | { 192 | session.Store(firstDummyEntity); 193 | session.Store(secondDummyEntity); 194 | session.SaveChanges(); 195 | } 196 | 197 | using (var session = _store.OpenSession()) 198 | { 199 | var retrievedBike = session.GetById(secondDummyEntity.Id); 200 | var retrievedCar = session.GetById(firstDummyEntity.Id); 201 | 202 | retrievedBike.WheelSize = 900; 203 | retrievedCar.PropertyOne = "Lada"; 204 | session.Store(retrievedCar); 205 | session.Store(retrievedBike); 206 | session.SaveChanges(); 207 | } 208 | 209 | using (var session = _store.OpenSession()) 210 | { 211 | var alteredBike = session.GetById(secondDummyEntity.Id); 212 | var alteredCar = session.GetById(firstDummyEntity.Id); 213 | 214 | alteredBike.WheelSize.ShouldBe(900); 215 | alteredCar.PropertyOne.ShouldBe("Lada"); 216 | 217 | } 218 | } 219 | 220 | [Fact] 221 | public void SaveChangesWithUpdates_Should_BeQueryable() 222 | { 223 | var dummyCars = new Fixture().Build() 224 | .With(x => x.PropertyOne, "Subaru") 225 | .CreateMany(100).ToList(); 226 | using (var session = _store.OpenSession()) 227 | { 228 | foreach (var car in dummyCars) 229 | { 230 | session.Store(car); 231 | session.SaveChanges(); 232 | } 233 | 234 | } 235 | 236 | using (var session = _store.OpenSession()) 237 | { 238 | //retrieve a couple of cars 239 | var car1ToAlter = session.GetById(dummyCars[15].Id); 240 | var car2ToAlter = session.GetById(dummyCars[85].Id); 241 | car1ToAlter.PropertyOne = "Ford"; 242 | car2ToAlter.PropertyOne = "Ford"; 243 | session.Store(car1ToAlter); 244 | session.Store(car2ToAlter); 245 | session.SaveChanges(); 246 | } 247 | 248 | using (var session = _store.OpenSession()) 249 | { 250 | var cars = session.Query().Where(c => c.PropertyOne == "Ford").ToList(); 251 | cars.Count.ShouldBe(2); 252 | } 253 | } 254 | 255 | public void Dispose() 256 | { 257 | using (var session = _store.OpenSession()) 258 | { 259 | session.DeleteAll(); 260 | session.DeleteAll(); 261 | } 262 | } 263 | } 264 | } 265 | 266 | -------------------------------------------------------------------------------- /Elephanet.Tests/StoreConventionsTests.cs: -------------------------------------------------------------------------------- 1 | using Elephanet.Conventions; 2 | using Elephanet.Serialization; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace Elephanet.Tests 7 | { 8 | public class StoreConventionsTests 9 | { 10 | [Fact] 11 | public void Ctor_Sets_JsonConverter_And_TableInfo() 12 | { 13 | var jsonConverter = new JilJsonConverter(); 14 | var info = new TableInfo(); 15 | 16 | var convention = new StoreConventions(jsonConverter, info); 17 | 18 | convention.JsonConverter.ShouldBeSameAs(jsonConverter); 19 | convention.TableInfo.ShouldBeSameAs(info); 20 | } 21 | 22 | [Fact] 23 | public void Ctor_Providing_IJsonConverter_Sets_JsonConverter_And_TableInfo_To_Default() 24 | { 25 | var jsonConverter = new JilJsonConverter(); 26 | 27 | var conventions = new StoreConventions(jsonConverter); 28 | 29 | conventions.JsonConverter.ShouldBeSameAs(jsonConverter); 30 | conventions.TableInfo.ShouldBeOfType(); 31 | } 32 | 33 | [Fact] 34 | public void EntityNotFoundBehavior_Defaults_To_Throw() 35 | { 36 | new StoreConventions() 37 | .EntityNotFoundBehavior.ShouldBe(EntityNotFoundBehavior.Throw); 38 | } 39 | 40 | [Fact] 41 | public void SetEntityNotFoundBehavior_Sets__EntityNotFoundBehavior() 42 | { 43 | var conventions = new StoreConventions(); 44 | 45 | conventions.SetEntityNotFoundBehavior(EntityNotFoundBehavior.ReturnNull); 46 | 47 | conventions.EntityNotFoundBehavior.ShouldBe(EntityNotFoundBehavior.ReturnNull); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Elephanet.Tests/StoreInfoTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Elephanet; 5 | using Xunit; 6 | using Shouldly; 7 | 8 | namespace Elephanet.Tests 9 | { 10 | public class StoreInfoTests 11 | { 12 | [Fact] 13 | public void StoreInfo_GivenANewTable_Should_AddItToItsTableNamesList() 14 | { 15 | StoreInfo storeInfo = new StoreInfo(); 16 | storeInfo.Add("someting or rather"); 17 | 18 | storeInfo.Tables.ShouldContain("someting or rather"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Elephanet.Tests/TableInfoTests.cs: -------------------------------------------------------------------------------- 1 | using Elephanet.Tests.Entities; 2 | using Xunit; 3 | using Shouldly; 4 | 5 | namespace Elephanet.Tests 6 | { 7 | public class ConventionTests 8 | { 9 | [Fact] 10 | public void TableInfo_GivenNoSchema_ShouldReturnSchemaOfPublic() 11 | { 12 | TableInfo tableInfo = new TableInfo(); 13 | tableInfo.Schema.ShouldBe("public"); 14 | } 15 | 16 | [Fact] 17 | public void TableInfo_GivenASchema_ShouldReturnThatSchema() 18 | { 19 | TableInfo tableInfo = new TableInfo("aschema"); 20 | tableInfo.Schema.ShouldBe("aschema"); 21 | } 22 | 23 | [Fact] 24 | public void TableInfo_GivenAType_ShouldReturn_TheCorrectTableNameWithoutSchema() 25 | { 26 | TableInfo tableInfo = new TableInfo("aschema"); 27 | tableInfo.TableNameWithoutSchema(typeof(EntityForSchemaConventionsTest)).ShouldBe("elephanet_tests_entities_entityforschemaconventionstest"); 28 | 29 | } 30 | 31 | [Fact] 32 | public void TableInfo_GivenAType_ShouldReturn_TheCorrectTableNameWithSchema() 33 | { 34 | TableInfo tableInfo = new TableInfo("aschema"); 35 | tableInfo.TableNameWithSchema(typeof(EntityForSchemaConventionsTest)).ShouldBe("aschema.elephanet_tests_entities_entityforschemaconventionstest"); 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Elephanet.Tests/TestStore.cs: -------------------------------------------------------------------------------- 1 |  2 | using Elephanet.Conventions; 3 | 4 | namespace Elephanet.Tests 5 | { 6 | public class TestStore : DocumentStore 7 | { 8 | private const string TestDbConnectionString = "Server=127.0.0.1;Port=5432;User id=store_user;password=my super secret password;database=store;"; 9 | 10 | public TestStore() : base(TestDbConnectionString, new StoreConventions()) 11 | { } 12 | 13 | public TestStore(IStoreConventions storeConventions) : base(TestDbConnectionString, storeConventions) 14 | { 15 | } 16 | 17 | /// 18 | /// Factory method for creating a configured TestStore 19 | /// 20 | /// 21 | public static TestStore CreateStoreWithEntityNotFoundBehaviorReturnNull() 22 | { 23 | var conventions = new StoreConventions(); 24 | conventions.SetEntityNotFoundBehavior(EntityNotFoundBehavior.ReturnNull); 25 | return new TestStore(conventions); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Elephanet.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Elephanet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elephanet", "Elephanet\Elephanet.csproj", "{F6F93A0A-0EB1-47A5-9089-DB6F56369F98}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elephanet.Tests", "Elephanet.Tests\Elephanet.Tests.csproj", "{1156667A-85F7-493C-BF33-F22284134559}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{A70354CF-567C-4D90-A635-8AC825CFA7EA}" 11 | ProjectSection(SolutionItems) = preProject 12 | .nuget\packages.config = .nuget\packages.config 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64298DF8-AB03-405A-9530-C036710DDBC2}" 16 | ProjectSection(SolutionItems) = preProject 17 | Performance1.psess = Performance1.psess 18 | EndProjectSection 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elephanet.Console", "Elephanet.Console\Elephanet.Console.csproj", "{7B67E4BB-55E4-4976-AA8E-72448CDE9E69}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {F6F93A0A-0EB1-47A5-9089-DB6F56369F98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {F6F93A0A-0EB1-47A5-9089-DB6F56369F98}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {F6F93A0A-0EB1-47A5-9089-DB6F56369F98}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {F6F93A0A-0EB1-47A5-9089-DB6F56369F98}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {1156667A-85F7-493C-BF33-F22284134559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {1156667A-85F7-493C-BF33-F22284134559}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {1156667A-85F7-493C-BF33-F22284134559}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {1156667A-85F7-493C-BF33-F22284134559}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {7B67E4BB-55E4-4976-AA8E-72448CDE9E69}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /Elephanet/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Elephanet/ConcurrentHashSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Runtime.Serialization; 6 | using System.Threading; 7 | 8 | namespace Elephanet 9 | { 10 | /// 11 | /// Concurrent HashSet implementaion from http://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework 12 | /// as .NET framework doesnt have one. 13 | /// 14 | [DebuggerDisplay("Count = {Count}")] 15 | [Serializable] 16 | public class ConcurrentHashSet : ICollection, ISet, ISerializable, IDeserializationCallback 17 | { 18 | private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); 19 | 20 | private readonly HashSet _hashSet = new HashSet(); 21 | 22 | public ConcurrentHashSet() 23 | { 24 | } 25 | 26 | public ConcurrentHashSet(IEqualityComparer comparer) 27 | { 28 | _hashSet = new HashSet(comparer); 29 | } 30 | 31 | public ConcurrentHashSet(IEnumerable collection) 32 | { 33 | _hashSet = new HashSet(collection); 34 | } 35 | 36 | public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) 37 | { 38 | _hashSet = new HashSet(collection, comparer); 39 | } 40 | 41 | protected ConcurrentHashSet(SerializationInfo info, StreamingContext context) 42 | { 43 | _hashSet = new HashSet(); 44 | 45 | // not sure about this one really... 46 | var iSerializable = _hashSet as ISerializable; 47 | iSerializable.GetObjectData(info, context); 48 | } 49 | 50 | #region Dispose 51 | 52 | public void Dispose() 53 | { 54 | Dispose(true); 55 | GC.SuppressFinalize(this); 56 | } 57 | 58 | protected virtual void Dispose(bool disposing) 59 | { 60 | if (disposing) 61 | if (_lock != null) 62 | _lock.Dispose(); 63 | } 64 | 65 | public IEnumerator GetEnumerator() 66 | { 67 | return _hashSet.GetEnumerator(); 68 | } 69 | 70 | ~ConcurrentHashSet() 71 | { 72 | Dispose(false); 73 | } 74 | 75 | public void OnDeserialization(object sender) 76 | { 77 | _hashSet.OnDeserialization(sender); 78 | } 79 | 80 | public void GetObjectData(SerializationInfo info, StreamingContext context) 81 | { 82 | _hashSet.GetObjectData(info, context); 83 | } 84 | 85 | IEnumerator IEnumerable.GetEnumerator() 86 | { 87 | return GetEnumerator(); 88 | } 89 | 90 | #endregion 91 | 92 | public void Add(T item) 93 | { 94 | _lock.EnterWriteLock(); 95 | try 96 | { 97 | _hashSet.Add(item); 98 | } 99 | finally 100 | { 101 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 102 | } 103 | } 104 | 105 | public void UnionWith(IEnumerable other) 106 | { 107 | _lock.EnterWriteLock(); 108 | _lock.EnterReadLock(); 109 | try 110 | { 111 | _hashSet.UnionWith(other); 112 | } 113 | finally 114 | { 115 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 116 | if (_lock.IsReadLockHeld) _lock.ExitReadLock(); 117 | } 118 | } 119 | 120 | public void IntersectWith(IEnumerable other) 121 | { 122 | _lock.EnterWriteLock(); 123 | _lock.EnterReadLock(); 124 | try 125 | { 126 | _hashSet.IntersectWith(other); 127 | } 128 | finally 129 | { 130 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 131 | if (_lock.IsReadLockHeld) _lock.ExitReadLock(); 132 | } 133 | } 134 | 135 | public void ExceptWith(IEnumerable other) 136 | { 137 | _lock.EnterWriteLock(); 138 | _lock.EnterReadLock(); 139 | try 140 | { 141 | _hashSet.ExceptWith(other); 142 | } 143 | finally 144 | { 145 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 146 | if (_lock.IsReadLockHeld) _lock.ExitReadLock(); 147 | } 148 | } 149 | 150 | public void SymmetricExceptWith(IEnumerable other) 151 | { 152 | _lock.EnterWriteLock(); 153 | try 154 | { 155 | _hashSet.SymmetricExceptWith(other); 156 | } 157 | finally 158 | { 159 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 160 | } 161 | } 162 | 163 | public bool IsSubsetOf(IEnumerable other) 164 | { 165 | _lock.EnterWriteLock(); 166 | try 167 | { 168 | return _hashSet.IsSubsetOf(other); 169 | } 170 | finally 171 | { 172 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 173 | } 174 | } 175 | 176 | public bool IsSupersetOf(IEnumerable other) 177 | { 178 | _lock.EnterWriteLock(); 179 | try 180 | { 181 | return _hashSet.IsSupersetOf(other); 182 | } 183 | finally 184 | { 185 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 186 | } 187 | } 188 | 189 | public bool IsProperSupersetOf(IEnumerable other) 190 | { 191 | _lock.EnterWriteLock(); 192 | try 193 | { 194 | return _hashSet.IsProperSupersetOf(other); 195 | } 196 | finally 197 | { 198 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 199 | } 200 | } 201 | 202 | public bool IsProperSubsetOf(IEnumerable other) 203 | { 204 | _lock.EnterWriteLock(); 205 | try 206 | { 207 | return _hashSet.IsProperSubsetOf(other); 208 | } 209 | finally 210 | { 211 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 212 | } 213 | } 214 | 215 | public bool Overlaps(IEnumerable other) 216 | { 217 | _lock.EnterWriteLock(); 218 | try 219 | { 220 | return _hashSet.Overlaps(other); 221 | } 222 | finally 223 | { 224 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 225 | } 226 | } 227 | 228 | public bool SetEquals(IEnumerable other) 229 | { 230 | _lock.EnterWriteLock(); 231 | try 232 | { 233 | return _hashSet.SetEquals(other); 234 | } 235 | finally 236 | { 237 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 238 | } 239 | } 240 | 241 | bool ISet.Add(T item) 242 | { 243 | _lock.EnterWriteLock(); 244 | try 245 | { 246 | return _hashSet.Add(item); 247 | } 248 | finally 249 | { 250 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 251 | } 252 | } 253 | 254 | public void Clear() 255 | { 256 | _lock.EnterWriteLock(); 257 | try 258 | { 259 | _hashSet.Clear(); 260 | } 261 | finally 262 | { 263 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 264 | } 265 | } 266 | 267 | public bool Contains(T item) 268 | { 269 | _lock.EnterWriteLock(); 270 | try 271 | { 272 | return _hashSet.Contains(item); 273 | } 274 | finally 275 | { 276 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 277 | } 278 | } 279 | 280 | public void CopyTo(T[] array, int arrayIndex) 281 | { 282 | _lock.EnterWriteLock(); 283 | try 284 | { 285 | _hashSet.CopyTo(array, arrayIndex); 286 | } 287 | finally 288 | { 289 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 290 | } 291 | } 292 | 293 | public bool Remove(T item) 294 | { 295 | _lock.EnterWriteLock(); 296 | try 297 | { 298 | return _hashSet.Remove(item); 299 | } 300 | finally 301 | { 302 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 303 | } 304 | } 305 | 306 | public int Count 307 | { 308 | get 309 | { 310 | _lock.EnterWriteLock(); 311 | try 312 | { 313 | return _hashSet.Count; 314 | } 315 | finally 316 | { 317 | if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); 318 | } 319 | 320 | } 321 | } 322 | 323 | public bool IsReadOnly 324 | { 325 | get { return false; } 326 | } 327 | } 328 | 329 | } 330 | -------------------------------------------------------------------------------- /Elephanet/Conventions/EntityNotFoundBehavior.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 Elephanet.Conventions 8 | { 9 | /// 10 | /// Enum to control the behavior of IDocumentSession.GetById 11 | /// 12 | public enum EntityNotFoundBehavior 13 | { 14 | /// 15 | /// When Entity is not found by Id, throw an EntityNotFoundException. Default behavior. 16 | /// 17 | Throw = 0, 18 | 19 | /// 20 | /// When Entity is not found by Id, return null 21 | /// 22 | ReturnNull = 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Elephanet/DocumentSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Npgsql; 4 | using System.Linq; 5 | using System.Data; 6 | using Elephanet.Serialization; 7 | using System.Text; 8 | using Elephanet.Conventions; 9 | using Elephanet.Helpers; 10 | 11 | 12 | /* 13 | *implement fts on all string columns with a query like the following... 14 | * 15 | * select body from elephanet_demo_toilet 16 | where lower(cast(body as text)) like '%sandy%point%victoria%' 17 | and similarity(lower(cast(body as text)), '%sandy%point%victoria%') > 0 18 | order by similarity(lower(cast(body as text)), '%sandy%point%victoria%') desc 19 | limit 20; 20 | * 21 | * 22 | * needs an index like 23 | * 24 | * CREATE INDEX toilet_search_idx ON elephanet_demo_toilet USING gin (lower(cast(body as text)) gin_trgm_ops) 25 | drop index toilet_search_idx; 26 | */ 27 | 28 | namespace Elephanet 29 | { 30 | public class DocumentSession : IDocumentSession 31 | { 32 | private readonly IDocumentStore _documentStore; 33 | private NpgsqlConnection _conn; 34 | protected readonly Dictionary _entities = new Dictionary(); 35 | readonly IJsonConverter _jsonConverter; 36 | private JsonbQueryProvider _queryProvider; 37 | private ITableInfo _tableInfo; 38 | 39 | 40 | public DocumentSession(IDocumentStore documentStore) 41 | { 42 | _documentStore = documentStore; 43 | _tableInfo = _documentStore.Conventions.TableInfo; 44 | _conn = new NpgsqlConnection(documentStore.ConnectionString); 45 | _conn.Open(); 46 | _jsonConverter = documentStore.Conventions.JsonConverter; 47 | _queryProvider = new JsonbQueryProvider(_conn, _jsonConverter, _tableInfo); 48 | } 49 | 50 | public void Delete(Guid id) 51 | { 52 | GetOrCreateTable(typeof(T)); 53 | using (var command = _conn.CreateCommand()) 54 | { 55 | command.CommandType = CommandType.Text; 56 | command.CommandText = String.Format(@"Delete FROM {0} WHERE id = :id;", _tableInfo.TableNameWithSchema(typeof(T))); 57 | command.Parameters.AddWithValue(":id", id); 58 | command.ExecuteNonQuery(); 59 | } 60 | } 61 | 62 | public void DeleteAll() 63 | { 64 | GetOrCreateTable(typeof(T)); 65 | using (var command = _conn.CreateCommand()) 66 | { 67 | command.CommandType = CommandType.Text; 68 | command.CommandText = String.Format(@"DELETE FROM {0};", _tableInfo.TableNameWithSchema(typeof(T))); 69 | command.ExecuteNonQuery(); 70 | } 71 | 72 | } 73 | 74 | public T LoadInternal(Guid id) 75 | { 76 | 77 | GetOrCreateTable(typeof(T)); 78 | using (var command = _conn.CreateCommand()) 79 | { 80 | command.CommandType = CommandType.Text; 81 | command.CommandText = String.Format(@"SELECT body FROM {0} WHERE id = :id LIMIT 1;", _tableInfo.TableNameWithSchema(typeof(T))); 82 | 83 | command.Parameters.AddWithValue(":id", id); 84 | 85 | using (var reader = command.ExecuteReader()) 86 | { 87 | if (reader.Read()) 88 | { 89 | return _jsonConverter.Deserialize(reader.GetString(0)); 90 | } 91 | 92 | return default(T); 93 | } 94 | } 95 | } 96 | 97 | public IJsonbQueryable Query() 98 | { 99 | IJsonbQueryable query = new JsonbQueryable(new JsonbQueryProvider(_conn, _jsonConverter, _tableInfo)); 100 | return query; 101 | } 102 | 103 | public void SaveChanges() 104 | { 105 | //save the cache out to the db 106 | SaveInternal(); 107 | } 108 | 109 | HashSet> MatchEntityToFinalTableAndTemporaryTable(Dictionary entities) 110 | { 111 | HashSet> typeToTableMap = new HashSet>(); 112 | 113 | var types = entities.Values.Select(v => v.GetType()).Distinct(); 114 | foreach (Type type in types) 115 | { 116 | typeToTableMap.Add(new Tuple(type, _tableInfo.TableNameWithSchema(type), Guid.NewGuid().ToString())); 117 | } 118 | 119 | return typeToTableMap; 120 | } 121 | 122 | void SaveInternal() 123 | { 124 | StringBuilder sb = new StringBuilder(); 125 | 126 | HashSet> matches = MatchEntityToFinalTableAndTemporaryTable(_entities); 127 | 128 | 129 | foreach (var item in _entities) 130 | { 131 | //make sure we have tables for all types 132 | GetOrCreateTable(item.Value.GetType()); 133 | } 134 | 135 | sb.Append("BEGIN;"); 136 | foreach (var match in matches) 137 | { 138 | sb.Append(string.Format("CREATE TEMPORARY TABLE \"{0}\" (id uuid, body jsonb);", match.Item3)); 139 | } 140 | 141 | foreach (var item in _entities) 142 | { 143 | sb.Append(string.Format("INSERT INTO \"{0}\" (id, body) VALUES ('{1}', '{2}');", matches.Where(c => c.Item1 == item.Value.GetType()).Select(j => j.Item3).First(), item.Key, _jsonConverter.Serialize(item.Value).EscapeQuotes())); 144 | } 145 | 146 | foreach (var match in matches) 147 | { 148 | sb.Append(string.Format("LOCK TABLE {0} IN EXCLUSIVE MODE;", match.Item2)); 149 | sb.Append(string.Format("UPDATE {0} SET body = tmp.body from \"{1}\" tmp where tmp.id = {0}.id;", match.Item2, match.Item3)); 150 | sb.Append(string.Format("INSERT INTO {0} SELECT tmp.id, tmp.body from \"{1}\" tmp LEFT OUTER JOIN {0} ON ({0}.id = tmp.id) where {0}.id IS NULL;", match.Item2, match.Item3)); 151 | } 152 | 153 | 154 | sb.Append("COMMIT;"); 155 | 156 | using (var command = _conn.CreateCommand()) 157 | { 158 | command.CommandTimeout = 60; 159 | command.CommandType = CommandType.Text; 160 | command.CommandText = sb.ToString(); 161 | command.ExecuteNonQuery(); 162 | } 163 | 164 | _entities.Clear(); 165 | } 166 | 167 | public void Store(T entity) 168 | { 169 | var id = IdentityFactory.SetEntityId(entity); 170 | _entities[id] = entity; 171 | } 172 | 173 | private bool IndexDoesNotExist(Type type) 174 | { 175 | using (var command = _conn.CreateCommand()) 176 | { 177 | command.CommandType = CommandType.Text; 178 | command.CommandText = string.Format(@"select count(*) 179 | from pg_indexes 180 | where schemaname = '{0}' 181 | and tablename = '{1}' 182 | and indexname = 'idx_{1}_body';", _tableInfo.Schema, _tableInfo.TableNameWithoutSchema(type)); 183 | var indexCount = (Int64)command.ExecuteScalar(); 184 | return indexCount == 0; 185 | } 186 | 187 | } 188 | private void CreateIndex(Type type) 189 | { 190 | if (IndexDoesNotExist(type)) 191 | { 192 | using (var command = _conn.CreateCommand()) 193 | { 194 | command.CommandType = CommandType.Text; 195 | command.CommandText = string.Format(@"CREATE INDEX idx_{0}_body ON {0} USING gin (body);", _tableInfo.TableNameWithoutSchema(type)); 196 | command.ExecuteNonQuery(); 197 | } 198 | } 199 | } 200 | 201 | 202 | private void GetOrCreateTable(Type type) 203 | { 204 | if (!_documentStore.StoreInfo.Tables.Contains(_tableInfo.TableNameWithSchema(type))) 205 | { 206 | _documentStore.StoreInfo.Tables.Add(_tableInfo.TableNameWithSchema(type)); 207 | try 208 | { 209 | using (var command = _conn.CreateCommand()) 210 | { 211 | command.CommandType = CommandType.Text; 212 | command.CommandText = String.Format(@" 213 | CREATE TABLE IF NOT EXISTS {0} 214 | ( 215 | id uuid NOT NULL, 216 | body jsonb NOT NULL, 217 | created timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'), 218 | CONSTRAINT pk_{1} PRIMARY KEY (id) 219 | );", _tableInfo.TableNameWithSchema(type), _tableInfo.TableNameWithoutSchema(type)); 220 | command.ExecuteNonQuery(); 221 | } 222 | } 223 | catch (Exception exception) 224 | { 225 | throw new Exception(String.Format("Could not create table {0}; see the inner exception for more information.", _tableInfo.TableNameWithSchema(type)), exception); 226 | } 227 | try 228 | { 229 | CreateIndex(type); 230 | } 231 | catch (Exception exception) 232 | { 233 | throw new Exception(String.Format("Could not create index on table {0}; see the inner exception for more information.", _tableInfo.TableNameWithSchema(type)), exception); 234 | } 235 | } 236 | } 237 | 238 | public void Dispose() 239 | { 240 | _conn.Close(); 241 | } 242 | 243 | 244 | public void Delete(T entity) 245 | { 246 | throw new NotImplementedException(); 247 | } 248 | 249 | public T GetById(Guid id) 250 | { 251 | GetOrCreateTable(typeof(T)); 252 | //hit the db first, so we get most up-to-date 253 | var entity = LoadInternal(id); 254 | //try the cache just incase hasn't been saved to db yet, but is in session 255 | if ((entity == null) && _entities.ContainsKey(id)) 256 | entity = (T)_entities[id]; 257 | 258 | if (entity == null) 259 | { 260 | if (_documentStore.Conventions.EntityNotFoundBehavior == EntityNotFoundBehavior.ReturnNull) 261 | { 262 | return default(T); 263 | } 264 | 265 | throw new EntityNotFoundException(id, typeof (T)); 266 | } 267 | return entity; 268 | } 269 | 270 | public IEnumerable GetByIds(IEnumerable ids) 271 | { 272 | 273 | GetOrCreateTable(typeof(T)); 274 | using (var command = _conn.CreateCommand()) 275 | { 276 | command.CommandType = CommandType.Text; 277 | command.CommandText = String.Format(@"SELECT body FROM {0} WHERE id in ({1});", _tableInfo.TableNameWithSchema(typeof(T)), JoinAndCommaSeperateAndSurroundWithSingleQuotes(ids)); 278 | Console.WriteLine(command.CommandText); 279 | 280 | List entities = new List(); 281 | 282 | using (var reader = command.ExecuteReader()) 283 | { 284 | while (reader.Read()) 285 | { 286 | T entity = _jsonConverter.Deserialize(reader.GetString(0)); 287 | entities.Add(entity); 288 | } 289 | } 290 | return entities; 291 | } 292 | } 293 | 294 | private string JoinAndCommaSeperateAndSurroundWithSingleQuotes(IEnumerable ids) 295 | { 296 | return string.Join(",", ids.Select(n => n.ToString().SurroundWithSingleQuote()).ToArray()); 297 | } 298 | 299 | public IEnumerable GetAll() 300 | { 301 | GetOrCreateTable(typeof(T)); 302 | using (var command = _conn.CreateCommand()) 303 | { 304 | command.CommandType = CommandType.Text; 305 | command.CommandText = String.Format(@"SELECT body FROM {0};", _tableInfo.TableNameWithSchema(typeof(T))); 306 | Console.WriteLine(command.CommandText); 307 | 308 | List entities = new List(); 309 | 310 | using (var reader = command.ExecuteReader()) 311 | { 312 | while (reader.Read()) 313 | { 314 | T entity = _jsonConverter.Deserialize(reader.GetString(0)); 315 | entities.Add(entity); 316 | } 317 | } 318 | return entities; 319 | } 320 | } 321 | 322 | 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /Elephanet/Elephanet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F6F93A0A-0EB1-47A5-9089-DB6F56369F98} 8 | Library 9 | Properties 10 | Elephanet 11 | Elephanet 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Jil.2.8.1\lib\net45\Jil.dll 35 | 36 | 37 | ..\packages\Npgsql.2.2.4.3\lib\net45\Mono.Security.dll 38 | 39 | 40 | ..\packages\Npgsql.2.2.4.3\lib\net45\Npgsql.dll 41 | 42 | 43 | ..\packages\Sigil.4.4.0\lib\net45\Sigil.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 101 | -------------------------------------------------------------------------------- /Elephanet/Elephanet.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Elephanet 5 | $version$ 6 | Elephanet 7 | James Kelly 8 | James Kelly 9 | https://github.com/YoloDev/elephanet/blob/master/LICENCE 10 | https://github.com/YoloDev/elephanet 11 | false 12 | A .NET api for PostgreSQLs jsonb that is easy to use 13 | postgresql jsonb npgsql 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Elephanet/EntityException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | namespace Elephanet 4 | { 5 | public class EntityException : Exception 6 | { 7 | public EntityException() 8 | : this(String.Empty, null) { } 9 | 10 | public EntityException(string message) 11 | : this(message, null) { } 12 | 13 | public EntityException(string message, Exception innerException) 14 | : base(message, innerException) { } 15 | 16 | public EntityException(SerializationInfo info, StreamingContext context) 17 | : base(info, context) { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Elephanet/EntityNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet 4 | { 5 | public class EntityNotFoundException : Exception 6 | { 7 | public EntityNotFoundException(Guid id, Type type) 8 | : base(string.Format("Entity of type {0} with id {1} could not be found.", type, id)) 9 | { 10 | 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Elephanet/Expressions/ExpressionEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | 7 | namespace Elephanet 8 | { 9 | // http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx 10 | 11 | public static class ExpressionEvaluator 12 | { 13 | public static Expression EvaluateSubtrees(Expression expression, Func canBeEvaluated) 14 | { 15 | return new SubtreeEvaluator(canBeEvaluated).Evaluate(expression); 16 | } 17 | 18 | public static Expression EvaluateSubtrees(Expression expression) 19 | { 20 | return new SubtreeEvaluator(CanBeEvaluatedLocally).Evaluate(expression); 21 | } 22 | 23 | private static bool CanBeEvaluatedLocally(Expression expression) 24 | { 25 | return expression.NodeType != ExpressionType.Parameter; 26 | } 27 | 28 | private sealed class SubtreeEvaluator : ExpressionVisitor 29 | { 30 | private readonly SubtreeNominator _nominator; 31 | private HashSet _candidates; 32 | 33 | internal SubtreeEvaluator(Func canBeEvaluated) 34 | { 35 | _nominator = new SubtreeNominator(canBeEvaluated); 36 | } 37 | 38 | internal Expression Evaluate(Expression node) 39 | { 40 | _candidates = _nominator.NominateSubtrees(node); 41 | 42 | return Visit(node); 43 | } 44 | 45 | public override Expression Visit(Expression node) 46 | { 47 | if (node == null) 48 | { 49 | return node; 50 | } 51 | else if (_candidates.Contains(node)) 52 | { 53 | return TryEvaluateCandidate(node); 54 | } 55 | else 56 | { 57 | return base.Visit(node); 58 | } 59 | } 60 | 61 | private static Expression TryEvaluateCandidate(Expression node) 62 | { 63 | return node.NodeType == ExpressionType.Constant ? node : EvaluateCandidate(node); 64 | } 65 | 66 | private static Expression EvaluateCandidate(Expression node) 67 | { 68 | var lambda = Expression.Lambda(node); 69 | 70 | var function = lambda.Compile(); 71 | 72 | return Expression.Constant(function.DynamicInvoke(null), node.Type); 73 | } 74 | } 75 | 76 | private sealed class SubtreeNominator : ExpressionVisitor 77 | { 78 | private readonly Func _canBeEvaluated; 79 | private HashSet _candidates; 80 | private bool _cannotBeEvaluated; 81 | 82 | internal SubtreeNominator(Func canBeEvaluated) 83 | { 84 | _canBeEvaluated = canBeEvaluated; 85 | } 86 | 87 | internal HashSet NominateSubtrees(Expression node) 88 | { 89 | _candidates = new HashSet(); 90 | 91 | Visit(node); 92 | 93 | return _candidates; 94 | } 95 | 96 | public override Expression Visit(Expression node) 97 | { 98 | if (node != null) 99 | { 100 | TryNominateSubtree(node); 101 | } 102 | 103 | return node; 104 | } 105 | 106 | private void TryNominateSubtree(Expression node) 107 | { 108 | var priorCannotBeEvaluated = _cannotBeEvaluated; 109 | 110 | _cannotBeEvaluated = false; 111 | 112 | base.Visit(node); 113 | 114 | if (!_cannotBeEvaluated) 115 | { 116 | if (_canBeEvaluated(node)) 117 | { 118 | _candidates.Add(node); 119 | } 120 | else 121 | { 122 | _cannotBeEvaluated = true; 123 | } 124 | } 125 | 126 | _cannotBeEvaluated |= priorCannotBeEvaluated; 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Elephanet/Expressions/JsonbExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace Elephanet.Expressions 7 | { 8 | 9 | 10 | 11 | public class JsonbExpression : Expression 12 | { 13 | Expression _jsonbTable; 14 | List _jsonbPaths; 15 | 16 | public JsonbExpression(ExpressionType expressionType, Type type) 17 | : base((ExpressionType)expressionType, type) 18 | { 19 | 20 | } 21 | } 22 | 23 | public class JsonbTableExpression: JsonbExpression 24 | { 25 | private string _name; 26 | public JsonbTableExpression(Type type) 27 | : base((ExpressionType)JsonbExpressionType.JsonbTable, type) 28 | { 29 | _name = string.Format("{0}_{1}", type.Namespace, type.Name); 30 | } 31 | 32 | public string Name { get { return _name; } } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Elephanet/Expressions/JsonbExpressionType.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet.Expressions 2 | { 3 | internal enum JsonbExpressionType 4 | { 5 | JsonbTable = 1000, 6 | JsonbPath, 7 | JsonbProperty 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Elephanet/Expressions/JsonbExpressionVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Npgsql; 6 | using System.Data; 7 | 8 | namespace Elephanet.Expressions 9 | { 10 | public class JsonbExpressionVisitor : ExpressionVisitor 11 | { 12 | private NpgsqlCommand _command; 13 | 14 | public JsonbExpressionVisitor() 15 | { 16 | _command = new NpgsqlCommand(); 17 | } 18 | 19 | public NpgsqlCommand Command { get { return _command; } } 20 | 21 | protected override Expression VisitExtension(Expression node) 22 | { 23 | var jsonbNode = node as JsonbExpression; 24 | return jsonbNode == null ? node : VisitJsonb(jsonbNode); 25 | } 26 | 27 | protected virtual Expression VisitSql(SqlExpression node) 28 | { 29 | _command = node.Query.Command; 30 | _command.CommandType = CommandType.Text; 31 | return node; 32 | } 33 | 34 | protected virtual Expression VisitJsonb(JsonbExpression node) 35 | { 36 | // switch (node.JsonbType) 37 | // { 38 | // case JsonbType.Type1: 39 | // return VisitJsonbType1((JsonbType1)node); 40 | // case JsonbType.Type2: 41 | // return VisitJsonbType2((JsonbType2)node); 42 | // default: 43 | return node; 44 | // } 45 | } 46 | 47 | // protected virtual Expression VisitJsonbType1(JsonbType1 node) 48 | // { 49 | // Visit node parts 50 | // } 51 | 52 | // protected virtual Expression VisitJsonbType2(JsonbType2 node) 53 | // { 54 | // Visit node parts 55 | // } 56 | 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Elephanet/Expressions/JsonbTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace Elephanet 7 | { 8 | public class JsonbTable 9 | { 10 | private string _name; 11 | public JsonbTable(Type type, Expression expression) 12 | { 13 | _name = string.Format("@0_@1", type.Namespace, type.Name); 14 | } 15 | 16 | public string Name { get { return _name; } } 17 | } 18 | 19 | public class JsonbPath 20 | { 21 | private string _name; 22 | public JsonbPath(string name, Expression expression) 23 | { 24 | _name = name; 25 | } 26 | 27 | public string Name { get { return _name; } } 28 | 29 | } 30 | 31 | public class JsonbValue 32 | { 33 | private string _value; 34 | private Expression _expression; 35 | public JsonbValue(string value, Expression expression) 36 | { 37 | _value = value; 38 | _expression = expression; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Elephanet/Expressions/SelectExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace Elephanet.Expressions 7 | { 8 | public class SelectExpression : Expression 9 | { 10 | private Expression _from; 11 | private Expression _where; 12 | public SelectExpression(Type type, Expression from, Expression where) 13 | { 14 | _from = from; 15 | _where = where; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Elephanet/Expressions/SqlExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Npgsql; 6 | 7 | namespace Elephanet.Expressions 8 | { 9 | public class SqlExpression : Expression 10 | { 11 | private Sql _query; 12 | 13 | public SqlExpression(Sql query) : base() { 14 | _query = query; 15 | } 16 | 17 | public Sql Query { get { return _query; } } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Elephanet/Expressions/SqlExpressionType.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 Elephanet.Expressions 8 | { 9 | internal enum SqlExpressionType 10 | { 11 | SqlQuery 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Elephanet/Extensions/QueryExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using Elephanet.Expressions; 7 | using Npgsql; 8 | 9 | namespace Elephanet 10 | { 11 | 12 | public static class StringExtension 13 | { 14 | public static string ReplaceDotWithUnderscore(this string text) 15 | { 16 | text = text.Replace(".", "_"); 17 | return text; 18 | } 19 | 20 | public static string SurroundWith(this string text, string ends) 21 | { 22 | return ends + text + ends; 23 | } 24 | 25 | public static string SurroundWithSingleQuote(this string text) 26 | { 27 | return SurroundWith(text, "'"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Elephanet/IDocumentSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Elephanet 5 | { 6 | public interface IDocumentSession : IDisposable 7 | { 8 | void Delete(Guid id); 9 | void Delete(T entity); 10 | T GetById(Guid id); 11 | IEnumerable GetByIds(IEnumerable ids); 12 | IEnumerable GetAll(); 13 | IJsonbQueryable Query(); 14 | void SaveChanges(); 15 | void Store(T entity); 16 | void DeleteAll(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Elephanet/IStore.cs: -------------------------------------------------------------------------------- 1 | namespace Elephanet 2 | { 3 | public interface IDocumentStore 4 | { 5 | IDocumentSession OpenSession(); 6 | string ConnectionString { get; } 7 | IStoreConventions Conventions { get; } 8 | IStoreInfo StoreInfo { get; } 9 | void Empty(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Elephanet/IStoreConventions.cs: -------------------------------------------------------------------------------- 1 | using Elephanet.Conventions; 2 | using Elephanet.Serialization; 3 | namespace Elephanet 4 | { 5 | public interface IStoreConventions 6 | { 7 | IJsonConverter JsonConverter { get; } 8 | ITableInfo TableInfo { get; } 9 | EntityNotFoundBehavior EntityNotFoundBehavior { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Elephanet/IStoreInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Elephanet 5 | { 6 | public interface IStoreInfo 7 | { 8 | /// 9 | /// Using custom ConcurrentHashSet. This may be a micro optimisation over using ConcurrentDictionary / ConcurrentList 10 | /// which should be switched out at the first sign of issues. 11 | /// 12 | string Name { get; } 13 | ConcurrentHashSet Tables { get; } 14 | void Add(string tableName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Elephanet/ITableInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet 4 | { 5 | /// 6 | /// Provides the flexibility to inject and control table naming behavior 7 | /// 8 | public interface ITableInfo 9 | { 10 | /// 11 | /// Return the table name for the given Type with its Schema 12 | /// 13 | /// The Type mapped to to the table 14 | /// Table name 15 | string TableNameWithSchema(Type type); 16 | 17 | /// 18 | /// Return the table name for the given Type without its Schema 19 | /// 20 | /// The Type mapped to the table 21 | /// Table name 22 | string TableNameWithoutSchema(Type type); 23 | 24 | /// 25 | /// The PostgreSql schema that all tables will be created in 26 | /// 27 | string Schema { get; } 28 | } 29 | } -------------------------------------------------------------------------------- /Elephanet/IdentityFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Elephanet 8 | { 9 | internal static class IdentityFactory 10 | { 11 | private static readonly ConcurrentDictionary _keyCache = 12 | new ConcurrentDictionary(); 13 | 14 | public static Guid SetEntityId(object value) 15 | { 16 | PropertyInfo propertyInfo; 17 | if (TryGetIdProperty(value, out propertyInfo) == false) 18 | { 19 | throw new EntityException("Entity does not have an ID property of type Guid."); 20 | } 21 | 22 | try 23 | { 24 | var id = (Guid) propertyInfo.GetValue(value, null); 25 | if (id != Guid.Empty) 26 | { 27 | return id; 28 | } 29 | 30 | id = Guid.NewGuid(); 31 | 32 | propertyInfo.SetValue(value, id, null); 33 | return id; 34 | } 35 | catch (Exception exception) 36 | { 37 | throw new EntityException("Could not get or set the ID property of the entity.", exception); 38 | } 39 | } 40 | 41 | public static Guid GetEntityId(object value) 42 | { 43 | PropertyInfo propertyInfo; 44 | if (TryGetIdProperty(value, out propertyInfo) == false) 45 | { 46 | throw new EntityException("Entity does not have an ID property of type Guid."); 47 | } 48 | 49 | try 50 | { 51 | return (Guid) propertyInfo.GetValue(value, null); 52 | } 53 | catch (Exception exception) 54 | { 55 | throw new EntityException("Could not get the ID property of the entity.", exception); 56 | } 57 | } 58 | 59 | private static bool TryGetIdProperty(object value, out PropertyInfo propertyInfo) 60 | { 61 | var type = value.GetType(); 62 | 63 | propertyInfo = _keyCache.GetOrAdd(type, 64 | typeofValue => typeofValue.GetProperties(BindingFlags.Public | BindingFlags.Instance) 65 | .Where(x => x.Name.Equals("id", StringComparison.OrdinalIgnoreCase)) 66 | .Where(x => x.PropertyType == typeof (Guid)) 67 | .FirstOrDefault(x => x.CanRead && x.CanWrite)); 68 | 69 | return (propertyInfo != null); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Elephanet/JsonbQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using Npgsql; 8 | using System.Data; 9 | using System.Text; 10 | 11 | using Elephanet.Expressions; 12 | using Elephanet.Serialization; 13 | 14 | namespace Elephanet 15 | { 16 | public interface IJsonbQueryProvider : IQueryProvider 17 | { 18 | NpgsqlConnection Connection {get;} 19 | IJsonConverter JsonConverter { get; } 20 | } 21 | 22 | public class JsonbQueryProvider : IJsonbQueryProvider 23 | { 24 | private readonly NpgsqlConnection _conn; 25 | private readonly IJsonConverter _jsonConverter; 26 | private QueryTranslator _translator; 27 | 28 | public JsonbQueryProvider(NpgsqlConnection connection, IJsonConverter jsonConverter, ITableInfo tableInfo) 29 | { 30 | _conn = connection; 31 | _jsonConverter = jsonConverter; 32 | _translator = new QueryTranslator(tableInfo); 33 | } 34 | 35 | public NpgsqlConnection Connection { get { return _conn; } } 36 | public IJsonConverter JsonConverter { get { return _jsonConverter; } } 37 | 38 | public object Execute(Expression expression) 39 | { 40 | string sql = _translator.Translate(expression); 41 | using (var command = new NpgsqlCommand(sql,_conn)) 42 | { 43 | Type elementType = TypeSystem.GetElementType(expression.Type); 44 | Type listType = typeof(List<>).MakeGenericType(elementType); 45 | var list = (IList)Activator.CreateInstance(listType); 46 | 47 | using (var reader = command.ExecuteReader()) 48 | { 49 | while (reader.Read()) 50 | { 51 | object entity = _jsonConverter.Deserialize(reader.GetString(0), elementType); 52 | list.Add(entity); 53 | } 54 | } 55 | 56 | return list; 57 | 58 | } 59 | 60 | } 61 | 62 | T IQueryProvider.Execute(Expression expression) 63 | { 64 | return (T)Execute(expression); 65 | } 66 | 67 | object IQueryProvider.Execute(Expression expression) 68 | { 69 | return Execute(expression); 70 | } 71 | 72 | 73 | IQueryable IQueryProvider.CreateQuery(Expression expression) 74 | { 75 | return new JsonbQueryable(this, expression); 76 | } 77 | 78 | IQueryable IQueryProvider.CreateQuery(Expression expression) 79 | { 80 | Type elementType = TypeSystem.GetElementType(expression.Type); 81 | try 82 | { 83 | return (IJsonbQueryable)Activator.CreateInstance(typeof(JsonbQueryable<>).MakeGenericType(elementType), new object[] { this, expression }); 84 | } 85 | catch (TargetInvocationException tie) 86 | { 87 | throw tie.InnerException; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Elephanet/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoloDev/elephanet/3816548ceb7342391b8611d3da34330089452936/Elephanet/NuGet.exe -------------------------------------------------------------------------------- /Elephanet/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("Elephanet")] 9 | [assembly: AssemblyDescription("A .NET api to PostgreSQL's jsonb that's easy to use")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("James Kelly")] 12 | [assembly: AssemblyProduct("Elephanet")] 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("65b6d608-9e5f-4eed-b765-ad6191d65bcb")] 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("0.1.2")] 36 | [assembly: AssemblyFileVersion("0.1.2")] 37 | -------------------------------------------------------------------------------- /Elephanet/QueryTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace Elephanet 9 | { 10 | 11 | 12 | public class QueryTranslator : ExpressionVisitor 13 | { 14 | const string columnName = "body"; 15 | private StringBuilder _sb; 16 | private ITableInfo _tableInfo; 17 | 18 | private Expression _where; 19 | private StringBuilder _limit; 20 | private StringBuilder _offset; 21 | private StringBuilder _orderBy; 22 | 23 | public QueryTranslator(ITableInfo tableInfo) 24 | { 25 | _sb = new StringBuilder(); 26 | _limit = new StringBuilder(); 27 | _offset = new StringBuilder(); 28 | _orderBy = new StringBuilder(); 29 | _tableInfo = tableInfo; 30 | } 31 | 32 | public string Translate(Expression expression) 33 | { 34 | var inlined = ExpressionEvaluator.EvaluateSubtrees(expression); 35 | Visit(inlined); 36 | 37 | _sb.Append(_orderBy); 38 | _sb.Append(_limit); 39 | _sb.Append(_offset); 40 | _sb.Append(";"); 41 | Console.WriteLine(_sb.ToString()); 42 | return _sb.ToString(); 43 | } 44 | 45 | private static Expression StripQuotes(Expression e) 46 | { 47 | while (e.NodeType == ExpressionType.Quote) 48 | { 49 | e = ((UnaryExpression)e).Operand; 50 | } 51 | return e; 52 | } 53 | 54 | protected override Expression VisitMethodCall(MethodCallExpression node) 55 | { 56 | if (node.Method.DeclaringType == typeof(Queryable)) 57 | { 58 | switch (node.Method.Name) 59 | { 60 | case "Where": 61 | { 62 | Type elementType = TypeSystem.GetElementType(node.Type); 63 | _sb.Append(string.Format("select {0} from {1} where {0} ", columnName, _tableInfo.TableNameWithSchema(elementType))); 64 | //Visit(node.Arguments[0]); 65 | LambdaExpression lambda = (LambdaExpression)StripQuotes(node.Arguments[1]); 66 | Visit(lambda.Body); 67 | return node; 68 | } 69 | case "Take": 70 | { 71 | _limit.Append(" limit "); 72 | VisitLimit((ConstantExpression)node.Arguments[1]); 73 | VisitMethodCall((MethodCallExpression)node.Arguments[0]); 74 | return node; 75 | } 76 | case "Skip": 77 | { 78 | _offset.Append(" offset "); 79 | VisitOffset((ConstantExpression)node.Arguments[1]); 80 | VisitMethodCall((MethodCallExpression)node.Arguments[0]); 81 | return node; 82 | 83 | } 84 | case "OrderBy": 85 | { 86 | Type elementType = TypeSystem.GetElementType(node.Type); 87 | LambdaExpression lambda = (LambdaExpression)StripQuotes(node.Arguments[1]); 88 | VisitOrderBy((MemberExpression)lambda.Body); 89 | VisitMethodCall((MethodCallExpression)node.Arguments[0]); 90 | return node; 91 | } 92 | case "OrderByDescending": 93 | { 94 | Type elementType = TypeSystem.GetElementType(node.Type); 95 | LambdaExpression lambda = (LambdaExpression)StripQuotes(node.Arguments[1]); 96 | VisitOrderByDesc((MemberExpression)lambda.Body); 97 | VisitMethodCall((MethodCallExpression)node.Arguments[0]); 98 | return node; 99 | 100 | } 101 | 102 | 103 | } 104 | } 105 | throw new NotSupportedException(string.Format("The method '{0}' is not supported", node.Method.Name)); 106 | } 107 | 108 | private Expression VisitOrderByDesc(MemberExpression node) 109 | { 110 | if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter) 111 | { 112 | _orderBy.Append(string.Format(" order by body->>'{0}' desc", node.Member.Name)); 113 | return node; 114 | } 115 | 116 | throw new NotSupportedException(string.Format("The member '{0}' is not supported from the OrderByDescending operator", node.Member.Name)); 117 | } 118 | 119 | protected override Expression VisitBinary(BinaryExpression node) 120 | { 121 | switch (node.NodeType) { 122 | case ExpressionType.Equal: 123 | _sb.Append("@>"); 124 | break; 125 | default: 126 | throw new NotSupportedException(string.Format("The operator '{0}' is not yet supported", node.NodeType)); 127 | } 128 | //wrap up values in json 129 | _sb.Append("'{"); 130 | Visit(node.Left); 131 | _sb.Append(":"); 132 | Visit(node.Right); 133 | _sb.Append("}'"); 134 | 135 | return node; 136 | } 137 | 138 | protected override Expression VisitConstant(ConstantExpression node) 139 | { 140 | _sb.Append(string.Format("\"{0}\"",node.Value)); 141 | return node; 142 | } 143 | 144 | private Expression VisitLimit(ConstantExpression node) 145 | { 146 | _limit.Append(string.Format("{0}",node.Value)); 147 | return node; 148 | } 149 | 150 | 151 | private Expression VisitOffset(ConstantExpression node) 152 | { 153 | _offset.Append(string.Format("{0}", node.Value)); 154 | return node; 155 | } 156 | 157 | private Expression VisitOrderBy(MemberExpression node) 158 | { 159 | if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter) 160 | { 161 | _orderBy.Append(string.Format(" order by body->>'{0}'", node.Member.Name)); 162 | return node; 163 | } 164 | 165 | throw new NotSupportedException(string.Format("The member '{0}' is not supported", node.Member.Name)); 166 | 167 | } 168 | 169 | protected override Expression VisitMember(MemberExpression node) 170 | { 171 | if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter) 172 | { 173 | _sb.Append(string.Format("\"{0}\"", node.Member.Name)); 174 | return node; 175 | } 176 | 177 | 178 | 179 | throw new NotSupportedException(string.Format("The member '{0}' is not supported", node.Member.Name)); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Elephanet/Queryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | 8 | namespace Elephanet 9 | { 10 | 11 | public interface IJsonbQueryable: IOrderedQueryable 12 | { 13 | } 14 | 15 | public interface IJsonbQueryable: IOrderedQueryable 16 | { 17 | } 18 | 19 | public class JsonbQueryable : IJsonbQueryable 20 | { 21 | IJsonbQueryProvider _provider; 22 | Expression _expression; 23 | 24 | public JsonbQueryable(IJsonbQueryProvider provider) { 25 | if (provider == null) { 26 | throw new ArgumentNullException("provider"); 27 | } 28 | _provider = provider; 29 | _expression = Expression.Constant(this); 30 | } 31 | 32 | public JsonbQueryable(IJsonbQueryProvider provider, Expression expression) { 33 | if (provider == null) { 34 | throw new ArgumentNullException("provider"); 35 | } 36 | if (expression == null) { 37 | throw new ArgumentNullException("expression"); 38 | } 39 | if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { 40 | throw new ArgumentOutOfRangeException("expression"); 41 | } 42 | _provider = provider; 43 | _expression = expression; 44 | } 45 | 46 | Expression IQueryable.Expression { 47 | get { return _expression; } 48 | } 49 | 50 | Type IQueryable.ElementType { 51 | get { return typeof(T); } 52 | } 53 | 54 | public IEnumerator GetEnumerator() { 55 | return ((IEnumerable)_provider.Execute(_expression)).GetEnumerator(); 56 | } 57 | 58 | IEnumerator IEnumerable.GetEnumerator() { 59 | return ((IEnumerable)_provider.Execute(_expression)).GetEnumerator(); 60 | } 61 | 62 | IQueryProvider IQueryable.Provider 63 | { 64 | get { return (IJsonbQueryProvider)_provider; } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Elephanet/Serialization/IJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet.Serialization 4 | { 5 | public interface IJsonConverter 6 | { 7 | string Serialize(T entity); 8 | T Deserialize(string json); 9 | object Deserialize(string json); 10 | object Deserialize(string json, Type type); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Elephanet/Serialization/JilJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using Jil; 2 | 3 | namespace Elephanet.Serialization 4 | { 5 | public class JilJsonConverter : IJsonConverter 6 | { 7 | private readonly Options _options; 8 | public JilJsonConverter() 9 | { 10 | _options = new Options(includeInherited: true, dateFormat:DateTimeFormat.ISO8601); 11 | } 12 | 13 | public string Serialize(T entity) 14 | { 15 | 16 | return JSON.Serialize(entity,_options); 17 | } 18 | 19 | public T Deserialize(string json) 20 | { 21 | return JSON.Deserialize(json,_options); 22 | } 23 | 24 | public object Deserialize(string json) 25 | { 26 | return JSON.DeserializeDynamic(json, _options); 27 | } 28 | 29 | public object Deserialize(string json, System.Type type) 30 | { 31 | return JSON.Deserialize(json, type,_options); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Elephanet/Sql.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Npgsql; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Globalization; 8 | using System.Threading; 9 | 10 | namespace Elephanet 11 | { 12 | 13 | public class Sql 14 | { 15 | private NpgsqlCommand _command; 16 | public Sql(string query, object[] parameters) 17 | { 18 | _command = new NpgsqlCommand(); 19 | 20 | _command.CommandText = String.Format(@"SELECT body FROM public.{0}_{1} WHERE body @> {2}", typeof(T).Namespace.ReplaceDotWithUnderscore(), typeof(T).Name, query); 21 | foreach (var entry in MatchParameters(query, parameters)) 22 | { 23 | string json = ConvertKeyValueToJson(entry); 24 | _command.Parameters.Add(new NpgsqlParameter(entry.Key,json)); 25 | } 26 | } 27 | 28 | private string ConvertKeyValueToJson(KeyValuePair entry) 29 | { 30 | CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture; 31 | TextInfo textInfo = cultureInfo.TextInfo; 32 | return string.Format("{{\"{0}\":\"{1}\"}}", textInfo.ToTitleCase(entry.Key.Substring(1)), entry.Value); 33 | } 34 | 35 | private Dictionary MatchParameters(string query, object[] parameters) 36 | { 37 | var matches = new Dictionary(); 38 | int counter = 0; 39 | foreach (Match match in Regex.Matches(query,(@"(? MatchParameters(string query, object[] parameters) 67 | { 68 | var matches = new Dictionary(); 69 | int counter = 0; 70 | foreach (Match match in Regex.Matches(query,(@"(? _tableNames; 15 | 16 | public DocumentStore(string connectionString) 17 | { 18 | _connectionString = connectionString; 19 | _conventions = new StoreConventions(); 20 | _storeInfo = new StoreInfo();; 21 | _tableNames = new List(); 22 | } 23 | 24 | public DocumentStore(string connectionString, IStoreConventions conventions) 25 | { 26 | _connectionString = connectionString; 27 | _conventions = conventions; 28 | _storeInfo = new StoreInfo(); 29 | } 30 | 31 | public DocumentStore(string connectionString, IStoreConventions conventions, IStoreInfo storeInfo) 32 | { 33 | _connectionString = connectionString; 34 | _conventions = conventions; 35 | _storeInfo = storeInfo; 36 | } 37 | 38 | public DocumentStore(string connectionString, IStoreInfo storeInfo) 39 | { 40 | _connectionString = connectionString; 41 | _conventions = new StoreConventions(); 42 | _storeInfo = storeInfo; 43 | } 44 | 45 | public IStoreConventions Conventions { get { return _conventions; }} 46 | 47 | public List TableNames { get {return _tableNames;} } 48 | 49 | public IDocumentSession OpenSession() 50 | { 51 | return new DocumentSession(this); 52 | } 53 | 54 | public string ConnectionString 55 | { 56 | get { return _connectionString; } 57 | } 58 | 59 | public IStoreInfo StoreInfo { get {return _storeInfo;}} 60 | 61 | 62 | public void Destroy() 63 | { 64 | var connection = new NpgsqlConnection(_connectionString); 65 | try 66 | { 67 | connection.Open(); 68 | foreach (var tablename in StoreInfo.Tables) 69 | { 70 | using (var command = connection.CreateCommand()) 71 | { 72 | command.CommandType = CommandType.Text; 73 | command.CommandText = String.Format(@"drop table {0};", tablename); 74 | command.ExecuteNonQuery(); 75 | } 76 | } 77 | } 78 | 79 | catch (NpgsqlException exception) 80 | { 81 | throw new Exception(String.Format("Could not drop table {0}; see the inner exception for more information.", _storeInfo.Name), exception); 82 | } 83 | 84 | finally 85 | { 86 | connection.Dispose(); 87 | } 88 | } 89 | 90 | public void Empty() 91 | { 92 | var connection = new NpgsqlConnection(_connectionString); 93 | try 94 | { 95 | connection.Open(); 96 | foreach (var tablename in StoreInfo.Tables) 97 | { 98 | using (var command = connection.CreateCommand()) 99 | { 100 | command.CommandType = CommandType.Text; 101 | command.CommandText = String.Format(@"delete from {0};", tablename); 102 | command.ExecuteNonQuery(); 103 | } 104 | } 105 | } 106 | 107 | catch (NpgsqlException exception) 108 | { 109 | throw new Exception(String.Format("Could not delete all from table {0}; see the inner exception for more information.", _storeInfo.Name), exception); 110 | } 111 | 112 | finally 113 | { 114 | connection.Dispose(); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Elephanet/StoreConventions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Elephanet; 3 | using Elephanet.Conventions; 4 | using Elephanet.Serialization; 5 | 6 | namespace Elephanet 7 | { 8 | public class StoreConventions : IStoreConventions 9 | { 10 | IJsonConverter _jsonConverter; 11 | private ITableInfo _tableInfo; 12 | private EntityNotFoundBehavior _entityNotFoundBehavior = EntityNotFoundBehavior.Throw; 13 | 14 | public StoreConventions() 15 | { 16 | _jsonConverter = new JilJsonConverter(); 17 | _tableInfo = new TableInfo(); 18 | } 19 | 20 | public StoreConventions(IJsonConverter jsonConverter) 21 | { 22 | _jsonConverter = jsonConverter; 23 | _tableInfo = new TableInfo(); 24 | } 25 | 26 | public StoreConventions(IJsonConverter jsonConverter, ITableInfo tableInfo) 27 | { 28 | _jsonConverter = jsonConverter; 29 | _tableInfo = tableInfo; 30 | } 31 | 32 | public IJsonConverter JsonConverter 33 | { 34 | get { return _jsonConverter; } 35 | } 36 | 37 | public ITableInfo TableInfo 38 | { 39 | get { return _tableInfo; } 40 | } 41 | 42 | public EntityNotFoundBehavior EntityNotFoundBehavior { get {return _entityNotFoundBehavior;} } 43 | 44 | /// 45 | /// Behavior of DocumentSession.GetById when the Entity is not found. 46 | /// 47 | /// 48 | public void SetEntityNotFoundBehavior(EntityNotFoundBehavior behavior) 49 | { 50 | _entityNotFoundBehavior = behavior; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Elephanet/StoreInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Elephanet 5 | { 6 | public class StoreInfo : IStoreInfo 7 | { 8 | private string _name; 9 | readonly ConcurrentHashSet _tableNames; 10 | public StoreInfo() 11 | { 12 | _name = "store"; 13 | _tableNames = new ConcurrentHashSet(); 14 | } 15 | 16 | public ConcurrentHashSet Tables { get { return _tableNames; } } 17 | 18 | public void Add(string tableName) 19 | { 20 | _tableNames.Add(tableName); 21 | } 22 | 23 | public StoreInfo(string storeName) 24 | { 25 | _name = storeName; 26 | } 27 | 28 | public string Name { get { return _name; } } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Elephanet/StringHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | 6 | namespace Elephanet.Helpers 7 | { 8 | public static class StringHelpers 9 | { 10 | public static string EscapeQuotes(this string text) 11 | { 12 | return text.Replace("'", "''"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Elephanet/TableInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Elephanet 4 | { 5 | public class TableInfo : ITableInfo 6 | { 7 | private string _schema; 8 | 9 | public TableInfo() 10 | { 11 | _schema = "public"; 12 | } 13 | 14 | public TableInfo(string schema) 15 | { 16 | _schema = schema; 17 | } 18 | 19 | public string TableNameWithSchema(Type type) 20 | { 21 | return String.Format("{0}.{1}_{2}", _schema, type.Namespace.ReplaceDotWithUnderscore().ToLower(), type.Name.ToLower()); 22 | } 23 | 24 | public string TableNameWithoutSchema(Type type) 25 | { 26 | return String.Format("{0}_{1}", type.Namespace.ReplaceDotWithUnderscore().ToLower(), type.Name.ToLower()); 27 | } 28 | 29 | public string Schema 30 | { 31 | get 32 | { 33 | return _schema; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Elephanet/TypeHelper.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 Elephanet 8 | { 9 | internal static class TypeSystem 10 | { 11 | internal static Type GetElementType(Type seqType) 12 | { 13 | Type ienum = FindIEnumerable(seqType); 14 | if (ienum == null) return seqType; 15 | return ienum.GetGenericArguments()[0]; 16 | } 17 | private static Type FindIEnumerable(Type seqType) 18 | { 19 | if (seqType == null || seqType == typeof(string)) 20 | return null; 21 | if (seqType.IsArray) 22 | return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType()); 23 | if (seqType.IsGenericType) 24 | { 25 | foreach (Type arg in seqType.GetGenericArguments()) 26 | { 27 | Type ienum = typeof(IEnumerable<>).MakeGenericType(arg); 28 | if (ienum.IsAssignableFrom(seqType)) 29 | { 30 | return ienum; 31 | } 32 | } 33 | } 34 | Type[] ifaces = seqType.GetInterfaces(); 35 | if (ifaces != null && ifaces.Length > 0) 36 | { 37 | foreach (Type iface in ifaces) 38 | { 39 | Type ienum = FindIEnumerable(iface); 40 | if (ienum != null) return ienum; 41 | } 42 | } 43 | if (seqType.BaseType != null && seqType.BaseType != typeof(object)) 44 | { 45 | return FindIEnumerable(seqType.BaseType); 46 | } 47 | return null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Elephanet/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 James Kelly 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 | -------------------------------------------------------------------------------- /create_store.bat: -------------------------------------------------------------------------------- 1 | "C:\Program Files\PostgreSQL\9.4\bin\psql.exe" -f create_store.sql -U postgres 2 | -------------------------------------------------------------------------------- /create_store.sql: -------------------------------------------------------------------------------- 1 | CREATE USER store_user with PASSWORD 'my super secret password'; 2 | CREATE DATABASE store; 3 | GRANT ALL PRIVILEGES ON DATABASE store to store_user; 4 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | cd Elephanet && nuget pack Elephanet.nuspec -Version $VERSION -IncludeReferencedProjects -Prop Configuration=Release && nuget push *.nupkg $NUGET_API_KEY -verbosity detailed 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ##Please Note## 2 | 3 | In the interest in not wasting peoples time, if you are considering using elephanet, please take a look at http://jasperfx.github.io/marten/ as it's far more complete that this project, with a far more active 4 | contributer base, yet it follows many of the same goals. 5 | 6 | [![Build Status](https://travis-ci.org/YoloDev/elephanet.svg?branch=master)](https://travis-ci.org/YoloDev/elephanet) 7 | ##Elephanet - A .NET document database built on PostgreSQL.## 8 | 9 | ###With an api thats easy to use### 10 | 11 | A document db api backed by Postgresql. 12 | 13 | Heavily influenced by the RavenDb .NET client, this libary provides a simple api to allow easy use of postgres as a document store, taking advantage of Postgresql 9.4 and its new jsonb indexing, allowing for fast querying of native json objects. 14 | 15 | ###Quick Start 16 | 17 | For Windows 18 | 19 | 1. Install Postgresql > 9.4 (available at http://www.postgresql.org/download/windows/). When you do so, pay particular attention to your postgres user password (you will need this in the next step) 20 | 2. Clone (or fork) this repository 21 | 3. Alter create_store.sql file and replace "my super secret password" with your own password 22 | 4. Run create_store.bat from within a cmd prompt. 23 | 24 | For Ubuntu (and likely other Debian based distros) 25 | 26 | 1. Install Postgresql via apt-get. Make sure it is greater than version 9.4 27 | 2. Install Mono (http://www.mono-project.com/docs/getting-started/install/linux/) 28 | 3. Clone (or fork) this repository 29 | 4. Alter create_store.sql file and replace "my super secret password" with your own password 30 | 5. run `psql -f create_store.sql -U postgres` 31 | 32 | ###Got Questions? 33 | 34 | - https://jabbr.net/#/rooms/elephanet (chat) 35 | - https://groups.google.com/d/forum/elephanet (forum) 36 | 37 | 38 | 39 | ###Example Code 40 | 41 | ``` 42 | using System; 43 | 44 | public class Car 45 | { 46 | public Guid Id {get;set;} 47 | public string Make {get;set;} 48 | public string Model {get;set;} 49 | public string ImageUrl {get;set;} 50 | public string NumberPlate {get;set;} 51 | } 52 | ``` 53 | 54 | ``` 55 | //create the datastore 56 | DocumentStore store = new DocumentStore("Server=127.0.0.1;Port=5432;User Id=store_user;password=my super secret password;database=store;"); 57 | 58 | 59 | //create the object 60 | var myAudi = new Car { 61 | Id = Guid.NewGuid(), 62 | Make = "Audi", 63 | Model = "A8", 64 | ImageUrl = "http://some_image_url", 65 | NumberPlate = "ABC029" 66 | }; 67 | 68 | //save the object to the document store 69 | using (var session = store.OpenSession()) 70 | { 71 | session.Store(myAudi); 72 | session.SaveChanges(); 73 | } 74 | 75 | //get the same car back out of the document store 76 | using (var session = store.OpenSession()) 77 | { 78 | var car = session.GetById(myAudi.Id); 79 | } 80 | 81 | 82 | //create a couple of other cars 83 | var myFord = new Car { 84 | Id = Guid.NewGuid(), 85 | Make = "Ford", 86 | Model = "Mustang", 87 | ImageUrl = "http://some_image_url", 88 | NumberPlate = "XYZ999" 89 | }; 90 | 91 | var myOldAudi { 92 | Id = Guid.NewGuid(), 93 | Make = "Audi", 94 | Model = "A5", 95 | ImageUrl = "http://some_image_url", 96 | NumberPlate = "ABC002" 97 | }; 98 | 99 | //store these other cars 100 | using (var session = store.OpenSession()) 101 | { 102 | session.Store(myOldAudi); 103 | session.Store(myFord); 104 | session.SaveChanges(); 105 | } 106 | 107 | //update existing object 108 | using (var session = store.OpenSession()) 109 | { 110 | var audi = session.GetById(myOldAudi.Id); 111 | audi.Image_Url = "http://some_new_url"; 112 | 113 | session.Store(audi); 114 | session.SaveChanges(); 115 | } 116 | 117 | //query by make 118 | using (var session = store.OpenSession()) 119 | { 120 | var audis = session.Query().Where(c => c.Make == "Audi").ToList(); 121 | } 122 | 123 | //delete 124 | using (var session = store.OpenSession()) 125 | { 126 | session.Delete(myOldAudi.Id); 127 | } 128 | 129 | //delete all of a particular type 130 | using (var session = store.OpenSession()) 131 | { 132 | session.DeleteAll(); 133 | } 134 | 135 | ``` 136 | 137 | ###Currently implemented### 138 | 139 | * You can ```session.Store(T entity)``` 140 | * You can ```session.SaveChanges();``` 141 | * You can ```session.GetById(Guid id)``` 142 | * You can ```session.GetByIds(IEnumerable ids)``` 143 | * You can ```session.Delete(Guid id)``` 144 | * You can ```session.GetAll();``` 145 | * You can ```session.DeleteAll();``` 146 | * You can ```session.Query(x => x.SomeAttribute == "some value").ToList();``` 147 | * You can ```session.Query(x => x.SomeAttribute == "some value").Take(10).Skip(5);``` 148 | * You can ```session.Query(x => x.SomeAttribute == "some value").OrderBy(c => c.SomeOtherAttibute);``` 149 | * You can ```session.Query(x => x.SomeAttribute == "some value").OrderByDescending(c => c.SomeOtherAttibute);``` 150 | 151 | ###Things of note: 152 | 153 | * You can implement your own custom json serialization (Jil is internalised by default) 154 | * Store() is a unit of work stored in memory. SaveChanges() flushes the in memory values to the database 155 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | ###v0.3.10 - 2015-09-15 2 | 3 | ####Improvements 4 | - tidy up IdentityFactory 5 | 6 | ###v0.3.9 - 2015-09-14 7 | 8 | ####Improvements 9 | - Improvements for thread safety, including running tests in parallel and general test tidyup 10 | 11 | ###v0.3.8 - 2015-09-09 12 | 13 | ####Improvements 14 | - GetById behavior (throw vs. returnnull) is now configurable via StoreConventions 15 | 16 | ###v0.3.7 - 2015-08-31 17 | 18 | ####Improvements 19 | - Modify the serializer to serialize dates to ISO8601 by default fixing https://github.com/YoloDev/elephanet/issues/19 20 | 21 | ###v0.3.6 - 2015-08-27 22 | 23 | ####Improvements 24 | - Modify the serlializer to serialize inherit properties 25 | 26 | ###v0.3.5 - 2015-08-24 27 | 28 | ####Improvements 29 | - Update time to timestamp (incorrectly implemented in past release) 30 | 31 | ###v0.3.4 - 2015-08-24 32 | 33 | ####Improvements 34 | - Create time by default at UTC, picked up by Frank Wise (https://github.com/fwise) 35 | 36 | ###v0.3.3 - 2015-08-24 37 | 38 | ####Improvements 39 | - Extract ITableInfo and inject into StoreConventions to allow overriding of table naming conventions with thanks to Frank Wise (https://github.com/fwise) 40 | 41 | ###v0.3.2 - 2015-08-23 42 | ####New Features 43 | - Add sql script for easy creation of data store database 44 | - Add bat file for running on windows to run sql script previously mentioned 45 | 46 | ####Improvements 47 | - Updated readme 48 | 49 | ###v0.3.1 - 2015-04-22 50 | ####New Features 51 | - Add support for .OrderBy() and .OrderByDescending() 52 | - Add support for .Skip() and .Take() 53 | - Add release-notes.md 54 | 55 | ####Improvements 56 | - Updated readme 57 | --------------------------------------------------------------------------------