Expression Conversion
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
6
down vote
favorite
Using Entity Framework we have a number of entities, which we want to hide from the rest of the calling code to remove the direct dependency on the database. We're doing so by using DTOs, which for the most part are identical to the entities. We also have an abstract generic provider and persister pattern which handle the conversion between the dtos and the entities, and all of the database interaction. The general loading method of the base abstract provider uses the following signature:
protected virtual IEnumerable<TDto> Load(Expression<Func<TEntity, bool>> filter)
So any provider that extends the abstract can look like this:
public IEnumerable<FooDto> GetSomeFilteredData(IEnumerable<string> identifiers)
return base.Load(entity => identifiers.Contains(entity.SomeProperty));
Therefore hiding the entities from the calling code.
This pattern works well for us, but I have been investigating how to manipulate expressions so that I could put the following in the base provider:
public virtual IEnumerable<TDto> Load(Expression<Func<TDto, bool>> filter)
The base provider would then convert the Expression<Func<TDto, bool>>
into the equivalent entity expression, and load the data as before, therefore removing the need to write tedious methods and give us more control in the calling code.
The end result has been the following ExpressionConverter
public class ExpressionConverter<Dto, Entity>
where Dto : class, new() where Entity : class, new()
private MappedConverter<Dto, Entity> converter;
public ExpressionConverter(MappedConverter<Dto, Entity> converter)
this.converter = converter;
public Expression<Func<Entity, bool>> ConvertExpr(Expression<Func<Dto, bool>> expr)
ParameterExpression entityParam = Expression.Parameter(typeof(Entity), "e");
Expression entityExpression = this.ConvertExpression(expr.Body, entityParam);
var result = Expression.Lambda<Func<Entity, bool>>(entityExpression, entityParam);
return result;
private Expression ConvertExpression(Expression expression, ParameterExpression entityParam)
if (expression is BinaryExpression binary)
return this.ConvertBinaryExpression(binary, entityParam);
if (expression is MemberExpression member)
return this.ConvertMemberExpression(member, entityParam);
if (expression is MethodCallExpression method)
return this.ConvertMethodCallExpression(method, entityParam);
return expression;
private Expression ConvertBinaryExpression(BinaryExpression binary, ParameterExpression param)
Expression left = this.ConvertExpression(binary.Left, param);
Expression right = this.ConvertExpression(binary.Right, param);
ExpressionType nodeType = binary.NodeType;
return this.CombineExpressions(left, right, nodeType);
private Expression ConvertMemberExpression(Expression expression, ParameterExpression entityParam)
if (this.IsParseableMemberExpression(expression))
MemberExpression memberExpr = expression as MemberExpression;
var value = Expression.Lambda(memberExpr).Compile().DynamicInvoke();
return Expression.Constant(value);
else if (this.IsConvertibleMember(expression))
MemberInfo dtoMember = (expression as MemberExpression).Member;
Mapping<Dto, Entity> mapping = this.converter.GetMappingFromMemberName<Dto>(dtoMember.Name);
MemberExpression entityMemberExpr = Expression.MakeMemberAccess(entityParam, mapping.EntityProperty);
return entityMemberExpr;
return expression;
private Expression ConvertMethodCallExpression(MethodCallExpression expression, ParameterExpression entityParam)
Expression objectExpr = this.ConvertMemberExpression(expression.Object, entityParam);
IEnumerable<Expression> argumentExpressions = expression.Arguments.Select(x => this.ConvertMemberExpression(x, entityParam));
MethodInfo method = expression.Method;
Expression result = Expression.Call(objectExpr, method, argumentExpressions);
return result;
private bool IsConvertibleMember(Expression expression)
return expression.NodeType == ExpressionType.MemberAccess;
private bool IsParseableMemberExpression(Expression expression)
if (expression is MemberExpression memberExpression)
return false;
private bool IsDateTimeExpression(MemberExpression expression)
Now)");
private Expression CombineExpressions(Expression left, Expression right, ExpressionType type)
switch (type)
case ExpressionType.And: return Expression.And(left, right);
case ExpressionType.AndAlso: return Expression.AndAlso(left, right);
case ExpressionType.Equal: return Expression.Equal(left, right);
case ExpressionType.ExclusiveOr: return Expression.ExclusiveOr(left, right);
case ExpressionType.GreaterThan: return Expression.GreaterThan(left, right);
case ExpressionType.GreaterThanOrEqual: return Expression.GreaterThanOrEqual(left, right);
case ExpressionType.LessThan: return Expression.LessThan(left, right);
case ExpressionType.LessThanOrEqual: return Expression.LessThanOrEqual(left, right);
case ExpressionType.NotEqual: return Expression.NotEqual(left, right);
case ExpressionType.Or: return Expression.Or(left, right);
case ExpressionType.OrElse: return Expression.OrElse(left, right);
default:
throw new Exception($"Unsupported expression type: type.ToString()");
Where MappedConverter<Dto, Entity>
is the following:
public class MappedConverter<Dto, Entity> where Dto : class, new() where Entity : class, new()
public List<Mapping<Dto, Entity>> Mappings get; set;
public MappedConverter(params Mapping<Dto, Entity> maps)
this.Mappings = maps.ToList();
public Mapping<Dto, Entity> GetMappingFromMemberName<T>(string name)
if (typeof(T) == typeof(Dto))
return this.Mappings.SingleOrDefault(x => x.DtoProperty.Name == name);
else if (typeof(T) == typeof(Entity))
return this.Mappings.SingleOrDefault(x => x.EntityProperty.Name == name);
throw new Exception($"Cannot get mapping for typeof(T).Name from MappedConverter<typeof(Dto).Name, typeof(Entity).Name>");
And Mapping<Dto, Entity>
is the following:
public class Mapping<Dto, Entity>
public PropertyInfo DtoProperty get;
public PropertyInfo EntityProperty get;
Notes:
The conversion only has to handle Expressions of the form Expression<Func<Dto, bool>>
I am specifically looking for ways to improve the IsParseableMemberExpression
and IsDateTimeExpression
methods
IsParseableMemberExpression
uses Regex
to see if the expression is representing a variable in the full expression. e.g. variable
in x => x.Value == variable
c# linq-expressions
add a comment |Â
up vote
6
down vote
favorite
Using Entity Framework we have a number of entities, which we want to hide from the rest of the calling code to remove the direct dependency on the database. We're doing so by using DTOs, which for the most part are identical to the entities. We also have an abstract generic provider and persister pattern which handle the conversion between the dtos and the entities, and all of the database interaction. The general loading method of the base abstract provider uses the following signature:
protected virtual IEnumerable<TDto> Load(Expression<Func<TEntity, bool>> filter)
So any provider that extends the abstract can look like this:
public IEnumerable<FooDto> GetSomeFilteredData(IEnumerable<string> identifiers)
return base.Load(entity => identifiers.Contains(entity.SomeProperty));
Therefore hiding the entities from the calling code.
This pattern works well for us, but I have been investigating how to manipulate expressions so that I could put the following in the base provider:
public virtual IEnumerable<TDto> Load(Expression<Func<TDto, bool>> filter)
The base provider would then convert the Expression<Func<TDto, bool>>
into the equivalent entity expression, and load the data as before, therefore removing the need to write tedious methods and give us more control in the calling code.
The end result has been the following ExpressionConverter
public class ExpressionConverter<Dto, Entity>
where Dto : class, new() where Entity : class, new()
private MappedConverter<Dto, Entity> converter;
public ExpressionConverter(MappedConverter<Dto, Entity> converter)
this.converter = converter;
public Expression<Func<Entity, bool>> ConvertExpr(Expression<Func<Dto, bool>> expr)
ParameterExpression entityParam = Expression.Parameter(typeof(Entity), "e");
Expression entityExpression = this.ConvertExpression(expr.Body, entityParam);
var result = Expression.Lambda<Func<Entity, bool>>(entityExpression, entityParam);
return result;
private Expression ConvertExpression(Expression expression, ParameterExpression entityParam)
if (expression is BinaryExpression binary)
return this.ConvertBinaryExpression(binary, entityParam);
if (expression is MemberExpression member)
return this.ConvertMemberExpression(member, entityParam);
if (expression is MethodCallExpression method)
return this.ConvertMethodCallExpression(method, entityParam);
return expression;
private Expression ConvertBinaryExpression(BinaryExpression binary, ParameterExpression param)
Expression left = this.ConvertExpression(binary.Left, param);
Expression right = this.ConvertExpression(binary.Right, param);
ExpressionType nodeType = binary.NodeType;
return this.CombineExpressions(left, right, nodeType);
private Expression ConvertMemberExpression(Expression expression, ParameterExpression entityParam)
if (this.IsParseableMemberExpression(expression))
MemberExpression memberExpr = expression as MemberExpression;
var value = Expression.Lambda(memberExpr).Compile().DynamicInvoke();
return Expression.Constant(value);
else if (this.IsConvertibleMember(expression))
MemberInfo dtoMember = (expression as MemberExpression).Member;
Mapping<Dto, Entity> mapping = this.converter.GetMappingFromMemberName<Dto>(dtoMember.Name);
MemberExpression entityMemberExpr = Expression.MakeMemberAccess(entityParam, mapping.EntityProperty);
return entityMemberExpr;
return expression;
private Expression ConvertMethodCallExpression(MethodCallExpression expression, ParameterExpression entityParam)
Expression objectExpr = this.ConvertMemberExpression(expression.Object, entityParam);
IEnumerable<Expression> argumentExpressions = expression.Arguments.Select(x => this.ConvertMemberExpression(x, entityParam));
MethodInfo method = expression.Method;
Expression result = Expression.Call(objectExpr, method, argumentExpressions);
return result;
private bool IsConvertibleMember(Expression expression)
return expression.NodeType == ExpressionType.MemberAccess;
private bool IsParseableMemberExpression(Expression expression)
if (expression is MemberExpression memberExpression)
return false;
private bool IsDateTimeExpression(MemberExpression expression)
Now)");
private Expression CombineExpressions(Expression left, Expression right, ExpressionType type)
switch (type)
case ExpressionType.And: return Expression.And(left, right);
case ExpressionType.AndAlso: return Expression.AndAlso(left, right);
case ExpressionType.Equal: return Expression.Equal(left, right);
case ExpressionType.ExclusiveOr: return Expression.ExclusiveOr(left, right);
case ExpressionType.GreaterThan: return Expression.GreaterThan(left, right);
case ExpressionType.GreaterThanOrEqual: return Expression.GreaterThanOrEqual(left, right);
case ExpressionType.LessThan: return Expression.LessThan(left, right);
case ExpressionType.LessThanOrEqual: return Expression.LessThanOrEqual(left, right);
case ExpressionType.NotEqual: return Expression.NotEqual(left, right);
case ExpressionType.Or: return Expression.Or(left, right);
case ExpressionType.OrElse: return Expression.OrElse(left, right);
default:
throw new Exception($"Unsupported expression type: type.ToString()");
Where MappedConverter<Dto, Entity>
is the following:
public class MappedConverter<Dto, Entity> where Dto : class, new() where Entity : class, new()
public List<Mapping<Dto, Entity>> Mappings get; set;
public MappedConverter(params Mapping<Dto, Entity> maps)
this.Mappings = maps.ToList();
public Mapping<Dto, Entity> GetMappingFromMemberName<T>(string name)
if (typeof(T) == typeof(Dto))
return this.Mappings.SingleOrDefault(x => x.DtoProperty.Name == name);
else if (typeof(T) == typeof(Entity))
return this.Mappings.SingleOrDefault(x => x.EntityProperty.Name == name);
throw new Exception($"Cannot get mapping for typeof(T).Name from MappedConverter<typeof(Dto).Name, typeof(Entity).Name>");
And Mapping<Dto, Entity>
is the following:
public class Mapping<Dto, Entity>
public PropertyInfo DtoProperty get;
public PropertyInfo EntityProperty get;
Notes:
The conversion only has to handle Expressions of the form Expression<Func<Dto, bool>>
I am specifically looking for ways to improve the IsParseableMemberExpression
and IsDateTimeExpression
methods
IsParseableMemberExpression
uses Regex
to see if the expression is representing a variable in the full expression. e.g. variable
in x => x.Value == variable
c# linq-expressions
1
I agree that having to referenceTEntity
is compromising your otherwise quite extensive separation of layers. However, the issue in only usingTDto
is that there is no guarantee that its relatedTEntity
has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues.
â Flater
May 2 at 11:26
@Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety
â JChristen
May 2 at 12:43
1
That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer.
â Flater
May 2 at 12:51
1
The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts.
â Flater
May 2 at 12:54
And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand.
â JChristen
May 3 at 6:27
add a comment |Â
up vote
6
down vote
favorite
up vote
6
down vote
favorite
Using Entity Framework we have a number of entities, which we want to hide from the rest of the calling code to remove the direct dependency on the database. We're doing so by using DTOs, which for the most part are identical to the entities. We also have an abstract generic provider and persister pattern which handle the conversion between the dtos and the entities, and all of the database interaction. The general loading method of the base abstract provider uses the following signature:
protected virtual IEnumerable<TDto> Load(Expression<Func<TEntity, bool>> filter)
So any provider that extends the abstract can look like this:
public IEnumerable<FooDto> GetSomeFilteredData(IEnumerable<string> identifiers)
return base.Load(entity => identifiers.Contains(entity.SomeProperty));
Therefore hiding the entities from the calling code.
This pattern works well for us, but I have been investigating how to manipulate expressions so that I could put the following in the base provider:
public virtual IEnumerable<TDto> Load(Expression<Func<TDto, bool>> filter)
The base provider would then convert the Expression<Func<TDto, bool>>
into the equivalent entity expression, and load the data as before, therefore removing the need to write tedious methods and give us more control in the calling code.
The end result has been the following ExpressionConverter
public class ExpressionConverter<Dto, Entity>
where Dto : class, new() where Entity : class, new()
private MappedConverter<Dto, Entity> converter;
public ExpressionConverter(MappedConverter<Dto, Entity> converter)
this.converter = converter;
public Expression<Func<Entity, bool>> ConvertExpr(Expression<Func<Dto, bool>> expr)
ParameterExpression entityParam = Expression.Parameter(typeof(Entity), "e");
Expression entityExpression = this.ConvertExpression(expr.Body, entityParam);
var result = Expression.Lambda<Func<Entity, bool>>(entityExpression, entityParam);
return result;
private Expression ConvertExpression(Expression expression, ParameterExpression entityParam)
if (expression is BinaryExpression binary)
return this.ConvertBinaryExpression(binary, entityParam);
if (expression is MemberExpression member)
return this.ConvertMemberExpression(member, entityParam);
if (expression is MethodCallExpression method)
return this.ConvertMethodCallExpression(method, entityParam);
return expression;
private Expression ConvertBinaryExpression(BinaryExpression binary, ParameterExpression param)
Expression left = this.ConvertExpression(binary.Left, param);
Expression right = this.ConvertExpression(binary.Right, param);
ExpressionType nodeType = binary.NodeType;
return this.CombineExpressions(left, right, nodeType);
private Expression ConvertMemberExpression(Expression expression, ParameterExpression entityParam)
if (this.IsParseableMemberExpression(expression))
MemberExpression memberExpr = expression as MemberExpression;
var value = Expression.Lambda(memberExpr).Compile().DynamicInvoke();
return Expression.Constant(value);
else if (this.IsConvertibleMember(expression))
MemberInfo dtoMember = (expression as MemberExpression).Member;
Mapping<Dto, Entity> mapping = this.converter.GetMappingFromMemberName<Dto>(dtoMember.Name);
MemberExpression entityMemberExpr = Expression.MakeMemberAccess(entityParam, mapping.EntityProperty);
return entityMemberExpr;
return expression;
private Expression ConvertMethodCallExpression(MethodCallExpression expression, ParameterExpression entityParam)
Expression objectExpr = this.ConvertMemberExpression(expression.Object, entityParam);
IEnumerable<Expression> argumentExpressions = expression.Arguments.Select(x => this.ConvertMemberExpression(x, entityParam));
MethodInfo method = expression.Method;
Expression result = Expression.Call(objectExpr, method, argumentExpressions);
return result;
private bool IsConvertibleMember(Expression expression)
return expression.NodeType == ExpressionType.MemberAccess;
private bool IsParseableMemberExpression(Expression expression)
if (expression is MemberExpression memberExpression)
return false;
private bool IsDateTimeExpression(MemberExpression expression)
Now)");
private Expression CombineExpressions(Expression left, Expression right, ExpressionType type)
switch (type)
case ExpressionType.And: return Expression.And(left, right);
case ExpressionType.AndAlso: return Expression.AndAlso(left, right);
case ExpressionType.Equal: return Expression.Equal(left, right);
case ExpressionType.ExclusiveOr: return Expression.ExclusiveOr(left, right);
case ExpressionType.GreaterThan: return Expression.GreaterThan(left, right);
case ExpressionType.GreaterThanOrEqual: return Expression.GreaterThanOrEqual(left, right);
case ExpressionType.LessThan: return Expression.LessThan(left, right);
case ExpressionType.LessThanOrEqual: return Expression.LessThanOrEqual(left, right);
case ExpressionType.NotEqual: return Expression.NotEqual(left, right);
case ExpressionType.Or: return Expression.Or(left, right);
case ExpressionType.OrElse: return Expression.OrElse(left, right);
default:
throw new Exception($"Unsupported expression type: type.ToString()");
Where MappedConverter<Dto, Entity>
is the following:
public class MappedConverter<Dto, Entity> where Dto : class, new() where Entity : class, new()
public List<Mapping<Dto, Entity>> Mappings get; set;
public MappedConverter(params Mapping<Dto, Entity> maps)
this.Mappings = maps.ToList();
public Mapping<Dto, Entity> GetMappingFromMemberName<T>(string name)
if (typeof(T) == typeof(Dto))
return this.Mappings.SingleOrDefault(x => x.DtoProperty.Name == name);
else if (typeof(T) == typeof(Entity))
return this.Mappings.SingleOrDefault(x => x.EntityProperty.Name == name);
throw new Exception($"Cannot get mapping for typeof(T).Name from MappedConverter<typeof(Dto).Name, typeof(Entity).Name>");
And Mapping<Dto, Entity>
is the following:
public class Mapping<Dto, Entity>
public PropertyInfo DtoProperty get;
public PropertyInfo EntityProperty get;
Notes:
The conversion only has to handle Expressions of the form Expression<Func<Dto, bool>>
I am specifically looking for ways to improve the IsParseableMemberExpression
and IsDateTimeExpression
methods
IsParseableMemberExpression
uses Regex
to see if the expression is representing a variable in the full expression. e.g. variable
in x => x.Value == variable
c# linq-expressions
Using Entity Framework we have a number of entities, which we want to hide from the rest of the calling code to remove the direct dependency on the database. We're doing so by using DTOs, which for the most part are identical to the entities. We also have an abstract generic provider and persister pattern which handle the conversion between the dtos and the entities, and all of the database interaction. The general loading method of the base abstract provider uses the following signature:
protected virtual IEnumerable<TDto> Load(Expression<Func<TEntity, bool>> filter)
So any provider that extends the abstract can look like this:
public IEnumerable<FooDto> GetSomeFilteredData(IEnumerable<string> identifiers)
return base.Load(entity => identifiers.Contains(entity.SomeProperty));
Therefore hiding the entities from the calling code.
This pattern works well for us, but I have been investigating how to manipulate expressions so that I could put the following in the base provider:
public virtual IEnumerable<TDto> Load(Expression<Func<TDto, bool>> filter)
The base provider would then convert the Expression<Func<TDto, bool>>
into the equivalent entity expression, and load the data as before, therefore removing the need to write tedious methods and give us more control in the calling code.
The end result has been the following ExpressionConverter
public class ExpressionConverter<Dto, Entity>
where Dto : class, new() where Entity : class, new()
private MappedConverter<Dto, Entity> converter;
public ExpressionConverter(MappedConverter<Dto, Entity> converter)
this.converter = converter;
public Expression<Func<Entity, bool>> ConvertExpr(Expression<Func<Dto, bool>> expr)
ParameterExpression entityParam = Expression.Parameter(typeof(Entity), "e");
Expression entityExpression = this.ConvertExpression(expr.Body, entityParam);
var result = Expression.Lambda<Func<Entity, bool>>(entityExpression, entityParam);
return result;
private Expression ConvertExpression(Expression expression, ParameterExpression entityParam)
if (expression is BinaryExpression binary)
return this.ConvertBinaryExpression(binary, entityParam);
if (expression is MemberExpression member)
return this.ConvertMemberExpression(member, entityParam);
if (expression is MethodCallExpression method)
return this.ConvertMethodCallExpression(method, entityParam);
return expression;
private Expression ConvertBinaryExpression(BinaryExpression binary, ParameterExpression param)
Expression left = this.ConvertExpression(binary.Left, param);
Expression right = this.ConvertExpression(binary.Right, param);
ExpressionType nodeType = binary.NodeType;
return this.CombineExpressions(left, right, nodeType);
private Expression ConvertMemberExpression(Expression expression, ParameterExpression entityParam)
if (this.IsParseableMemberExpression(expression))
MemberExpression memberExpr = expression as MemberExpression;
var value = Expression.Lambda(memberExpr).Compile().DynamicInvoke();
return Expression.Constant(value);
else if (this.IsConvertibleMember(expression))
MemberInfo dtoMember = (expression as MemberExpression).Member;
Mapping<Dto, Entity> mapping = this.converter.GetMappingFromMemberName<Dto>(dtoMember.Name);
MemberExpression entityMemberExpr = Expression.MakeMemberAccess(entityParam, mapping.EntityProperty);
return entityMemberExpr;
return expression;
private Expression ConvertMethodCallExpression(MethodCallExpression expression, ParameterExpression entityParam)
Expression objectExpr = this.ConvertMemberExpression(expression.Object, entityParam);
IEnumerable<Expression> argumentExpressions = expression.Arguments.Select(x => this.ConvertMemberExpression(x, entityParam));
MethodInfo method = expression.Method;
Expression result = Expression.Call(objectExpr, method, argumentExpressions);
return result;
private bool IsConvertibleMember(Expression expression)
return expression.NodeType == ExpressionType.MemberAccess;
private bool IsParseableMemberExpression(Expression expression)
if (expression is MemberExpression memberExpression)
return false;
private bool IsDateTimeExpression(MemberExpression expression)
Now)");
private Expression CombineExpressions(Expression left, Expression right, ExpressionType type)
switch (type)
case ExpressionType.And: return Expression.And(left, right);
case ExpressionType.AndAlso: return Expression.AndAlso(left, right);
case ExpressionType.Equal: return Expression.Equal(left, right);
case ExpressionType.ExclusiveOr: return Expression.ExclusiveOr(left, right);
case ExpressionType.GreaterThan: return Expression.GreaterThan(left, right);
case ExpressionType.GreaterThanOrEqual: return Expression.GreaterThanOrEqual(left, right);
case ExpressionType.LessThan: return Expression.LessThan(left, right);
case ExpressionType.LessThanOrEqual: return Expression.LessThanOrEqual(left, right);
case ExpressionType.NotEqual: return Expression.NotEqual(left, right);
case ExpressionType.Or: return Expression.Or(left, right);
case ExpressionType.OrElse: return Expression.OrElse(left, right);
default:
throw new Exception($"Unsupported expression type: type.ToString()");
Where MappedConverter<Dto, Entity>
is the following:
public class MappedConverter<Dto, Entity> where Dto : class, new() where Entity : class, new()
public List<Mapping<Dto, Entity>> Mappings get; set;
public MappedConverter(params Mapping<Dto, Entity> maps)
this.Mappings = maps.ToList();
public Mapping<Dto, Entity> GetMappingFromMemberName<T>(string name)
if (typeof(T) == typeof(Dto))
return this.Mappings.SingleOrDefault(x => x.DtoProperty.Name == name);
else if (typeof(T) == typeof(Entity))
return this.Mappings.SingleOrDefault(x => x.EntityProperty.Name == name);
throw new Exception($"Cannot get mapping for typeof(T).Name from MappedConverter<typeof(Dto).Name, typeof(Entity).Name>");
And Mapping<Dto, Entity>
is the following:
public class Mapping<Dto, Entity>
public PropertyInfo DtoProperty get;
public PropertyInfo EntityProperty get;
Notes:
The conversion only has to handle Expressions of the form Expression<Func<Dto, bool>>
I am specifically looking for ways to improve the IsParseableMemberExpression
and IsDateTimeExpression
methods
IsParseableMemberExpression
uses Regex
to see if the expression is representing a variable in the full expression. e.g. variable
in x => x.Value == variable
c# linq-expressions
asked May 2 at 9:13
JChristen
311
311
1
I agree that having to referenceTEntity
is compromising your otherwise quite extensive separation of layers. However, the issue in only usingTDto
is that there is no guarantee that its relatedTEntity
has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues.
â Flater
May 2 at 11:26
@Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety
â JChristen
May 2 at 12:43
1
That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer.
â Flater
May 2 at 12:51
1
The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts.
â Flater
May 2 at 12:54
And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand.
â JChristen
May 3 at 6:27
add a comment |Â
1
I agree that having to referenceTEntity
is compromising your otherwise quite extensive separation of layers. However, the issue in only usingTDto
is that there is no guarantee that its relatedTEntity
has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues.
â Flater
May 2 at 11:26
@Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety
â JChristen
May 2 at 12:43
1
That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer.
â Flater
May 2 at 12:51
1
The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts.
â Flater
May 2 at 12:54
And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand.
â JChristen
May 3 at 6:27
1
1
I agree that having to reference
TEntity
is compromising your otherwise quite extensive separation of layers. However, the issue in only using TDto
is that there is no guarantee that its related TEntity
has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues.â Flater
May 2 at 11:26
I agree that having to reference
TEntity
is compromising your otherwise quite extensive separation of layers. However, the issue in only using TDto
is that there is no guarantee that its related TEntity
has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues.â Flater
May 2 at 11:26
@Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety
â JChristen
May 2 at 12:43
@Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety
â JChristen
May 2 at 12:43
1
1
That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer.
â Flater
May 2 at 12:51
That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer.
â Flater
May 2 at 12:51
1
1
The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts.
â Flater
May 2 at 12:54
The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts.
â Flater
May 2 at 12:54
And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand.
â JChristen
May 3 at 6:27
And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand.
â JChristen
May 3 at 6:27
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193435%2fexpression-conversion%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
I agree that having to reference
TEntity
is compromising your otherwise quite extensive separation of layers. However, the issue in only usingTDto
is that there is no guarantee that its relatedTEntity
has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues.â Flater
May 2 at 11:26
@Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety
â JChristen
May 2 at 12:43
1
That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer.
â Flater
May 2 at 12:51
1
The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts.
â Flater
May 2 at 12:54
And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand.
â JChristen
May 3 at 6:27