Using Expressions to combine Funcs with different signature - c#

I am using the following class to wrap some DocumentDB access which allows me to store multiple entities in the same collection:
public class TypedEntity<T> {
public string Type { get; set; }
public T Item { get; set; }
public TypedEntity(T item) {
Id = Guid.NewGuid().ToString();
Item = item;
Item.Id = Id;
Type = typeof (T).FullName;
}
}
The usage of this class is encapsulated inside a repository class. I'm trying to build the API of the repository class such that the consumer doesn't need to know about the usage of TypedEntity<T> and can instead treat it as a source for just <T>. For example, the repository has a method with this signature:
public async Task<IQueryable<T>> WhereAsync(Func<T, bool> predicate)
In order to actually retrieve this data, the predicate needs to be combined/converted with one that interacts with TypedEntity<T>. This is the pseudo code that I'm picturing in my head for what I'd ultimately like to achieve:
public async Task<IQueryable<T>> WhereAsync(Func<T, bool> predicate) {
// remembering that dataSource is talking to a backing store of TypedEntity<T>
var queryable = dataSource.Where(x => x.Type == typeof(T).FullName && predicate(x.Item));
// ... other business logic stuff
}
This actually builds but ultimately results in an Expression that uses .Invoke around the passed in predicate (which DocumentDb is unable to understand). Is there some way that I can combine the type part with the passed in Func to build up the Expression manually?

You'll want to take in an Expression<> rather than just a Func<>. Then, it should be fairly easy to apply it in a Where() clause after you've performed a Select() projection:
public async Task<IQueryable<T>> WhereAsync(Expression<Func<T, bool>> predicate) {
// remembering that dataSource is talking to a backing store of TypedEntity<T>
var typeName = typeof(T).FullName;
var queryable = dataSource.Where(x => x.Type == typeName)
.Select(x => x.Item)
.Where(predicate);
// ... other business logic stuff
}

Related

Entity Framework Search Like or Equal

I'm using EntityFrameworkCore and am trying to create a simplified instance of searching for either 'equal to' or 'like' based whether the search object contians the wildcard character.
Here's the base of what I'm working with
public class Person
{
public string Name;
public string MothersName;
public string FathersName;
}
public class SearchPerson
{
public string Name;
}
public class Program
{
public void FindPerson(SearchPerson searchPerson)
{
if (!string.IsNullOrEmpty(searchPerson.Name))
{
if (searchPerson.Name.Contains("%"))
{
EFPersonObject.Where(m => EF.Functions.Like(m.Name, searchPerson.Name));
}
else
{
EFPersonObject.Where(m => m.Name == searchPerson.Name);
}
}
}
}
If my SearchPerson class extends to 5 or 10 or 15 possible search params, there is a lot of repeated code. I should be able to implement some reflection in an extension and using Jim C's response here, get and pass the name of the property and simplify a lot of it down to one line
public static class SearchExtension
{
public static void FindLike<T>(this DbSet<T> model, PropertyInfo info, string searchValue) where T : class
{
if (!string.IsNullOrEmpty(searchValue))
{
if (searchValue.Contains("%"))
{
model.Where(m => EF.Functions.Like(typeof(T).GetProperty(info.Name).GetValue(model, null).ToString(), searchValue));
}
else
{
model.Where(m => typeof(T).GetProperty(info.Name).GetValue(model, null).ToString() == searchValue);
}
}
}
}
Usage:
EFPersonObject.FindLike(typeof(Person).GetProperty(RemoteMgr.GetPropertyName(()=>typeof(Person).Name)), searchPerson.Name);
(I haven't tested it yet, but if it isn't right, it should be close), but I'm going to assume I'm going to take a performance hit. Is there another way to implement this where reflection isn't needed to avoid the performance hit?
Using reflection (and other non SQL translatable) calls inside the query expression tree is not a good idea. In EF Core 1x and 2.x it will cause client evaluation, and EF Core v3+ will throw exception similar to EF 6.
LINQ to Entities best work with expressions. And once you need expression, you'd better make your custom extension method receive lambda expression directly rather than PropertyInfo obtained via lambda expression as in the linked topic.
Here is a sample implementation of the above:
public static partial class QueryableExtensions
{
public static IQueryable<T> WhereMatch<T>(this IQueryable<T> source, Expression<Func<T, string>> expr, string searchValue)
{
if (string.IsNullOrEmpty(searchValue))
return source;
else if (searchValue.Contains("%"))
return source.Where(expr.Map(value => EF.Functions.Like(value, searchValue)));
else
return source.Where(expr.Map(value => value == searchValue));
}
static Expression<Func<TSource, TTarget>> Map<TSource, TIntermediate, TTarget>(this Expression<Func<TSource, TIntermediate>> source, Expression<Func<TIntermediate, TTarget>> target)
=> Expression.Lambda<Func<TSource, TTarget>>(Expression.Invoke(target, source.Body), source.Parameters);
}
The main method is WhereMatch. It uses a small Expression helper method called Map for composing lambda expressions from other lambda expressions.
Sample usage would be:
// SearchPerson searchPerson
// DbContext db
var query = db.Set<Person>()
.WhereMatch(p => p.Name, searchPerson.Name)
.WhereMatch(p => p.MothersName, searchPerson.MothersName)
.WhereMatch(p => p.FathersName, searchPerson.FathersName);
For Equality comparison you should use ==:
EFPersonObject.Where(m => m.Name == searchPerson.Name);
For LIKE :
like 'something%': (StartsWith Method)
EFPersonObject.Where(m => m.Name.StartsWith(searchPerson.Name));
like '%something': (EndsWith Method)
EFPersonObject.Where(m => m.Name.EndsWith(searchPerson.Name));
like '%something%': (Contains Method)
EFPersonObject.Where(m => m.Name.Contains(searchPerson.Name));

