Entity Framework non generic Include on dynamic ObjectSet - entity-framework

I've ran into a problem while creating an data access layer for my application. I am working with derived entities in Entity Framework. If I try to create an ObjectSet for a derived Entity, I get this Exception:
Blockquote There are no EntitySets defined for the specified entity type 'Type Name'. If 'Type Name' is a derived type, use the base type instead.
I've tried to resolve this problem via Reflection. (if the Entity Type is derived by an Entity Type => get an ObjectSet for the base Type)
I found this: How can I obtain ObjectSet<T> from Entity-Framework at runtime where T is dynamic? but I haven't found how to use Include and Where on the built up ObjectSet.
protected IEnumerable<IDataObject> GetData(Type entityType, Expression<Func<dynamic, bool>> whereClause, Expression<Func<dynamic, dynamic>>[] includes)
{
if (typeof(IDataObject).IsAssignableFrom(entityType.BaseType))
{
return GetData(entityType.BaseType, whereClause, includes);
}
var contextType = this.Context.GetType();
MethodInfo createObjectSetMethod = contextType.GetMethod("CreateObjectSet", new Type[] {}).MakeGenericMethod(entityType);
// Builds up an ObjectSet<EntityType>
dynamic objectSet = createObjectSetMethod.Invoke(this.Context, new object[] { });
dynamic query = objectSet;
if (includes != null)
{
foreach (var include in includes)
{
query = query.Include(include);
}
}
if (whereClause == null)
{
whereClause = (item) => true;
}
query = query.Where(whereClause);
return query.ToList().OfType<IDataObject>();
}
The code runs as intended, as long I don't use the Includes and WhereClause.
When I call this function I dont know the resolved ObjectSet (T-Parameter) at compile time.
Is there any way to use dynamic Expressions in an generic ObjectSet?
Thanks in advance.

The issue looks to be with the
query = query.Include(include);
the var include would be an Expression<Func<dynamic,dynamic>>
The Include Method you access is expecting a string or
Expression<Func<T, TProperty>>
Have you tried to pass the include as a string path? I would expect that to work.
Otherwise you need to construct the expression tree with code
since you cant pass Dynamic objects to
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class;
There is also dynamic linq library to can check out.
System.Linq.Dynamic can be found at following links
http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
with an intro here http://www.scottgu.com/blogposts/dynquery/dynamiclinqcsharp.zip
This is an easier alternative using text to build expression trees.
Building expressions trees with code is more powerful and flexible but harder.

Related

Non-recognized member in lambda expression of EF Core ThenInclude in Extension method

I'm trying to write an extension method to include a certain property (text element, themselves containing a collection of translations) that are present in many of my entity models.
I had no problem with the .Include function:
public static IIncludableQueryable<T, IEnumerable<Translation>> IncludeTextBitWithTranslations<T>(this IQueryable<T> source, Expression<Func<T, TextBit>> predicate) where T: class
{
var result = source.Include(predicate).ThenInclude(t => t.Translations);
return result;
}
And tests proved successful.
Now, in some cases, I have entities that have all their texts in a child - for example Article entity has an ArticleInfo property that contains a few text elements. So I figure I just needed to do another extension that was a ThenInclude instead. With a few differences I finally get this :
public static IIncludableQueryable<TEntity, ICollection<Translation>> ThenIncludeTextBitWithTranslations<TEntity, TPreviousProperty, TextBit>(this IIncludableQueryable<TEntity, TPreviousProperty> source, Expression<Func<TPreviousProperty, TextBit>> predicate) where TEntity: class
{
var result = source.ThenInclude(predicate)
.ThenInclude(t => t.Translations);
return result;
}
And now I get this error:
'TextBit' does not contain a definition for 'Translations' and no extension method 'Translations' accepting an argument of 'TextBit' type was found
This error appears on the last lambda expression t => t.Translations.
This error is extremely weird for me, I've been looking all over the internet for some help on the matter but I was unsuccessful.
I tried forcing the type to the ThenInclude by adding them manually :
var result = source.ThenInclude(predicate)
.ThenInclude<TEntity, TextBit, ICollection<Translation>>(t => t.Translations);
but without success.
Does anyone have some clues as to why?
I'm very much at a loss here
You have extra type parameter TextBit in second one (ThenIncludeTextBitWithTranslations<TEntity, TPreviousProperty, TextBit>), so it is considered as a generic type, not an actual one, remove it:
public static IIncludableQueryable<TEntity, ICollection<Translation>> ThenIncludeTextBitWithTranslations<TEntity, TPreviousProperty>(this IIncludableQueryable<TEntity, TPreviousProperty> source, Expression<Func<TPreviousProperty, TextBit>> predicate) where TEntity: class
{
var result = source.ThenInclude(predicate).ThenInclude(t => t.Translations);
return result;
}

2nd level caching EF codeFirst and Predicate not working on a where clause

