I have a table like:
TABLE_XY
|ID |SOURCE|
|1 |value_aa|
|2 |other_aa|
|3 |eeeaa|
And the generated query should be:
select * from TABLE_XY where SOURCE like '%\_aa' ESCAPE '\'
I know that there are two options which would fit my needs
var result =
session.QueryOver<TableXy>()
.WhereRestrictionOn(x => x.Source)
.IsLike("%\_aa", MatchMode.Exact, '\\')
.List();
or
var result2 = session
.CreateCriteria<TableXy>()
.Add(LikeExpression("Source", "%\\_aa", MatchMode.Exact, '\\', false))
.List();
But I have to use a Linq based implementation. I'm working with dynamically created expression trees, which sometimes will be executed with the Linq to Object Provider or Linq to Nhibernate.
But currently only this method is supported:
var result = session
.Query<TableXy>()
.Where(x => NHibernate.Linq.SqlMethods.Like(x.Source, "%\\_aa"))
.ToList();
How can I extend the Nhibernate Linq Provider to support?
SqlMethods.IsLike(string source, string pattern, char? escape);
Okay, this is a pretty involved answer and there may very well be problems with it, but I was able to get a like operator with an escape piece working.
This involves a few steps, but basically what we're doing is adding a new type of HqlTreeNode that can handle the escape portion of the like operator.
Create an extension method that you'll use in LINQ queries. This method does not need an implementation--we'll provide that later:
public static class LinqExtensions
{
public static bool IsLikeWithEscapeChar(
this string input,
string like,
char? escapeChar)
{
throw new NotImplementedException();
}
}
Create an HqlEscape tree node that we will use to represent the escape portion of the like operator:
public class HqlEscape : HqlExpression
{
public HqlEscape(IASTFactory factory, params HqlTreeNode[] children)
: base(HqlSqlWalker.ESCAPE, "escape", factory, children)
{
}
}
Create an HqlLikeWithEscape tree node. The default HqlLike node cannot handle the escape part, so we need to create a new node that can handle three children:
public class HqlLikeWithEscape : HqlBooleanExpression
{
public HqlLikeWithEscape(IASTFactory factory, HqlExpression lhs, HqlExpression rhs, HqlEscape escape)
: base(HqlSqlWalker.LIKE, "like", factory, lhs, rhs, escape)
{
}
}
Create a generator for the IsLikeWithEscapeChar extension method we defined earlier. This class' responsibility is to take the information the method is invoked with and return an HQL tree structure that will ultimately be turned into SQL:
public class CustomLikeGenerator : BaseHqlGeneratorForMethod
{
public CustomLikeGenerator()
{
this.SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition(
() => LinqExtensions.IsLikeWithEscapeChar(null, null, null))
};
}
public override HqlTreeNode BuildHql(
MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Is there a better way to do this?
var factory = new ASTFactory(new ASTTreeAdaptor());
HqlTreeNode escapeCharNode = visitor.Visit(arguments[2]).AsExpression();
var escapeNode = new HqlEscape(factory, escapeCharNode);
HqlLikeWithEscape likeClauseNode =
new HqlLikeWithEscape(
factory,
visitor.Visit(arguments[0]).AsExpression(),
visitor.Visit(arguments[1]).AsExpression(),
escapeNode);
return likeClauseNode;
}
}
As you can see, we've utilized the new HQL tree nodes we defined earlier. The major downside to this approach is that it required me to manually create an ASTFactory and ASTTreeAdaptor. The use of these classes is usually encapsulated inside of HqlTreeBuilder, but HqlTreeBuilder doesn't lend itself to being subclassed. Would appreciate some input on this if someone has some advice.
Create a new LINQ to HQL generators registry. This class just just associates our extension method with the HQL implementation we provided in step 4:
public class LinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public LinqToHqlGeneratorsRegistry() : base()
{
RegisterGenerator(
ReflectionHelper.GetMethodDefinition(() => LinqExtensions.IsLikeWithEscapeChar(null, null, null)),
new CustomLikeGenerator());
}
}
Update your configuration to use the new LinqToHqlGeneratorsRegistry:
cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
(Finally) use your new extension method in a query:
session.Query<Person>().Where(p => p.FirstName.IsLikeWithEscapeChar("%Foo", '\\'))
Note that you need to specify the wildcard character. This could be smoothed out, but that wouldn't be too hard to do.
This is the first time I've extended HQL this way so again, there could be issues with this solution. I was only able to test on SQL Server, but I'm reasonable confident it should work given that it creates the same tree structure that an HQL query does.
Solution here, should be surprisingly simple:
var result = session
.Query<TableXy>()
// instead of this
//.Where(x => NHibernate.Linq.SqlMethods.Like(x.Source, "%\\_aa"))
// This will add sign % at the beginning only
.Where(x => x.Source.EndsWith("[_]aa"));
// or wrap it on both sides with sign: %
.Where(x => x.Source.Contains("[_]aa"));
.ToList();
Trick is to use ruglar like expression style for underscore [_]
Related
I have a case where I need to send tens of thousands of ids to the graphql server in the filtering query.
The query now generated by the HT is something like this:
_dbContext.
Forms
.Where(c=>staticLiistOfIds.Contains(c.Id))
.Select(c=>new {C.Name,C.Age});
I have two problems with this:
slow performance
SQL Server Limit I guess is around 32K
I have found a Nuget library to convert this static list to a temp table,so now I want to override the HT middle to rewrite the above query generated to the following:
_dbContext.
Forms
.Where(c=>_dbContext..AsQueryableValues(staticLiistOfIds).Contains(c.Id))
.Select(c=>new {C.Name,C.Age});
This will create a temp table for this static list of ids so I will be able to solve the above two problems that I have.
So since i didn't get answers, I had to ask from the Slack of HotChocolate's Team and hopefully, they provided me with the documentation extending-filtering/extending-iqueryable:
in case the link was broken, here is
Extending IQueryable The default filtering implementation uses
IQueryable under the hood. You can customize the translation of
queries by registering handlers on the QueryableFilterProvider.
The following example creates a StringOperationHandler that supports
case-insensitive filtering:
// The QueryableStringOperationHandler already has an implemenation of CanHandle
// It checks if the field is declared in a string operation type and also checks if
// the operation of this field uses the `Operation` specified in the override property further
// below
public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHandler
{
// For creating a expression tree we need the `MethodInfo` of the `ToLower` method of string
private static readonly MethodInfo _toLower = typeof(string)
.GetMethods()
.Single(
x => x.Name == nameof(string.ToLower) &&
x.GetParameters().Length == 0);
// This is used to match the handler to all `eq` fields
protected override int Operation => DefaultFilterOperations.Equals;
public override Expression HandleOperation(
QueryableFilterContext context,
IFilterOperationField field,
IValueNode value,
object parsedValue)
{
// We get the instance of the context. This is the expression path to the propert
// e.g. ~> y.Street
Expression property = context.GetInstance();
// the parsed value is what was specified in the query
// e.g. ~> eq: "221B Baker Street"
if (parsedValue is string str)
{
// Creates and returnes the operation
// e.g. ~> y.Street.ToLower() == "221b baker street"
return Expression.Equal(
Expression.Call(property, _toLower),
Expression.Constant(str.ToLower()));
}
// Something went wrong 😱
throw new InvalidOperationException();
}
}
This operation handler can be registered on the convention:
public class CustomFilteringConvention : FilterConvention
{
protected override void Configure(IFilterConventionDescriptor descriptor)
{
descriptor.AddDefaults();
descriptor.Provider(
new QueryableFilterProvider(
x => x
.AddDefaultFieldHandlers()
.AddFieldHandler<QueryableStringInvariantEqualsHandler>()));
}
}
// and then
services.AddGraphQLServer()
.AddFiltering<CustomFilteringConvention>();
To make this registration easier, Hot Chocolate also supports
convention and provider extensions. Instead of creating a custom
FilterConvention, you can also do the following:
services
.AddGraphQLServer()
.AddFiltering()
.AddConvention<IFilterConvention>(
new FilterConventionExtension(
x => x.AddProviderExtension(
new QueryableFilterProviderExtension(
y => y.AddFieldHandler<QueryableStringInvariantEqualsHandler>()))));
but I was suggested that doing this way(sending up to 100k list of string ids to the graphQL server) is not a good approach. so I decided to take another approach by writing a custom simple dynamic LINQ generates.
Thanks.
I'm creating unit tests in which I will be comparing lists of objects with one another.
Currently I am using Fluent assertions in combination with specflow and nunit. I already use the Fluent Assertions to make a comparison as following:
public void TestShizzle()
{
// I normally retrieve these lists from a moq database or a specflow table
var expected = list<myObject>
{
new myObject
{
A = 1,
B = "abc"
}
}
var found = list<myObject>
{
new myObject
{
A = 1,
B = "def"
}
}
// this comparison only compares a few columns. The comparison is also object dependent. I would like to make this dynamic
found.Should().BeEquivalentTo(
expected,
options =>
options.Including(x => x.A));
}
What I really want is to be able to use generics instead of a specified type. I also want to decide which properties to compare at compile time. This is because of the large number of tables in the database. I think i need to use Linq Expressions for this, but I don't know how to go about this. The function should look something like this:
public void GenericShizzle<T>(List<T> expected, List<T> found, IEnumerable<PropertyInfo> properties)
{
Expression<Func<T, object>> principal;
foreach(var property in properties)
{
// create the expression for including fields
}
found.Should().BeEquivalentTo(
expected,
options =>
// here is need to apply the expression.
}
I have no real idea how to get the correct expression for the job, or if this even the best method. I think I need to create an property expression that is understood by the include function, but maybe a different method can be used?
There is Including method overload accepting Expression<Func<IMemberInfo, bool>> which can be used to dynamically filter members based on information about them:
IEnumerable<PropertyInfo> properties = ...;
var names = properties
.Select(info => info.Name)
.ToHashSet();
found.Should()
.BeEquivalentTo(expected,
options => options.Including((IMemberInfo mi) => names.Contains(mi.Name))); // or just .Including(mi => names.Contains(mi.Name))
I have a query which filters results:
public IEnumerable<FilteredViewModel> GetFilteredQuotes()
{
return _context.Context.Quotes.Select(q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.Where(qpi => q.User.Id == qpi.ItemOrder))
});
}
In the where clause I'm using the parameter q to match a property against a property from the parameter qpi.
Because the filter will be used in several places I'm trying to rewrite the where clause to an expression tree which would look like something like this:
public IEnumerable<FilteredViewModel> GetFilteredQuotes()
{
return _context.Context.Quotes.Select(q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.AsQueryable().Where(ExpressionHelper.FilterQuoteProductImagesByQuote(q)))
});
}
In this query the parameter q is used as a parameter to the function:
public static Expression<Func<QuoteProductImage, bool>> FilterQuoteProductImagesByQuote(Quote quote)
{
// Match the QuoteProductImage's ItemOrder to the Quote's Id
}
How would I implement this function? Or should I use a different approach alltogether?
If I understand correctly, you want to reuse an expression tree inside another one, and still allow the compiler to do all the magic of building the expression tree for you.
This is actually possible, and I have done it in many occasions.
The trick is to wrap your reusable part in a method call, and then before applying the query, unwrap it.
First I would change the method that gets the reusable part to be a static method returning your expression (as mr100 suggested):
public static Expression<Func<Quote,QuoteProductImage, bool>> FilterQuoteProductImagesByQuote()
{
return (q,qpi) => q.User.Id == qpi.ItemOrder;
}
Wrapping would be done with:
public static TFunc AsQuote<TFunc>(this Expression<TFunc> exp)
{
throw new InvalidOperationException("This method is not intended to be invoked, just as a marker in Expression trees!");
}
Then unwrapping would happen in:
public static Expression<TFunc> ResolveQuotes<TFunc>(this Expression<TFunc> exp)
{
var visitor = new ResolveQuoteVisitor();
return (Expression<TFunc>)visitor.Visit(exp);
}
Obviously the most interesting part happens in the visitor.
What you need to do, is find nodes that are method calls to your AsQuote method, and then replace the whole node with the body of your lambdaexpression. The lambda will be the first parameter of the method.
Your resolveQuote visitor would look like:
private class ResolveQuoteVisitor : ExpressionVisitor
{
public ResolveQuoteVisitor()
{
m_asQuoteMethod = typeof(Extensions).GetMethod("AsQuote").GetGenericMethodDefinition();
}
MethodInfo m_asQuoteMethod;
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (IsAsquoteMethodCall(node))
{
// we cant handle here parameters, so just ignore them for now
return Visit(ExtractQuotedExpression(node).Body);
}
return base.VisitMethodCall(node);
}
private bool IsAsquoteMethodCall(MethodCallExpression node)
{
return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == m_asQuoteMethod;
}
private LambdaExpression ExtractQuotedExpression(MethodCallExpression node)
{
var quoteExpr = node.Arguments[0];
// you know this is a method call to a static method without parameters
// you can do the easiest: compile it, and then call:
// alternatively you could call the method with reflection
// or even cache the value to the method in a static dictionary, and take the expression from there (the fastest)
// the choice is up to you. as an example, i show you here the most generic solution (the first)
return (LambdaExpression)Expression.Lambda(quoteExpr).Compile().DynamicInvoke();
}
}
Now we are already half way through. The above is enough, if you dont have any parameters on your lambda. In your case you do, so you want to actually replace the parameters of your lambda to the ones from the original expression. For this, I use the invoke expression, where I get the parameters I want to have in the lambda.
First lets create a visitor, that will replace all parameters with the expressions that you specify.
private class MultiParamReplaceVisitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> m_replacements;
private readonly LambdaExpression m_expressionToVisit;
public MultiParamReplaceVisitor(Expression[] parameterValues, LambdaExpression expressionToVisit)
{
// do null check
if (parameterValues.Length != expressionToVisit.Parameters.Count)
throw new ArgumentException(string.Format("The paraneter values count ({0}) does not match the expression parameter count ({1})", parameterValues.Length, expressionToVisit.Parameters.Count));
m_replacements = expressionToVisit.Parameters
.Select((p, idx) => new { Idx = idx, Parameter = p })
.ToDictionary(x => x.Parameter, x => parameterValues[x.Idx]);
m_expressionToVisit = expressionToVisit;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Expression replacement;
if (m_replacements.TryGetValue(node, out replacement))
return Visit(replacement);
return base.VisitParameter(node);
}
public Expression Replace()
{
return Visit(m_expressionToVisit.Body);
}
}
Now we can advance back to our ResolveQuoteVisitor, and hanlde invocations correctly:
protected override Expression VisitInvocation(InvocationExpression node)
{
if (node.Expression.NodeType == ExpressionType.Call && IsAsquoteMethodCall((MethodCallExpression)node.Expression))
{
var targetLambda = ExtractQuotedExpression((MethodCallExpression)node.Expression);
var replaceParamsVisitor = new MultiParamReplaceVisitor(node.Arguments.ToArray(), targetLambda);
return Visit(replaceParamsVisitor.Replace());
}
return base.VisitInvocation(node);
}
This should do all the trick.
You would use it as:
public IEnumerable<FilteredViewModel> GetFilteredQuotes()
{
Expression<Func<Quote, FilteredViewModel>> selector = q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.Where(qpi => ExpressionHelper.FilterQuoteProductImagesByQuote().AsQuote()(q, qpi)))
};
selector = selector.ResolveQuotes();
return _context.Context.Quotes.Select(selector);
}
Of course I think you can make here much more reusability, with defining expressions even on a higher levels.
You could even go one step further, and define a ResolveQuotes on the IQueryable, and just visit the IQueryable.Expression and creating a new IQUeryable using the original provider and the result expression, e.g:
public static IQueryable<T> ResolveQuotes<T>(this IQueryable<T> query)
{
var visitor = new ResolveQuoteVisitor();
return query.Provider.CreateQuery<T>(visitor.Visit(query.Expression));
}
This way you can inline the expression tree creation. You could even go as far, as override the default query provider for ef, and resolve quotes for every executed query, but that might go too far :P
You can also see how this would translate to actually any similar reusable expression trees.
I hope this helps :)
Disclaimer: Remember never copy paste code from anywhere to production without understanding what it does. I didn't include much error handling here, to keep the code to minimum. I also didn't check the parts that use your classes if they would compile. I also don't take any responsability for the correctness of this code, but i think the explanation should be enough, to understand what is happening, and fix it if there are any issues with it.
Also remember, that this only works for cases, when you have a method call that produces the expression. I will soon write a blog post based on this answer, that allows you to use more flexibility there too :P
Implementing this your way will cause an exception thrown by ef linq-to-sql parser. Within your linq query you invokes FilterQuoteProductImagesByQuote function - this is interpreted as Invoke expression and it simply cannot be parsed to sql. Why? Generally because from SQL there is no possibility to invoke MSIL method. The only way to pass expression to query is to store it as Expression> object outside of the query and then pass it to Where method. You can't do this as outside of the query you will not have there Quote object. This implies that generally you cannot achieve what you wanted. What you possibly can achieve is to hold somewhere whole expression from Select like this:
Expression<Func<Quote,FilteredViewModel>> selectExp =
q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.AsQueryable().Where(qpi => q.User.Id == qpi.ItemOrder)))
};
And then you may pass it to select as argument:
_context.Context.Quotes.Select(selectExp);
thus making it reusable. If you would like to have reusable query:
qpi => q.User.Id == qpi.ItemOrder
Then first you would have to create different method for holding it:
public static Expression<Func<Quote,QuoteProductImage, bool>> FilterQuoteProductImagesByQuote()
{
return (q,qpi) => q.User.Id == qpi.ItemOrder;
}
Application of it to your main query would be possible, however quite difficult and hard to read as it will require defining that query with use of Expression class.
I found a piece of code of the following form:
public static Expression<Func<Invoice, CustomerContact>> GetCustomerContact()
{
return i => new CustomerContact {
FirstName = i.Customer.FirstName,
LastName = i.Customer.LastName,
Email = i.Customer.Email,
TelMobile = i.Customer.TelMobile,
};
}
In other parts of the code, I want to get the same lightweight CustomerContact object, only not from the Invoice, but from the Customer itself. So the obvious thing to do would be to have:
public static Expression<Func<Customer, CustomerContact>> GetCustomerContact()
{
return c => new CustomerContact {
FirstName = c.FirstName,
LastName = c.LastName,
Email = c.Email,
TelMobile = c.TelMobile,
};
}
and then change the Expression taking Invoice as input to refer to this method, i.e. something like this:
public static Expression<Func<Invoice, CustomerContact>> GetCustomerContact()
{
return i => GetCustomerContact(i.Customer); // doesn't compile
}
What's the correct syntax for this?
You can use Expression.Invoke:
var paramExpr = Expression.Parameter(typeof(Invoice), "i");
var propertyEx = Expression.Property(paramExpr, "Customer");
var body = Expression.Invoke(GetCustomerContactFromCustomer(), propertyEx);
return Expression.Lambda<Func<Invoice, CustomerContact>>(body, paramExpr);
Do note that some LINQ providers have problems with such invocation-expressions.
The easiest way to work around this (and to give you more convenient syntax) is to use LINQKit:
var expr = GetCustomerContactFromCustomer();
Expression<Func<Invoice, CustomerContact>> result = i => expr.Invoke(i.Customer);
return result.Expand();
Are you sure you need to use an Expression? If you don't need different Linq providers to convert code trees into queries, then consider using just Func, instead. If you just use Func so that the method signatures are:
public static Func<Customer, CustomerContact> GetCustomerContact();
and
public static Func<Customer, CustomerContact> GetCustomerContact();
Then your syntax would be fine for constructing the second Func off of the first one. Of course, this will only work for in-memory objects (with Linq-to-objects).
The problem is that in order to build an Expression, you have to explicitely build the evaluation tree, which can be quite hairy (using the various static methods on Expression). Because of this hairiness, there are several helper packages, including LINQKit.
I'm looking to add a method to my base repository class that allows me to use LIKE expressions but I'm not quite sure of how to go about this. I want to create a generic method that looks at the expression tree passed in and looks for wildcard characters in the string values passed in. It would then generate the QueryOver statement accordingly.
I have the following currently:
public IList<T> FindAll(Expression<Func<T, bool>> criteria, char wildCard)
{
return SessionFactory.GetCurrentSession()
.QueryOver<T>()
.Where(criteria)
.List();
}
Obviously the hard part is yet to come. I need to look through the expression tree and build the query using QueryOver dynamically. Looking for some pointers on how to proceed with this. Or am I just wasting my time here and should just create individual methods in my repositories that handle the LIKE queries?
Additional Criteria
Ideally I'd like to tell the difference between the following:
search*
*search
*search*
So the query generated would be:
field LIKE 'search%'
field LIKE '%search'
field LIKE '%search%'
There's two ways to write a Like expression in QueryOver.
If you do it off the Where clause:
.Where(Restrictions.Like(Projections.Property<T>(*projected property*), *string value*, MatchMode.Anywhere))
However this is kinda long to write.
So you can use WhereRestrictionOn:
.WhereRestrictionOn(*projected property*).IsLike(*string value*, MatchMode.Anywhere)
This means you need to pass in two parameters like:
FindAll<User>(x => x.FirstName, "bob");
You may be able to use .Contains, .StartsWith, .EndsWith, but I'm not sure.
FindAll<User>(x => x.FirstName.Contains("bob"));
FindAll<User>(x => x.FirstName.StartsWith("bob"));
FindAll<User>(x => x.FirstName.EndsWith("bob"));
I don't think those work in NHibernate.
Hope that helps.
I don't really understand what you want to do. Do you want for a query such as
session.QueryOver<T>().Where(x => x.property == "*substring*").List();
to generate a property LIKE "%substring%" query? In most Linq providers the String.Contains method is transformed in a "LIKE" query, and thus you wouldn't need to look for wildcard characters in the expression tree, only for the String.Contains method.
In case of the latter, you would have to parse the expression tree looking for a String.Contains() method. This could be very troublesome (http://msdn.microsoft.com/en-us/library/bb397951.aspx). Also, I can't see in your method which property is to be "compared" with the LIKE operator.
Anyways, I think it would be easier to pass a ICriterion to your .Where(), such as
.Where(new NHibernate.Criterion.LikeExpression("property", "%value%"))
, and append your other conditions with .And() right after that. The drawback is losing strongly-typed queries.
After digging for a while for a solution about the problem of translating expressions of the form
session.QueryOver<T>().Where(x => x.StringAttrbute.StartsWith("ajoofa"))
into SQL of the form
SELECT * FROM {table} WHERE {string_attribute} LIKE 'ajoofa%'
I came up with the following solution: Yu have to Register custom method calls for the Standard string functions .Contains(), .StartsWith, .EndsWith().
God knows why these functions are not registered by default within NHibernate. The following code should help you out.
/// Perform the registration of custom methods
/// </summary>
public static void Register()
{
if (!_registered)
{
_registered = true;
String str = null;
ExpressionProcessor.RegisterCustomMethodCall(() => str.StartsWith(null), ProcessStartsWith);
ExpressionProcessor.RegisterCustomMethodCall(() => str.EndsWith(null), ProcessEndsWith);
ExpressionProcessor.RegisterCustomMethodCall(() => str.Contains(null), ProcessContains);
}
}
static ICriterion ProcessStartsWith(MethodCallExpression methodCallExpression)
{
ExpressionProcessor.ProjectionInfo projection = ExpressionProcessor.FindMemberProjection(methodCallExpression.Object);
object value = ExpressionProcessor.FindValue(methodCallExpression.Arguments[0]) + "%";
return projection.CreateCriterion(Restrictions.Like, Restrictions.Like, value);
}
static ICriterion ProcessEndsWith(MethodCallExpression methodCallExpression)
{
ExpressionProcessor.ProjectionInfo projection = ExpressionProcessor.FindMemberProjection(methodCallExpression.Object);
object value = "%" + ExpressionProcessor.FindValue(methodCallExpression.Arguments[0]);
return projection.CreateCriterion(Restrictions.Like, Restrictions.Like, value);
}
static ICriterion ProcessContains(MethodCallExpression methodCallExpression)
{
ExpressionProcessor.ProjectionInfo projection = ExpressionProcessor.FindMemberProjection(methodCallExpression.Object);
object value = "%" + ExpressionProcessor.FindValue(methodCallExpression.Arguments[0]) + "%";
return projection.CreateCriterion(Restrictions.Like, Restrictions.Like, value);
}