I'm developing and multi-tier application solution and my solution looks like this
Business.DomainObject
Business.Process (* Actual business layer)
Data.Mapper
Data.Repository
Data.Sql.Entity (* Actual data layer that has .dbml file)
My Business.Process project (business layer) is only knows Business.DomainObject and Data.Repository projects, so doesn't know and not in relationship with Data.Sql.Entity project
I'm sending business (domain) objects to repository and do mapping inside this project, then I'm doing CRUD process inside repository layer with relationship data layers.
My traditional domain object is like this, it has only properties
public class UserBO
{
public string Email { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
My business layer class is like this,
Repository r = new UserRepository();
r.Insert(myUserDomainObject);
Everything is OK, but I have some problems about my "predicate" queries.
I have a method in my repository interface
bool IsExists(Expression<Func<BusinessObject,bool>> predicate);
and use it in my business layer like this;
Repository r = new UserRepository(); <br/>
r.IsExists(p => p.Email.Equals("email address"));
as you can see, its parameter is "business object", but my actual repository (that connects with database) using "dataobject" that is inside my dbml file.
public override bool IsExists(Expression<Func<DataObject, bool>> predicate){
return _table.Where(predicate).Any();
}
I want convert this two predicate,
for example I'll send UserBO and convert to UserDataObject
how can i do that?
You have to analyze the parameters and the body of the LambdaExpression and build a new one. You can access the body it using predicate.Body and the parameters using predicate.Parameters. The new expression has a parameter of type UserBO instead of UserDataObject. In addition, the ParameterExpression(s) in the body must use the new parameter.
Of course, this assumes that it is a simple scenario where the BO and the DataObject have the same properties. More complex transformations are possible but need a deeper analysis of the expression tree.
In order to vive you a starting Point, I out together a sample with a very similar BusinessObject and DataObject. The key component is a class derived from ExpressionVisitor that performs the conversion:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
namespace ConvertExpression
{
public class BusinessObject
{
public int Value { get; set; }
}
public class DataObject
{
public int Value { get; set; }
}
internal class ExpressionConverter : ExpressionVisitor
{
public Expression Convert(Expression expr)
{
return Visit(expr);
}
private ParameterExpression replaceParam;
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T) == typeof(Func<BusinessObject, bool>))
{
replaceParam = Expression.Parameter(typeof(DataObject), "p");
return Expression.Lambda<Func<DataObject, bool>>(Visit(node.Body), replaceParam);
}
return base.VisitLambda<T>(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(BusinessObject))
return replaceParam; // Expression.Parameter(typeof(DataObject), "p");
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType == typeof(BusinessObject))
{
var member = typeof(DataObject).GetMember(node.Member.Name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).FirstOrDefault();
if (member == null)
throw new InvalidOperationException("Cannot identify corresponding member of DataObject");
return Expression.MakeMemberAccess(Visit(node.Expression), member);
}
return base.VisitMember(node);
}
}
public class ConvertExpression
{
public static void Main()
{
BusinessObject[] bos = { new BusinessObject() { Value = 123 }, new BusinessObject() { Value = 246 } };
DataObject[] dos = { new DataObject() { Value = 123 }, new DataObject() { Value = 246 } };
Expression<Func<BusinessObject, bool>> boExpr = x => x.Value == 123;
var conv = new ExpressionConverter();
Expression<Func<DataObject, bool>> doExpr = (Expression<Func<DataObject, bool>>) conv.Convert(boExpr);
var selBos = bos.Where(boExpr.Compile());
Console.WriteLine("Matching BusinessObjects: {0}", selBos.Count());
foreach (var bo in selBos)
Console.WriteLine(bo.Value);
var compDoExpr = doExpr.Compile();
var selDos = dos.Where(doExpr.Compile());
Console.WriteLine("Matching DataObjects: {0}", selDos.Count());
foreach (var dataObj in selDos)
Console.WriteLine(dataObj.Value);
Console.ReadLine();
}
}
}
If you need it i created a small fluent library to create lambda functions on the fly without directly coping with System.Linq.Expressions. Between the other things it contains exactly what you need, just to give an example to accomply the compare on the city you could do like this:
//Cached somewhere
var compareLambda= ExpressionUtil.GetComparer<CityBO>(p =>
p.Id.Value,ComparaisonOperator.Equal);
//Then in the execution
Repository.IsExists(p=>compareLambda(p,city id));
The code and documentation are here: Kendar Expression Builder with the unit tests that are pretty self explanatory
While the nuget package is here: Nuget Expression Builder
your code works well with single property like
Repository.IsExists(p => p.Email.Equals("abc#xyz.com"));
but as i mentioned, my domain object classes have some nested class properties like "City", when i try this
Repository.IsExists(p => p.City.Id.Equals(city id));
it throws an exception;
Property
'Business.DomainObject.SystemCommon.ExtendedProperty.PrimaryKey
Id' is not defined for type
'Data.Sql.Entity.LinqDataContext.City'
i understand this exception, because my City class like this;
public class CityBO : IDomainObject
{
public PrimaryKey Id { get; set; }
public string Name { get; set; }
public EntityReferance<CountryBO> Country { get; set; }
public LocationInfoBO Location { get; set; }
public StatusInfo Status { get; set; }
}
and PrimaryKey property (class) is
public class PrimaryKey
{
public string Value { get; set; }
}
I'm using this property, because of my entities have different Primary Key type and also I'm planning implementation of Nosql db in future..
i want thank you for your code, it is useful for me when I'm query with single property.
regards
giving credit to #Markus, here is a generic converter class...
internal class ExpressionConverter<TInput, TOutput> : ExpressionVisitor
{
public Expression<Func<TOutput, bool>> Convert(Expression<Func<TInput, bool>> expression)
{
return (Expression<Func<TOutput, bool>>)Visit(expression);
}
private ParameterExpression replaceParam;
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T) == typeof(Func<TInput, bool>))
{
replaceParam = Expression.Parameter(typeof(TOutput), "p");
return Expression.Lambda<Func<TOutput, bool>>(Visit(node.Body), replaceParam);
}
return base.VisitLambda<T>(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(TInput))
return replaceParam;
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType == typeof(TInput))
{
var member = typeof(TOutput).GetMember(node.Member.Name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).FirstOrDefault();
if (member == null)
throw new InvalidOperationException("Cannot identify corresponding member of DataObject");
return Expression.MakeMemberAccess(Visit(node.Expression), member);
}
return base.VisitMember(node);
}
}
Related
I'm writing an extension method in order to do translation with Automapper.
I have some classes :
public class TranslatableClass : ITranslatable<TranslationClass>
{
public string Id { get; set; }
public string Label { get; set; }
public string Description { get; set; }
public List<TranslationClass> Translations { get; set; }
public string OtherEntityId { get; set; }
public string OtherEntityLabel { get; set; }
public List<OtherEntityTranslation> OtherEntityTranslations { get; set; }
}
public class TranslationClass : ITranslation
{
public Guid LanguageId { get; set; }
public string Label { get; set; }
public string Description { get; set; }
}
public class TranslatedClass
{
public string Id { get; set; }
public string Label { get; set; }
public string Description { get; set; }
public string OtherEntityLabel { get; set; }
}
public class OtherEntityTranslation : ITranslation
{
public string Label { get; set; }
public Guid LanguageId { get; set; }
}
I'd like to get an extension method like this one :
cfg.CreateMap<TranslatableClass, TranslatedClass>()
.ForMember(t => t.OtherEntityLabel, opt => opt.MapFromTranslation(t => t.OtherEntityTranslations, oet => oet.Label));
And my extension method looks like this one
public static void MapFromTranslation<TSource, TDestination, TMember, TTranslation>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt, Func<TSource, IEnumerable<TTranslation>> getTranslations, Func<TTranslation, string> getValue)
where TTranslation : ITranslation
{
opt.MapFrom((src, _, _, context) =>
{
string result = null; // here is the pain point ; I'd like to get the value as if I was automapper
if (context.Options.Items.TryGetValue(LANGUAGE, out object contextLanguage) && contextLanguage is Guid languageId)
{
var translations = getTranslations(src);
var translation = translations.FirstOrDefault(t => t.LanguageId == languageId);
if (translation != null)
{
result = getValue(translation);
}
}
return result;
});
}
The issue I'm facing is I can't find a nice way to get the default behavior of AutoMapper when I don't have a translation. In this implementation, if I don't find a translation for my language, the value will be null while it should be the value of the source object (which is the default value).
I try to put PreCondition before the MapFrom but that doesn't map the property so I get null too.
I can try to get the value from the source object with reflexion but I will lose all the capabilities of Automapper like naming convention and other stuffs.
public static void MapFromTranslation<TSource, TDestination, TMember, TTranslation>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt, Func<TSource, IEnumerable<TTranslation>> getTranslations, Func<TTranslation, string> getValue)
where TTranslation : ITranslation
{
var destinationMember = opt.DestinationMember as PropertyInfo;
var source = typeof(TSource);
var sourceProperty = source.GetProperty(destinationMember.Name);
if (sourceProperty != null)
{
opt.MapFrom((src, _, _, context) =>
{
string result = sourceProperty.GetValue(src) as string; // Get value from source as if it was the mapper
if (context.Options.Items.TryGetValue(LANGUAGE, out object contextLanguage) && contextLanguage is Guid languageId)
{
var translations = getTranslations(src);
if (translations != null)
{
var translation = translations.FirstOrDefault(t => t.LanguageId == languageId);
if (translation != null)
{
var value = getValue(translation);
if (!String.IsNullOrWhiteSpace(value))
{
result = value;
}
}
}
}
return result;
});
}
else
{
throw new Exception($"Can't map property {opt.DestinationMember.Name} from {source.Name}");
}
}
Let's re-define configuration without using extension method, trying to simplify things. Following mapping example, we can implement custom IValueResolver
cfg.CreateMap<TranslatableClass, TranslatedClass>()
.ForMember(dest => dest.OtherEntityLabel, opt => opt.MapFrom<CustomResolver>();
Implementing IValueResolver<TranslatableClass, TranslatedClass, string> interface:
public class CustomResolver: IValueResolver<TranslatableClass, TranslatedClass, string>
{
public string Resolve(TranslatableClass source, TranslatedClass destination, string member, ResolutionContext context)
{
string result = source.Label; /* needed effect! */
/* can we simplify this condition? */
if (context.Options.Items.TryGetValue(source.OtherEntityLabel, out object contextLanguage)
&& contextLanguage is Guid languageId)
{
var translations = source.OtherEntityTranslations;
var translation = translations.FirstOrDefault(t => t.LanguageId == languageId);
if (translation != null)
{
result = translation.Label;
};
}
return result;
}
}
Here comes same logic from
MapFromTranslation<TSource, TDestination, TMember, ... extension method provided below, let's put that logic right - we map TSource as TranslatableClass to TDestination as TranslatedClass.
Also, I believe that if (context.Options.Items.TryGetValue(...)) should be removed for simplicity too (are we trying to get languageId here?)
So, by using Custom Value Resolvers feature we can simplify mapper configuration and refactor for test coverage or debugging needs.
Update
I do want to use this extensions method on 50 others entity and I
won't write custom resolver for each one
Using Expressions instead of reflection should help to implement 'generic solution'. Solution is to define a cache of mapping expressions to access TSource and TDestination properties.
public static class MappingCache<TFirst, TSecond>
{
static MappingCache()
{
var first = Expression.Parameter(typeof(TFirst), "first");
var second = Expression.Parameter(typeof(TSecond), "second");
var secondSetExpression = MappingCache.GetSetExpression(second, first);
var blockExpression = Expression.Block(first, second, secondSetExpression, first);
Map = Expression.Lambda<Func<TFirst, TSecond, TFirst>>(blockExpression, first, second).Compile();
}
public static Func<TFirst, TSecond, TFirst> Map { get; private set; }
}
Next, let's try to define generic lambda-expressions for both
Func<TTranslation, string> getValue and getTranslations(...
e.g.:
public static Expression GetSetExpression(ParameterExpression sourceExpression, params ParameterExpression[] destinationExpressions)
{
/** AutoMapper also can be used here */
/* compile here all (source)=>(destination) expressions */
var destination = destinationExpressions
.Select(parameter => new
{
Parameter = parameter,
Property = parameter.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.FirstOrDefault(property => IsWritable(property) && IsOfType(property, sourceExpression.Type))
})
.FirstOrDefault(parameter => parameter.Property != null);
if (destination == null)
{
throw new InvalidOperationException(string.Format("No writable property of type {0} found in types {1}.", sourceExpression.Type.FullName, string.Join(", ", destinationExpressions.Select(parameter => parameter.Type.FullName))));
}
/* Here is the generic version of mapping code! */
return Expression.IfThen(
Expression.Not(Expression.Equal(destination.Parameter, Expression.Constant(null))),
Expression.Call(destination.Parameter, destination.Property.GetSetMethod(), sourceExpression));
}
Next goes IsWritable(PropertyInfo property) that is used to check validate properties, try to implement convention-based property filtering (names, attributes, etc.) here
public static bool IsWritable(PropertyInfo property)
{
/* eliminating reflection code from extension method */
return property.CanWrite && !property.GetIndexParameters().Any();
}
Next IsOfType(PropertyInfo... and IsSubclassOf methods, - define simple rules of proper TSource->TDestination ways of mapping...
public static bool IsOfType(PropertyInfo property, Type type)
{
/* here AutoMapper could be used too, making filtering needed destination entities by following some convention */
return property.PropertyType == type || IsSubclassOf(type, property.PropertyType) || property.PropertyType.IsAssignableFrom(type);
}
public static bool IsSubclassOf(Type type, Type otherType)
{
return type.IsSubclassOf(otherType);
}
}
Trying to implement convention based mapping approach:
public static void MapFromTranslation<TSource, TDestination, TMember, TTranslation>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt, Expression<Func<TSource, TDestination, TMember, TTranslation>> mapping )
where TTranslation : ITranslation
Wiring around the Expression<Func<TSource,TDestination,TMember, TTranslation> mapping and the MappingCache<TSource,TDestination,TMember, TTranslation>.Map is the next step. Our lambda expression represents the property transformation intent generically (mapping,conversion,validating,navigating, etc...), and when compiled lambda called with parameters passed, we get the result of such transformation.
Expression:
MappingCache<TSource,TDestination,TMember, TTranslation>.GetSetExpression(first, second, third, proxy...
Function:
var result = MappingCache<TSource,TDestination,TMember, TTranslation>.Map(first,second,third,...
Keeping statically compiled lambda-delegates abstractions open, we can cover every needed mapping aspect with proper tests, - seems like the generic approach that could be used to address the question
Access default map function in MapFrom to fallback
(c) Dapper.Mapper + Tests
I'm building a generic interface to expose selected string properties out of a class, and then I want to search for a text inside every one of those fields, to check if it's a match.
Here's my IFieldExposer interface:
using System;
using System.Collections.Generic;
public interface IFieldExposer<T>
{
IEnumerable<Func<T, string>> GetFields();
}
Now, I implement it like this in my DataClass to expose the properties I would like to iterate. Note that I'm also exposing a property from my ChildClass:
using System;
using System.Collections.Generic;
class DataClass : IFieldExposer<DataClass>
{
public string PropertyOne { get; set; }
public string PropertyTwo { get; set; }
public ChildClass Child { get; set; }
public IEnumerable<Func<DataClass, string>> GetFields()
{
return new List<Func<DataClass, string>>
{
a => a.PropertyOne,
b => b.Child.PropertyThree
};
}
}
class ChildClass
{
public string PropertyThree { get; set; }
}
I've also created extension methods for IFieldExposer<T> because I want to keep it simple and be able to simply call obj.Match(text, ignoreCase) everywhere else in my code. This method should tell me if my object is a match for my text. Here's the code for the ExtensionClass, which isn't working as expected:
using System;
using System.Linq.Expressions;
using System.Reflection;
public static class ExtensionClass
{
public static bool Match<T>(this IFieldExposer<T> obj, string text, bool ignoreCase)
{
Func<bool> expression = Expression.Lambda<Func<bool>>(obj.CreateExpressionTree(text, ignoreCase)).Compile();
return expression();
}
private static Expression CreateExpressionTree<T>(this IFieldExposer<T> obj, string text, bool ignoreCase)
{
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
var exposedFields = obj.GetFields();
if (ignoreCase)
{
// How should I do convert these to lower too?
// exposedFields = exposedFields.Select(e => e.???.ToLower());
text = text.ToLower();
}
Expression textExp = Expression.Constant(text);
Expression orExpressions = Expression.Constant(false);
foreach (var field in exposedFields)
{
//How should I call the contains method on the string field?
Expression fieldExpression = Expression.Lambda<Func<string>>(Expression.Call(Expression.Constant(obj), field.Method)); //this doesn't work
Expression contains = Expression.Call(fieldExpression, containsMethod, textExp);
orExpressions = Expression.Or(orExpressions, contains);
}
return orExpressions;
}
}
Please check the comments in the code above. I would like to know how to convert all my string properties to lowercase (if desired) and how to call string.Contains in each one of them. I get this error when I create my fieldExpression:
Method 'System.String <GetFields>b__12_0(DataClass)' declared on type 'DataClass+<>c' cannot be called with instance of type 'DataClass'
I don't have experience working with Expression Trees. I've spent hours reading docs and other answers for similar issues but I still can't understand how to achieve what I want... I have no clue what to do now.
I'm testing this in a console app so here's the main class if you want to build it yourself:
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
var data = new DataClass
{
PropertyOne = "Lorem",
PropertyTwo = "Ipsum",
Child = new ChildClass
{
PropertyThree = "Dolor"
}
};
var dataList = new List<DataClass> { data };
var results = dataList.Where(d => d.Match("dolor", true));
}
}
EDIT
I forgot to mention that my dataList should be IQueryable and I want to execute my code in SQL, that's why I'm trying to build the expression trees myself. So it appears my example code should be:
var dataList = new List<DataClass> { data };
var query = dataList.AsQueryable();
var results = query.Where(ExtensionClass.Match<DataClass>("lorem dolor"));
while my method becomes: (I'm following #sjb-sjb's answer and changed the GetFields() method in IFieldExposer<T> to a SelectedFields property)
public static Expression<Func<T, bool>> Match<T>(string text, bool ignoreCase) where T : IFieldExposer<T>
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "obj");
MemberExpression selectedFieldsExp = Expression.Property(parameter, "SelectedFields");
LambdaExpression lambda = Expression.Lambda(selectedFieldsExp, parameter).Compile();
[...]
}
And then it seems that I have to dinamically call selectedFieldsExp with Expression.Lambda. I came up with:
Expression.Lambda(selectedFieldsExp, parameter).Compile();
and that works, but I don't know how to properly call DynamicInvoke() for the lambda expression.
It throws Parameter count mismatch. if I call it without parameters and Object of type 'System.Linq.Expressions.TypedParameterExpression' cannot be converted to type 'DataClass'. if I do DynamicInvoke(parameter).
Any ideas?
Before getting to the implementation, there are some design flaws that needs to be fixed.
First, almost all query providers (except LINQ to Object which simply compiles the lambda expressions to delegates and executes them) don't support invocation expressions and custom (unknown) methods. That's because they do not execute the expressions, but translate them to something else (SQL for instance), and translation is based on pre knowledge.
One example of invocation expression are Func<...> delegates. So the first thing you should do is to use Expression<Func<...>> wherever you currently have Func<...>.
Second, the query expression trees are built statically, i.e. there is no real object instance you can use to obtain the metadata, so the idea of IFieldExposer<T> won't work. You'd need a statically exposed list of expressions like this:
class DataClass //: IFieldExposer<DataClass>
{
// ...
public static IEnumerable<Expression<Func<DataClass, string>>> GetFields()
{
return new List<Expression<Func<DataClass, string>>>
{
a => a.PropertyOne,
b => b.Child.PropertyThree
};
}
}
Then the signature of the method in question could be like this
public static Expression<Func<T, bool>> Match<T>(
this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
with usage like this
var dataList = new List<DataClass> { data };
var query = dataList.AsQueryable()
.Where(DataClass.GetFields().Match("lorem", true));
Now the implementation. The desired expression could be built purely with Expression class methods, but I'll show you an easier (IMHO) method, which composes expression from compile time expression by replacing the parameter(s) with other expression(s).
All you need is a small helper utility method for replacing lambda expression parameter with another expression:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
Internally it uses ExpressionVistor to find each instance of the passed ParameterExpression and replace it with the passed Expression.
With this helper method, the implementation could be like this:
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> Match<T>(this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
{
Expression<Func<string, bool>> match;
if (ignoreCase)
{
text = text.ToLower();
match = input => input.ToLower().Contains(text);
}
else
{
match = input => input.Contains(text);
}
// T source =>
var parameter = Expression.Parameter(typeof(T), "source");
Expression anyMatch = null;
foreach (var field in fields)
{
// a.PropertyOne --> source.PropertyOne
// b.Child.PropertyThree --> source.Child.PropertyThree
var fieldAccess = field.Body.ReplaceParameter(field.Parameters[0], parameter);
// input --> source.PropertyOne
// input --> source.Child.PropertyThree
var fieldMatch = match.Body.ReplaceParameter(match.Parameters[0], fieldAccess);
// matchA || matchB
anyMatch = anyMatch == null ? fieldMatch : Expression.OrElse(anyMatch, fieldMatch);
}
if (anyMatch == null) anyMatch = Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(anyMatch, parameter);
}
}
The input => input.ToLower().Contains(text) or input => input.Contains(text) is our compile time match expression, which we then replace the input parameter with the body of the passed Expression<Func<T, string>> lambda expressions, with their parameter replaced with a common parameter used in the final expression. The resulting bool expressions are combined with Expression.OrElse which is the equivalent of the C# || operator (while Expression.Or is for bitwise | operator and in general should not be used with logical operations). Same btw for && - use Expression.AndAlso and not Expression.And which is for bitwise &.
This process is pretty much the expression equivalent of the string.Replace. In case the explanations and code comments are not enough, you can step through the code and see the exact expression transformations and expression building process.
There is no need to get into the complexities of dynamically creating an Expression, because you can just invoke the Func delegate directly:
public interface IFieldExposer<T>
{
IEnumerable<Func<T,string>> SelectedFields { get; }
}
public static class FieldExposerExtensions
{
public static IEnumerable<Func<T,string>> MatchIgnoreCase<T>( this IEnumerable<Func<T,string>> stringProperties, T source, string matchText)
{
return stringProperties.Where(stringProperty => String.Equals( stringProperty( source), matchText, StringComparison.OrdinalIgnoreCase));
}
}
class DataClass : IFieldExposer<DataClass>
{
public string PropertyOne { get; set; }
public string PropertyTwo { get; set; }
public ChildClass Child { get; set; }
public IEnumerable<Func<DataClass, string>> SelectedFields {
get {
return new Func<DataClass, string>[] { #this => #this.PropertyOne, #this => #this.Child.PropertyThree };
}
}
public override string ToString() => this.PropertyOne + " " + this.PropertyTwo + " " + this.Child.PropertyThree;
}
class ChildClass
{
public string PropertyThree { get; set; }
}
Then to use it,
class Program
{
static void Main(string[] args)
{
var data = new DataClass {
PropertyOne = "Lorem",
PropertyTwo = "Ipsum",
Child = new ChildClass {
PropertyThree = "Dolor"
}
};
var data2 = new DataClass {
PropertyOne = "lorem",
PropertyTwo = "ipsum",
Child = new ChildClass {
PropertyThree = "doloreusement"
}
};
var dataList = new List<DataClass>() { data, data2 };
IEnumerable<DataClass> results = dataList.Where( d => d.SelectedFields.MatchIgnoreCase( d, "lorem").Any());
foreach (DataClass source in results) {
Console.WriteLine(source.ToString());
}
Console.ReadKey();
}
}
Following up on my comment above, I think you could do it like this:
class DataClass
{
…
static public Expression<Func<DataClass,bool>> MatchSelectedFields( string text, bool ignoreCase)
{
return #this => (
String.Equals( text, #this.PropertyOne, (ignoreCase? StringComparison.OrdinalIgnoreCase: StringComparison.Ordinal))
|| String.Equals( text, #this.Child.PropertyThree, (ignoreCase? StringComparison.OrdinalIgnoreCase: StringComparison.Ordinal))
);
}
}
Then the query is just
Expression<Func<DataClass,bool>> match = DataClass.MatchSelectedFields( "lorem", ignoreCase);
IEnumerable<DataClass> results = dataList.Where( d => match(d));
I wouldn't usually post a second answer but I thought it would be useful to see how to avoid dynamic modification of Expressions.
Caveat: I didn't actually try to compile it.
I would like to extend an expression (Expression<Func<TModel, TProperty>>):
class CustomModel
{
public BlobModel Blob { get; set; }
public class BlobModel
{
public string SubBlob { get; set; }
}
}
var expression = model => model.Blog;
var subBlobExpression = expression.???.SubBlob;
It is possible?
The goal is to use the validation in a shared partialview (ASP.NET MVC project).
The htmlAttributes in the ValidationMessageFor method doesn't work and use the model without Expression either!
Unclear what you exactly want and what limits you have, BUT:
// Taken (with some cuts) from https://stackoverflow.com/a/32007349/613130
// A simple expression visitor to replace some nodes of an expression
// with some other nodes. Can be used with anything, not only with
// ParameterExpression
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
public static Expression<Func<T, TProp2>> ExtendExpression<T, TProp1, TProp2>(Expression<Func<T, TProp1>> exp1, Expression<Func<TProp1, TProp2>> exp2)
{
Expression body3 = new SimpleExpressionReplacer(exp2.Parameters[0], exp1.Body).Visit(exp2.Body);
Expression<Func<T, TProp2>> exp3 = Expression.Lambda<Func<T, TProp2>>(body3, exp1.Parameters);
return exp3;
}
Use it like:
Expression<Func<CustomModel, CustomModel.BlobModel>> expression = model => model.Blob;
var exp2 = ExtendExpression(expression, x => x.SubBlob);
I use an expression replacer (SimpleExpressionReplacer) to "merge" together the two expresion (expression and x => x.SubBlob).
I have the following:
public class P1 {
public P2 P2 { get; set; }
public P4 P4 { get; set; }
}
public class P2 {
public P3 P3 { get; set; }
}
public class P3 { }
public class P4 { }
I need a Mapper for P1 as follows:
Mapper mapper = new Mapper<P1>();
But I would like to use it as follows:
Mapper<P1> mapper = Mapper.For<P1>()
.Add(p1 => p1.P2).And(p2 => p2.P3)
.Add(p1 => p1.P4);
So I have:
public class Mapper {
public static Mapper<T> For<T>() {
return new Mapper<T>();
}
}
public class Mapper<T> {
private List<LambdaExpression> _expressions = new List<LambdaExpression>();
public Mapper<T> Add<K>(Expression<Func<T, K>> expression) {
// Add expression to expressions
return this;
}
public Mapper<T> And<K>(Expression<Func<T, K>> expression) {
// Add expression to expressions
return this;
}
}
My problem is how to deal with Child Properties.
Note that my Mapper code only allows to do this:
Mapper<P1> mapper = Mapper.For<P1>()
.Add(p1 => p1.P2).And(p1 => p1.P2.P3);
And not this:
Mapper<P1> mapper = Mapper.For<P1>()
.Add(p1 => p1.P2).And(p2 => p2.P3);
I think the methods should return Mapper but in fact I want to create a Mapper ...
K is only a way to define the expression when calling a method.
Does anyone knows how to do this?
UPDATE
Answer with ContinuationMapper:
public class Mapper<T> {
protected List<LambdaExpression> Paths { get; set; } = new List<LambdaExpression>();
public ContinuationMapper<T, TCont> Add<TCont>(Expression<Func<T, TCont>> path) {
Paths.Add(path);
return new ContinuationMapper<T, TCont>(Paths);
}
}
public class ContinuationMapper<TBase, TCurrent> : Mapper<TBase> {
public ContinuationMapper(List<LambdaExpression> paths) {
Paths = paths;
}
public ContinuationMapper<TBase, TNext> And<TNext>(Expression<Func<TCurrent, TNext>> path) {
base.Paths.Add(path);
return new ContinuationMapper<TBase, TNext>(base.Paths);
}
}
I see the following problems with your current approach:
It shouldn't be possible to do an And before an Add.
At the end of an And (or an Add), you have two types of type information: The base type for a new Add, and the continuation type for a new And. You cannot "store" that type of information at compile-time in a generic type with only one generic parameter.
Luckily, the type system can help here, if we split your one class in two:
public class Mapper<T>
{
public ContinuationMapper<T, TCont> Add<TCont>(Expression<Func<T, TCont>> expression) {
// ...
return new ContinuationMapper<T, TCont>(...);
}
}
public class ContinuationMapper<TBase, TCurrent> : Mapper<TBase>
{
public ContinuationMapper<TBase, TNext> And<TNext>(Expression<Func<TCurrent, TNext>> expression) {
// ...
return new ContinuationMapper<TBase, TNext>(...);
}
}
Obviously, you will have to pass the state of your expression list to the new classes, which is left as an exercise to the reader. As an added bonus, you can even make your classes immutable, if you want.
In essence you don't want to bind the method types to the class type so you need to use 2 new generic types. sort of like the following.
public Mapper<T> Add<V,K>(Expression<Func<V, K>> expression) {
// Add expression to expressions
return this;
}
public Mapper<T> And<V,K>(Expression<Func<V, K>> expression) {
// Add expression to expressions
return this;
}
Example:
Mapper<P1> mapper = Mapper.For<P1>()
.Add((P1 p1) => p1.P2).And((P2 p2) => p2.P3)
.Add((P1 p1) => p1.P4);
If you want the Add to be tied to the T and the and to be tied to the last property you'll need to provide back a wrapper class like
Mapper<T,V>{
public Mapper<T> Add<V>(Expression<Func<T, V>> expression) {
// Add expression to expressions
return Mapper.Wrap<T,V>(this);
}
public Mapper<T> And<V>(Expression<Func<V, K>> expression) {
// Add expression to expressions
return this;
}
}
for access control purposes in a intensive DB use system I had to implement an objectset wrapper, where the AC will be checked.
The main objective is make this change preserving the existing code for database access, that is implemented with linq to entities all over the classes (there is no centralized layer for database).
The ObjectSetWrapper created is like that:
public class ObjectSetWrapper<TEntity> : IQueryable<TEntity> where TEntity : EntityObject
{
private IQueryable<TEntity> QueryableModel;
private ObjectSet<TEntity> ObjectSet;
public ObjectSetWrapper(ObjectSet<TEntity> objectSetModels)
{
this.QueryableModel = objectSetModels;
this.ObjectSet = objectSetModels;
}
public ObjectQuery<TEntity> Include(string path)
{
return this.ObjectSet.Include(path);
}
public void DeleteObject(TEntity #object)
{
this.ObjectSet.DeleteObject(#object);
}
public void AddObject(TEntity #object)
{
this.ObjectSet.AddObject(#object);
}
public IEnumerator<TEntity> GetEnumerator()
{
return QueryableModel.GetEnumerator();
}
public Type ElementType
{
get { return typeof(TEntity); }
}
public System.Linq.Expressions.Expression Expression
{
get { return this.QueryableModel.Expression; }
}
public IQueryProvider Provider
{
get { return this.QueryableModel.Provider; }
}
public void Attach(TEntity entity)
{
this.ObjectSet.Attach(entity);
}
public void Detach(TEntity entity)
{
this.ObjectSet.Detach(entity);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.QueryableModel.GetEnumerator();
}
}
It's really simple and works for simple queries, like that:
//db.Product is ObjectSetWrapper<Product>
var query = (from item in db.Product where item.Quantity > 0 select new { item.Id, item.Name, item.Value });
var itensList = query.Take(10).ToList();
But when I have subqueries like that:
//db.Product is ObjectSetWrapper<Product>
var query = (from item in db.Product
select new
{
Id = item.Id,
Name = item.Name,
SalesQuantity = (from sale in db.Sale where sale.ProductId == item.Id select sale.Id).Count()
}).OrderByDescending(x => x.SalesQuantity);
var productsList = query.Take(10).ToList();
I get NotSupportedException, saying I can't create a constant value of my inner query entity type:
Unable to create a constant value of type 'MyNamespace.Model.Sale'.
Only primitive types or enumeration types are supported in this
context.
How can I get my queries working? I don't really need to make my wrapper an ObjectSet type, I just need to use it in queries.
Updated
I have changed my class signature. Now it's also implementing IObjectSet<>, but I'm getting the same NotSupportedException:
public class ObjectSetWrapper<TEntity> : IQueryable<TEntity>, IObjectSet<TEntity> where TEntity : EntityObject
EDIT:
The problem is that the following LINQ construction is translated into LINQ expression containing your custom class inside (ObjectSetWrapper).
var query = (from item in db.Product
select new
{
Id = item.Id,
Name = item.Name,
SalesQuantity = (from sale in db.Sale where sale.ProductId == item.Id select sale.Id).Count()
}).OrderByDescending(x => x.SalesQuantity);
LINQ to Entities tries to convert this expression into SQL statement, but it has no idea how to deal with the custom classes (as well as custom methods).
The solution in such cases is to replace IQueryProvider with the custom one, which should intercept the query execution and translate LINQ expression, containing custom classes/methods into valid LINQ to Entities expression (which operates with entities and object sets).
Expression conversion is performed using the class, derived from ExpressionVisitor, which performs expression tree traversal, replacing relevant nodes, to the nodes which can be accepted by LINQ to Entities
Part 1 - IQueryWrapper
// Query wrapper interface - holds and underlying query
interface IQueryWrapper
{
IQueryable UnderlyingQueryable { get; }
}
Part 2 - Abstract QueryWrapperBase (not generic)
abstract class QueryWrapperBase : IQueryProvider, IQueryWrapper
{
public IQueryable UnderlyingQueryable { get; private set; }
class ObjectWrapperReplacer : ExpressionVisitor
{
public override Expression Visit(Expression node)
{
if (node == null || !typeof(IQueryWrapper).IsAssignableFrom(node.Type)) return base.Visit(node);
var wrapper = EvaluateExpression<IQueryWrapper>(node);
return Expression.Constant(wrapper.UnderlyingQueryable);
}
public static Expression FixExpression(Expression expression)
{
var replacer = new ObjectWrapperReplacer();
return replacer.Visit(expression);
}
private T EvaluateExpression<T>(Expression expression)
{
if (expression is ConstantExpression) return (T)((ConstantExpression)expression).Value;
var lambda = Expression.Lambda(expression);
return (T)lambda.Compile().DynamicInvoke();
}
}
protected QueryWrapperBase(IQueryable underlyingQueryable)
{
UnderlyingQueryable = underlyingQueryable;
}
public abstract IQueryable<TElement> CreateQuery<TElement>(Expression expression);
public abstract IQueryable CreateQuery(Expression expression);
public TResult Execute<TResult>(Expression expression)
{
return (TResult)Execute(expression);
}
public object Execute(Expression expression)
{
expression = ObjectWrapperReplacer.FixExpression(expression);
return typeof(IQueryable).IsAssignableFrom(expression.Type)
? ExecuteQueryable(expression)
: ExecuteNonQueryable(expression);
}
protected object ExecuteNonQueryable(Expression expression)
{
return UnderlyingQueryable.Provider.Execute(expression);
}
protected IQueryable ExecuteQueryable(Expression expression)
{
return UnderlyingQueryable.Provider.CreateQuery(expression);
}
}
Part 3 - Generic QueryWrapper<TElement>
class QueryWrapper<TElement> : QueryWrapperBase, IOrderedQueryable<TElement>
{
private static readonly MethodInfo MethodCreateQueryDef = GetMethodDefinition(q => q.CreateQuery<object>(null));
public QueryWrapper(IQueryable<TElement> underlyingQueryable) : this(null, underlyingQueryable)
{
}
protected QueryWrapper(Expression expression, IQueryable underlyingQueryable) : base(underlyingQueryable)
{
Expression = expression ?? Expression.Constant(this);
}
public virtual IEnumerator<TElement> GetEnumerator()
{
return ((IEnumerable<TElement>)Execute<IEnumerable>(Expression)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression { get; private set; }
public Type ElementType
{
get { return typeof(TElement); }
}
public IQueryProvider Provider
{
get { return this; }
}
public override IQueryable CreateQuery(Expression expression)
{
var expressionType = expression.Type;
var elementType = expressionType
.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()
.Single();
var createQueryMethod = MethodCreateQueryDef.MakeGenericMethod(elementType);
return (IQueryable)createQueryMethod.Invoke(this, new object[] { expression });
}
public override IQueryable<TNewElement> CreateQuery<TNewElement>(Expression expression)
{
return new QueryWrapper<TNewElement>(expression, UnderlyingQueryable);
}
private static MethodInfo GetMethodDefinition(Expression<Action<QueryWrapper<TElement>>> methodSelector)
{
var methodCallExpression = (MethodCallExpression)methodSelector.Body;
return methodCallExpression.Method.GetGenericMethodDefinition();
}
}
Part 4 - finally your ObjectSetWrapper
public class ObjectSetWrapper<TEntity> : IQueryable<TEntity>, IQueryWrapper where TEntity : class
{
private IQueryable<TEntity> QueryableModel;
private ObjectSet<TEntity> ObjectSet;
public ObjectSetWrapper(ObjectSet<TEntity> objectSetModels)
{
this.QueryableModel = new QueryWrapper<TEntity>(objectSetModels);
this.ObjectSet = objectSetModels;
}
public ObjectQuery<TEntity> Include(string path)
{
return this.ObjectSet.Include(path);
}
public void DeleteObject(TEntity #object)
{
this.ObjectSet.DeleteObject(#object);
}
public void AddObject(TEntity #object)
{
this.ObjectSet.AddObject(#object);
}
public IEnumerator<TEntity> GetEnumerator()
{
return QueryableModel.GetEnumerator();
}
public Type ElementType
{
get { return typeof(TEntity); }
}
public System.Linq.Expressions.Expression Expression
{
get { return this.QueryableModel.Expression; }
}
public IQueryProvider Provider
{
get { return this.QueryableModel.Provider; }
}
public void Attach(TEntity entity)
{
this.ObjectSet.Attach(entity);
}
public void Detach(TEntity entity)
{
this.ObjectSet.Detach(entity);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.QueryableModel.GetEnumerator();
}
IQueryable IQueryWrapper.UnderlyingQueryable
{
get { return this.ObjectSet; }
}
}
Your inner query fails because you are referencing another dataset when you should be traversing foreign keys:
SalesQuantity = item.Sales.Count()