I have a question about caching with Entity Framework code first.
I need to cache my query's results and I came about something that I didn't know.
Func<T, bool> predicate does not work when filtering whilst Expression<Func<T, bool>> predicate does.
Maybe I'm missing the obvious.
Here is my scenario:
Whenever I call a method, e.g."GetOrders", I use a method called "GetCachedModels" internally to get the cached version.
When subsequently many calls are made with
"GetOrders(customerNo)" it checks the cache and the get it from there if it's there. That is the theory.
However when using Func predicate it does not find the item, but when using the Expression version it does?
My question is how do you use "Expressions" with a Where clause on a list?
The other solution would be to have a method for each search, e.g. "myservice.GetCustomer(etc..) or myservice.GetOrders(etc..) instead of generic
myservice.GetAll(); Which means adding many methods to the interface.
mycode:
public interface IGenericRepository
{
IList<T> GetAll<T>() where T : class;
IList<T> Find<T>(Func<T, bool> predicate) where T : class; //I use this so that I could use the predicate in an where clause against a list.
etc....
}
In my repository I have something like:
public IList<T> Find<T>(Func<T, bool> predicate) where T : class
{
List<T> models = GetCachedModels<T>().ToList();
var result= models.Where(predicate).ToList(); --Does not work!!! tried also with(expression.Compile()).ToList(); !!still no results!!!!
return result;
}
internal IList<T> GetCachedModels<T>() where T : class
{
IList<T> models;
Type typeParameterType = typeof(T);
string cacheName = string.Format("{0}Cache", typeParameterType.Name);
object cacheView = DbCache.Get(cacheName);
if (cacheView == null)
{
models = Set<T>().ToList();
DbCache.Add(cacheName, models, DateTime.Now.AddHours(1));
}
else
{
models = (IList<T>)cacheView;
}
return models;
}
//below method works but cannot use cache!!!!
public IList<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
{
return Set<T>().Where(predicate).ToList();
}
Expression predicates works only for IQueryable interface. List doesn't inherit it, so if you want to use this expression, you need to return IQueryable in GetCachedModels method, and return Set, so it can query this data. And then you can place it.
Otherwise if you want to cache all items from Set, then you need to pass Func instead of Expression, and then use it in Where extension method, like this - http://dotnetfiddle.net/5YsIy3

Can you use Lambda expressions against ObjectSets in Entity Framework?

