-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
46 changed files
with
3,180 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 2012 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFunctors", "src\EntityFunctors\EntityFunctors.csproj", "{4AAE6600-6894-4F00-9F2A-E336590E6A62}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFunctors.Tests", "test\EntityFunctors.Tests\EntityFunctors.Tests.csproj", "{E750C699-1EA7-4B68-A384-F47744BA224D}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{E750C699-1EA7-4B68-A384-F47744BA224D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{E750C699-1EA7-4B68-A384-F47744BA224D}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{E750C699-1EA7-4B68-A384-F47744BA224D}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{E750C699-1EA7-4B68-A384-F47744BA224D}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
namespace EntityFunctors.Associations | ||
{ | ||
using System; | ||
using System.Diagnostics.Contracts; | ||
using System.Linq.Expressions; | ||
using Extensions; | ||
|
||
public class CollectionAssociation<TSource, TTarget> : CollectionAssociationBase<TSource, TTarget> | ||
{ | ||
public LambdaExpression Converter { get; private set; } | ||
|
||
public CollectionAssociation(PropertyPart source, PropertyPart target, LambdaExpression converter) | ||
: base(source, target) | ||
{ | ||
Contract.Assert(converter != null); | ||
Contract.Assert(converter.Parameters.Count == 1); | ||
Contract.Assert(converter.Parameters[0].Type == source.Property.PropertyType.GetItemType()); | ||
Contract.Assert(converter.ReturnType == target.Property.PropertyType.GetItemType()); | ||
|
||
Converter = converter; | ||
} | ||
|
||
protected override LambdaExpression CreateSelector(Type @from, Type to, ParameterExpression expands, IMappingRegistry registry) | ||
{ | ||
return Converter; | ||
} | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/EntityFunctors/Associations/CollectionAssociationBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
namespace EntityFunctors.Associations | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.Contracts; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using Extensions; | ||
|
||
public abstract class CollectionAssociationBase<TSource, TTarget> : IExpandable, IMappingAssociation | ||
{ | ||
private static readonly MethodInfo ToArray; | ||
|
||
private static readonly MethodInfo Select; | ||
|
||
public PropertyPart Source { get; private set; } | ||
|
||
public PropertyPart Target { get; private set; } | ||
|
||
public MappingDirection Direction | ||
{ | ||
get { return MappingDirection.Read; } | ||
} | ||
|
||
protected string Expand { get; set; } | ||
|
||
static CollectionAssociationBase() | ||
{ | ||
Expression<Func<IEnumerable<int>, IEnumerable<int>>> exp = xs => xs.Select(x => x).ToArray(); | ||
|
||
ToArray = ((MethodCallExpression)exp.Body).Method.GetGenericMethodDefinition(); | ||
|
||
Select = ((MethodCallExpression)((MethodCallExpression)exp.Body).Arguments[0]).Method.GetGenericMethodDefinition(); | ||
} | ||
|
||
protected CollectionAssociationBase(PropertyPart source, PropertyPart target) | ||
{ | ||
Contract.Assert(source != null); | ||
Contract.Assert(target != null); | ||
|
||
var propTarget = target.Property; | ||
|
||
if (!(propTarget.PropertyType.IsGenericType && propTarget.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>))) | ||
throw new InvalidOperationException( | ||
string.Format( | ||
"Collection mapping supports only IEnumerable<T> type for target property {0} but found {1}", | ||
typeof(TTarget).Name + "." + propTarget.Name, | ||
propTarget.PropertyType | ||
) | ||
); | ||
|
||
Target = target; | ||
|
||
Source = source; | ||
} | ||
|
||
public virtual Expression BuildMapper(ParameterExpression @from, ParameterExpression to, IMappingRegistry registry, ParameterExpression expands) | ||
{ | ||
if (!(@from.Type == typeof(TSource) && to.Type == typeof(TTarget))) | ||
return Expression.Empty(); | ||
|
||
var propFrom = Expression.Property(@from, Source.Property); | ||
var propTo = Expression.Property(to, Target.Property); | ||
|
||
var itemTypeFrom = Source.Property.PropertyType.GetItemType(); | ||
var itemTypeTo = Target.Property.PropertyType.GetItemType(); | ||
|
||
Expression mapper = Expression.Assign( | ||
propTo, | ||
Expression.Condition( | ||
propFrom.CreateCheckForDefault(), | ||
Target.Property.PropertyType.GetDefaultExpression(), | ||
Expression.Convert( | ||
Expression.Call( | ||
ToArray.MakeGenericMethod(itemTypeTo), | ||
Expression.Call( | ||
Select.MakeGenericMethod(itemTypeFrom, itemTypeTo), | ||
propFrom, | ||
CreateSelector(itemTypeFrom, itemTypeTo, expands, registry) | ||
) | ||
), | ||
Target.Property.PropertyType | ||
) | ||
) | ||
); | ||
|
||
if (expands != null && !string.IsNullOrWhiteSpace(Expand)) | ||
mapper = | ||
Expression.IfThen( | ||
expands.CreateContains(Expression.Constant(Expand, typeof(string))), | ||
mapper | ||
); | ||
|
||
return mapper; | ||
} | ||
|
||
public PropertyInfo RewritableProperty | ||
{ | ||
get { return Target.Property; } | ||
} | ||
|
||
public Expression Rewrite(Expression original, ParameterExpression parameter) | ||
{ | ||
return Expression.Property(parameter, Source.Property); | ||
} | ||
|
||
public IEnumerable<KeyValuePair<PropertyInfo, Delegate>> ValueConverters | ||
{ | ||
get { yield break; } | ||
} | ||
|
||
protected abstract LambdaExpression CreateSelector(Type @from, Type to, ParameterExpression expands, IMappingRegistry registry); | ||
|
||
public void Expandable() | ||
{ | ||
Expand = Target.Property.GetContractName(); | ||
} | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/EntityFunctors/Associations/ComponentCollectionAssociation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
namespace EntityFunctors.Associations | ||
{ | ||
using System; | ||
using System.Linq.Expressions; | ||
|
||
public class ComponentCollectionAssociation<TSource, TTarget> : CollectionAssociationBase<TSource, TTarget> | ||
{ | ||
public ComponentCollectionAssociation(PropertyPart source, PropertyPart target) | ||
:base(source, target) | ||
{ | ||
|
||
} | ||
|
||
protected override LambdaExpression CreateSelector(Type @from, Type to, ParameterExpression expands, IMappingRegistry registry) | ||
{ | ||
var paramFrom = Expression.Variable(@from); | ||
var varTo = Expression.Variable(to); | ||
|
||
var ctor = to.GetConstructor(Type.EmptyTypes); | ||
|
||
if (ctor == null) | ||
throw new InvalidOperationException(string.Format( | ||
"Type {0} must declare public parameterless constructor to be mapped from {1}", | ||
to, | ||
@from | ||
)); | ||
|
||
//todo: filter out expands | ||
var mapper = registry.GetMapper(paramFrom, varTo, expands); | ||
|
||
if (mapper == null) | ||
throw new InvalidOperationException(string.Format( | ||
"Component collection registration mapping {0} <--> {1} requires mapping for types {2} <--> {3} that wasn't found", | ||
typeof(TSource).Name + "." + Source.Property.Name, | ||
typeof(TTarget).Name + "." + Target.Property.Name, | ||
@from, | ||
to | ||
)); | ||
|
||
return Expression.Lambda( | ||
Expression.Block( | ||
new[] { varTo }, | ||
new[] { Expression.Assign(varTo, Expression.New(ctor)), mapper, varTo } | ||
), | ||
paramFrom | ||
); | ||
} | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
src/EntityFunctors/Associations/ComponentToComponentAssociation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
namespace EntityFunctors.Associations | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.Contracts; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using Extensions; | ||
|
||
public class ComponentToComponentAssociation<TSource, TTarget> : IAccessable, IExpandable, IMappingAssociation | ||
{ | ||
public PropertyPart Source { get; private set; } | ||
|
||
public PropertyPart Target { get; private set; } | ||
|
||
public MappingDirection Direction { get; private set; } | ||
|
||
protected string Expand { get; set; } | ||
|
||
public ComponentToComponentAssociation(PropertyPart source, PropertyPart target) | ||
{ | ||
Contract.Assert(source != null); | ||
Contract.Assert(target != null); | ||
|
||
Source = source; | ||
Target = target; | ||
|
||
Direction = MappingDirection.All; | ||
} | ||
|
||
public Expression BuildMapper(ParameterExpression @from, ParameterExpression to, IMappingRegistry registry, ParameterExpression expands) | ||
{ | ||
Contract.Assert(@from.Type == typeof(TSource) || @from.Type == typeof(TTarget)); | ||
Contract.Assert(to.Type == typeof(TSource) || to.Type == typeof(TTarget)); | ||
|
||
var direction = @from.Type == typeof(TTarget) ? MappingDirection.Write : MappingDirection.Read; | ||
|
||
if ((Direction & direction) != direction) | ||
return Expression.Empty(); | ||
|
||
var partFrom = direction == MappingDirection.Write ? Target : Source; | ||
var partTo = direction == MappingDirection.Write ? Source : Target; | ||
|
||
var propFrom = Expression.Property(@from, partFrom.Property); | ||
var propTo = Expression.Property(to, partTo.Property); | ||
|
||
var typeFrom = partFrom.Property.PropertyType; | ||
var typeTo = partTo.Property.PropertyType; | ||
|
||
var varFrom = Expression.Variable(typeFrom); | ||
var varTo = Expression.Variable(typeTo); | ||
|
||
//todo: filter out expands | ||
var mapper = registry.GetMapper(varFrom, varTo, expands); | ||
|
||
if (mapper == null) | ||
throw new InvalidOperationException(string.Format( | ||
"Component registration mapping {0} <--> {1} requires mapping for types {2} <--> {3} that wasn't found", | ||
typeof(TSource).Name + "." + Source.Property.Name, | ||
typeof(TTarget).Name + "." + Target.Property.Name, | ||
Source.Property.PropertyType, | ||
Target.Property.PropertyType | ||
)); | ||
|
||
var body = new List<Expression>(); | ||
|
||
if (direction == MappingDirection.Read) | ||
{ | ||
//create target object (only direct mapping: entity -> dto) | ||
var ctor = typeTo.GetConstructor(Type.EmptyTypes); | ||
|
||
if (ctor == null) | ||
throw new InvalidOperationException(string.Format( | ||
"Type {0} must declare public parameterless constructor to be mapped from {1}", | ||
typeTo, | ||
typeFrom | ||
)); | ||
|
||
body.Add(Expression.Assign(propTo, Expression.New(ctor))); | ||
} | ||
|
||
//variable assignments | ||
body.Add(Expression.Assign(varFrom, propFrom)); | ||
body.Add(Expression.Assign(varTo, propTo)); | ||
|
||
body.Add(mapper); | ||
|
||
var result = | ||
Expression.IfThenElse( | ||
propFrom.CreateCheckForDefault(), | ||
Expression.Assign(propTo, typeTo.GetDefaultExpression()), | ||
Expression.Block(new[] {varFrom, varTo}, body) | ||
); | ||
|
||
if (direction == MappingDirection.Read && expands != null && !string.IsNullOrWhiteSpace(Expand)) | ||
result = Expression.IfThen( | ||
expands.CreateContains(Expression.Constant(Expand, typeof(string))), | ||
result | ||
); | ||
|
||
return result; | ||
|
||
} | ||
|
||
public PropertyInfo RewritableProperty | ||
{ | ||
get { return Target.Property; } | ||
} | ||
|
||
public Expression Rewrite(Expression original, ParameterExpression parameter) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public IEnumerable<KeyValuePair<PropertyInfo, Delegate>> ValueConverters | ||
{ | ||
get { yield break; } | ||
} | ||
|
||
public void ReadOnly() | ||
{ | ||
Direction = MappingDirection.Read; | ||
} | ||
|
||
public void WriteOnly() | ||
{ | ||
Direction = MappingDirection.Write; | ||
} | ||
|
||
public void Read() | ||
{ | ||
AddDirection(MappingDirection.Read); | ||
} | ||
|
||
public void Write() | ||
{ | ||
AddDirection(MappingDirection.Write); | ||
} | ||
|
||
private void AddDirection(MappingDirection val) | ||
{ | ||
if ((Direction & val) != val) | ||
Direction |= val; | ||
} | ||
|
||
public void Expandable() | ||
{ | ||
Expand = Target.Property.GetContractName(); | ||
} | ||
} | ||
} |
Oops, something went wrong.