Skip to content

Commit

Permalink
Merge pull request #28 from e13tech/db
Browse files Browse the repository at this point in the history
Various Db enhancements
  • Loading branch information
JJBussert committed Feb 17, 2021
2 parents 3c24a0e + 26ed17f commit 22b02bb
Show file tree
Hide file tree
Showing 21 changed files with 569 additions and 41 deletions.
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

0 comments on commit 22b02bb

Please sign in to comment.