I am using EF5, although I am sure it is a more general EF Question.
I cannot get the following to work. I keep getting casting error:
Unable to cast object of type 'System.Data.Objects.ObjectQuery`1[StdOrgUser]' to type 'System.Data.Objects.ObjectSet`1[StdOrgUser]'.
For the code:
public ObjectSet<StdOrgUser> StdOrgUser
{
get
{
if ((_StdOrgUser == null))
{
_StdOrgUser = base.CreateObjectSet<StdOrgUser>("StdOrgUser");
_StdOrgUser = (ObjectSet<StdOrgUser>) _StdOrgUser.Where(r => r.IsActive == false);
}
return _StdOrgUser;
}
}
It compiles fine. Intellisense enables me to choose LINQ operators etc. It is when I run it, that I get the above runtime error.
Where am I going wrong?
Many thanks for any help.
The ObjectSet class implements (amongst other things) IQueryable and IEnumerable, both of these interfaces have an extension method Where, see here and here. Neither IQueryable nor IEnumerable (which are the respective return types of the extension methods) can be cast back to ObjectSet.
The following line of code cannot be evaluated until run time:
_StdOrgUser = (ObjectSet<StdOrgUser>) _StdOrgUser.Where(r => r.IsActive == false);
but if you remove the cast the code will not compile:
_StdOrgUser = _StdOrgUser.Where(r => r.IsActive == false);
UPDATE
For querying you could change the return type of StdOrgUsers from ObjectSet to IQueryable but you lose all the other methods such as Add, Attach etc. You can't apply a standard filter using this technique. You could have an extension method called ActiveUsers()
public static IQueryable<StdOrgUser> ActiveUsers(this ObjectSet<StdOrgUser> users)
{
return users.Where(r => r.IsActive == false);
}
what you need to do is remember to use it in each query (not very pretty but it does clearly show intent)
var results = myContext
.StdOrgUser
.ActiveUsers()
.Where(//some filter);

Combining Includes with Entity Framework

I generally use a generic repository to boilerplate my EF queries so I have to write limited code and also use caching. The source code for the repository can be found here.
The backbone query within the code is this one below. FromCache<T>() is an IEnumerable<T> extension method that utilizes the HttpContext.Cache to store the query using a stringified representation of the lambda expression as a key.
public IQueryable<T> Any<T>(Expression<Func<T, bool>> expression = null)
where T : class, new()
{
// Check for a filtering expression and pull all if not.
if (expression == null)
{
return this.context.Set<T>()
.AsNoTracking()
.FromCache<T>(null)
.AsQueryable();
}
return this.context.Set<T>()
.AsNoTracking<T>()
.Where<T>(expression)
.FromCache<T>(expression)
.AsQueryable<T>();
}
Whilst this all works it is subject to the N+1 problem for related tables since If I were to write a query like so:
var posts = this.ReadOnlySession.Any<Post>(p => p.IsDeleted == false)
.Include(p => p.Author);
The Include() will have no effect on my query since it has already been run in order to be cached.
Now I know that I can force Entity Framework to use eager loading within my model by removing the virtual prefix on my navigation properties but that to me feels like the wrong place to do it as you cannot predict the types of queries you will be making. To me it feels like something I would be doing in a controller class. What I am wondering is whether I can pass a list of includes into my Any<T>() method that I could then iterate though when I make the call?
ofDid you mean something like...
IQueryable<T> AnyWithInclude<T,I>(Expression<Func<T,bool>> predicate,
Expression<Func<T,I>> includeInfo)
{
return DbSet<T>.where(predicate).include(includeInfo);
}
the call
Context.YourDbSetReference.AnyWithInclude(t => t.Id==someId, i => i.someNavProp);
In response to extra question on as collection.
I realised late, there was an overload on Property. You can just pass a string
This might work but call is not easy. Well I find it hard.
IQueryable<T> GetListWithInclude<I>(Expression<Func<T, bool>> predicate,
params Expression<Func<T, I>>[] IncludeCollection);
so i tried
public virtual IQueryable<T> GetListWithInclude(Expression<Func<T, bool>> predicate,
List<string> includeCollection)
{ var result = EntityDbSet.Where(predicate);
foreach (var incl in includeCollection)
{
result = result.Include(incl);
}
return result;
}
and called with
var ic = new List<string>();
ic.Add("Membership");
var res = context.DbSte<T>.GetListWithInclude( t=>t.UserName =="fred", ic);
worked as before.
In the interest of clarity I'm adding the solution I came up with based upon #soadyp's answer.
public IQueryable<T> Any<T>(Expression<Func<T, bool>> expression = null,
params Expression<Func<T, object>>[] includeCollection)
where T : class, new()
{
IQueryable<T> query = this.context.Set<T>().AsNoTracking().AsQueryable<T>();
if (includeCollection.Any())
{
query = includeCollection.Aggregate(query,
(current, include) => current.Include(include));
}
// Check for a filtering expression and pull all if not.
if (expression != null)
{
query = query.Where<T>(expression);
}
return query.FromCache<T>(expression, includeCollection)
.AsQueryable<T>();
}
Usage:
// The second, third, fourth etc parameters are the strongly typed includes.
var posts = this.ReadOnlySession.Any<Post>(p => p.IsDeleted == false,
p => p.Author);

Entity Framework and repository pattern problem

I am using generic repository pattern with methods:
private ObjectQuery<T> ObjectQueryList()
{
var list = CamelTrapEntities.CreateQuery<T>(EntitySetName);
return list;
}
public IQueryable<T> List()
{
return ObjectQueryList();
}
Metod List() returns IQueryable<T>, becase IQueryable<T> is easy to mock. I also have extension method:
public static IQueryable<T> Include<T>(this IQueryable<T> obj, string path)
{
if (obj is ObjectQuery<T>)
(obj as ObjectQuery<T>).Include(path);
return obj;
}
This method is used outside of repository to get entity list with navigation properties already loaded, for example: List.Include("CreatedBy"). The problem is that it doesn't work. All includes are ignored. when I change List() method to
public ObjectQuery<T> List()
{
return ObjectQueryList();
}
everything works fine.
How should I implement repository pattern to be able to execute more complex queries?
Reflector gave me an answer:
public ObjectQuery<T> Include(string path)
{
EntityUtil.CheckStringArgument(path, "path");
return new ObjectQuery<T>(base.QueryState.Include<T>((ObjectQuery<T>) this, path));
}
Include returns new ObjectQuery object and my Include function returned old object. Changing to
public static IQueryable<T> Include<T>(this IQueryable<T> obj, string path)
{
if (obj is ObjectQuery<T>)
return (obj as ObjectQuery<T>).Include(path);
return obj;
}
solved the problem. Few hours lost and I hate Entity Framework more:)
It made me also realise that I should create another List function with Include parameter and not allow doing includes outside repository.
Here is the most comprehensive Repository pattern implementation I've seen for EF. I can't say for sure if it will allow you to do Include(), but if I read the implementation right it should.
With EntityFramework 4.1, DbExtensions (System.Data.Entity.DbExtensions) resolves this problem, and natively adds both .Include([string path]) and .Include([property expression]) for any IQueryable<T>.
Just be sure that the project using your repository references EntityFramework, and, as with any extension methods, specify using System.Data.Entity; in your class file to gain access to these extensions.