Entity Framework Search Like or Equal - c#

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

Related

Entity Framework LINQ Expression on object within object

Editing this question in the hope to make it clearer.
We have entity framework code first setup. I've simplified two classes for the purposes of example, in reality there are around 10+ more classes similar to the 'Record', where Item is a navigational property/foreign key.
Item class:
public class Item
{
public int Id { get; set; }
public int AccountId { get; set; }
public List<UserItemMapping> UserItemMappings { get; set; }
public List<GroupItemMapping> GroupItemMappings { get; set; }
}
Record class:
public class Record
{
public int ItemId { get; set; }
public Item Item { get; set; }
}
this.User is an injected user object into each repo and is contained on the repository base.
We have an Item repository with the following code:
var items = this.GetAll()
.Where(i => i.AccountId == this.User.AccountId);
I created the follow expression on the repository base to easily filter on that (in the hope of re-use). We cannot use static extension methods due to how LINQ to entities works (System.NotSupportedException "LINQ to Entities does not recognize the method X and this method cannot be translated into a store expression.").
protected Expression<Func<Item, bool>> ItemIsOnAccount()
{
return item => item.AccountId == this.User.AccountId;
}
I have solved the case of the above, by doing this:
var items = this.GetAll().Where(this.ItemIsOnAccount());
We have additional filtering based on user permissions within that account (again, another case where I do not want to repeat this code in every repo we have):
protected Expression<Func<Item, bool>> SubUserCanAccessItem()
{
return item => this.User.AllowAllItems
|| item.UserItemMappings.Any(d => d.UserId.Value == this.User.Id)
|| item.GroupItemMappings.Any(vm =>
vm.Group.GroupUserMappings
.Any(um => um.UserId == this.User.Id));
}
Which I am able to use as follows:
var items = this.GetAll().Where(this.SubUserCanAccessItem());
However, what we also need, in the Record repository is a way to solve the following:
var records = this.GetAll()
.Where(i => i.Item.AccountId == this.User.AccountId);
Because Item is a single navigational property, I do not know how to apply the expressions I have created to this object.
I want to reuse the expression I created in the repo base on all of these other repos, so that my 'permission based' code is all in the same place, but I cannot simply throw it in because the Where clause in this case is of Expression< Func < Record,bool >>.
Creating an interface with a method of:
Item GetItem();
on it and putting it on the Record class does not work because of LINQ to entities.
I cannot also create a base abstract class and inherit from it, because there could be other objects than Item that need to be filtered on. For instance a Record could also have a 'Thing' on it that has permission logic. Not all objects will require to be filtered by 'Item' and 'Thing', some by only one, some by another, some by both:
var items = this.GetAll()
.Where(this.ItemIsOnAccount())
.Where(this.ThingIsOnAccount());
var itemType2s = this.GetAll().Where(this.ThingIsOnAccount());
var itemType3s = this.GetAll().Where(this.ItemIsOnAccount());
Due to this having a single parent class would not work.
Is there a way in which I can reuse the expressions I have already created, or at least create an expression/modify the originals to work across the board within the OTHER repos that of course return their own objects in a GetAll, but all have a navigation property to Item? How would I need to modify the other repos to work with these?
Thanks
The first step for expression reusability is to move the expressions to a common static class. Since in your case they are tied to User, I would make them User extension methods (but note that they will return expressions):
public static partial class UserFilters
{
public static Expression<Func<Item, bool>> OwnsItem(this User user)
=> item => item.AccountId == user.AccountId;
public static Expression<Func<Item, bool>> CanAccessItem(this User user)
{
if (user.AllowAllItems) return item => true;
return item => item.UserItemMappings.Any(d => d.UserId.Value == user.Id) ||
item.GroupItemMappings.Any(vm => vm.Group.GroupUserMappings.Any(um => um.UserId == user.Id));
}
}
Now the Item repository would use
var items = this.GetAll().Where(this.User.OwnsItem());
or
var items = this.GetAll().Where(this.User.CanAccessItem());
In order to be reusable for entities having Item reference, you would need a small helper utility for composing lambda expressions from other lambda expressions, similar to Convert Linq expression "obj => obj.Prop" into "parent => parent.obj.Prop".
It's possible to implement it with Expression.Invoke, but since not all query providers support for invocation expressions (EF6 doesn't for sure, EF Core does), as usual we'll use a custom expression visitor for replacing a lambda parameter expression with another arbitrary expression:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { Source = source, Target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : node;
}
}
And the two composing functions are as follows (I don't like the name Compose, so sometimes I use the name Map, sometimes Select, Bind, Transform etc., but functionally they do the same. In this case I'm using Apply and ApplyTo, with the only difference being the transformation direction):
public static partial class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Apply<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> outer, Expression<Func<TInner, TResult>> inner)
=> Expression.Lambda<Func<TOuter, TResult>>(inner.Body.ReplaceParameter(inner.Parameters[0], outer.Body), outer.Parameters);
public static Expression<Func<TOuter, TResult>> ApplyTo<TOuter, TInner, TResult>(this Expression<Func<TInner, TResult>> inner, Expression<Func<TOuter, TInner>> outer)
=> outer.Apply(inner);
}
(Nothing special there, code provided for completeness)
Now you could reuse the original filters by "applying" them to a expression which selects Item property from another entity:
public static partial class UserFilters
{
public static Expression<Func<T, bool>> Owns<T>(this User user, Expression<Func<T, Item>> item)
=> user.OwnsItem().ApplyTo(item);
public static Expression<Func<T, bool>> CanAccess<T>(this User user, Expression<Func<T, Item>> item)
=> user.CanAccessItem().ApplyTo(item);
}
and add the following to the entity repository (in this case, Record repository):
static Expression<Func<Record, Item>> RecordItem => entity => entity.Item;
which would allow you to use there
var records = this.GetAll().Where(this.User.Owns(RecordItem));
or
var records = this.GetAll().Where(this.User.CanAccess(RecordItem));
This should be enough to satisfy your requirements.
You can go further and define an interface like this
public interface IHasItem
{
Item Item { get; set; }
}
and let the entities implement it
public class Record : IHasItem // <--
{
// Same as in the example - IHasItem.Item is auto implemented
// ...
}
then add additional helpers like this
public static partial class UserFilters
{
public static Expression<Func<T, Item>> GetItem<T>() where T : class, IHasItem
=> entity => entity.Item;
public static Expression<Func<T, bool>> OwnsItem<T>(this User user) where T : class, IHasItem
=> user.Owns(GetItem<T>());
public static Expression<Func<T, bool>> CanAccessItem<T>(this User user) where T : class, IHasItem
=> user.CanAccess(GetItem<T>());
}
which would allow you omit the RecordItem expression in the repository and use this instead
var records = this.GetAll().Where(this.User.OwnsItem<Record>());
or
var records = this.GetAll().Where(this.User.CanAccessItem<Record>());
Not sure if it gives you a better readability, but is an option, and syntactically is closer to Item methods.
For Thing etc. just add similar UserFilters methods.
As a bonus, you can go even further and add the usual PredicateBuilder methods And and Or
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body,
right.Body.ReplaceParameter(right.Parameters[0], left.Parameters[0])), left.Parameters);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Expression.Lambda<Func<T, bool>>(Expression.OrElse(left.Body,
right.Body.ReplaceParameter(right.Parameters[0], left.Parameters[0])), left.Parameters);
}
so you could use something like this if needed
var items = this.GetAll().Where(this.User.OwnsItem().Or(this.User.CanAccessItem()));
in the Item repository, or
var records = this.GetAll().Where(this.User.OwnsItem<Record>().Or(this.User.CanAccessItem<Record>()));
in the Record repository.
I can't really tell if this could work in your case, depends on how your entities might be setup, but one thing you can try is to have an interface like IHasItemProperty with a GetItem() method and have the entities where you want to use this implement that interface. Something like this :
public interface IHasItemProperty {
Item GetItem();
}
public class Item: IHasItemProperty {
public Item GetItem() {
return this;
}
public int UserId {get; set;}
}
public class Record: IHasItemProperty {
public Item item{get;set;}
public Item GetItem() {
return this.item;
}
}
public class Repo
{
protected Expression<Func<T, bool>> ItemIsOnAccount<T>() where T: IHasItemProperty
{
return entity => entity.GetItem().UserId == 5;
}
}
I have used an int just to make things simpler.
You should be able to do this with .AsQueryable().
class Account
{
public IEnumerable<User> Users { get; set; }
public User SingleUser { get; set; }
static void Query()
{
IQueryable<Account> accounts = new Account[0].AsQueryable();
Expression<Func<User, bool>> userExpression = x => x.Selected;
Expression<Func<Account, bool>> accountAndUsersExpression =
x => x.Users.AsQueryable().Where(userExpression).Any();
var resultWithUsers = accounts.Where(accountAndUsersExpression);
Expression<Func<Account, bool>> accountAndSingleUserExpression =
x => new[] { x.SingleUser }.AsQueryable().Where(userExpression).Any();
var resultWithSingleUser = accounts.Where(accountAndSingleUserExpression);
}
}
class User
{
public bool Selected { get; set; }
}
You should only use sql (or your database like) items for the predicate. If you put this.User.AccountId into your lambda, that does not exists at database and can't be parsed by it, that's the source of your error message.

