A generic entity comparison expression builder - c#

As stated here EntityFramework (at least in version 6) does not support user types comparison (and thus, entities comparison) which is not practical for an ORM.
I have a scenario where I have a lot of code that does entities comparison that should be translated to SQL.
All my entities have an Id field of type int.
class Entity
{
public int Id { get; set; }
}
For queries where I want to compare entities in a Where clause, I would like to be able the compiler to detect if trying to perform an invalid comparison.
Considere the following classes :
class SomeEntity : Entity
{
public RelationEntity_Id int{ get; set; }
public RelationEntity Relation { get; set; }
}
class RelationEntity : Entity
{
}
With the following syntax there is such a check on the types:
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().Where(e => s.Relation == relation);
}
With the following one, we are only comparing int and this could be error prone :
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().Where(e => s.Relation.Id == relation.Id);
}
Because EntityFramework needs to be told how the comparison on objects should be done, I'm looking for a way create a generic Expression returning an expression Comparing the Id of a SomeEntity with the Id of a RelationEntity.
Something like this :
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().Where(e => AreEquals(s.Relation, relation));
}
AreEquals would be adapting the Expression Tree for the SQL generated correctly comparing on entity Ids.
I have found this post which seems to be a good start, but I can't get it to append the '.Id' part of the Expression.
Any idea of how I could achieve this, or what could be the good way to go ?

Here you go.
The helper function (inside some static class):
public static IQueryable<T> WhereEquals<T>(this IQueryable<T> source, T target)
where T : Entity
{
var item = Expression.Parameter(typeof(T), "item");
var body = Expression.Equal(
Expression.Property(item, "Id"),
Expression.Property(Expression.Constant(target), "Id"));
var predicate = Expression.Lambda<Func<T, bool>>(body, item);
return source.Where(predicate);
}
and the usage:
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().WhereEquals(relation);
}

Related

Generic Find Method on Repository using Mongo

I am trying to make a generic repository with a find operation that takes an expression. I have the following right now (I am using FastMapper for projecting from my entity objects to an external contract object):
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Project().To<T>().Where(predicate).ToList();
}
The problem is I get the following exception: "Where with predicate after a projection is not supported."
I can do the following, but this would severely poor performance wise because it would involve retrieving every record from the database before performing the filtering:
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Project().To<T>().ToList().AsQueryable().Where(predicate).ToList();
}
I am wondering if there is a way to convert the expression from a T to a Y object so that I could do the following instead (which I think would be the most performant because it then passes the filtering down to the database and only performs Project on the resultset):
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Where(predicate).Project().To<T>().ToList();
}
any help would be greatly appreciated. Thanks.
Update
So, using the information from this question (Question) I was able to get closer to what I was looking for. I am now able to do the following:
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var newPredicate = TransformPredicateLambda<T, Y>(predicate);
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Where(newPredicate).Project().To<T>().ToList();
}
The only thing I have left to resolve is retrieving the fastmapper mappings at this point (if property is null part):
protected override Expression VisitMember(MemberExpression node)
{
var dataContractType = node.Member.ReflectedType;
var activeRecordType = _typeConverter(dataContractType);
var property = activeRecordType.GetProperty(node.Member.Name);
if (property == null)
{
}
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
property
);
return converted;
}
essentially, some of my objects might look like this:
//object used throughout my code
public class Store
{
public string StoreId {get; set;}
public Account Account {get; set;}
...
}
//object used in access layer only
public class Store
{
public string StoreId {get; set;}
public string AccountId {get; set;}
...
}
And in my initialization script, I define a type adapter like so:
TypeAdapterConfig<Store, Models.Store>.NewConfig()
.MapFrom(a => a.Id, s => s.StoreId != null ? new ObjectId(s.StoreId) : new ObjectId())
.MapFrom(d => d.AccountId, s => s.Account.AccountId)
.IgnoreNullValues(true);
The reason you can't do a projection from Y to T prior to handing the query to MongoDB is that MongoDB doesn't know anything about T. We only know about Y (because that is the type of the collection). For instance:
class Person
{
[BsonElement("fn")]
public string FirstName { get; set; }
[BsonElement("ln")]
public string LastName { get; set; }
}
class PersonView
{
public string FullName { get; set; }
}
And your projector does something like this:
person => new PersonView { FullName = person.FirstName + person.LastName }
We don't have access to the code in the projector and have no idea that FullName is the concatenation of FirstName and LastName and therefore can't tell MongoDB to do this.
The LINQ support in the 1.x version of the driver cannot target the Aggregation Framework, which is the only place this would be legal, and only assuming your projector produced an Expression<Func<Person, PersonView>> and not a compiled Func<Person, PersonView>. However, the 2.x version of the driver will have better support for this, although it really depends on what FastMapper does underneath.
======
So, your current options are this:
Change the method signature to be Expression<Func<Y, bool>>. This would not only satisfy your needs, but also limit the documents you are projecting to only those that pass the filter.
If T inherits from Y, you could start with an OfType and not even need the projector: collection.AsQueryable<Y>().OfType<T>.Where(predicate).ToList();

