How to use MethodCallExpression.Update - linq

I'm trying and failing to use an ExpressionVisitor to modify an expression that calls a method. I have a SearchService that encapsulates the search logic and want to be able to amend the search arguments passed.
The class in which the SearchFunc should be modified and run:
public class SearchService
{
public Expression<Func<string, string, List<int>>> SearchFunc { get; set; }
public void Run()
{
SearchModifier modifier = new SearchModifier();
Expression<Func<string, string, List<int>>> newFunc = (Expression<Func<string, string, List<int>>>)modifier.Modify(SearchFunc);
}
}
SearchModifier is defined as:
public class SearchModifier : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
Debug.Print(string.Format("VisitMethodCall: {0}", node.ToString()));
//VisitMethodCall: value(ExpressionTree_test.MainWindow)._adminRepository.SearchUsers("orig val", "orig val2")
//trying to use the Update method to create an amended MethodCallExpression
List<ConstantExpression> newargs = new List<ConstantExpression>();
newargs.Add(Expression.Constant("my new arg 1", typeof(string)));
newargs.Add(Expression.Constant("my new arg 2", typeof(string)));
MethodCallExpression methodCallExpression = node.Update(node, newargs);
//causes exception
//Method 'System.Collections.Generic.List`1[System.Int32] SearchUsers(System.String, System.String)' declared
//on type 'ExpressionTree_test.AdminRepository' cannot be called
//with instance of type 'System.Collections.Generic.List`1[System.Int32]'
Debug.Print(string.Format("Amended VisitMethodCall: {0}", methodCallExpression.ToString()));
return base.VisitMethodCall(node);
}
The Run method is called like this:
_searchService = new SearchService();
_searchService.SearchFunc = (t, s) => _adminRepository.SearchUsers("orig val", "orig val2");
I can't find much information on using the MethodCallExpression.Update method so am not sure I'm doing this correctly. How to I change the values of the arguments in the method?
Of course there may be a better way of doing this and any suggestions gratefully received...

You're not using the result of the Update method. You should pass it to base.VisitMethodCall instead of node:
return base.VisitMethodCall(methodCallExpression);
EDIT
Sorry, I misread the question... The first argument to Update is not the expression node being visited, it's the instance on which the method is called. So the code should be:
node.Update(node.Object, newargs);

Related

Is it possible to pass null reference to Init view model?

I have this call on one view model
ShowViewModel<MyViewModel>(
new MyParams { ... }
);
On MyViewModel I have this Init method which works perfect
public void Init(MyParams params)
{
if (params != null)
{
// some logic
}
else
{
// some other logic
}
}
On another view model I have
ShowViewModel<MyViewModel>();
I expect to receive null on MyViewModel init method, instead of that I get an instance of 'MyParams'. That's generating problems since I have specific logic to handle the call with no parameters
I have custom presenter logic that might responsible for this, but at first sight I couldn't identify any custom logic as responsible. Is this the standard behavior for complex params?
Unfortunately, no there isn't a way to pass null using a parameters object.
The reason is that when Mvx creates the ViewModel and attempts to call the Init method, it will first convert your object instance into a simple dictionary (key/value pairs). If you use the no arg version, then it creates an empty dictionary. At this point, it creates an MvxBundle which includes the dictionary.
When Mvx is finally ready to call your Init method, it takes this dictionary and attempts to create an actual object.
It's this method that creates the instance to pass to Init.
MvxSimplePropertyDictionaryExtensionMethods.Read()
https://github.com/MvvmCross/MvvmCross/blob/8a824c797747f74716fc64c2fd0e8765c29b16ab/MvvmCross/Core/Core/Platform/MvxSimplePropertyDictionaryExtensionMethods.cs#L54-L72
public static object Read(this IDictionary<string, string> data, Type type)
{
var t = Activator.CreateInstance(type);
var propertyList =
type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(p => p.CanWrite);
foreach (var propertyInfo in propertyList)
{
string textValue;
if (!data.TryGetValue(propertyInfo.Name, out textValue))
continue;
var typedValue = MvxSingletonCache.Instance.Parser.ReadValue(textValue, propertyInfo.PropertyType,
propertyInfo.Name);
propertyInfo.SetValue(t, typedValue, new object[0]);
}
return t;
}
Notice how it calls Activator.CreateInstance(type) which will always return an instance.
So that is why you'll never get an null value in Init.
My recommendation is to simply add a property to your MyParams object and set that in your no-arg version. Then in Init you can check the property to determine what to do.
Something like:
ShowViewModel<MyViewModel>(new MyParams { HasNoParams = true });
public void Init(MyParams myParams)
{
if (myParams.HasNoParams)
{
// do null flow here
}
else
{
// do non-null flow here
}
}
You can use Dictionary< TKey, TValue> as your params.

