Why is the Lambda Expression not valid in this Include()? - c#

I'm working with EFCore for the first time, after moving from EF6, and for some reason I keep getting this error:
System.InvalidOperationException: 'Lambda expression used inside Include is not valid.'
Here is my Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
using (var IJ = IJContext.CreateNew(false))
{
var ChargeList = iJ.Charges.Where(charge => charge.CourtCase.CaseNumber == "13457894561")
.Include(charge => charge.ChargeDate)
.ToList();
return View(ChargeList);
}
}
}
Am I missing something important here? Does EFCore handle Lambdas totally differently or something?

It seems that the ChargeDate is not a related entity. Check Retaled entities documentation to see the purpose of include
Seems like you either have not a relationship between the types and that you want to use something like the select property to get new objects based on the data already retrieved from the query.

If you look at the signature of the Include extension method it looks like as follows:
public static IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TProperty>> navigationPropertyPath)
where TEntity : class
{
// Method body
}
The paramter navigationPropertyPath expecting a lambda expression representing the navigation property to eager load the data for that navigation property. But you are passing the entity property ChangeDate which is not navigation property.
For more details: EF Core Eager loading

Related

Ef Core generic repository include nested navigation properties

I am using unit of work with repository pattern and I use this method to get direct navigation properties
public async Task<IEnumerable<T>> GetAllIncluding(params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = dbSet;
return await includes.Aggregate(query, (current, include) => current.Include(include)).ToListAsync();
}
and I use it like this
public async Task<IEnumerable<SomeEntity>> GetAllDetailsAsync()
{
var data = await unitOfWork.Service.GetAllIncluding(
x => x.DirectNavigation1,
x => x.DirectNavigation2,
x => x.DirectNavigation3) ;
}
I try to use
x => x.DirectNavigation3.Select(o => o.NestedNavigation1)
But then I got a error.
In order to do this, I had to change my params to
GetAllIncluding(params string[] includes)
with the same implementation no change at all
The code works just fine but I don't like work with string 😒 due to its difficulty in maintenance
Please share any alternative ways of including nested navigation properties without using string.
Thank you in advance.
The real question seems to be how to load all related properties. This has nothing to do with repositories. A DbContext already is a multi-entity repository and Unit-of-Work anyway.
EF Core 6 added the ability to automatically load navigations by using the AutoInclude() call in the model :
modelBuilder.Entity<Theme>().Navigation(e => e.ColorScheme).AutoInclude();
This can be turned off in queries with IgnoreAutoIncludes(), eg
var themes = context.Themes.IgnoreAutoIncludes().ToList();
It's not possible to chain Include calls from Expression<Func<T, object>> expressions. In LINQ the expressions are ... expressions that specify properties or code that eventually gets translated to something else. They aren't functions that get called. Besides, specifying all those expressions would take as many lines as chaining Include methods and look uglier.
In .Include(theme=>theme.ColorScheme) the expression isn't returning the value of ColorScheme. The Expression<Func<TEntity,TProperty>> is used to find the correct property to use using reflection :
public static IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TProperty>> navigationPropertyPath)
where TEntity : class
{
Check.NotNull(navigationPropertyPath, nameof(navigationPropertyPath));
return new IncludableQueryable<TEntity, TProperty>(
source.Provider is EntityQueryProvider
? source.Provider.CreateQuery<TEntity>(
Expression.Call(
instance: null,
method: IncludeMethodInfo.MakeGenericMethod(typeof(TEntity), typeof(TProperty)),
arguments: new[] { source.Expression, Expression.Quote(navigationPropertyPath) }))
: source);
}

Access foreign key properties from IQueryable<T> in Entity Framework Core 3.1

I'm writing a IQueryable<T> extension method and I would like to get the foreign keys of the navigation properties of T. I have figured out that I can get access to them through IModel.FindEntityType(T).GetNavigations(), the question is how to best get access to the IModel while keeping the method as simple as possible.
At the moment the method looks something like this:
public static IQueryable<TQuery> DoMagic<TQuery>(this IQueryable<TQuery> query, IModel model)
{
var entity = model.FindEntityType(entityType);
var navigationProperties = entity.GetNavigations();
...
}
I would love if I wouldn't need to pass IModel as an argument like this:
public static IQueryable<TQuery> DoMagic<TQuery>(this IQueryable<TQuery> query)
{
var entity = Model.FindEntityType(entityType);
var navigationProperties = entity.GetNavigations();
...
}
I have considered adding a IServiceCollection extension method and passing the DbContext and setting IModel as a static property to the IQueryable<T> extension method. The problem with that is that it is limiting me to one DbContext.
I have also looked into the option to add a extension method to DbContextOptionsBuilder but haven't really figured out best way to achieve what I want to do this way.
Maybe there is other ways to get access to the foreign keys? Any help is greatly appreciated!
Edit
From the navigation properties I want to access the ForeignKey-property to know what property is used to resolve the navigation property. Something like:
navigationProperties.ToList().Select(x => x.ForeignKey);
One way of getting the navigation properties:
public static IQueryable<TQuery> DoMagic<TQuery>(this IQueryable<TQuery> query)
{
var navigationProperties = typeof(TQuery).GetProperties()
.Where(p => (typeof(IEnumerable).IsAssignableFrom(p.PropertyType) && p.PropertyType != typeof(string)))
.ToArray();
...
}
UPDATE:
Trying to get DbContext which has the Model property from IQueryable would result in some not pretty code, this link shows how it could be done:
.Net EF Core 2.1 Get DbContext from IQueryable argument
If you are using dependency injection, you could put the instance of DbContext into the container in the scope of the web request or the operation and inject it into the class with the DoMagic method.
Another way could to add the method to the descendant of DbContext class, then the model parametere would not be needed.

