Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various Db enhancements #28

Merged
merged 3 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions E13.Common.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E13.Common.Nunit.UI", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E13.Common.Api.AzureAD", "src\E13.Common.Api.AzureAD\E13.Common.Api.AzureAD.csproj", "{3A9E46CA-6D1A-4811-93DB-1D9C0193E135}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E13.Common.Data.Db.Tests", "test\E13.Common.Data.Db.Tests\E13.Common.Data.Db.Tests.csproj", "{E57FB194-8742-4183-8FBF-0EDA6C09F4A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -101,6 +103,10 @@ Global
{3A9E46CA-6D1A-4811-93DB-1D9C0193E135}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A9E46CA-6D1A-4811-93DB-1D9C0193E135}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A9E46CA-6D1A-4811-93DB-1D9C0193E135}.Release|Any CPU.Build.0 = Release|Any CPU
{E57FB194-8742-4183-8FBF-0EDA6C09F4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E57FB194-8742-4183-8FBF-0EDA6C09F4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E57FB194-8742-4183-8FBF-0EDA6C09F4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E57FB194-8742-4183-8FBF-0EDA6C09F4A6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -109,6 +115,7 @@ Global
{C60CA223-5F6E-47BF-ACFE-66CD54F0D2DC} = {30D836B3-FF29-49DC-B503-94B8F9B6DC50}
{C4E41E15-6FBB-4861-84E8-B3ECA206948C} = {30D836B3-FF29-49DC-B503-94B8F9B6DC50}
{1C96B136-221B-4BF9-8626-F14637A37555} = {30D836B3-FF29-49DC-B503-94B8F9B6DC50}
{E57FB194-8742-4183-8FBF-0EDA6C09F4A6} = {30D836B3-FF29-49DC-B503-94B8F9B6DC50}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {86FB5264-87DD-494C-885E-05EA38F8DB19}
Expand Down
74 changes: 44 additions & 30 deletions src/E13.Common.Data.Db/BaseDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Runtime.CompilerServices;
using System.Text;
using System.Diagnostics;
using System.Reflection;

namespace E13.Common.Data.Db
{
Expand All @@ -15,40 +16,53 @@ public abstract class BaseDbContext : DbContext
/// <summary>
/// The user name used when the user name is null
/// </summary>
private const string UnknownUser = "*Unknown";
public const string UnknownUser = "*Unknown";

protected ILogger Logger { get;}
protected string User { get; set; }
protected BaseDbContext(ILogger logger, string user)
protected BaseDbContext(DbContextOptions options, ILogger logger)
: base(options)
{
Logger = logger;
User = user;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}

public override int SaveChanges()
public int SaveChanges(string user, string source = null)
{
var caller = new StackFrame(1).GetMethod();
TagEntries($"{caller.DeclaringType}.{caller.Name}");
if(source == null)
{
var caller = new StackFrame(1).GetMethod();
source = $"{caller.DeclaringType}.{caller.Name}";
}

TagEntries(source, user);
var result = base.SaveChanges();

// saving after a reload effectively clears the change tracker affecting no records
Reload();
base.SaveChanges();

return result;
}

public override int SaveChanges()
{
var caller = new StackFrame(1).GetMethod();

return SaveChanges(UnknownUser, $"{caller.DeclaringType}.{caller.Name}");
}

public void Reload() => ChangeTracker.Entries()
.Where(e => e.Entity != null).ToList()
.ForEach(e => e.State = EntityState.Detached);

private void TagEntries(string source)
private void TagEntries(string source, string user)
{
var e = ChangeTracker.Entries().ToList();

var entries = ChangeTracker.Entries().Where(e =>
e.Entity is IEntity &&
(e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted)
Expand All @@ -61,28 +75,28 @@ e.Entity is IEntity &&
Logger.LogDebug($"Entity: {entry.Entity.GetType().Name}, State: {entry.State}");
var utcNow = DateTime.UtcNow;

if (entry.Entity is IEntity)
if (entry.State == EntityState.Added && entry.Entity is ICreatable creatable)
{
creatable.Created = utcNow;
creatable.CreatedBy = user;
creatable.CreatedSource = source;
}

if (entry.Entity is IModifiable modifiable)
{
if (entry.State == EntityState.Added)
{
((IEntity)entry.Entity).Created = utcNow;
((IEntity)entry.Entity).CreatedBy = User ?? UnknownUser;
((IEntity)entry.Entity).CreatedSource = source;
}

((IEntity)entry.Entity).Modified = utcNow;
((IEntity)entry.Entity).ModifiedBy = User ?? UnknownUser;
((IEntity)entry.Entity).ModifiedSource = source;

if (entry.State == EntityState.Deleted && entry.Entity is IDeletable)
{
// Implementing IDeletable implies soft deletes required
entry.State = EntityState.Modified;

((IDeletable)entry.Entity).Deleted = utcNow;
((IDeletable)entry.Entity).DeletedBy = User ?? UnknownUser;
((IDeletable)entry.Entity).DeletedSource = source;
}
modifiable.Modified = utcNow;
modifiable.ModifiedBy = user;
modifiable.ModifiedSource = source;
}

if (entry.State == EntityState.Deleted && entry.Entity is IDeletable deletable)
{
// Implementing IDeletable implies soft deletes required
entry.State = EntityState.Modified;

deletable.Deleted = utcNow;
deletable.DeletedBy = user;
deletable.DeletedSource = source;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/E13.Common.Data.Db/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ public virtual int Count(Expression<Func<TEntity, bool>> predicate = null)
/// <param name="entity">The entity to insert.</param>
public virtual void Insert(TEntity entity)
{
var entry = DbSet.Add(entity);
DbSet.Add(entity);
}

/// <summary>
Expand Down
15 changes: 15 additions & 0 deletions src/E13.Common.Domain/ICreatable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E13.Common.Domain
{
public interface ICreatable : IEntity
{
string CreatedBy { get; set; }
string CreatedSource { get; set; }
DateTime Created { get; set; }
}
}
4 changes: 3 additions & 1 deletion src/E13.Common.Domain/IDeletable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public interface IDeletable : IEntity
{
string DeletedBy { get; set; }
string DeletedSource { get; set; }
DateTime Deleted { get; set; }
DateTime? Deleted { get; set; }
public bool IsDeleted()
{ return Deleted == null; }
}
}
8 changes: 0 additions & 8 deletions src/E13.Common.Domain/IEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,5 @@ namespace E13.Common.Domain
public interface IEntity
{
Guid Id { get; set; }

string CreatedBy { get; set; }
string CreatedSource { get; set; }
DateTime Created { get; set; }

string ModifiedBy { get; set; }
string ModifiedSource { get; set; }
DateTime Modified { get; set; }
}
}
15 changes: 15 additions & 0 deletions src/E13.Common.Domain/IModifiable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E13.Common.Domain
{
public interface IModifiable : IEntity
{
string ModifiedBy { get; set; }
string ModifiedSource { get; set; }
DateTime? Modified { get; set; }
}
}
51 changes: 51 additions & 0 deletions test/E13.Common.Data.Db.Tests/BaseDbContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using E13.Common.Data.Db.Tests.Sample;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using System;
using System.Linq;

namespace E13.Common.Data.Db.Tests
{
public class BaseDbContextTests
{
private TestDbContext Context;

[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddDbContext<TestDbContext>(o => o.UseInMemoryDatabase($"{Guid.NewGuid()}"));

Context = services.BuildServiceProvider().GetService<TestDbContext>();
Context.AddTestData();
}

/// <summary>
/// InMemory Data should initialize to a single entry in every table
/// </summary>
[Test]
public void InMemory_Baseline_OnePerTable()
{
Context.Creatables.Count().Should().Be(1);
Context.Modifiables.Count().Should().Be(1);
Context.Deletable.Count().Should().Be(1);
Context.Effectable.Count().Should().Be(1);
Context.Ownable.Count().Should().Be(1);
}

/// <summary>
/// InMemory Data should initialize with a non-empty guid for the Id
/// </summary>
[Test]
public void InMemory_Baseline_EmptyGuids()
{
Context.Creatables.All(e => e.Id == Guid.Empty).Should().BeFalse();
Context.Modifiables.All(e => e.Id == Guid.Empty).Should().BeFalse();
Context.Deletable.All(e => e.Id == Guid.Empty).Should().BeFalse();
Context.Effectable.All(e => e.Id == Guid.Empty).Should().BeFalse();
Context.Ownable.All(e => e.Id == Guid.Empty).Should().BeFalse();
}
}
}
64 changes: 64 additions & 0 deletions test/E13.Common.Data.Db.Tests/BaseDbContext_ICreatableTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using E13.Common.Data.Db.Tests.Sample;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using System;
using System.Linq;

namespace E13.Common.Data.Db.Tests
{
public class BaseDbContext_ICreatableTests
{
private TestDbContext Context;

[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddDbContext<TestDbContext>(o => o.UseInMemoryDatabase($"{Guid.NewGuid()}"));

Context = services.BuildServiceProvider().GetService<TestDbContext>();
Context.AddTestData();
}
[Test]
public void InitialData_CreatedSource_AddTestData()
{
var arranged = Context.Creatables.First();

arranged.CreatedSource.Should().Be("E13.Common.Data.Db.Tests.Sample.TestDbContext.AddTestData");
}

[Test]
public void InitialData_CreatedBy_Unknown()
{
var arranged = Context.Creatables.First();

arranged.CreatedBy.Should().Be(BaseDbContext.UnknownUser);
}

[Test]
public void SaveChanges_UnknownUser_CreatedByUnknown()
{
var id = Guid.NewGuid();
Context.Creatables.Add(new TestCreatable { Id = id });
Context.SaveChanges();

var arranged = Context.Creatables.First(e => e.Id == id);

arranged.CreatedBy.Should().Be(BaseDbContext.UnknownUser);
}

[Test]
public void SaveChanges_NamedUser_CreatedByNamedUser()
{
var id = Guid.NewGuid();
Context.Creatables.Add(new TestCreatable { Id = id });
Context.SaveChanges(nameof(SaveChanges_NamedUser_CreatedByNamedUser));

var arranged = Context.Creatables.First(e => e.Id == id);

arranged.CreatedBy.Should().Be(nameof(SaveChanges_NamedUser_CreatedByNamedUser));
}
}
}
57 changes: 57 additions & 0 deletions test/E13.Common.Data.Db.Tests/BaseDbContext_IDeletableTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using E13.Common.Data.Db.Tests.Sample;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using System;
using System.Linq;

namespace E13.Common.Data.Db.Tests
{
public class BaseDbContext_IDeletableTests
{
private TestDbContext Context;

[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddDbContext<TestDbContext>(o => o.UseInMemoryDatabase($"{Guid.NewGuid()}"));

Context = services.BuildServiceProvider().GetService<TestDbContext>();
Context.AddTestData();
}

[Test]
public void SaveChanges_Deleting_SetsDeleted()
{
Context.Deletable.Count().Should().Be(1);
Context.Deletable.Count(e => e.Deleted == null).Should().Be(1);
var arranged = Context.Deletable.First();

Context.Deletable.Remove(arranged);
Context.SaveChanges();
var a = Context.Deletable.Count();

Context.Deletable.Count().Should().Be(1);
Context.Deletable.Count(e => e.Deleted == null).Should().Be(0);
Context.Deletable.Count(e => e.Deleted != null).Should().Be(1);
}

[Test]
public void SaveChangesForUser_Deleting_SetsDeletedBy()
{
Context.Deletable.Count().Should().Be(1);
Context.Deletable.Count(e => e.DeletedBy == null).Should().Be(1);
var arranged = Context.Deletable.First();

Context.Deletable.Remove(arranged);
Context.SaveChanges(nameof(SaveChangesForUser_Deleting_SetsDeletedBy));

Context.Deletable.Count().Should().Be(1);
Context.Deletable.Count(e => e.DeletedBy == null).Should().Be(0);
Context.Deletable.Count(e => e.DeletedBy == nameof(SaveChangesForUser_Deleting_SetsDeletedBy)).Should().Be(1);
}

}
}
Loading