Access property of a generic type inside a function

I have a generic function GetDocuments<T> that's querying the CosmosDB API. The generic is constrained by a custom IDocument interface. At the moment, I'm passing an enum as an argument to this function that determines the type of the document -- however, my interface has the document type as a property, so it seems like I should be able to access that somehow instead of passing another arg.
Because my argument is in an Expression, I'm not sure how to access that value (I'm not sure if using the API to access expression params is the right approach). If I had an IDocument as an argument, it seems pretty straightforward to access it.
Given this code, how can I access the DocumentType without passing it to GetDocuments<T>?
Function definition:
public IEnumerable<T> GetDocuments<T>(Expression<Func<T, bool>> predicate, Enumerations.DocumentType type) where T : IDocument
{
var results = Client.CreateDocumentQuery<T>(GetDocumentCollectionUri(), GetFeedOptions())
.Where(predicate)
.Where(s => s.DocumentType == type)
.ToList();
return results;
}
Interface definition:
public interface IDocument
{
[JsonProperty(PropertyName = "id")]
string Id { get; set; }
[JsonProperty(PropertyName = "documentType")]
Enumerations.DocumentType DocumentType { get; }
}
Function call:
var messages = mailboxRepository.GetDocuments<MailboxMessageTemplate>(
s => s.UserId == user.ID,
Enumerations.DocumentType.MessageTemplate);
You can do that by pre-creating your expression and just adding it to your query.
Here is the expression that would do the trick.
internal static Expression<Func<T, bool>> TypeSpecificExpression<T>() where T : class
{
var parameter = Expression.Parameter(typeof(IDocument));
var member = Expression.Property(parameter, nameof(IDocument.Enumerations.DocumentType));
var contant = Expression.Constant(nameof(T));
var body = Expression.Equal(member, contant);
var extra = Expression.Lambda<Func<T, bool>>(body, parameter);
return extra;
}
You can then simple change your method to be:
public IEnumerable<T> GetDocuments<T>(Expression<Func<T, bool>> predicate) where T : IDocument
{
var results = Client.CreateDocumentQuery<T>(GetDocumentCollectionUri(), GetFeedOptions())
.Where(predicate && TypeSpecificExpression())
.ToList();
return results;
}
Obviously I don't have access to the Enumerations.DocumentType enum so you might need to do some tweeting on the value you are setting here: var contant = Expression.Constant(nameof(T));
On a side note, you should not be calling .ToList() like that on CreateDocumentQuery. You are synchornizing a query that can be a serious performance hit. You should be using the .AsDocumentQuery() method to get the query and then call query.ExecuteNextAsync when query.HasMoreResults.
On a second side note, it looks like you are trying to build something that the library Cosmonaut already does, including the feature you just asked a question for (you can find that method here). It's worth taking a look.
Disclaimer: I made Cosmonaut
So you are looking for a way to convert an Expression<Func<T, bool>> to a Func<T, bool>?
You can call Compile.
Compiles the lambda expression described by the expression tree into executable code and produces a delegate that represents the lambda expression.
var results = Client.CreateDocumentQuery<T>(GetDocumentCollectionUri(), GetFeedOptions())
.Where(predicate.Compile())
.Where(s => s.DocumentType == type)
.ToList();

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();

