How to use Expressions to invoke a method call with a generic list as the parameter? - linq

We're using the very Excellent ToStringBuilder in our project as a performant, generic backing for our ToString implementations. It worked fine for debugging until I needed to generate a string representation of an object graph to check if it had changed in between loading and closing. Previously I had used a MemoryStream to write the object out to xml, but this seemed heavyweight so I decided to try out using ToStringBuilder, which is where I hit a showstopper...
Our object graph uses generic typed lists heavily, so when the lists are printed out they look like the following:
PropertyName:{System.Collections.Generic.List`1[Namespace.Path.To.MyClassDto]}
Instead of enumerating through the list and invoking ToString on each object, which is fine as that's default behaviour (btw, ToStringBuilder supports object[], but we don't want to retrofit our entire Dto layer just to fix this problem).
I tried to patch the code in question (ToStringBuilder.cs, line 177) to recognise when the type is a generic list, and then invoke string.Join(", ", list), but I couldn't get my head around how the linq reflection API handles generics.
The first thing I tried was to get a handle to the String.Join(IEnumerable<>) method like this:
var stringJoinMethod = typeof(string).GetMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });
But GetMethod returned null so that didn't work. I eventually found this StackOverflow question that showed me how to get a generic method by signature (call getmethods() instead and filter the results). That got me the correct method handle, so I tried to do something like this:
private void AppendMember(MemberInfo memberInfo)
{
AppendQuotesIfRequiredForType(memberInfo);
Type type = GetMemberType(memberInfo);
var memberAppendMethod = typeof(StringBuilder).GetMethod("Append", new[] { type });
Expression getMemberValue = Expression.MakeMemberAccess(TargetArgExpression, memberInfo);
if (type.IsValueType)
{
Type appendArgType = memberAppendMethod.GetParameters()[0].ParameterType;
if (type != appendArgType)
{
getMemberValue = Expression.TypeAs(getMemberValue, typeof(object));
}
//my code begins here.
_appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));
}
else if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>)))
{
// now to emit some code to do the below, you wouldn't think it'd be this hard...
// string.Join(", ", genericList);
AppendStartOfMembers();
//this returns null, because generics are not well supported by the reflection API, boo!
var stringJoinMethod = typeof(string).GetGenericMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });
var CommaSpace = Expression.Constant(", ");
// this doesn't work, throws an ArgumentException as below
getMemberValue = Expression.Call(stringJoinMethod, CommaSpace, getMemberValue);
_appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));
AppendEndOfMembers();
}
else
{
//primitives like strings
_appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));
}
//my code ends here.
AppendQuotesIfRequiredForType(memberInfo);
}
This errors with the following exception:
System.ArgumentException: "Method System.String Join[T](System.String, System.Collections.Generic.IEnumerable`1[T]) is a generic method definition"
at System.Linq.Expressions.Expression.ValidateMethodInfo(MethodInfo method)
at System.Linq.Expressions.Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1)
at MyNameSpace.Common.ToStringBuilder`1.AppendMember(MemberInfo memberInfo) in C:\myproject\MyNamespace.Common\ToStringBuilder.cs:line 206
I started googling that error message and found people talking about using Expression.Lamba() to wrap calls to generic methods, at which point I realised I was way out of my depth.
So, assuming I have a List mylist, how do I generate an Expression as above that will do the equivalent of string.Join(", ", mylist); ?
thanks!

To get the "generic type" of your generic list (the type of "T"), you can do
var genericListType= type.GetGenericArguments()[0];
so in your elseif, you could do (it might be easier, I just stay as close as possible to your code)
else if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>)))
{
// now to emit some code to do the below, you wouldn't think it'd be this hard...
// string.Join(", ", genericList);
AppendStartOfMembers();
//this returns null, because generics are not well supported by the reflection API, boo!
var stringJoinMethod = typeof(string).GetGenericMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });
var CommaSpace = Expression.Constant(", ");
var genericListType= type.GetGenericArguments()[0];
var genericStringJoinMethod = stringJoinMethod.MakeGenericMethod(new[]{genericListType});
// this doesn't work, throws an ArgumentException as below
getMemberValue = Expression.Call(genericStringJoinMethod , CommaSpace, getMemberValue);
_appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));
AppendEndOfMembers();
}

Related

Unable to cast object of type ‘System.String’ to type ‘AuthBotES.ReturnIntents’

Currently I had integrate LUIS with Bot Framework v4.
When I search for result match with Intent,
the Bot return me with this Error:
Error : Unable to cast object of type ‘System.String’ to type ‘AuthBotES.ReturnIntents’.
My Source code as below:
if (stepContext.Result != null)
{
var result = (ReturnIntents)stepContext.Result;
var msg = $"{result}";
await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);
}
and my ReturnIntents classes.
public class ReturnIntents
{
public string Intent { get; set; }
public double Score { get; set; }
public string Entities { get; set; }
}
A few issues here:
The first code block you posted looks to be for handling the result of a dialog, not for processing a LUIS result.
The cast from string to ReturnIntents will always fail.
Even if your cast of stepContext.Result to ReturnIntents did work, your msg variable would only contain namespace.to.class.ReturnIntents (the string representation of the object type, not the string representation of the objects properties.
Your msg variable is redundant.
I will address these in the order that they occur.
1 - Incorrect code block
This block of code looks suspiciously like code used to process a dialog and here e.g.:
var result = (bool)stepContext.Result;
Rather than the code for handling a LUIS result e.g.:
var dispatchResult = await cognitiveModels.DispatchService.RecognizeAsync(dc.Context, CancellationToken.None);
2 - Casting error
The error is telling you that it doesn't know how to convert a string object to a ReturnIntents object. To convert the string to your object you could use a couple of methods:
Use the NewtonSoft.Json NuGet package to allow you to turn the JSON string into your object as explained here.
2) A user defined type conversion as detailed in the official docs here and explained in this answer.
This error is a red herring in terms of your solution because I believe you're accidentally copied in the wrong block of code.
3 - Incorrect ToString behaviour
To get the string value of a ReturnIntents you will need to override the ToString method for the class and write your own custom implementation.
4 - Redundant cast
This:
// We know that this cast fails, and that stepContext.Result is a string
var result = (ReturnIntents)stepContext.Result;
// This will only return <namespace.path>.ReturnIntents (if the cast above works)
var msg = $"{result}";
// Passing in message msg isn't required, we can just pass in stepContext.Result
await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);
Becomes:
var result = stepContext.Result;
await stepContext.Context.SendActivityAsync(MessageFactory.Text(result), cancellationToken);
So what I actually think you actually want is the following:
var dispatchResult = await cognitiveModels.DispatchService.RecognizeAsync<ReturnIntents>(dc.Context, CancellationToken.None);
Which will send the user's input off to LUIS, and deserialize the response into a ReturnIntents object.
Edit to provide solution to the OP
The ExecuteLuisQuery method called here, and defined here returns a ReturnIntents object.
This object is passed as an option to the ReturnIntentDialog here. Because this comes through as an instance of the object type you have a few options inside your FinalStepAsync method here to turn your options object into a ReturnIntents object.:
Casting
ReturnIntents returnIntents = null;
if (stepContext.Options is ReturnIntents)
{
returnIntents = (ReturnIntents)stepContext.Options;
}
Deserializing
using Newtonsoft.Json;
ReturnIntents returnIntents = null;
if (stepContext.Options is ReturnIntents)
{
returnIntents = JsonConvert.DeserializeObject<ReturnIntents>(JsonConvert.SerializeObject(stepContext.Options));
}

Castle Core Invocation create a cache key from intercepted method

I'm using a interface interceptor to cache all methods that starts with "Get" but i can't figure out how to generate a unique cache key for every unknown parameter it can be anything and using GetHashCode is not an option as i can't be 100% sure that they have overridden the GetHashCode.
some thoughts was someting in the line of How can I create a unique hashcode for a JObject?
where JSON is used for a JObject i was thinking on JSON serialize every parameter then get the hash code like explained in the link above:
var obj = JToken.Parse(jsonString);
var comparer = new JTokenEqualityComparer();
var hashCode = comparer.GetHashCode(obj);
However i think this will be a performence hit so how can this be solved ?
The code i have so far is this but it wont handle the complex type situation where .ToString won't generate the raw value type like int, string etc.
private string CreateCacheKey(IInvocation invocation)
{
string className = invocation.TargetType.FullName;
string methodName = invocation.Method.Name;
var builder = new StringBuilder(100);
builder.Append(className);
builder.Append(".");
builder.Append(methodName);
for (int i = 0; i < invocation.Arguments.Length; i++)
{
var argument = invocation.Arguments[i];
var argumentValue = invocation.GetArgumentValue(i);
builder.Append("_");
builder.Append(argument);
if (argument != argumentValue)
{
builder.Append(argumentValue);
}
}
return string.Format("{0}-{1}", this.provider.CacheKey, builder);
}
I ended up using GetHashCode as it is the only viable solution if thay dont override the method it will not cache.

Reflection + Linq + DbSet

I use EF code-first 4.1. in my application. Now I want to get entities through WCF services using generic types.
I'm trying to reflect generic type and invoke the method ToList of DbSet Object.
Here is my code:
public string GetAllEntries(string objectType)
{
try
{
var mdc =
Globals.DbConnection.Create(#"some_db_connection", true);
// Getting assembly for types
var asob = Assembly.GetAssembly(typeof(CrmObject));
// getting requested object type from assembly
var genericType = asob.GetType(objectType, true, true);
if (genericType.BaseType == typeof(CrmObject))
{
// Getting Set<T> method
var method = mdc.GetType().GetMember("Set").Cast<MethodInfo>().Where(x => x.IsGenericMethodDefinition).FirstOrDefault();
// Making Set<SomeRealCrmObject>() method
var genericMethod = method.MakeGenericMethod(genericType);
// invoking Setmethod into invokeSet
var invokeSet = genericMethod.Invoke(mdc, null);
// invoking ToList method from Set<> invokeSet
var invokeToList = invokeSet.GetType().GetMember("ToList").Cast<MethodInfo>().FirstOrDefault();
//this return not referenced object as result
return invokeToList.ToString();
}
return null;
}
catch (Exception ex)
{
return ex.Message + Environment.NewLine + ex.StackTrace;
}
}
In fact then I write the code like return mdc.Set<SomeRealCrmObject>().ToList() - works fine for me! But then I use the generic types I cannot find the method ToList in object DbSet<SomeRealCrmObject>().
Eranga is correct, but this is an easier usage:
dynamic invokeSet = genericMethod.Invoke(mdc, null);
var list = Enumerable.ToList(invokeSet);
C# 4's dynamic takes care of the cumbersome generic reflection for you.
ToLIst() is not a member of DbSet/ObjectSet but is an extension method.
You can try this instead
var method = typeof(Enumerable).GetMethod("ToList");
var generic = method.MakeGenericMethod(genericType);
generic.Invoke(invokeSet, null);
I had a similar situation where I needed a way to dynamically load values for dropdown lists used for search criteria on a page allowing users to run adhoc queries against a given table. I wanted to do this dynamically so there would be no code change on the front or backend when new fields were added. So using a dynamic type and some basic reflection, it solved my problem.
What I needed to do was invoke a DbSet property on a DbContext based on the generic type for the DbSet. So for instance the type might be called County and the DbSet property is called Counties. The load method below would load objects of type T (the County) and return an array of T objects (list of County objects) by invoking the DbSet property called Counties. EntityList is just a decorator object that takes a list of lookup items and adds additional properties needed for the grid on the front-end.
public T[] Load<T>() where T : class
{
var dbProperty = typeof(Data.BmpDB).GetProperties().FirstOrDefault(
x => x.GetMethod.ReturnType.GenericTypeArguments[0].FullName == typeof(T).FullName);
if (dbProperty == null)
return null;
dynamic data = dbProperty.GetMethod.Invoke(BmpDb, null);
var list = Enumerable.ToList(data) as List<T>;
var entityList = new Data.EntityList<T>(list);
return entityList.Results;
}

IEqualityComparer exception

I am using Entity Framework 4.0 and trying to use the "Contains" function of one the object sets in my context object. to do so i coded a Comparer class:
public class RatingInfoComparer : IEqualityComparer<RatingInfo>
{
public bool Equals(RatingInfo x, RatingInfo y)
{
var a = new {x.PlugInID,x.RatingInfoUserIP};
var b = new {y.PlugInID,y.RatingInfoUserIP};
if(a.PlugInID == b.PlugInID && a.RatingInfoUserIP.Equals(b.RatingInfoUserIP))
return true;
else
return false;
}
public int GetHashCode(RatingInfo obj)
{
var a = new { obj.PlugInID, obj.RatingInfoUserIP };
if (Object.ReferenceEquals(obj, null))
return 0;
return a.GetHashCode();
}
}
when i try to use the comparer with this code:
public void SaveRatingInfo2(int plugInId, string userInfo)
{
RatingInfo ri = new RatingInfo()
{
PlugInID = plugInId,
RatingInfoUser = userInfo,
RatingInfoUserIP = "192.168.1.100"
};
//This is where i get the execption
if (!context.RatingInfoes.Contains<RatingInfo>(ri, new RatingInfoComparer()))
{
//my Entity Framework context object
context.RatingInfoes.AddObject(ri);
context.SaveChanges();
}
}
i get an execption:
"LINQ to Entities does not recognize the method 'Boolean Contains[RatingInfo](System.Linq.IQueryable1[OlafCMSLibrary.Models.RatingInfo], OlafCMSLibrary.Models.RatingInfo,
System.Collections.Generic.IEqualityComparer1[OlafCMSLibrary.Models.RatingInfo])' method, and his method cannot be translated into a store expression."
Since i am not proficient with linQ and Entity Framework i might be making a mistake with my use of the "var" either in the "GetHashCode" function or in general.
If my mistake is clear to you do tell me :) it does not stop my project! but it is essential for me to understand why a simple comparer doesnt work.
Thanks
Aaron
LINQ to Entities works by converting an expression tree into queries against an object model through the IQueryable interface. This means than you can only put things into the expression tree which LINQ to Entities understands.
It doesn't understand the Contains method you are using, so it throws the exception you see. Here is a list of methods which it understands.
Under the Set Methods section header, it lists Contains using an item as supported, but it lists Contains with an IEqualityComparer as not supported. This is presumably because it would have to be able to work out how to convert your IEqualityComparer into a query against the object model, which would be difficult. You might be able to do what you want using multiple Where clauses, see which ones are supported further up the document.

How to access data into IQueryable?

I have IQueryable object and I need to take the data inside the IQueryable to put it into Textboxs controls. Is this possible?
I try something like:
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.????
}
Update:
I'm doing this:
public IQueryable getData(String tableName, Hashtable myparams)
{
decimal id = 0;
if (myparams.ContainsKey("id") == true)
id = (decimal)myparams["id"];
Type myType= Type.GetType("ORM_Linq." + tableName + ", ORM_Linq");
return this.GetTable(tableName , "select * from Articu where id_tipo_p = '" + id + "'");
}
public IQueryable<T> GetTable<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class
{
return _datacontext.GetTable<T>().Where(predicate);
}
This returns a {System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable1[ORM_Linq.Articu]}`
I don't see any method like you tell me. I see Cast<>, Expression, ToString...
EDIT: Updated based on additional info from your other posts...
Your getData method is returning IQueryable instead of a strongly typed result, which is why you end up casting it. Try changing it to:
public IQueryable<ORM_Linq.Articu> getData(...)
Are you trying to query for "Articu" from different tables?
With the above change in place, your code can be rewritten as follows:
ORM_Linq.Articu result = mydata.SingleOrDefault();
if (result != null)
{
TextBoxCode.Text = result.id.ToString();
TextBoxName.Text = result.descrip;
}
If you have a single result use SingleOrDefault which will return a default value if no results are returned:
var result = mydata.SingleOrDefault();
if (result != null)
{
textbox1.text = result.ProductName; // use the column name
}
else
{
// do something
}
If you have multiple results then loop over them:
foreach (var item in mydata)
{
string name = item.ProductName;
int id = item.ProductId;
// etc..
}
First, you should be using a strongly-typed version of IQueryable. Say that your objects are of type MyObject and that MyObject has a property called Name of type string. Then, first change the parameter mydata to be of type IQueryable<MyObject>:
public void setdata (IQueryable<MyObject> mydata)
Then we can write a body like so to actually get some data out of. Let's say that we just want the first result from the query:
public void setdata (IQueryable<MyObject> mydata) {
MyObject first = mydata.FirstOrDefault();
if(first != null) {
textbox1.Text = first.Name;
}
}
Or, if you want to concatenate all the names:
public void setdata(IQueryable<MyObject> mydata) {
string text = String.Join(", ", mydata.Select(x => x.Name).ToArray());
textbo1.Text = text;
}
Well, as the name suggests, an object implementing IQueryable is... Queryable! You'll need to write a linq query to get at the internal details of your IQueryable object. In your linq query you'll be able to pull out its data and assign bits of it where ever you'd like - like your text box.
Here's a great starting place for learning Linq.
I think you find the same mental struggle when coming from FoxPro and from DataSet. Really nice, powerful string-based capabilities(sql for query, access to tables and columns name) in these worlds are not available, but replaced with a compiled, strongly-typed set of capabilities.
This is very nice if you are statically defining the UI for search and results display against a data source known at compile time. Not so nice if you are trying to build a system which attaches to existing data sources known only at runtime and defined by configuration data.
If you expect only one value just call FirstOrDefault() method.
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.FirstOrDefault().PropertyName;
}

Resources