How to use the where clause with IQueryable<dynamic> - c#

I am using Entity Framework Core with reflection to generate some forms dynamically. Everything is working except the WHERE Clause. I get the following error:
An expression tree may not contain a dynamic operation
I am able to fix this by converting my IQueryable to a List, but that introduces different problems that i would like to avoid.
Here is my code:
public async void ViewCollection(PropertyInfo propertyInfo)
{
Type propertyType = propertyInfo.PropertyType;
InversePropertyAttribute inversePropertyAttribute = (InversePropertyAttribute)ReflectionHelpers.GetAttribute(propertyInfo, typeof(InversePropertyAttribute));
//GET THE TYPE OF THE COLLECTION
Type collectionType = propertyInfo.PropertyType.GenericTypeArguments[0];
//GET THE INVERSE PROPERTY INFO
PropertyInfo inverseProperty = collectionType.GetProperty(inversePropertyAttribute.Property);
//GET THE FOREIGN KEY ATTRIBUTE FROM THE INVERSE PROPERTY
ForeignKeyAttribute foreignKeyAttribute = (ForeignKeyAttribute)ReflectionHelpers.GetAttribute(inverseProperty, typeof(ForeignKeyAttribute));
//GET THE FOREIGN KEY PROPERTY FROM THE FOREIGN KEY ATTRIBUTE
PropertyInfo foreignKeyProperty = collectionType.GetProperty(foreignKeyAttribute.Name);
//GET INCLUDED TYPE NAMES BY FOREIGN KEY
IEnumerable<string> includedTypes = collectionType.GetProperties().Where(p => p.PropertyType.IsClass).Where(p => ReflectionHelpers.HasAttribute(p, typeof(ForeignKeyAttribute))).Select(r => r.Name);
//GET THE DATA SET
IQueryable<dynamic> items = ReflectionHelpers.GetDbCollectionByType(Db, collectionType);
//INCLUDE THE INCLUDED TYPES BY NAME
foreach (string includedType in includedTypes) items = items.Include(includedType);
//THIS IS WHERE THE ERROR IS
items = items.Where(i => foreignKeyProperty.GetValue(i, null) == PrimaryKeyProperty.GetValue(Item, null));
await ShowCollection(collectionType, items, propertyInfo.Name);
}
How can i solve this with out changing my type to a list?

You can't. IQueryable is part of language integrated query (LINQ) - it's statically typed, so you cannot use value types.
IQueryable behind the scenes is an expression tree that represents a query that has not yet been executed. The expression tree part represents the query. The static types represent the data that the query operates over.
Your best alternative is to build the expression tree by hand using the expression tree API.

You can't build queries over dynamic because it's not supported natively in expression trees. Instead you should base your work on either non generic IQueryable or generic IQueryable<object>. e.g. if the ReflectionHelpers.GetDbCollectionByType is calling DbContext.Set<T> dynamically (similar to here Dynamically access table in EF Core 2.0), you should be able to cast it to IQueryable<object>:
var items = (IQueryable<object>)ReflectionHelpers.GetDbCollectionByType(Db, collectionType);
To add Where clause, you shouldn't use reflection calls inside the predicate expression, but build it dynamically using the Expression class methods (from System.Linq.Expressions namespace). Something like this:
// (object i) => (({collectionType})i).{ForeignKey} == Item.{PrimaryKey}
var parameter = Expression.Parameter(typeof(object), "i");
var body = Expression.Equal(
Expression.Property(Expression.Convert(parameter, collectionType), foreignKeyProperty),
Expression.Property(Expression.Constant(Item), PrimaryKeyProperty));
var predicate = Expression.Lambda<Func<object, bool>>(body, parameter);
and then
items = items.Where(predicate); // still IQueryable<object>

Related

Building a parameterized EntityFramework Core Expression

Hi, I'm trying to build an Expression to get a generic entity by its primary key and getting a parameterized sql query.
Currently I can get the correct WHERE query, but it isn't parameterized.
public async Task<TDbo> Get(TKey key, Expression<Func<TEntity, TKey>> keySelector)
{
var propertyRef = keySelector.Body;
var parameter = keySelector.Parameters[0];
var constantRef = Expression.Constant(key);
var equals = Expression.Equal(propertyRef, constantRef);
var comparer = Expression.Lambda<Func<TEntity, bool>>(equals, parameter);
return await _context.Set<TDbo>().SingleOrDefaultAsync(comparer);
}
This results in the following query:
SELECT e.\"Id\", e.\"Name\" \r\n FROM \"People\" AS e\r\nWHERE e.\"Id\" = 1\r\nLIMIT 2,
instead of the wanted:
SELECT e.\"Id\", e.\"Name\" \r\n FROM \"People\" AS e\r\nWHERE e.\"Id\" = #__s_0\r\nLIMIT 2
It's because of Expression.Constant(key). Value constant expressions are not parameterized by the query translator. What you need is an expression referring to a property or field of another expression (which could be constant). That's basically what C# compiler emits for closures.
One way is to actually use the C# compiler to create lambda expression with closure and take the body:
Expression<Func<TKey>> keyValue = () => key;
var variableRef = key.Body;
(the variableRef is a replacement of yours constantRef)
Another way is to use anonymous, tuple or specific class type to create explicit closure instance and bind the corresponding property or field. For instance, with anonymous type:
var variableRef = Expression.Property(Expression.Constant(new { key }), "key");
or with System.Tuple:
var variableRef = Expression.Property(Expression.Constant(Tuple.Create(key)), "Item1");
The actual method doesn't really matter (I personally prefer the first variant with lambda) - all they will cause creating parameter by EF Core query translator.