Limit collection to retrieve only recent entries for readonly entity

The User entity can have thousands of UserOperations. Sometimes I don't want to retrieve (for readonly entity) all of them but only "the recent 10 OR not completed".
public class SimpleForm
{
public class User : EntityBase
{
// ...
private ISet<UserOperation> _recentOperations = new HashedSet<UserOperation>();
public virtual ISet<UserOperation> RecentOperations { get { return _recentOperations; } set { _recentOperations = value; } }
}
}
So how can I specify it? I think I could use mapping overrides?
I understand I could make this with a seperate query but can it be done by entity mapping?
Also I wonder if there is a possibility to do the some for non-readonly entity where I can modify the collection of operations?
UPDATE
I tried to use
DateTime dateTime = (DateTime.UtcNow - TimeSpan.FromDays(15));
mapping.HasMany(x => x.RecentOperations)
.Where(x => x.EndedAt == null || x.EndedAt < dateTime);
but it says "Unable to convert expression to SQL".
I replaced it with
mapping.HasMany(x => x.RecentOperations)
.Where(x => x.EndedAt == null);
and now it throws null reference exception inside
в FluentNHibernate.Utils.ExpressionToSql.Convert(Object value)
в FluentNHibernate.Utils.ExpressionToSql.Convert(ConstantExpression expression)
в FluentNHibernate.Utils.ExpressionToSql.Convert[T](Expression`1 expression, UnaryExpression body)
There are 2 general ways how to filter mapped collections.
The first is a bit rigid, fixed, in a mapping defined where="" clause:
6.2. Mapping a Collection (...in fluent .Where(bool expr) or .Where(Sql statement string)
The second and maybe really suitable in this scenario, is dynamic version called filter:
18.1. NHibernate filters
NHibernate adds the ability to pre-define filter criteria and attach those filters at both a class and a collection level. A filter criteria is the ability to define a restriction clause very similiar to the existing "where" attribute available on the class and various collection elements. Except these filter conditions can be parameterized. The application can then make the decision at runtime whether given filters should be enabled and what their parameter values should be. Filters can be used like database views, but parameterized inside the application....
The implementation in fluent would look like this:
public class RecentFilter : FilterDefinition
{
public RecentFilter()
{
WithName("RecentFilter")
.WithCondition("( :EndedAtDate IS NULL OR EndedAt < :EndedAtDate )")
.AddParameter("EndedAtDate",NHibernate.NHibernateUtil.DateTime);
}
}
this is the filter, and here is its usage in a fluent mapping:
mapping
.HasMany(x => x.RecentOperations)
...
.ApplyFilter<RecentFilter>();
In runtime, we can turn filter on/off on the ISession level:
session.EnableFilter("RecentFilter")
.SetParameter("EndedAtDate",DateTime.Now.AddDays(-15));
See also:
property filter with fluent nHibernate automapping
Syntax to define a NHibernate Filter with Fluent Nhibernate?
Is it possible to use NHibernate Filters to filter through references?

MVC4 linq query to object doesn't come out as the correct object

Error: The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery (snip) ...looking for type 'Advocate'
Controller method looks like this:
[HttpGet]
public ActionResult AdvocateEdit(int id)
{
var advocate = from a in db.Query<Advocate>().Include(a => a.AdvocateId)
where (a.AdvocateId == id)
select a;
return View(advocate);
}
The view is indeed typed to the Advocate #model and after stepping through, I'm pretty sure the problem is this query. It needs to be of type Advocate when it returns.
db.Query is an IQueryable<T> method in my DbContext that returns Set<T>().
Let me know if more info is needed. Thanks people
ADDED ----
DbContext.cs
public interface IAcmeDb : IDisposable
{
IQueryable<T> Query<T>() where T : class;
}
public class AcmeDb : DbContext, IAcmeDb
{
public AcmeDb() : base("name=AcmeDB") {}
public DbSet<Advocate> Advocates { get; set; }
IQueryable<T> IAcmeDb.Query<T>()
{
return Set<T>();
}
}
I think you want to pass an Advocate element to the view, not the query itself. Try this:
return View(advocate.First());
The advocate object in your code is of type IQueryable, if your view expects an Advocate object, just get the first element of the query with First() or FirstOrDefault().
If your view requires a single Advocate and there is always only one entity for a given id then you want:
[HttpGet]
public ActionResult AdvocateEdit(int id)
{
try
{
Advocate advocate = db.Query<Advocate>().Single(a => a.AdvocateId == id);
return View(advocate);
}
catch(InvalidOperationException ex)
{
//handle the case where no entity matches the supplied id (return status code 404?), or
//there are no Advocates at all (redirect to a create page?), or
//more than one entity matches (return status code 500)
}
}
There is at least one notable problem. LINQ tends to use deferred execution. No where in your code do I see you forcing the query to execute. Unless somewhere in View(advocate) you enumerate the IEnumberable<T> produced by the query then you will not be dealing with objects of type Advocate.
I would recommend adding a ToList() or ToArray() call to the end of your query, this will ensure that it actually gets executed. If that doesn't work I would break the query apart and make sure db.Query<Advocate>() is returning what you'd expect.
List<Advocate> = db.Query<Advocate>().Include(a => a.AdvocateId)
.Where(a.AdvocateId == id)
.Select(a => a).ToList();
I've converted your query to method syntax since it's what's familiar to me. The above example will likely solve your problems. I think to keep query syntax you should just put the whole left hand side in parents then tack on the ToList() call however I'm not certain what the correct syntax is.
EDIT: Based on other comments the problem was not the deferred execution but rather the attempt to pass a collection to a method looking for a single instance. Going with the same code above, you just need to change the ToList() to FirstOrDefault() and your code will work;
List<Advocate> = db.Query<Advocate>().Include(a => a.AdvocateId)
.Where(a.AdvocateId == id)
.Select(a => a).FirstOrDefault();
Note: the default value for reference types is null this could of course cause you problems if you don't have the proper checks in place for nullity.

DBContext Find with Includes - where lambda with Primary keys

I am writing a generic repository to interface with EF using DBContext.
I have a generic Get() method which receives a primary key value and returns the entity:
public class DALRepository<DALEntity> : IDisposable, IGenericRepository<DALEntity> where DALEntity : class
{
private IDbSet<DALEntity> dbSet;
private NWEntities context;
public DALRepository()
{
context = new NWEntities();
context.Configuration.LazyLoadingEnabled = false;
dbSet = context.Set<DALEntity>();
}
Here's a simple get method - just works on the PK - exactly what I want.
public DALEntity Get(string ID)
{
return dbSet.Find(ID);
}
I now want to change this to allow the consumer to pass in a list of includes - so as well as returning just a customer they can request to return the orders as well. Here's where I'm running into trouble. If I do this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
}
I can't use find with the IQueryable. And I can't Find() directly because I can't pass includes to it. If I use a where lambda instead on the IQueryable, how do I tell it to use the PK of the entity? I guess I could have a generic constraint that insists the generic type must implement some IPkey interface with a well-defined primary column name such as "ID", but then I can't use the entities generated by DBContext as they woudl have to implement this interface. I can change the T4 to do this if I need - and I've already changed it to emit XML comments so not too averse to that - but does anyone have a simpler way? I suppose what I need is an overloaded find() which accepts a list of includes.
So my question is either how to use Find with includes, or how to write the lambda where it knows the PK? I can't receive such a lambda as a parameter as this will ultimately be consumed by a WCF service.
Kind of wierd answering your own question but in case anyone else has this issue here's what I did. I used the dynamic LINQ stuff and used the string overloaded version of .Where(). I used one of the links mentioned to figure out how to grab the primary key (and mine is from a DBContext as well), and the method now looks like this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
var set = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<DALEntity>();
var entitySet = set.EntitySet;
string[] keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
Debug.Assert(keyNames.Length == 1, "DAL does not work with composite primary keys or tables without primary keys");
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Where(keyNames[0] + "= #0", ID);
return query.FirstOrDefault();
}
You could get your Key dinamycally the way it is described here : https://stackoverflow.com/a/10796471/971693
That being said, I can't see a way without using some reflection :
public IEnumerable<DALEntity> Get(params string IDs)
{
var objectSet = objectContext.CreateObjectSet<YourEntityType>();
var keyNames = objectSet.EntitySet.ElementType.KeyMembers.First(k => k.Name);
return dbSet.Where(m => ID.Contains((string)m.GetType().GetProperty(keyNames ).GetValue(m, null));
}
First of, params string IDs will let you pass 1 or more ID and will result in an array of string.
The first part of the function is to dynamically get the name of your primary key.
The second part creates a query to return all elements from your set where the primary key value (obtained through reflection) is contained within the array of IDs received in parameter.
Use Linq's Single or First methods, which allow you to search on IQueryable objects.
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Single(x=>x.Id == ID);
}

Categories