Skip to content

Commit

Permalink
✨feat: Auto filters & update objects (#7)
Browse files Browse the repository at this point in the history
* ✨feat: Change tracking proxy factory

Added support for creating change tracking proxies for intrafaces and classes. Proxy tracks changes on writable virtual properties (for classes) or all writable properties (for interfaces).

Changes are availabe through the IChangeTrack interface on the object.

* ✨feat: Automapping of filters & updates

Introduced default auto mappers for entites which provide out-of-the box ability to use column based filtering and updateing. This reduces boilerplate code required to setup an entity. Extended filter and update properties are still supported and full control of the mapping is supported.

BREAKING CHANGE: Removed IQueryFilter interface

* version bump

* Alpha version

* Composite interface builder

* Fix issue with missing properties

* Version fixes & bump

* Final version bump
  • Loading branch information
lukaferlez committed Apr 11, 2024
1 parent 2520060 commit 6af5e1e
Show file tree
Hide file tree
Showing 46 changed files with 1,465 additions and 313 deletions.
59 changes: 59 additions & 0 deletions src/Simpleverse.Repository.Db.Test/DapperHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Dapper;
using Moq;
using Moq.Dapper;
using Simpleverse.Repository.Db;
using Simpleverse.Repository.Db.SqlServer;
using Simpleverse.Repository.Db.Test.SqlServer.Entity;
using System;
using System.Data;
using System.Data.Common;
using System.Linq;

namespace Simpleverse.Repository.Db.Test
{
public class DapperHelper
{
private readonly Mock<DbConnection> _mockConnection;

public DapperHelper()
{
_mockConnection = new Mock<DbConnection>();
_mockConnection
.SetupDapperAsync(c => c.QueryAsync<EntityModel>(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>(), null, null))
.ReturnsAsync(Array.Empty<EntityModel>());
}

public SqlRepository Instance()
=> new SqlRepository(() => _mockConnection.Object);

private IDbCommand LastDbCommandInvocation()
{
var invocation = _mockConnection
.Invocations
.LastOrDefault(x => x.Method.Name == nameof(IDbConnection.CreateCommand));

if (invocation == null)
return null;

return (IDbCommand)invocation.ReturnValue;
}

public string Query()
{
var command = LastDbCommandInvocation();
if (command == null)
return string.Empty;

return command.CommandText;
}

public IDataParameterCollection Parameters()
{
var command = LastDbCommandInvocation();
if (command == null)
return null;

return command.Parameters;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
using Simpleverse.Repository.Db.Test.SqlServer;
using StackExchange.Profiling;
using System;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using System.Threading.Tasks;

namespace Simpleverse.Repository.Db.Test
{
public class TestFixture : IClassFixture<DatabaseFixture>
public class DatabaseTestFixture : TestFixture, IClassFixture<DatabaseFixture>
{
protected readonly ITestOutputHelper _output;
protected readonly DatabaseFixture _fixture;

public TestFixture(DatabaseFixture fixture, ITestOutputHelper output)
public DatabaseTestFixture(DatabaseFixture fixture, ITestOutputHelper output)
: base(output)
{
_fixture = fixture;
_output = output;
}
}

public class TestFixture
{
protected readonly ITestOutputHelper _output;

public TestFixture(ITestOutputHelper output)
{
_output = output;
MiniProfiler.StartNew();
}

public ProfileWithOutput Profile(string profilerName = null)
{
return new ProfileWithOutput(_output, profilerName);
Expand All @@ -30,7 +38,7 @@ public T Profile<T>(Func<T> profilerBlock)

public T Profile<T>(string profilerName, Func<T> profilerBlock)
{
using(var profiler = Profile(profilerName))
using (var profiler = Profile(profilerName))
return profilerBlock();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<PackageReference Include="Dapper.Contrib" Version="2.0.78" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.3.8" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Moq.Dapper" Version="1.0.7" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Simpleverse.Repository.Db.Test.SqlServer
{
[Collection("SqlServerCollection")]
public class DeleteTests : TestFixture
public class DeleteTests : DatabaseTestFixture
{
public DeleteTests(DatabaseFixture fixture, ITestOutputHelper output)
: base(fixture, output)
Expand Down
70 changes: 70 additions & 0 deletions src/Simpleverse.Repository.Db.Test/SqlServer/Entity/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Dapper.Contrib.Extensions;

namespace Simpleverse.Repository.Db.Test.SqlServer.Entity
{
public interface IEntityModel
{
int Id { get; set; }
string Name { get; set; }
bool Active { get; set; }
}

public class Entity : Entity<EntityModel>
{
public Entity(DbRepository repository)
: base(repository, new Table<EntityModel>("I"))
{
}
}

[Table("IEntity")]
public class EntityModel : IEntityModel
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual bool Active { get; set; }
}

public class EntityExtend : Entity<EntityModelExtended>
{
public EntityExtend(DbRepository repository)
: base(repository, new Table<EntityModelExtended>("I"))
{
}
}

public interface IEntityModelExtended : IEntityModel
{
string Description { get; set; }
int DummyValue { get; set; }
}

public class EntityModelExtended : EntityModel, IEntityModelExtended
{
public string NormalizedName => Name.ToUpper();
public virtual string Description { get; set; }
public virtual int DummyValue { get; set; }
}

public class EntityCustom : Entity<EntityModelExtended>
{
public EntityCustom(DbRepository repository)
: base(repository, new Table<EntityModelExtended>("I"))
{
}

protected override void Filter(QueryBuilder<EntityModelExtended> builder, EntityModelExtended filter)
{
base.Filter(builder, filter);
IfChanged(filter, x => x.DummyValue, () => builder.Where(x => x.DummyValue, filter.DummyValue));
}
}

public class EntityInterfaceExtended : Entity<EntityModelExtended, IEntityModelExtended, DbQueryOptions>
{
public EntityInterfaceExtended(DbRepository repository)
: base(repository, new Table<EntityModelExtended>("I"))
{
}
}
}
197 changes: 197 additions & 0 deletions src/Simpleverse.Repository.Db.Test/SqlServer/Entity/EntityProxyTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace Simpleverse.Repository.Db.Test.SqlServer.Entity
{
[Collection("SqlServerCollection")]
public class EntityProxyTest : TestFixture
{
public EntityProxyTest(ITestOutputHelper output)
: base(output)
{
}

[Fact]
public async Task ListAsyncWithParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new Entity(repositoryHelper.Instance());

// act
await entity.ListAsync(
filter =>
{
filter.Name = "test";
filter.Active = false;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @I_Name", query);
Assert.Contains("[I].[Active] = @I_Active", query);
}

[Fact]
public async Task UpdateAsyncWithParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new Entity(repositoryHelper.Instance());

// act
await entity.UpdateAsync(
update =>
{
update.Name = "test";
},
filter =>
{
filter.Active = false;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @Set_I_Name", query);
Assert.Contains("[I].[Active] = @I_Active", query);
}

[Fact]
public async Task ListAsyncWithExtendParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new EntityExtend(repositoryHelper.Instance());

// act
await entity.ListAsync(
filter =>
{
filter.Name = "test";
filter.Active = false;
filter.DummyValue = 1;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @I_Name", query);
Assert.Contains("[I].[Active] = @I_Active", query);
Assert.Contains("[I].[DummyValue] = @I_DummyValue", query);
}

[Fact]
public async Task UpdateAsyncWithExtendedParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new EntityExtend(repositoryHelper.Instance());

// act
await entity.UpdateAsync(
update =>
{
update.Name = "test";
update.Description = "test";
},
filter =>
{
filter.DummyValue = 1;
filter.Active = false;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @Set_I_Name", query);
Assert.Contains("[I].[Description] = @Set_I_Description", query);
Assert.Contains("[I].[Active] = @I_Active", query);
Assert.Contains("[I].[DummyValue] = @I_DummyValue", query);
}

[Fact]
public async Task ListAsyncWithCustomParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new EntityCustom(repositoryHelper.Instance());

// act
await entity.ListAsync(
filter =>
{
filter.Name = "test";
filter.Active = false;
filter.DummyValue = 1;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @I_Name", query);
Assert.Contains("[I].[Active] = @I_Active", query);
Assert.Contains("[I].[DummyValue] = @I_DummyValue", query);
}

[Fact]
public async Task UpdateAsyncWithCustomParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new EntityCustom(repositoryHelper.Instance());

// act
await entity.UpdateAsync(
update =>
{
update.Name = "test";
update.Description = "test";
},
filter =>
{
filter.DummyValue = 1;
filter.Active = false;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @Set_I_Name", query);
Assert.Contains("[I].[Description] = @Set_I_Description", query);
Assert.Contains("[I].[Active] = @I_Active", query);
Assert.Contains("[I].[DummyValue] = @I_DummyValue", query);
}

[Fact]
public async Task UpdateAsyncWithExtendedInterfaceParametersTest()
{
// arange
var repositoryHelper = new DapperHelper();
var entity = new EntityInterfaceExtended(repositoryHelper.Instance());

// act
await entity.UpdateAsync(
update =>
{
update.Name = "test";
update.Description = "test";
},
filter =>
{
filter.DummyValue = 1;
filter.Active = false;
}
);

// assert
var query = repositoryHelper.Query();
Assert.Contains("[I].[Name] = @Set_I_Name", query);
Assert.Contains("[I].[Description] = @Set_I_Description", query);
Assert.Contains("[I].[Active] = @I_Active", query);
Assert.Contains("[I].[DummyValue] = @I_DummyValue", query);
}
}
}
Loading

0 comments on commit 6af5e1e

Please sign in to comment.