Combine property selector expression tree and value to create a predicate for EF filtering - create filter from lambda selector and value

Given a simple class with arbitrary properties (for discussion lets say Id, Name, and Description)
and given an instance of that class, I want to find matching entries in the database by specifying the property to match
I'm trying to do something in this respect similar to the AddOrUpdate method of EF, but I need the entity returned to me for further processing.
var match = new SomeClass{Name="Whatever"};
var found = Context.SomeClass.Find(x=>x.Name, match);
public static T Find<T>(this DbSet<T> set, Expression<Func<T, object>> matchOn, T matchAgainst) where T : class {
var func = matchOn.Compile();
var valueToFind = func(matchAgainst);
var combinedExpression = //matchon + "=" + valueToFind;
var found = set.FirstOrDefault(combinedExpression);
return found;
}
That gives me the value of the property in the passed in object, but I need to now combine that value with the passed in expression and pass it to the db set.
IE, the code I'm effectively trying to run is set.FirstOrDefault(x=>x.Name==valueToFind) How do I take the matchon expression (which contains x=>x.Name) and combine that with the ==valueToFind to get the x=>x.Name==valueToFind from them?
How do I build the combined expression? (I realize the "string" code above is completely wrong, but I was trying to get across what I need the function to do, but I have no idea what that syntax would look like.)
For manually coded examples, it would be easy enough just to pass in a hardcoded lambda with the value set, but my use case involves running through a collection of objects and finding the match for each one, so the value will not be known until runtime, and the method must work against arbitrary types and various properties, so I can't hardcode the property name either.
If you have a property selector, and a value to compare to, you can get an expression tree like this:
public static Func<TEntity, bool> GetComparer<TEntity,TProperty>(
Expression<Func<TEntity,TProperty>> selector, TProperty value)
{
var propertyRef = selector.Body;
var parameter = selector.Parameters[0];
var constantRef = Expression.Constant(value);
var comparer
= Expression.Lambda<Func<TEntity, bool>>
(Expression.Equal(propertyRef, constantRef), parameter)
.Compile();
return comparer;
}
Sample usage:
var comparer = GetComparer<Person, string>(p => p.Name, "John");
var persons = Person.GetPersons();
var john = persons.FirstOrDefault(comparer);

How to convert PropertyInfo to property expression and use it to invoke generic method?

How to convert PropertyInfo to property expression which can be used to invoke StructuralTypeConfiguration<TStructuralType>.Ignore<TProperty>(Expression<Func<TStructuralType, TProperty>> propertyExpression) method?
I tried to use Expression.Property() to construct expression but I am getting following error when I use this expression as propertyExpression parameter:
The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly.
This error probably refers to TProperty type parameter which I don't know how to specify having only PropertyInfo.
I am doing this in relation to: Use Entity Framework's StructuralTypeConfiguration.Ignore() to Ignore all properties but specified set.
UPDATE
Code which is not working:
var propertyInfo = typeof(Foo).GetProperties()[0];
var expression = Expression.Default(typeof(Foo));
var expressionProperty = Expression.Property(expression, propertyInfo);
Ignore(expressionProperty);
var entityType = propertyInfo.DeclaringType;
var parameter = Expression.Parameter(entityType, "entity");
var property = Expression.Property(parameter, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, parameter);
structureConfiguration.GetType()
.GetMethod("Ignore")
.MakeGenericMethod(propertyInfo.PropertyType)
.Invoke(structureConfiguration, new[]{lambda});
Property expressions require the property access to be on a specific object. There's a few options you can take here. First, if this is being done within one of your entity objects, you can simple use a ConstantExpression to build the property expression:
// Already have PropertyInfo in propInfo
Expression.Property(Expression.Constant(this, this.GetType()), propInfo)
However, since you need a Expression<Func<TStructuralType, TProperty>>, then it seems like you're going to have to build it using a ParameterExpression:
ParameterExpression pe = Parameter.Expression(typeof(MyEntity), "eParam");
Expression propExp = Expression.Property(pe, propInfo);
HOWEVER, here's the kicker... This is just a MemberExpression. To convert to the expression you need, you need to use Expression.Lambda to get a Func<> expression of the type you need. The problem? You don't know the type of the property to define the generic parameters of the lambda expression!
Expression<Func<MyEntity, ????>> eFunc = Expression.Lambda<Func<MyEntity, ????>>(propExp, pe);
This is the crux of the problem of doing it this way. That's not to say it can't be done... It's just that using this method IN THIS WAY isn't going to work. You'll have to use a bit runtime and static typing trickery (as well as judicious use of Actions instead of Funcs) to get this to work correctly.
TProperty exists only in the c# source code text. The compiler always resolves it to a concrete type. If you have a method
void Test<T>(T arg)
{
}
and call it like this
Test("hello");
Test(3);
The compiler generates code for two methods!
void Test(string arg)
{
}
void Test(int arg)
{
}
This means that you have to supply concrete types for your generic parameters if you want to have an invokable method.
This code will get you an Expression<Func<>> of the desired type. Note that there is an Expression.Lambda(...) override that doesn't need you to specify the type of the Func returned.
var t = typeof(Foo);
var pi = t.GetProperty(...);
var prm = Expression.Parameter(t, t.Name);
var prx = Expression.Property(prm, pi);
var lambda = Expression.Lambda(prx, prm);
Note that in many cases you don't have to bother with creating the Expression<Func<>> this way, assuming structureConfiguration below is a StructureConfiguration<Foo>, type inference will allow you to write something like this:
structureConfiguration.Ignore(f => f.Bar);

