Skip to content

Commit

Permalink
Composite interface builder
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaferlez committed Apr 10, 2024
1 parent f9003b3 commit 85f3f70
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>Dapper, Bulk, Merge, Upsert, Delete, Insert, Update, Repository</PackageTags>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>2.1.0-alpha</Version>
<Version>2.1.0-alpha2</Version>
<Description>High performance operation for MS SQL Server built for Dapper ORM. Including bulk operations Insert, Update, Delete, Get as well as Upsert both single and bulk.</Description>
<AssemblyVersion>2.1.0.1</AssemblyVersion>
<FileVersion>2.1.0.1</FileVersion>
<AssemblyVersion>2.1.0.2</AssemblyVersion>
<FileVersion>2.1.0.2</FileVersion>
<RepositoryUrl>https://github.com/lukaferlez/Simpleverse.Repository</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<EmbedAllSources>true</EmbedAllSources>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,41 @@ public void MultipleChange()
Assert.Single(changeTracker.Changed);
Assert.Single(changeTracker.Changed.Where(x => x == nameof(TestInterface.IntNullableProperty)));
}

[Fact]
public void CompositInterfaceProxy()
{
var proxy = ChangeProxyFactory.Create<CompositInterface>();

proxy.IntNullableProperty = null;
proxy.IntNullableProperty = 2;
proxy.Int2Property = 1;

var changeTracker = (IChangeTrack)proxy;

Assert.Equal(2, changeTracker.Changed.Count());
Assert.Single(changeTracker.Changed.Where(x => x == nameof(CompositInterface.IntNullableProperty)));
Assert.Single(changeTracker.Changed.Where(x => x == nameof(CompositInterface.Int2Property)));
}

[Fact]
public void DuplicatePropertyInterfaceProxy()
{
var proxy = ChangeProxyFactory.Create<DuplicatePropertyInterface>();

proxy.IntProperty = 2;

var changeTracker = (IChangeTrack)proxy;

Assert.Single(changeTracker.Changed);
Assert.Single(changeTracker.Changed.Where(x => x == nameof(DuplicatePropertyInterface.IntProperty)));
}

[Fact]
public void NameDuplicatePropertyInterfaceProxy()
{
Assert.Throws<NotSupportedException>(() => ChangeProxyFactory.Create<NameDuplicatePropertyInterface>());
}
}

public interface TestInterface
Expand Down Expand Up @@ -89,4 +124,19 @@ public TestClassNoParameterlessConstructor(int value)
public virtual string StringProperty { get; set; }
public virtual DateTime DateTimeProperty { get; set; }
}

public interface CompositInterface : TestInterface
{
int Int2Property { get; set; }
}

public interface DuplicatePropertyInterface : TestInterface
{
int IntProperty { get; set; }

Check warning on line 135 in src/Simpleverse.Repository.Test/ChangeTracking/ChangeProxyFactoryTest.cs

View workflow job for this annotation

GitHub Actions / build

'DuplicatePropertyInterface.IntProperty' hides inherited member 'TestInterface.IntProperty'. Use the new keyword if hiding was intended.
}

public interface NameDuplicatePropertyInterface : TestInterface
{
string IntProperty { get; set; }

Check warning on line 140 in src/Simpleverse.Repository.Test/ChangeTracking/ChangeProxyFactoryTest.cs

View workflow job for this annotation

GitHub Actions / build

'NameDuplicatePropertyInterface.IntProperty' hides inherited member 'TestInterface.IntProperty'. Use the new keyword if hiding was intended.
}
}
45 changes: 43 additions & 2 deletions src/Simpleverse.Repository/ChangeTracking/ChangeProxyFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
Expand All @@ -15,7 +16,6 @@ public static class ChangeProxyFactory
private static readonly ConcurrentDictionary<Type, Type> _typeCache = new ConcurrentDictionary<Type, Type>();

public static T Create<T>()
where T : class
{
Type typeOfT = typeof(T);

Expand Down Expand Up @@ -181,7 +181,8 @@ private static void AddTargetTypeImplementation(this TypeBuilder typeBuilder, Ty
else
typeBuilder.SetParent(typeOfT);

foreach (var property in typeOfT.GetProperties())
var properties = ExtractProperties(typeOfT);
foreach (var property in properties)
{
if (typeOfT.IsInterface)
typeBuilder.AddInterfaceProxyProperty(typeOfT, property, onChangeMethod);
Expand All @@ -190,6 +191,46 @@ private static void AddTargetTypeImplementation(this TypeBuilder typeBuilder, Ty
}
}

private static IEnumerable<PropertyInfo> ExtractProperties(Type type)
{
if (!type.IsInterface)
return type.GetProperties();

var interfaceTypes = ExtractInterfaces(type);

var properties = interfaceTypes
.SelectMany(x => x.GetProperties())
.GroupBy(x => x.Name)
.ToArray();

var duplicateProperties = properties
.Where(x => x.GroupBy(x => x.PropertyType).Count() > 1)
.SelectMany(x => x)
.ToArray();
if (duplicateProperties.Any())
{
var ex = new NotSupportedException("Multiple properties with the same name are not supported");
ex.Data.Add("DuplicateProperties", duplicateProperties.Select(x => x.DeclaringType.Name + "." + x.Name));
throw ex;
}

return properties.Select(x => x.First());
}

private static IEnumerable<Type> ExtractInterfaces(Type type)
{
var interfaceTypes = new List<Type>();
foreach (var typeOfInterface in type.GetInterfaces())
{
interfaceTypes.AddRange(ExtractInterfaces(typeOfInterface));
}

if (type.IsInterface)
interfaceTypes.Add(type);

return interfaceTypes;
}

private static void AddInterfaceProxyProperty(this TypeBuilder typeBuilder, Type typeOfT, PropertyInfo sourceProperty, MethodInfo onChangeMethod)
{
var propertyName = sourceProperty.Name;
Expand Down
6 changes: 3 additions & 3 deletions src/Simpleverse.Repository/Simpleverse.Repository.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>Repository</PackageTags>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.1.0-alpha</Version>
<Version>1.1.0-alpha2</Version>
<Description>Base repository elements.</Description>
<AssemblyVersion>1.1.0.1</AssemblyVersion>
<FileVersion>1.1.0.1</FileVersion>
<AssemblyVersion>1.1.0.2</AssemblyVersion>
<FileVersion>1.1.0.2</FileVersion>
<RepositoryUrl>https://github.com/lukaferlez/Simpleverse.Repository</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
Expand Down

0 comments on commit 85f3f70

Please sign in to comment.