Add code for using MongoDB

This commit is contained in:
Robert Marshall 2020-06-21 22:39:10 +01:00
parent dd370e27cb
commit 313d668bfe
8 changed files with 218 additions and 1 deletions

View file

@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Api.Blog.Tests", "R
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.csproj", "{9F55FA01-FEC9-4326-8338-4EF014E127A4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Data.MongoDB", "Robware.Data.MongoDB\Robware.Data.MongoDB.csproj", "{D48723D5-053A-4654-B37D-CC2FA62625C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Data.MongoDB.Tests", "Robware.Data.MongoDB.Tests\Robware.Data.MongoDB.Tests.csproj", "{E4361445-DD13-4DB3-A278-4198F4DEE00A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -48,6 +52,14 @@ Global
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.Build.0 = Release|Any CPU
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Release|Any CPU.Build.0 = Release|Any CPU
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -5,12 +5,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
<ProjectReference Include="..\Robware.Data\Robware.Data.csproj" />
<ProjectReference Include="..\Robware.Data.MongoDB\Robware.Data.MongoDB.csproj" />
</ItemGroup>

View file

@ -1,6 +1,6 @@
using System;
namespace Robware.Data {
namespace Robware.Blog {
public class ItemNotFoundException:Exception {
public object Parameters { get; }

View file

@ -0,0 +1,54 @@
using FluentAssertions;
using MongoDB.Driver;
using NSubstitute;
using Robware.Blog;
using Robware.Blog.Data.MongoDB.State;
using System;
using System.Threading.Tasks;
using Xunit;
namespace Robware.Data.MongoDB.Tests {
public class BlogRepositoryTests {
[Fact]
public async Task SavePost_WithNewPost_GetsNewIdAndSetsTimestampAndSavesAsync() {
var collection = Substitute.For<IMongoCollection<BlogPostState>>();
collection.CountDocumentsAsync(Arg.Any<FilterDefinition<BlogPostState>>()).Returns(10);
var database = Substitute.For<IMongoDatabase>();
database.GetCollection<BlogPostState>("blog").Returns(collection);
var repo = new BlogRepository(database);
var post = new BlogPost();
await repo.SavePost(post);
post.Id.Should().Be(11);
post.Timestamp.Should().BeCloseTo(DateTime.Now);
await collection.Received(1).InsertOneAsync(Arg.Any<BlogPostState>());
}
[Fact]
public async Task SavePost_WithExistingPost_DoesNotSetTimestampAndSavesAsync() {
var collection = Substitute.For<IMongoCollection<BlogPostState>>();
var database = Substitute.For<IMongoDatabase>();
database.GetCollection<BlogPostState>("blog").Returns(collection);
var repo = new BlogRepository(database);
var post = new BlogPost() {
Id = 1
};
await repo.SavePost(post);
post.Id.Should().Be(1);
post.Timestamp.Should().Be(new DateTime());
await collection.Received(1).InsertOneAsync(Arg.Any<BlogPostState>());
}
[Fact]
public async Task GetCountAsync_ReturnsCountFromDatabase() {
var collection = Substitute.For<IMongoCollection<BlogPostState>>();
collection.CountDocumentsAsync(Arg.Any<FilterDefinition<BlogPostState>>()).Returns(10);
var database = Substitute.For<IMongoDatabase>();
database.GetCollection<BlogPostState>("blog").Returns(collection);
var repo = new BlogRepository(database);
(await repo.GetCountAsync()).Should().Be(10);
}
}
}

View file

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
<PackageReference Include="NSubstitute" Version="4.2.2" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Robware.Data.MongoDB\Robware.Data.MongoDB.csproj" />
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using MongoDB.Driver;
using Robware.Blog;
using Robware.Blog.Data.MongoDB.State;
namespace Robware.Data.MongoDB {
public class BlogRepository : IBlogRepository {
private readonly IMongoCollection<BlogPostState> _collection;
public BlogRepository(IMongoDatabase database) {
_collection = database.GetCollection<BlogPostState>("blog");
}
private BlogPost MapStateToPost(BlogPostState state) =>
new BlogPost {
Id = state.Id,
Title = state.Title,
Content = state.Content,
Timestamp = state.Timestamp,
Draft = state.Draft,
Url = state.Url,
UserId = state.UserId,
};
private IEnumerable<BlogPost> MapStateToPost(IEnumerable<BlogPostState> states) => states.Select(MapStateToPost);
public async Task DeletePostAsync(int id) {
var update = Builders<BlogPostState>.Update.Set(nameof(BlogPostState.Deleted), true);
await _collection.UpdateOneAsync(post => post.Id == id, update);
}
public async Task<IEnumerable<BlogPost>> GetAllPostsAsync() => MapStateToPost((await _collection.FindAsync(post => true)).ToEnumerable());
public async Task<int> GetCountAsync() => (int)await _collection.CountDocumentsAsync(post => !post.Deleted);
public async Task<BlogPost> GetLatestPostAsync() => (await GetLatestPostsAsync(1)).FirstOrDefault();
public async Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int limit, int offset = 0) {
var filter = Builders<BlogPostState>.Filter.Eq(nameof(BlogPostState.Deleted), false);
var sort = Builders<BlogPostState>.Sort.Descending(nameof(BlogPostState.Timestamp));
var options = new FindOptions<BlogPostState> {
Sort = sort,
Limit = limit,
Skip = offset
};
var states = (await _collection.FindAsync<BlogPostState>(filter, options)).ToEnumerable();
return MapStateToPost(states);
}
private async Task<BlogPost> GetPostAsync(Expression<Func<BlogPostState, bool>> filter) {
var result = (await _collection.FindAsync(filter)).FirstOrDefault();
if (result == null)
throw new ItemNotFoundException(nameof(GetPostAsync), null);
return MapStateToPost(result);
}
public async Task<BlogPost> GetPostByIdAsync(int id) => await GetPostAsync(post => post.Id == id && !post.Deleted);
public async Task<BlogPost> GetPostByUrlAsync(string url) => await GetPostAsync(post => post.Url == url && !post.Deleted);
public async Task<BlogPost> SavePost(BlogPost post) {
if (post.Id == 0) {
post.Id = (await GetCountAsync()) + 1;
post.Timestamp = DateTime.Now;
}
var mongoPost = new BlogPostState(post);
await _collection.InsertOneAsync(mongoPost);
return post;
}
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,33 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
namespace Robware.Blog.Data.MongoDB.State {
public class BlogPostState {
public BlogPostState()
{
}
public BlogPostState(BlogPost basePost) {
Id = basePost.Id;
Title = basePost.Title;
Content = basePost.Content;
Timestamp = basePost.Timestamp;
Draft = basePost.Draft;
Url = basePost.Url;
UserId = basePost.UserId;
Deleted = false;
}
[BsonId]
public int Id { get; set; }
public string Title { get; private set; }
public string Content { get; private set; }
public DateTime Timestamp { get; private set; }
public string Draft { get; private set; }
public string Url { get; private set; }
public int UserId { get; private set; }
public bool Deleted { get; private set; }
}
}