Passing A DbSet<T> created at runtime via reflection to Queryable - c#

I'm trying to execute a dynamic linq query where my DbSet Type is created at runtime via reflection an I'm getting the error:
"The best overloaded method match for
'System.Linq.Queryable.Where(System.Linq.IQueryable,
System.Linq.Expressions.Expression>)'
has some invalid arguments"
Here's my code
MyDataContext db = new MyDataContext ();
var dbType = db.GetType();
var dbSet = dbType.GetProperty("MyType").GetValue(db,null);
dbSet.GetType().InvokeMember("Local", BindingFlags.GetProperty, null, dbSet , null)
//just to show that it equal
dbSet.Equals(db.MyType); //returns true;
//here i create a dynamic expression tree
dynamic l = Expression.Lambda(delagateType, greater, param);
//here it fails when i pass in my dbSet var but not when i pass db.MyType
dynamic q = ((IEnumerable<object>)Queryable.Where(dbSet , l)).ToList();

The problem is that your dynamic call contains two parameters, the first being static and the second being dynamic. In such case the compiler is using the static type information for the first argument, which is different - object when you pass dbSet variable and DbSet<MyType> when you pass db.MyType.
The trick is to hide the static type information from the compiler like this
dynamic q = ((IEnumerable<object>)Queryable.Where((dynamic)dbSet, l)).ToList();

Related

Why Lambda expression parameter appears in method parameter list?

