My problem is, that every time when I try querying the database with LinQ methods, I get either Null when using SingleOrDefault(), or an InvalidOperationException when using Single().
Here's my code.
var currentUser = UserManager.FindById(User.Identity.GetUserId());
var post = ApplicationDbContext.Posts
.Include(c => c.Author)
.SingleOrDefault(c => c.Author.Id == currentUser.Id);
if (post == null)
return View(new CertainPost());
I'm suspicious if it ain't wrong that I've used the .Include method on author and in the same time I used the author value to query the DB. If that's the issue, how should I write this properly? Can I in some way use SingleOrDefault() method later in my code, when Author will be loaded? I've been doing it like this, but I find this way very messy
//Such a mess
var posts = ApplicationDbContext.Posts
.Include(c => c.Author)
.ToList();
foreach(var item in posts)
{
if(item.Author.Id == currentUser.Id)
var post = ApplicationDbContext.Posts.SingleOrDefault(c=>c.Id==item.Id)
}
So, what code should I write to compromise on usefulness and optimization?
The problem is likely that you have multiple posts for that author. The only meaningful logical difference between the non-working code and the working but "messy" code is that, in the working code, you're selecting by an item id.
The difference between Single and SingleOrDefault is only that when the first fails, it raises an exception, while the second will simply return null. With either, there's actually two scenarios that will make them fail:
There are no matching items
There are more than one matching items
Since you're sure that the first is not the situation (you've confirmed there is a post), then the second is the issue.
Long and short, you need to either provide some other differentiating detail to SingleOrDefault such that it can match one and only one post, or you need to just use something like Where and return all matching posts:
var post = ApplicationDbContext.Posts
.Include(c => c.Author)
.SingleOrDefault(c => c.Author.Id == currentUser.Id && c.Id == postId);
Or
var posts = ApplicationDbContext.Posts
.Include(c => c.Author)
.Where(c => c.Author.Id == currentUser.Id);
FWIW, you don't need to use Include for any relationships that are part of the query. In the case of Author, it has to be joined in order to figure out if its id equals the current user's id, so it will already be included.
Additionally, it's an unnecessary query to lookup the user when all your need is the id. You already have the id, since you used that to look the user up in the first place. You might have done that because passing User.Identity.GetUserId() directly to your query will raise an exception. However, that only occurs because Entity Framework doesn't know how to translate that method to something it can do in a SQL query. If you pass it just the value, you're fine:
var userId = User.Identity.GetUserId();
var posts = ApplicationDbContext.Posts
.Where(c => c.Author.Id == userId);
Related
This is more of a syntax question than an actual bug or error, as I finally got what I wanted working. But I want to understand and perhaps improve upon my current solution.
Schema
Let's assume I have a Users table, with a one-to-many relationship to a table Posts, and a further one-to-one relationship table of Authors - one for each Post.
I want to write a custom repository function to get all Users, with all Posts, with each Author per Post.
Attempt #1 (Doesn't Work)
I thought I could do something like:
public IQueryable<User> GetUsersWithPostsAndAuthors()
{
var query = GetAll();
// include all details on user object
return query
.Include(user => user.Posts.Select(x => x.Author));
}
it doesn't seem to include the Author entity. Actually, I was getting the following error:
Lambda expression used inside Include is not valid.
Attempt #2 (Also Doesn't Work)
Then I thought that maybe those Posts need to be in the query first, so I tried this:
public IQueryable<User> GetUsersWithPostsAndAuthors()
{
var query = GetAll();
// include all details on user object
return query
.Include(user => user.Posts)
.Include(user => user.Posts.Select(x => x.Author)
}
Unfortunately, I got the same error:
Lambda expression used inside Include is not valid.
Attempt #3 (Works!)
However, if I use the version of Include where you can provide a string navigationPropertyPath (which actually I don't like since it's just a hardcoded string), with something like this:
public IQueryable<User> GetUsersWithPostsAndAuthors()
{
var query = GetAll();
// include all details on user object
return query
.Include(user => user.Posts)
.Include("Posts.Author");
}
The query works as expected!
What is going on here? I thought the Select projection would do the same as Include. (And there seem to be some answers on Stackoverflow suggesting that.)
More importantly, is there a way to accomplish what I want without hardcoding the Posts.Author in the Include call? I'd like to have static type checks here.
What is going on here?
No offense, but nothing more than not quite understanding what Include is for. It's only for including navigation properties, not for projections.
The syntax is quite clear:
Include for navigation properties off of the root of the query:
.Include(user => user.Posts)
ThenInclude for navigation properties off of included navigation properties:
.Include(user => user.Posts).ThenInclude(p => p.Author)
The latter example is equivalent to .Include("Posts.Author"), but the lambda syntax is preferred because of compile-time checking. In the old EF6 version there was no ThenInclude and the syntax for including more levels was as you wrote: .Include(user => user.Posts.Select(x => x.Author)).
A projection is a Select in the LINQ query, not inside an Include statement. For example:
return query.Select(u => new { u.Id, u.Name });
Projections and Includes exclude one another. In the projection there's nothing in which a navigation property can be included. A query like:
return query
.Include(u => u.Posts)
.Select(u => new
{
u.Id,
u.Name,
Posts = u.Posts.Select(p => p.Title)
});
will completely ignore the Include. There's no trace of it in the generated SQL: only Post.Title will be queried, not all Post fields, as an Include would do.
In Sql, I know we have the IN operator and the equivalent of that is .Contains() in LINQ but i am stuck on one problem here
Consider the following linq query:
_dbContext.Offices.Where(o => o.Id == policy.OfficeId).FirstOrDefaultAsync()
Suppose i were to introduce a policies (plural) object which is a collection object each policy in the collection has its own officeId, how do I check if the offices collection consists of the officesId from policies collection object? I would like to do this in method syntax if possible. Thanks in advance.
Without having access to your data model, your answer does seem plausible; however, you need to have a call to .ToListAsync() at the end for this to compile. Also, it's more efficient to use Any() (rather than .Select.Contains):
await _dbContext.Offices.Where(o => policies.Any(p => p.OfficeId == o.Id)).ToListAsync();
I'm not sitting in front of a compiler, but if you just want the first one, the following is even better IIRC:
await _dbContext.Offices.FirstOrDefaultAsync(o => policies.Any(p => p.OfficeId == o.Id));
I think the following might work:
await _dbContext.Offices.Where(o => policies.Select(p => p.OfficeId).Contains(o.Id))
This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 5 years ago.
Here is my expression:
Course course = db.Courses
.Include(
i => i.Modules.Where(m => m.IsDeleted == false)
.Select(s => s.Chapters.Where(c => c.IsDeleted == false))
).Include(i => i.Lab).Single(x => x.Id == id);
I know the cause is Where(m => m.IsDeleted == false) in the Modules portion, but why does it cause the error? More importantly, how do I fix it?
If I remove the where clause it works fine but I want to filter out deleted modules.
.Include is used to eagerly load related entities from the db. I.e. in your case make sure the data for modules and labs is loaded with the course.
The lamba expression inside the .Include should be telling Entity Framework which related table to include.
In your case you are also trying to perform a condition inside of the include, which is why you are receiving an error.
It looks like your query is this:
Find the course matching a given id, with the related module and lab. As long as the matching module and chapter are not deleted.
If that is right, then this should work:
Course course = db.Courses.Include(c => c.Modules)
.Include(c => c.Lab)
.Single(c => c.Id == id &&
!c.Module.IsDeleted &&
!c.Chapter.IsDeleted);
but why does it cause the error?
I can imagine that sometimes the EF team regrets the day they introduces this Include syntax. The lambda expressions suggest that any valid linq expression can be used to subtly manipulate the eager loading. But too bad, not so. As I explained here the lambdas only serve as a disguised string argument to the underlying "real" Include method.
how do I fix it?
Best would be to project to another class (say, a DTO)
db.Courses.Select(x => new CourseDto {
Id = x.Id,
Lab = x.Lab,
Modules = x.Modules.Where(m => !m.IsDeleted).Select( m => new ModuleDto {
Moudle = m,
Chapters = x.Chapters.Where(c => c.IsDeleted)
}
}).Single(x => x.Id == id);
but that may be a major modification for you.
Another option is to disable lazy loading and pre-load the non-deleted Modules and Chapters of the course in the context by the Load command. Relationship fixup will fill the right navigation properties. The Include for Lab will work normally.
By the way, there is a change request for this feature.
I am using LINQ-to-Entities (EF 6.1.3) to perform the following query:
var users = msgList.Select(m => m.From)
.Union(msgList.Select(m => m.To))
.Distinct()
.Where(u => u.ID != userId) //userId is an assigned local var.
.ToList();
msgList is a List (already fetched, not a queryable and lazy loading is off) of Messages which consists of some fields like From and To which are guaranteed to be non-null. Both From and To were Included in the original query, so they are guaranteed to be non-null.
My User object is also guaranteed to be non-null, so there's nothing that can actually be null.
However, this line is sometimes throwing a null pointer exception, and sometimes executing perfectly with the exact same user, exact same database, exactly same data (nothing altered). Load is not an issue as it's a code not yet in production and I'm the only one testing it.
The exception seems to be thrown at the Where call:
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
How can this happen?
UPDATE: This is of course not a duplicate of What is a NullReferenceException, and how do I fix it?. Any sane developer with even a little knowledge in .NET/C#/OOP knows what that error is and that this question has nothing to do with it, even though it involves that exception as a part of it.
UPDATE 2: I've switched it to assigning to a list each line, as suggested below:
var msgListSelection = msgList.Select(m => m.From).ToList();
var union = msgListSelection.Union(msgList.Select(m => m.To)).ToList();
var distinct = union.Distinct().ToList();
var where = distinct.Where(u => u.ID != userId).ToList();
var users = where;
The exception occurs at the where line:
var where = distinct.Where(u => u.ID != User.ID).ToList();
If distinct returned null, it would have been thrown on ToList call of var distinct = union.Distinct().ToList(); on the line above.
Am I missing something?
UPDATE 2: My User class is a POCO C# type mapped to an Entity type in my database which has an ID property of long, and my Message class is again a POCO type mapped in Entity Framework, with navigation properties From and To to some User instances guaranteed to be non-null. They are annotated as Required and I've also checked them at the database level just to be sure.
UPDATE 3: My EF context lives from the beginning of the request (set at a delegating handler in the beginning of the request) to the end. I don't think the problem is related to the lifespan of the DbContext as there are many controllers with the same mechanism with tens of methods that access the context, and I'm only having such problem with this particular method.
UPDATE 4: I've added a null check on distincts:
var distinct = union.Distinct().ToList();
if(distinct == null)
{
throw new Exception("distinct was null");
}
var where = distinct.Where(u => u.ID != userId).ToList();
It seems to pass that point with no problem, but throw the null pointer exception at the last line var where = distinct.Where(u => u.ID != userId).ToList(); which sorts out the possibility that distinct may be null.
UPDATE 5: I've wrote an API testing tool and sent about 250 requests to the same endpoint with the same user. The first one failed with this error, and all the rest succeeded successfully. There seems to be a problem with the first request.
You may be experiencing what is caused by the closure principle. You reference the User property in your LINQ query. Because the LINQ query in itself is executed as an (anonymous) method delegate, the closure principle applies.
Quoting the above link:
In essence, a closure is a block of code which can be executed at a
later time, but which maintains the environment in which it was first
created - i.e. it can still use the local variables etc of the method
which created it, even after that method has finished executing.
The usage of the User property is subject to this principle. Its value can have changed upon the execution of the LINQ query. To protect against this, the User property should be copied to a local variable and that referenced in the LINQ query. Like so:
var user = User;
var users = msgList.Select(m => m.From)
.Union(msgList.Select(m => m.To))
.Distinct()
.Where(u => u.ID != user.ID)
.ToList();
Update
When using a local reference copy to the user property, another possibility for the NullReferenceException may lie with the Select-Union-Distinct methods. When calling ToList, the Where clause is executed on all items in the union of the two Select clauses. By default, Distinct executes the Equals method from the IQuality interface, which would be called on the elements from Select(m => m.From) . If this element is null, it would cause the NullReferenceException.
This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 5 years ago.
Here is my expression:
Course course = db.Courses
.Include(
i => i.Modules.Where(m => m.IsDeleted == false)
.Select(s => s.Chapters.Where(c => c.IsDeleted == false))
).Include(i => i.Lab).Single(x => x.Id == id);
I know the cause is Where(m => m.IsDeleted == false) in the Modules portion, but why does it cause the error? More importantly, how do I fix it?
If I remove the where clause it works fine but I want to filter out deleted modules.
.Include is used to eagerly load related entities from the db. I.e. in your case make sure the data for modules and labs is loaded with the course.
The lamba expression inside the .Include should be telling Entity Framework which related table to include.
In your case you are also trying to perform a condition inside of the include, which is why you are receiving an error.
It looks like your query is this:
Find the course matching a given id, with the related module and lab. As long as the matching module and chapter are not deleted.
If that is right, then this should work:
Course course = db.Courses.Include(c => c.Modules)
.Include(c => c.Lab)
.Single(c => c.Id == id &&
!c.Module.IsDeleted &&
!c.Chapter.IsDeleted);
but why does it cause the error?
I can imagine that sometimes the EF team regrets the day they introduces this Include syntax. The lambda expressions suggest that any valid linq expression can be used to subtly manipulate the eager loading. But too bad, not so. As I explained here the lambdas only serve as a disguised string argument to the underlying "real" Include method.
how do I fix it?
Best would be to project to another class (say, a DTO)
db.Courses.Select(x => new CourseDto {
Id = x.Id,
Lab = x.Lab,
Modules = x.Modules.Where(m => !m.IsDeleted).Select( m => new ModuleDto {
Moudle = m,
Chapters = x.Chapters.Where(c => c.IsDeleted)
}
}).Single(x => x.Id == id);
but that may be a major modification for you.
Another option is to disable lazy loading and pre-load the non-deleted Modules and Chapters of the course in the context by the Load command. Relationship fixup will fill the right navigation properties. The Include for Lab will work normally.
By the way, there is a change request for this feature.