diff --git a/.gitignore b/.gitignore
index cd9f35a..2bd200a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,369 @@
-\.vs/
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+# User-specific files
+*.rsuser
+*.suo
*.user
-.couscous/
-docs/Template-Dark/
-.idea/
\ No newline at end of file
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# MacOS file systems
+**/.DS_STORE
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JetBrains Rider
+.idea/
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*[.json, .xml, .info]
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# Sqlite example databases
+*.db
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..7f73136
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,22 @@
+
+
+ netcoreapp3.1
+ 3.1.*
+ 4.0.*
+ 2.11.*
+
+
+
+ $(NoWarn);1591
+ true
+ true
+
+
+
+
+ 2.4.1
+ 5.10.3
+ 31.0.3
+ 4.14.6
+
+
diff --git a/JsonApiDotNetCore.MongoDb.sln b/JsonApiDotNetCore.MongoDb.sln
index 5dbe7c9..f83b15b 100644
--- a/JsonApiDotNetCore.MongoDb.sln
+++ b/JsonApiDotNetCore.MongoDb.sln
@@ -1,44 +1,39 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.MongoDb", "src\JsonApiDotNetCore.MongoDb\JsonApiDotNetCore.MongoDb.csproj", "{7F964050-3BB1-4E5C-8458-A974B956908F}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{262566DF-A206-4026-8018-69C66936EFCF}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EAC8B3B-BD8B-4FFF-8807-37FCA45BD8BD}"
- ProjectSection(SolutionItems) = preProject
- .gitignore = .gitignore
- EndProjectSection
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{0AB9C87F-0A30-4875-B9BF-38F606E79734}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AEA5DF2E-26B5-4D81-AA6A-ADA690E3E0D8}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Release|Any CPU.Build.0 = Release|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {7F964050-3BB1-4E5C-8458-A974B956908F} = {262566DF-A206-4026-8018-69C66936EFCF}
- {0AB9C87F-0A30-4875-B9BF-38F606E79734} = {AEA5DF2E-26B5-4D81-AA6A-ADA690E3E0D8}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {F7741F43-2CB1-4C5E-9886-FDCB0B973E30}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DDAB9F03-9137-4BA5-9932-96C006C88583}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.MongoDb", "src\JsonApiDotNetCore.MongoDb\JsonApiDotNetCore.MongoDb.csproj", "{E8C38068-3E3E-477D-A09A-D536D662FC1C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Debug|x64.Build.0 = Debug|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Debug|x86.Build.0 = Debug|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Release|x64.ActiveCfg = Release|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Release|x64.Build.0 = Release|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Release|x86.ActiveCfg = Release|Any CPU
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E8C38068-3E3E-477D-A09A-D536D662FC1C} = {DDAB9F03-9137-4BA5-9932-96C006C88583}
+ EndGlobalSection
+EndGlobal
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..509975f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+Copyright (c) 2020 Alvaro Nicoli
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..28cac0c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,73 @@
+# JsonApiDotNetCore MongoDB Repository
+
+Plug-n-play implementation of `IResourceRepository` allowing you to use MongoDB with your `JsonApiDotNetCore` APIs.
+
+## Installation and Usage
+
+```bash
+dotnet add package JsonApiDotNetCore.MongoDb
+```
+
+### Models
+
+```cs
+public sealed class Book : IIdentifiable
+{
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Name { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+}
+```
+
+### Controllers
+
+```cs
+public sealed class BooksController : JsonApiController
+{
+ public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ {
+ }
+}
+```
+
+### Middleware
+
+```cs
+public class Startup
+{
+ public IServiceProvider ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(sp =>
+ {
+ var client = new MongoClient(Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
+ return client.GetDatabase(Configuration.GetSection("DatabaseSettings:Database").Value);
+ });
+
+ services.AddResourceRepository>();
+
+ services.AddJsonApi(resources: builder =>
+ {
+ builder.Add();
+ });
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouting();
+ app.UseJsonApi();
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
+ }
+}
+```
+
+## Limitations
+
+- Relationships are not supported
diff --git a/src/JsonApiDotNetCore.MongoDb/AssemblyInfo.cs b/src/JsonApiDotNetCore.MongoDb/AssemblyInfo.cs
deleted file mode 100644
index 4283264..0000000
--- a/src/JsonApiDotNetCore.MongoDb/AssemblyInfo.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("UnitTests")]
-[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
diff --git a/src/JsonApiDotNetCore.MongoDb/Data/MongoEntityRepository.cs b/src/JsonApiDotNetCore.MongoDb/Data/MongoEntityRepository.cs
deleted file mode 100644
index c926da0..0000000
--- a/src/JsonApiDotNetCore.MongoDb/Data/MongoEntityRepository.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-namespace JsonApiDotNetCore.MongoDb.Data
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using JsonApiDotNetCore.Data;
- using JsonApiDotNetCore.Extensions;
- using JsonApiDotNetCore.Internal.Query;
- using JsonApiDotNetCore.Models;
- using JsonApiDotNetCore.MongoDb.Extensions;
- using JsonApiDotNetCore.Services;
- using MongoDB.Bson;
- using MongoDB.Driver;
-
- public class MongoEntityRepository
- : IEntityRepository
- where TEntity : class, IIdentifiable
- {
- private readonly IMongoDatabase db;
-
- private readonly string collectionName;
-
- private readonly IJsonApiContext jsonApiContext;
-
- public MongoEntityRepository(IMongoDatabase db, string collectionName, IJsonApiContext jsonApiContext)
- {
- this.db = db;
- this.collectionName = collectionName;
- this.jsonApiContext = jsonApiContext;
- }
-
- private IMongoCollection Collection => this.db.GetCollection(this.collectionName);
-
- private IQueryable Entities => this.Collection.AsQueryable();
-
- public async Task CountAsync(IQueryable entities)
- {
- return (int)await this.Collection.CountAsync(Builders.Filter.Empty);
- }
-
- public async Task CreateAsync(TEntity entity)
- {
- await this.Collection.InsertOneAsync(entity);
-
- return entity;
- }
-
- public async Task DeleteAsync(TId id)
- {
- var result = await this.Collection.DeleteOneAsync(Builders.Filter.Eq(e => e.Id, id));
-
- return result.IsAcknowledged && result.DeletedCount > 0;
- }
-
- public IQueryable Filter(IQueryable entities, FilterQuery filterQuery)
- {
- return entities.Filter(this.jsonApiContext, filterQuery);
- }
-
- public Task FirstOrDefaultAsync(IQueryable entities)
- {
- return entities.FirstOrDefaultAsync();
- }
-
- public IQueryable Get()
- {
- List fields = this.jsonApiContext.QuerySet?.Fields;
- if (fields?.Any() ?? false)
- {
- return this.Entities.Select(fields);
- }
-
- return this.Entities;
- }
-
- public Task GetAndIncludeAsync(TId id, string relationshipName)
- {
- // this is a document DB, no relations!
- return this.GetAsync(id);
- }
-
- public Task GetAsync(TId id)
- {
- return this.Collection.Find(Builders.Filter.Eq(e => e.Id, id)).SingleOrDefaultAsync();
- }
-
- public IQueryable Include(IQueryable entities, string relationshipName)
- {
- // this is a document DB, no relations!
- return entities;
- }
-
- public async Task> PageAsync(IQueryable entities, int pageSize, int pageNumber)
- {
- return await entities.PageForward(pageSize, pageNumber).ToListAsync();
- }
-
- public Task SingleOrDefaultAsync(IQueryable queryable)
- {
- return queryable.SingleOrDefaultAsync();
- }
-
- public IQueryable Sort(IQueryable entities, List sortQueries)
- {
- return entities.Sort(sortQueries);
- }
-
- public Task> ToListAsync(IQueryable entities)
- {
- return entities.ToListAsync();
- }
-
- public async Task UpdateAsync(TId id, TEntity entity)
- {
- var existingEntity = await this.GetAsync(id);
-
- if (existingEntity == null)
- {
- return null;
- }
-
- foreach (var attr in this.jsonApiContext.AttributesToUpdate)
- {
- attr.Key.SetValue(existingEntity, attr.Value);
- }
-
- foreach (var relationship in this.jsonApiContext.RelationshipsToUpdate)
- {
- relationship.Key.SetValue(existingEntity, relationship.Value);
- }
-
- await this.Collection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, id), existingEntity);
-
- return existingEntity;
- }
-
- public Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds)
- {
- throw new NotImplementedException();
- }
-
- internal IEnumerable GetAllDocuments() => this.Collection.Find(new BsonDocument()).ToEnumerable();
- }
-}
diff --git a/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs
deleted file mode 100644
index f245c35..0000000
--- a/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace JsonApiDotNetCore.MongoDb.Extensions
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using MongoDB.Driver;
- using MongoDB.Driver.Linq;
-
- public static class MongoIQueryableExtensions
- {
- public static async Task> ToListAsync(this IQueryable queryable)
- {
- return await IAsyncCursorSourceExtensions.ToListAsync(ToMongoQueryable(queryable));
- }
-
- public static async Task SingleOrDefaultAsync(this IQueryable queryable)
- {
- return await IAsyncCursorSourceExtensions.SingleOrDefaultAsync(ToMongoQueryable(queryable));
- }
-
- public static async Task FirstOrDefaultAsync(this IQueryable queryable)
- {
- return await IAsyncCursorSourceExtensions.FirstOrDefaultAsync(ToMongoQueryable(queryable));
- }
-
- private static IMongoQueryable ToMongoQueryable(IQueryable queryable)
- {
- if (!(queryable is IMongoQueryable mongoQueryable))
- {
- throw new ArgumentException($"This MongoDB-specific extension method expects a {nameof(IMongoQueryable)} and cannot work with {nameof(IQueryable)} of type {queryable.GetType().Name}.");
- }
-
- return mongoQueryable;
- }
- }
-}
diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
index dac47a6..7a9e960 100644
--- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
+++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
@@ -1,12 +1,29 @@
- netcoreapp2.0
+ 4.0.0
+ $(NetCoreAppVersion)
+ true
+
+ jsonapi;json:api;dotnet;core;MongoDB
+ Persistence layer implementation for use of MongoDB in applications using JsonApiDotNetCore
+ https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb
+ MIT
+ false
+ true
+ true
+ embedded
+
+
+
+
+
+
-
-
+
+
diff --git a/src/JsonApiDotNetCore.MongoDb/MongoDbRepository.cs b/src/JsonApiDotNetCore.MongoDb/MongoDbRepository.cs
new file mode 100644
index 0000000..bcbbfd2
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb/MongoDbRepository.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
+using JsonApiDotNetCore.Repositories;
+using JsonApiDotNetCore.Resources;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+using JsonApiDotNetCore.Queries;
+using JsonApiDotNetCore.Queries.Expressions;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+
+namespace JsonApiDotNetCore.MongoDb
+{
+ ///
+ /// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB.
+ ///
+ public class MongoDbRepository
+ : IResourceRepository
+ where TResource : class, IIdentifiable
+ {
+ private readonly IMongoDatabase _mongoDatabase;
+ private readonly ITargetedFields _targetedFields;
+ private readonly IResourceContextProvider _resourceContextProvider;
+ private readonly IResourceFactory _resourceFactory;
+
+ public MongoDbRepository(
+ IMongoDatabase mongoDatabase,
+ ITargetedFields targetedFields,
+ IResourceContextProvider resourceContextProvider,
+ IResourceFactory resourceFactory)
+ {
+ _mongoDatabase = mongoDatabase ?? throw new ArgumentNullException(nameof(mongoDatabase));
+ _targetedFields = targetedFields ?? throw new ArgumentNullException(nameof(targetedFields));
+ _resourceContextProvider = resourceContextProvider ?? throw new ArgumentNullException(nameof(resourceContextProvider));
+ _resourceFactory = resourceFactory ?? throw new ArgumentNullException(nameof(resourceFactory));
+ }
+
+ protected virtual IMongoCollection Collection => _mongoDatabase.GetCollection(typeof(TResource).Name);
+
+ ///
+ public virtual async Task> GetAsync(QueryLayer layer,
+ CancellationToken cancellationToken)
+ {
+ if (layer == null) throw new ArgumentNullException(nameof(layer));
+
+ var resources = await ApplyQueryLayer(layer).ToListAsync(cancellationToken);
+ return resources.AsReadOnly();
+ }
+
+ ///
+ public virtual Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken)
+ {
+ var resourceContext = _resourceContextProvider.GetResourceContext();
+ var layer = new QueryLayer(resourceContext)
+ {
+ Filter = topFilter
+ };
+
+ var query = ApplyQueryLayer(layer);
+ return query.CountAsync(cancellationToken);
+ }
+
+ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer layer)
+ {
+ if (layer == null) throw new ArgumentNullException(nameof(layer));
+
+ var source = GetAll();
+
+ var nameFactory = new LambdaParameterNameFactory();
+ var builder = new QueryableBuilder(
+ source.Expression,
+ source.ElementType,
+ typeof(Queryable),
+ nameFactory,
+ _resourceFactory,
+ _resourceContextProvider,
+ DummyModel.Instance);
+
+ var expression = builder.ApplyQuery(layer);
+ return (IMongoQueryable)source.Provider.CreateQuery(expression);
+ }
+
+ protected virtual IQueryable GetAll()
+ {
+ return Collection.AsQueryable();
+ }
+
+ ///
+ public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken)
+ {
+ var resource = _resourceFactory.CreateInstance();
+ resource.Id = id;
+
+ return Task.FromResult(resource);
+ }
+
+ ///
+ public virtual Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase,
+ CancellationToken cancellationToken)
+ {
+ if (resourceFromRequest == null) throw new ArgumentNullException(nameof(resourceFromRequest));
+ if (resourceForDatabase == null) throw new ArgumentNullException(nameof(resourceForDatabase));
+
+ foreach (var attribute in _targetedFields.Attributes)
+ {
+ attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest));
+ }
+
+ return Collection.InsertOneAsync(resourceForDatabase, new InsertOneOptions(), cancellationToken);
+ }
+
+ ///
+ public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken)
+ {
+ var resources = await GetAsync(queryLayer, cancellationToken);
+ return resources.FirstOrDefault();
+ }
+
+ ///
+ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken)
+ {
+ if (resourceFromRequest == null) throw new ArgumentNullException(nameof(resourceFromRequest));
+ if (resourceFromDatabase == null) throw new ArgumentNullException(nameof(resourceFromDatabase));
+
+ foreach (var attr in _targetedFields.Attributes)
+ attr.SetValue(resourceFromDatabase, attr.GetValue(resourceFromRequest));
+
+ await Collection.ReplaceOneAsync(
+ Builders.Filter.Eq(e => e.Id, resourceFromDatabase.Id),
+ resourceFromDatabase,
+ new ReplaceOptions(),
+ cancellationToken);
+ }
+
+ ///
+ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken)
+ {
+ var result = await Collection.DeleteOneAsync(
+ Builders.Filter.Eq(e => e.Id, id),
+ new DeleteOptions(),
+ cancellationToken);
+
+ if (!result.IsAcknowledged)
+ {
+ throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB."));
+ }
+
+ if (result.DeletedCount == 0)
+ {
+ throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist."));
+ }
+ }
+
+ ///
+ public virtual Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public virtual Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public virtual Task RemoveFromToManyRelationshipAsync(TResource primaryResource,
+ ISet secondaryResourceIds,
+ CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB.
+ ///
+ public class MongoDbRepository : MongoDbRepository
+ where TResource : class, IIdentifiable
+ {
+ public MongoDbRepository(
+ IMongoDatabase mongoDatabase,
+ ITargetedFields targetedFields,
+ IResourceContextProvider resourceContextProvider,
+ IResourceFactory resourceFactory)
+ : base(mongoDatabase, targetedFields, resourceContextProvider, resourceFactory)
+ {
+ }
+ }
+
+ internal sealed class DummyModel : IModel
+ {
+ public static IModel Instance { get; } = new DummyModel();
+
+ public object this[string name] => throw new NotImplementedException();
+
+ private DummyModel()
+ {
+ }
+
+ public IAnnotation FindAnnotation(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerable GetAnnotations()
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerable GetEntityTypes()
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEntityType FindEntityType(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/test/UnitTests/.gitignore b/test/UnitTests/.gitignore
deleted file mode 100644
index 0ca27f0..0000000
--- a/test/UnitTests/.gitignore
+++ /dev/null
@@ -1,234 +0,0 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-build/
-bld/
-[Bb]in/
-[Oo]bj/
-
-# Visual Studio 2015 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# DNX
-project.lock.json
-artifacts/
-
-*_i.c
-*_p.c
-*_i.h
-*.ilk
-*.meta
-*.obj
-*.pch
-*.pdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/packages/*
-# except build/, which is used as an MSBuild target.
-!**/packages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/packages/repositories.config
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Microsoft Azure ApplicationInsights config file
-ApplicationInsights.config
-
-# Windows Store app package directory
-AppPackages/
-BundleArtifacts/
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.pfx
-*.publishsettings
-node_modules/
-orleans.codegen.cs
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-
-# SQL Server files
-*.mdf
-*.ldf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-
-# FAKE - F# Make
-.fake/
diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj
deleted file mode 100644
index e5ff8f6..0000000
--- a/test/UnitTests/UnitTests.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- netcoreapp2.0
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-