Linq Expressions: Create a max query using a generic DbSet

In order to simplify creating fake data for unit testing, I want to have a function that can create a generic Entity (which I use as an underlying object for a lot of other classes). The entity has an index, and, for generating a new index, I want to simply find the currently highest number and add one.
I am using Index for a lot of other classes, and would like to make this function as generic as possible. My problem is that I don't know how to specify what DbSet to use in my generic function GetMaxID.
This is what I got so far:
private Entity CreateGenericEntity()
{
return new Entity()
{
Guid = Guid.NewGuid(),
Index = GetMaxID<Entity>(x => x.Index) + 1,
};
}
private int GetMaxID<TEntity>(Expression<Func<TEntity, int>> expression)
{
return _repository.Set<TEntity>().Max(expression);
}
_repository has a bunch of different IDbSets properties, such as
public IDbSet<Customers> Customers{ get; set; }
public IDbSet<Orders> Orders{ get; set; }
etc.
I found that I was missing the declaration of TEntity, which was fixed by appending where TEntity : class to my function. I also had to change the expression to accept int? in order to handle the case where the query returns a null value. The complete function looks like this (if anyone is interested)
private int GetMaxID<TEntity>(Expression<Func<TEntity, int?>> expression) where TEntity : class
{
return _repository.Set<TEntity>().Max(expression) ?? 0;
}

How do I make an anonymous method run in LINQ to Entities?

I'm trying to build a generic method that EF4.1 to look in both the Database and the Local memory for a particular row in a table that matches a particular criteria.
So far, this is what I have this.
This is the caller.
dbEntities.MyTables.LocalAndDb(delegate(MyTable s)
{ return s.Description.Contains("test"); });
This is LocalAndDb
public static object LocalAndDb<T>(this DbSet<T> myTable, Func<T, bool> function) where T : class
{
// look in local
var item = myTable.Local.Where(o => function((T)o)).FirstOrDefault()
// if not exist, look in the database
if (item == null)
{
Expression<Func<T, bool>> predicate = (u) => function(u);
item = myTable.Where(predicate).FirstOrDefault();
}
return item;
}
The problem is with this line.
item = myTable.Where(predicate).FirstOrDefault();
When it calls the database, it throws this error.
"The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."
I imagine it's because I'm passing in an anonymous method and it doesn't know how to turn this into SQL. I thought converting it to an Expression object would do the trick but it's still not working for me.
What do I need to do to make a anonymous method become something that LINQ can turn into SQL?
To make this work, you need to pass the lambda expression to LocalAndDb as an expression tree (so that LINQ to Entities can analyze the code and translate it to SQL):
public static object LocalAndDb<T>(this DbSet<T> myTable,
Expression<Func<T, bool>> expr) where T : class {
// ...
if (item == null) {
item = myTable.Where(expr).FirstOrDefault();
}
return item;
}
Then, of course, the problem is that you cannot execute the expression tree when checking the in-memory data. One way to solve this is to use the Compile method of Expression<T>, but that will be a bit inefficient (depending on your scenario).
Another option is to just pass the condition as both function and expression tree:
public static object LocalAndDb<T>(this DbSet<T> myTable,
Func<T, boo> function, Expression<Func<T, bool>> expr) where T : class {
var item = myTable.Local.Where(o => function((T)o)).FirstOrDefault();
if (item == null) {
item = myTable.Where(expr).FirstOrDefault();
}
return item;
}
table.LocalAndDb(t => t.Foo > 10, t => t.Foo > 10);
This is a bit ugly, but it doesn't require inefficient compilation at runtime. If you want a slightly more sophisticated solution, then you can define your own type to keep pre-compiled functions:
class Precompiled<T1, T2> {
public Precompiled(Expression<Func<T1, T2>> expr) {
this.Expression = expr;
this.Function = expr.Compile();
}
public Expression<Func<T1,T2>> Expression { get; private set; }
public Func<T1,T2> Function { get; private set; }
}

How to reflect over T to build an expression tree for a query?