Translating generic eager load method from EF6 to EF Core

For EF6, I had a method in my generic repository that I exposed to all service layers in order to retrieve entities from the database with any nested properties as needed:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
{
var queryable = set.AsQueryable();
return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
This way, I could use the method in the following way:
var data = repo.OldMethod(x => x.Papers, => x.People.Select(y => y.Addresses)).ToList();
In EF6, this would load the Papers navigation property, the People navigation property, and the Addresses navigation property on each person. This, as expected, throws an exception in EFCore. Because of the switch to Include-->ThenInclude method in EFCore, I'm not quite sure how to easily replicate this at my service layer which I'd like to not require any information about EntityFramework.
This has been asked many times since the initial release of EF Core. Earlier prerelease versions of EF Core even were supporting it, but then it has been removed from EF Core code (I guess in order to promote the new Include / ThenInclude pattern).
While Include / ThenInclude pattern looks more clear (besides the current Intellisense issues), it has one major drawback - requires access to EntityFrameworkQueryableExtensions, thus reference to Microsoft.EntityFrameworkCore assembly. While paramsExpression>` pattern has no such requirement.
The good thing is the one can relatively easily add that functionality. The EF6 source code is publicly available on GitHub, and from there we can see that it uses a method called TryParsePath to build dot separated string path which then is passed to the string overload of Include method.
The same can be applied in EF Core. We can probably use the EF6 code, but I'm going to provide my own version. It can be easily be seen that the supported constructs are member accessors or calls to method called Select with 2 arguments, the second being LambdaExpression.
Following is my interpretation of the above, encapsulated in two custom extension methods:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore
{
public static class IncludeExtensions
{
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> includePaths) where T : class
=> includePaths.Aggregate(source, (query, path) => query.Include(path));
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> includePaths) where T : class
=> source.Include(includePaths.Select(e => GetIncludePath(e?.Body)));
static string GetIncludePath(Expression source, bool allowParameter = false)
{
if (allowParameter && source is ParameterExpression)
return null; // ok
if (source is MemberExpression member)
return CombinePaths(GetIncludePath(member.Expression, true), member.Member.Name);
if (source is MethodCallExpression call && call.Method.Name == "Select"
&& call.Arguments.Count == 2 && call.Arguments[1] is LambdaExpression selector)
return CombinePaths(GetIncludePath(call.Arguments[0]), GetIncludePath(selector.Body));
throw new Exception("Invalid Include path.");
}
static string CombinePaths(string path1, string path2)
=> path1 != null ? path1 + "." + path2 : path2;
}
}
The first is simply helper for calling multiple string includes (taken from my answer to Entity Framework Core 2.0.1 Eager Loading on all nested related entities). The second is the method in question, which converts the expressions to strings and call the first. The main work is done by GetIncludePath private method which recursively processes the expression based on the aforementioned rules, plus one additional rule - when navigating bottom up, it should end with lambda parameter.
Now the implementation of the method is question is simple as that:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
=> set.Include(includeProperties);
When I first started using EFCore (switching from EF6), I built these extension methods to translate the "old" way of including x => x.People.Select(y => y.Addresses) to strings like "People.Addresses", which are supported by EFCore too;
public static class Extensions
{
private class ReferencedPropertyFinder : ExpressionVisitor
{
private readonly Type _ownerType;
private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();
private Expression _parameterExpression;
private int _currentPosition = 0;
public ReferencedPropertyFinder(Type ownerType)
{
_ownerType = ownerType;
}
public IReadOnlyList<PropertyInfo> Properties
{
get { return _properties; }
}
protected override Expression VisitMember(MemberExpression node)
{
var propertyInfo = node.Member as PropertyInfo;
if (propertyInfo != null) {
var currentParameter = GetParameter(node);
if (_parameterExpression == currentParameter) {
_properties.Insert(_currentPosition, propertyInfo);
} else {
_properties.Add(propertyInfo);
_parameterExpression = currentParameter;
_currentPosition = _properties.Count() - 1;
}
}
return base.VisitMember(node);
}
private ParameterExpression GetParameter(MemberExpression node)
{
if (node.Expression is ParameterExpression) {
return (ParameterExpression)node.Expression;
} else {
return GetParameter((MemberExpression)node.Expression);
}
}
}
private static IReadOnlyList<PropertyInfo> GetReferencedProperties<T, U>(this Expression<Func<T, U>> expression)
{
var v = new ReferencedPropertyFinder(typeof(T));
v.Visit(expression);
return v.Properties;
}
public static string ToPropertyPath<T>(this Expression<Func<T, object>> expression)
{
var properties = expression.GetReferencedProperties();
var path = string.Join(".", properties.Select(x => x.Name));
return path;
}
}
Incorporating these in your code, you could say something like:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
{
var queryable = set.AsQueryable();
return includeProperties.Aggregate(queryable, (current, includeProperty) =>
current.Include(includeProperty.ToPropertyPath()));
}

Entity Framework combine lambda expressions from different entities

I have 2 lambda expressions that are virtually the same. One returns Expression<Func<Person, bool>> and the other returns Expression<Func<Admission, bool>>.
There is a direct relationship between these 2 entities. A Person can have many Admissions.
The two expressions look like this:
Person expression:
public static Expression<Func<Person, bool>> SearchPerson(string searchTerm)
{
if(string.IsNullOrEmpty(searchTerm))
{
return p => true;
}
return p => p.Nhino.Contains(searchTerm) ||
p.Firstname.Contains(searchTerm) ||
p.Lastname.Contains(searchTerm) ||
p.Pfname.Contains(searchTerm);
}
Admission expression:
public static Expression<Func<Admission, bool>> SearchPerson(string searchTerm)
{
if(string.IsNullOrEmpty(searchTerm))
{
return a => true;
}
return a => a.Person.Nhino.Contains(searchTerm) ||
a.Person.Firstname.Contains(searchTerm) ||
a.Person.Lastname.Contains(searchTerm) ||
a.Person.Pfname.Contains(searchTerm);
}
These search expressions are used extensively throughout the system.
Notice that the return statements are virtually identical, and if our client wants to add more search criteria to the list I want to have this logic in one place and not have to update it for both the Admission and Person expressions.
Is there a way to create an expression or similar that can be used in both the People.Where() clause and the Admissions.Where() clause (or even other Parent/Child relationships that want to search by the same Person criteria) without duplicating code?
Provided that you have control over the design of these classes, you can implement an interface with a predicate function.
For example:
public interface ITestable
{
Func<string, bool> Predicate { get; }
}
public class Admission : ITestable
{
// ...other members...
public Person Person { get; }
public Expression<Func<string, bool>> Predicate =>
x => Person.Predicate(x);
}
public class Person : ITestable
{
// ...other members...
public Expression<Func<string, bool>> Predicate =>
x => string.IsNullOrEmpty(x) || FirstName.Contains(x) || LastName.Contains(x);
}
// The calling code:
IQueryable<ITestable> people =
source.Where(x => x.Predicate(searchTerm));

Expression for OrderBy linq with inner expression

I have the following expresssion for sorting:
this.Students = this.Students.OrderBy(x => x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault().TotalMarks);
While my idea is to abstract the Expression for
x => x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(
p => p.ExamId).FirstOrDefault().TotalMarks
to be made as an lambda Expression so that I can use like
this.Students = this.Students.OrderBy(sortExpression);
It's because I have many sort fields like the TotalMarks defined above, and I want to just create the Expression from the sort field and then call the OrderBy.
I know from this link, we can create an expression where child property is used, but not getting with the inner expressions.
Currently I have given a switch case and written the same stuff in each case like
this.Students = this.Students.OrderBy(x => x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault().SubjectName);
So my idea is to create kindof ExpressionBuilder with a static method which builds the expression passing on the fieldName, like
public static Expression BuildSortExpression(string fieldName) {}
You can easily pull most of the logic into a method:
private int sortExpression(Student x) {
return x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault().TotalMarks;
}
Assuming TotalMarks is an int.
Then you would just need to use:
this.Students.OrderBy(x => sortExpression(x));
or add it as a property of Student.
Warning:
If you are using this with an ORM (linq to SQL, Entity framework, etc), this will not execute as efficiently as the previous code!
Trying to create a reusable expression variable will end up being a lot more work than just creating your own extension method to do the whole ordering:
public static IQueryable<Student> OrderByMarks(this IQueryable<Student> students)
{
return students.OrderBy(student => student.ExamData
.OrderByDescending(exam => exam.ExamDate)
.ThenBy(exam => exam.ExamId)
.FirstOrDefault().TotalMarks);
}
Then you can use it like so:
this.Students = this.Students.OrderByMarks();
Got a solution for this using the idea by Ben.
Created a
Dictionary<string, Func<Student, Object>>
with the sort fields as key and the func as
new Func<Student, object>((Student student) => { return GetLatestExam(student).TotalMarks; })
And the GetLatestExam static method as
private static Study GetLatestExam(Student student)
{
return student.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault();
}
Then in the actual sort, I just have to call like this:
public void Sort(string sortField, bool sortAscending)
{
// based on the sort field, get the sorting expression and execute.
if(sortAscending)
{
this.Students= this.Students.OrderBy(student=>this._customSortColumns[sortField](student));
}
else
{
this.Patients = this.Patients.OrderByDescending(student=>this._customSortColumns[sortField](student));
}
}

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