Using delete on a LINQ to Sql Data Context with an Interface - 'Interface is not mapped as a table'

I have three entities in my data context which inherit IFileData, but I want just a single delete method. How can this be achieved?
The code below give the error The type "IFileData" is not mapped as a Table.
public void ImageDelete<T>(T instance) where T : class, IFileData
{
using (var db = this.CreateDataContext())
{
db.GetTable<T>().DeleteOnSubmit(instance);
db.SubmitChanges();
}
}
The only solution I have so far is to do a switch on the .GetType() which is a bit messy.
Thanks!
Solution:
As Marc mentioned, I used dynamic in the calling method:
ImageDelete((dynamic)(instance as IFileData));
You could build the Expression manually, using the actual T ? Untested (since that Delete doesn't exist?), but something like:
var param = Expression.Parameter(typeof(T), "x");
var lambda = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Property(param, "Key"),
Expression.Constant(instance.Key)
), param);
This then isn't bound to IFileData, but to T. It will fail, however, if IFileData.Key is implemented via explicit interface implementation - there must be an obvious Key field/property on T.
Then:
var table = db.GetTable<T>();
var obj = table.SingleOrDefault(lambda);
if(obj != null) table.DeleteOnSubmit(obj);
db.SubmitChanges();

Linq2SQL - selecting items using reflection

I'm trying to load a collection of entities using Linq2SQL. The problem is, I dont know what the entities are,IList<object>. I have tried to select them using reflection, but I get an out of memory error when I do the select, I presume because the context is unable to parse my expression,and is loading everything from the DB.
If anyone has any advice on this, or an alternative way to do what I want, please let me know.
foreach (object entity in requiredEntities)
{
Type entityType = entity.GetType();
IQueryable<object> entityTable = (IQueryable<object>)dataContext.GetTable(entityType);
// grab the objects primary key field
var pkeyField = entityType.GetProperties().SingleOrDefault(p =>
p.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute),true)
.Cast<System.Data.Linq.Mapping.ColumnAttribute>()
.Any(attrib => attrib.IsPrimaryKey));
object pkeyValue = pkeyField.GetValue(entity,null);
Func<object,bool> primaryKeySelector = o => pkeyField.GetValue(o,null) == pkeyValue;
// crash here, out of memory exception
object result = entityTable.Where(primaryKeySelector).SingleOrDefault();
}
By using a delegate you are forcing it to use LINQ-to-Objects, which is why it is running out of memory. What you need to do is build an Expression instead. Equally, it is bad practice to use the attributes as that is not the only model that LINQ-to-SQL supports; it is preferable to look at dataContext.Mapping.GetMetaType(entityType) to get the primary key.
If you have 4.0, the following should work:
var entityType = typeof(User);
var metaType = dataContext.Mapping.GetMetaType(entityType);
var member = metaType.DataMembers.Single(m => m.IsPrimaryKey).Member;
var param = Expression.Parameter(entityType);
var body = Expression.Equal(Expression.MakeMemberAccess(param, member),
Expression.MakeMemberAccess(Expression.Constant(entity), member));
dynamic table = dataContext.GetTable(entityType);
object result = Cheeky(table, body, param);
with
static T Cheeky<T>(ITable<T> source, Expression body, ParameterExpression param)
where T : class
{
var predicate = Expression.Lambda<Func<T, bool>>(body, param);
return source.SingleOrDefault(predicate);
}

Categories