Get PropertyInfo from multiple property objects found in an Expression object

I need to change a function that accepts one Expression with one property inside and give it the ability to work with 2 properties at least.
I have the following base class that contains nested ElementHelper class
public class DomainObjectViewModel<TModel> where TModel : DomainObject
{
public class ElementHelper
{
public static void Create<T1>(TModel model, Expression<Func<TModel, T1>> expression)
{
var getPropertyInfo = GetPropertyInfo(expression);
//Do cool stuff
}
private static PropertyInfo GetPropertyInfo<T1>(Expression<Func<TModel, T1>> propertyExpression)
{
return (PropertyInfo)((MemberExpression)propertyExpression.Body).Member;
}
}
}
-ElementHelper class contains a Create function that gets the propertyInfo of the expression and only works if you pass one property in the expression.
Then I have the following inherited class that uses the helper function in the constructor.
public class ProductViewModel : DomainObjectViewModel<ProductEditViewModel>
{
public ProductViewModel(ProductEditViewModel model)
{
//It works for one property in the Expression
ElementHelper.Create(model, x => x.LaunchDate);
//Give the ability to pass multiple paramenters in the expression
ElementHelper.Create(model, x => new { x.LaunchDate, x.ApplyLaunchDateChanges });
}
}
I think I can use NewExpression (new { x.LaunchDate, x.ApplyLaunchDateChanges }) in order to pass it a collection of properties, but I cannot make it work.
Would you use same approach?
How you can split the passed Expression so you can get the propertyinfo of each properties found in the NewExpression object?
Well, since ElementHelper.GetPropertyInfo is your own method, you can decide what is allowed to pass, and then handle it appropriately inside that method.
Currently you handle only MemberExpression, so that's why it works only with single property accessor. If you want to be able to pass new { ... }, you need to add support for NewExpression like this:
private static IEnumerable<PropertyInfo> GetPropertyInfo<T1>(Expression<Func<TModel, T1>> propertyExpression)
{
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression != null)
return Enumerable.Repeat((PropertyInfo)memberExpression.Member, 1);
var newExpression = propertyExpression.Body as NewExpression;
if (newExpression != null)
return newExpression.Arguments.Select(item => (PropertyInfo)((MemberExpression)item).Member);
return Enumerable.Empty<PropertyInfo>(); // or throw exception
}

Can XUnit handle tests handle class and decimal parameters in the same method?