I'm trying to build a generic class to work with entities from EF. This class talks to repositories, but it's this class that creates the expressions sent to the repositories. Anyway, I'm just trying to implement one virtual method that will act as a base for common querying. Specifically, it will accept a an int and it only needs to perform a query over the primary key of the entity in question.
I've been screwing around with it and I've built a reflection which may or may not work. I say that because I get a NotSupportedException with a message of LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object, System.Object[])' method, and this method cannot be translated into a store expression. So then I tried another approach and it produced the same exception but with the error of The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities. I know it's because EF will not parse the expression the way L2S will.
Anyway, I'm hopping someone with a bit more experience can point me into the right direction on this. I'm posting the entire class with both attempts I've made.
public class Provider<T> where T : class {
protected readonly Repository<T> Repository = null;
private readonly string TEntityName = typeof(T).Name;
[Inject]
public Provider(
Repository<T> Repository) {
this.Repository = Repository;
}
public virtual void Add(
T TEntity) {
this.Repository.Insert(TEntity);
}
public virtual T Get(
int PrimaryKey) {
// The LINQ expression node type 'ArrayIndex' is not supported in
// LINQ to Entities.
return this.Repository.Select(
t =>
(((int)(t as EntityObject).EntityKey.EntityKeyValues[0].Value) == PrimaryKey)).Single();
// LINQ to Entities does not recognize the method
// 'System.Object GetValue(System.Object, System.Object[])' method,
// and this method cannot be translated into a store expression.
return this.Repository.Select(
t =>
(((int)t.GetType().GetProperties().Single(
p =>
(p.Name == (this.TEntityName + "Id"))).GetValue(t, null)) == PrimaryKey)).Single();
}
public virtual IList<T> GetAll() {
return this.Repository.Select().ToList();
}
protected virtual void Save() {
this.Repository.Update();
}
}
UPDATE for #Gabe
This is what my repository class looks like:
public class Repository<T> where T : class {
protected readonly ObjectContext ObjectContext = null;
private readonly IObjectSet<T> ObjectSet = null;
[Inject]
public Repository(
ObjectContext ObjectContext) {
this.ObjectContext = ObjectContext;
this.ObjectSet = this.ObjectContext.CreateObjectSet<T>();
}
public virtual void Delete(
T Entity) {
this.ObjectSet.DeleteObject(Entity);
}
public virtual void Insert(
T Entity) {
this.ObjectSet.AddObject(Entity);
}
public virtual IQueryable<T> Select() {
return this.ObjectSet;
}
public virtual IQueryable<T> Select(
Expression<Func<T, bool>> Selector) {
return this.ObjectSet.Where(Selector);
}
public virtual void Update() {
this.ObjectContext.SaveChanges();
}
}
The names of the methods are based on the SQL functions, not on the LINQ methods, which is where I think you're getting confused on how my repository functions.
As Pauli alludes to, you need to manually create Expression trees, although reflection isn't necessary in this case. Here's how you could write your Get function:
public virtual T Get(
int PrimaryKey)
{
var param = Expression.Parameter(typeof(T));
// create expression for param => param.TEntityNameId == PrimaryKey
var lambda = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Property(param, TEntityName + "Id"),
Expression.Constant(PrimaryKey)),
param);
return this.Repository.Single(lambda);
}
Also, note that your GetAll function doesn't need Select -- return Repository.ToList(); will work just as well.

LINQ Expression help with Func TEntity,TType

I have a repository method that accepts an order by parameter in the form:
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity,string>> orderBy)
Now that works fine when trying to sort by a property of type string,
var entities = rep.Get(x => x.Name);
but what if i want to sort by double or int or any other type.
Doing something like var entities = rep.Get(x => x.Price); obviously throws a compile error saying I can't convert double to string.
How can I make this more generic so I can sort by any property in my entity, or at least the properties where the type implements IComparable or something similar?
The repository class itself should have a type parameter. Then you don't need to specify the entity type when accessing the repository members. Like this:
public interface IRep<TEntity>
{
IEnumerable<TEntity> Get<TOrderBy>(
Expression<Func<TEntity, TOrderBy>> orderBy
);
}
An example entity class:
public class MyEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
}
An example repository implementation:
public class Rep<TEntity> : IRep<TEntity> { ... }
Now you can just consume it like this:
var a = new Rep<MyEntity>();
var b = a.Get(x => x.Name); // string
var c = a.Get(x => x.Price); // decimal
This is how Linq does it. :)
public IEnumerable<TEntity> Get<TEntity, TOrderBy>(Expression<Func<TEntity,TOrderBy>> orderBy)

Categories

Resources