How do I read an Expression's contents?

I have a class that works as a repository of some sort, and grants access to a database. I'm trying to customize it to allow queries with expressions.
So, I want to be able to do this:
IList<MyClass> myList = myRepository.GetBy(x => x.Name == "SomeName");
//and...
IList<MyClass> myList2 = myRepository.GetBy(x => x.Name == "SomeName" && x.ID = 5);
This is what I need to have on the repository function:
public IList<T> GetBy(Expression<Func<T, bool>> expression)
{
//Set up the query, etc
//I'm at the WHERE clause, and I need to get the property(ies) name(s) of the expression and their values to properly set the WHERE
}
How can I do this?
What you want to do is this:
IList <MyClass> myList2 = myRepository.GetBy (x => x.Name == "SomeName" && x.ID = 5);
It is true that you can represent x => x.Name == "SomeName" && x.ID = 5 with Expression <Func <T, bool >>
but also what you can do with the delegate Func <T, bool> only.
Regardless of which were to take the data will always be from an IEnumerable <T> so you always will have the Where method (whenever you use the namespace System.Linq), which accepts as a parameter a delegate Func <T, bool> . If the object IEnumerable <T> is a DbSet <T>, this will take care of transforming the delegate Func <T, bool> in a sql query. Remember that a Linq query, which is what is being used, is only executed when the query data is used or agrees with methods ToList () or ToArray () for example.
example:
IEnumerable <MyClass> list = ... from wherever you get the data even from DbSet of EntityFramework
var query = list.Where (x => x.Name == "SomeName" && x.ID = 5);
query is a shost a query, it contains no data until this is done
foreach (var x in list) is being consumed, so the query is executed
{
   var c = x.Name;
}
or this
`var temp = query.ToList ();`
This force to stored in a List <MyClass>
With all this I want to say that if you use the DbSet of EntityFramework, what happens is that the delegate Func <T, bool> is transformed into a sql query, so that the data manager is responsible for filtering data (as it should be).
 From this alone you would have to simply have your method
public IList <T> GetBy (Func <T, bool> expression)
{
    origen.Where (expression).ToList();
}
if I understand your question correctly, you shoul inherit you repository interfaces from base generic repository interface.
public interface IRepositoryBase<TEntity>
{
IList<TEntity> GetBy(Expression<Func<TEntity, bool>> expression)
}
and repository realisation from base repository implementation
public abstract class RepositoryBase<TEntity>: IRepositoryBase<TEntity>
{
public MyEntities EntitiesContext { get; set; }
public IList<TEntity> GetBy(Expression<Func<TEntity, bool>> expression)
{
return EntitiesContext.Set<TEntity>().Where(filter).ToList()
}
}

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; }
}

Categories

Resources