I have a test method with the following signature:
public void TheBigTest(MyClass data, decimal result)
{
And I'd like to run this in XUnit 2.1. I've got my CalculationData class all set up and that works if I remove the second parameter. But when I try to pass in the expected result as a second parameter by doing:
[Theory, ClassData(typeof(CalculationData)), InlineData(8893)]
It doesn't work. The test fails with a:
The test method expected 2 parameter values, but 1 parameter value was
provided.
Any ideas?
The class specified in the ClassData attribute needs to be an enumerable class that returns all of the parameters for the test method, not just the first one.
So, in your example, you would need something like:
public class CalculationData : IEnumerable<object[]>
{
IEnumerable<object[]> parameters = new List<object[]>()
{
new object[] { new MyClass(), 8893.0m },
new object[] { new MyClass(), 1234.0m },
// ... other data...
};
public IEnumerator<object[]> GetEnumerator()
{
return parameters.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
You can then add parameters to your MyClass class to enhance your test data.

How to access argument properties when writing custom NHibernate HQL generator

I have the following custom type with a method on it to determine if one span of time overlaps another
public struct DateTimeSpan
{
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public bool Overlaps(DateTimeSpan overlap)
{
//....
}
}
I am trying to write a custom HQL generator so that when I use this method within my data access LINQ queries it will generate to appropriate SQL when querying the database.
This is the start of my BaseHqlGeneratorForMethod that attempts to compare the End property of one DateTimeSpan with the other
public class DateSpanOverlapsDateTimeSpanHqlGenerator : BaseHqlGeneratorForMethod
{
public DateSpanOverlapsDateTimeSpanHqlGenerator()
{
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeSpan>(x => x.Overlaps(new DateTimeSpan()))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder builder, IHqlExpressionVisitor visitor)
{
var endTargetProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
Expression endTargetExpression = Expression.MakeMemberAccess(targetObject, endTargetProperty);
var endArgumentProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
Expression endArgumentExpression = Expression.MakeMemberAccess(arguments[0], endArgumentProperty);
return builder.GreaterThanOrEqual(visitor.Visit(endTargetExpression).AsExpression(), visitor.Visit(endArgumentExpression).AsExpression());
}
}
I have proven that the End property in the targetObject is being evaluated fine but no matter what I do I cannot get it to evaluate the End property in arguments[0]. The above code is just one example of what I have tried (and seems the most obvious given it works for the targetObject) with most things I try ending up with the exception Antlr.Runtime.NoViableAltException
One obvious different between targetObject and arguments[0] is that targetObject is of type PropertyExpression and arguments[0] is of type ConstantExpression. I assume this means there needs to be different ways to access them but I cannot work out what it is!
treeBuilder.Constant(arg.SubProperty); will cache your object so you will end up to have problem when you will run your query.
you should add an overload into your DateTimeSpan
public bool Overlaps(DateTime? start, DateTime? end)
match the signature into your DateSpanOverlapsDateTimeSpanHqlGenerator
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeSpan>(span => span.Overlaps(default(DateTime?), default(DateTime?)))
};
and get the values in this way:
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder builder, IHqlExpressionVisitor visitor)
{
var startProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.Start);
var endProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
MemberExpression targetStartExpression = Expression.MakeMemberAccess(targetObject, startProperty);
MemberExpression targetEndExpression = Expression.MakeMemberAccess(targetObject, endProperty);
HqlExpression startDateExpression = visitor.Visit(arguments[0]).AsExpression();
HqlExpression endDateExpression = visitor.Visit(arguments[1]).AsExpression();
....
}
after that business as usual:
builder.LessThanOrEqual(visitor.Visit(targetStartExpression).AsExpression(), endDateExpression)
I don't know if you ever resolved this or are still interested in an answer, however I stumbled across this question myself while looking for a solution to this exact problem, so thought I would add my solution for others.
While debugging I noticed that the target object parameter is passed in as an expression of type PropertyExpression, whereas my argument was passed in as a type of ConstantExpression, so while it's perfectly valid to add to PropertyExpression using Expression.Property, that doesn't work with a constant value.
Instead I extract the value of the constant and access the values of sub-properties directly, creating new constant expressions which I can then use to build the query expression.
I don't know if this is the best, or most elegant way to do it, but it worked for me. I hope it helps.
public class MyObject
{
public double SubProperty { get; set; }
}
private HqlTreeNode GenerateHQL(
MethodInfo method,
Expression targetObject,
ReadOnlyCollection<Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
MyObject arg = (MyObject)((ConstantExpression)arguments[0]).Value;
HqlConstant argExpression = treeBuilder.Constant(arg.SubProperty);
...
...
}

Passing an expression to a method in NHibernate results in Object of type 'ConstantExpression' cannot be converted to type 'LambdaExpression'

This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:
System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.
Classes involvend:
public class A
{
public virtual int Id { get; set; }
public virtual ISet<B> DataFields { get; set; }
}
public class B
{
public virtual int Id { get; set; }
}
Sample test code:
[TestMethod]
public void TestMethod1()
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
Console.Write("Number of records is {0}", records.Count());
}
}
[TestMethod]
public void TestMethod2()
{
GetAsWhereB(b => b.Id == 1);
}
private void GetAsWhereB(Func<B, bool> where)
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(where));
Console.Write("Number of records is {0}", records.Count());
}
}
This is one problem:
private void GetAsWhereB(Func<B, bool> where)
That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:
private void GetAsWhereB(Expression<Func<B, bool>> where)
As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:
var records = session.Query<A>().Where<A>(a => a.DataFields.
Any(b => b.Id == 1));
you make it clear that the "Any" call is on DataFields:
var records = session.Query<A>().Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)
Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.
private void GetAsWhereB(Func<B, bool> where)
{
Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(w));
Console.Write("Number of records is {0}", records.Count());
}
}

Resources