I am trying to get the parameters list of a class method using Roslyn and noticed a strange behavior that Roslyn considers and returns a Lambda parameter used inside the body of the method as as one of the parameters of the method, which cause an error in my code. Why does Roslyn consider a lambda parameter as of the method's parameters?
Here is the code:
var paramDeclaratons = memmeth.DescendantNodes().OfType<ParameterSyntax>();
foreach (var mempara in paramDeclaratons)
{
String paramType = mempara.Type.ToFullString().Trim(); //Here it crashes with System.NullReferenceException because Lambda returns no type!
The code which is parsed:
public void Method1(RequestId requestId)
{
...
var packetsToKeep = this.queuedPackets.Where(p => p.RequestId != requestId)
p is returned as one of the parameters of Method1 with no type
Assuming memmeth is a MethodDeclarationSyntax, then what you want is to access its ParameterList.Parameters:
var paramDeclaratons = memmeth.ParameterList.Parameters;

Converting lambda or method group to Action<T> where T is only known at runtime

I'm dynamically creating a type from a string passed to me at runtime.
I have the following code at the moment:
string messageType = "CustomerCreatedMessage";
//Convert the string to a Type
var type = Type.GetType(messageType);
//Create a Type of Action<CustomerCreatedMessage>
var action = typeof(Action<>).MakeGenericType(messageType);
How would I make a variable of the type of action and assign the lambda m=> SetActive(true) to it? I will then use the variable (named ??? here) in the following code:
//Create a generic method "Register" on my Messenger class that takes an Action<CustomerCreatedMessage> parameter.
var method = typeof(Messenger).GetMethod("Register");
var genericMethod = method.MakeGenericMethod(action);
//Invoke the created method with our script in it
genericMethod.Invoke(Messenger.Default, ???);
My goal is to be able to call my generic method Messenger.Default.Register<> with an Action<> that takes as type parameter a type created from my messageType string.
I have tried the following already:
Convert.ChangeType(m=>SetActive(true),action);
var filledAction = Activator.CreateInstance(action);
filledAction = m=>SetActive(true);
But both methods complain about converting a lambda expression to an object because it is not a delegate.
I get a similar error if I put SetActive(true) into a method and try to pass the method group.
Given that you're not using the parameter at all, I'd create a new generic method:
private static void GenericSetActive<T>(T ignored)
{
SetActive(true);
}
Then create a delegate using that:
var genericMethod = typeof(Foo).GetMethod("GenericSetActive",
BindingFlags.NonPublic |
BindingFlags.Static);
var concreteMethod = genericMethod.MakeGenericMethod(type);
var actionInstance = Delegate.CreateDelegate(action, concreteMethod);
Basically, you can't use a lambda expression here because you don't have a specific delegate type to convert it to.

Reflection with Entity Framework

I have table name as string in my hand. Help to access the entity using reflection. My query is,
TestEntities entity = new TestEntities();
var countryList = entity.GetType().GetProperty("Countries").GetValue(entity, null) as
typeof(ObjectSet).MakeGenericType(Type.GetType("CRUDService.Country"));
I queried country table using reflection and try to converting result as Country type. But it's not working. Kindly help me to convert specific type at runtime.
Expression var value = obj astypeof(YourType) is invalid; as keyword cannot be followed by operator typeof; you actually need to reference the type without the help of typeof
Your only option is to treat it as a non generic ObjectQuery since you don't know the type at code time:
TestEntities entity = new TestEntities();
var countryList =
entity.GetType().GetProperty("Countries").GetValue(entity, null) as ObjectQuery;
Be sure this will return an ObjectSet<Country>. The problem you face now is that you loose the generic benefits of ObjectSet<T>. You can solve this later by creating a generic extension method that can be used once your code knows about the actual ElementType of your IQueryable.
public static ObjectSet<T> Cast<T>(this ObjectQuery objectSet)
{
if(!(objectSet is ObjectSet<T>))
throw new Exception("Invalid instance passed or entity type specified");
return objectSet as ObjectSet&ltT>;
}
Use var countries = countryList.Cast<Country>(); to get your generic ObjectSet<>

Using method parameter as generic type argument

I have a class (SearchParameters) that holds search parameters, i then create a linq query based on these using a generic class called Querybuilder. This returns the results and all works perfectly.
The results are displayed in GridView, i am currently implementing custom sorting for the gridivew, I add the field to be searched to the SearchParameters object (using a fluent interface)
SearchParameters=SearchParameters.SortResultsBy(e.SortExpression,e.NewSortOrder.ToString());
I need the datatype of the columns to be used as a generic parameter to my AddOrderByClause() method:
public void AddOrderByClause<D>(string field, Type sourceDateType)
{
var orderExpression = Expression.Lambda<Func<T, D>>(Expression.Property(resultExpression, field), resultExpression);
rootExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { typeof(T), typeof(D) },
rootExpression,
orderExpression);
}
I can easily find the data type of the columns, but how do i pass it to the AddOrderByClause() (generic parameter D)?
public void AddOrderByClause<D,E>(string field, E sourceDataType)
{
.....
}
Use reflection to get the method AddOrderByClause, then use MakeGenericMethod to get the generic. This is the general idea (a bit vague because I don't know what all your types are named).
Type MyTypeParameter; // TODO set this to type parameter D
Type type; // TODO set this to the type that contains the method "AddOrderByClause"
MethodInfo method = type.GetMethod("AddOrderByClause");
MethodInfo genericMethod = method.MakeGenericMethod(typeof(MyTypeParameter));
genericMethod.Invoke(MyClassInstance, FieldParam, SourceDataParam);
but how do i pass it to the AddOrderByClause() (generic parameter D)?
Seems like you already have datatype of the columns, and you figuring out how to pass it to Generic method
Below is example
First modify AddOrderByClause to accept T (used later in your function)
public void AddOrderByClause<D,T>(String field)
{
....
}
Then call AddOrderByClause like
var data = SearchParameter;// Use actual data if SearchParameter is not correct
AddOrderByClause<D,date.GetType()>(fieldData);// assuming fieldData is something different
var data = SearchParameter;// Use actual data if SearchParameter is not currect

Executing DynamicExpression with unknown types

If anyone is very familar with the Linq.Dynamic namespace I could use some help -- couldn't find any indepth resources on the internet.
Basically I'm using DynamicExpression.ParseLambda to create an expression where the type is not known at compile time,
public Expression GetExpression(Type t, List<QueryFilter> filters)
{
// pseudo code
// extracts a string representation of the query as 'expressionString'
return DynamicExpression.ParseLambda(t, typeof(Boolean), expressionString, values);
}
Where a QueryFilter is:
public class QueryFilter
{
string propertyName;
ExpressionType operationType;
object value;
}
Which represents a simple binary function like "Age > 15" or something.
This is how the 'GetExpression' function works, it takes 2 types -- one that is the input type and one that is the output type, and ultimately generates what would normally be created with a Func delegate. It also takes a string that represents the query and a params object[] of values, which are 'expressionString' and 'values' above, respectively.
However I am having trouble executing the dynamic expression in LINQ-to-SQL, using a DataContext generated from SqlMetal (.dbmc file).
DatabaseContext db = new DatabaseContext(connectionString);
var filter = DynamicExpressionBuilder.
GetExpression(typeof(SysEventLogT), sysEventFilters)
var query = db.SysEventLogT.Where(filter);
Produces the following error,
System.Data.Linq.Table<DBReporting.Linq.Data.SysEventLogT>
does not contain a definition for 'Where' and the best extension method overload
System.Linq.Dynamic.DynamicQueryable.Where<T>(System.Linq.IQueryable<T>, string, params object[])
has some invalid arguments.
I know that my DataContext instance actually treats the sql tables as properties...do I need to reflect with GetProperty() somehow for this to work? Or perhaps I need to create another .Where extension?
Your GetExpression is returning an Expression type - the DynamicQueryable.Where method, when used as an extension method, expects a string as the first parameter.
You need your call to Where to look like this:
var query = db.SysEventLogT.Where("Age > #0", 15);
Also, you could try the following, just to be explicit:
var query = db.SysEventLogT.AsQueryable().Where("Age > #0", 15);
Note that if easier, you can build a sting containing the full filter and not use the params object[] parameter at all:
var query = db.SysEventLogT.AsQueryable().Where("Age > 15");

Categories

Resources