-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Result of left joining a nullable nominal type should be null (i.e. no instance) and not an instance with all null property values #22517
Comments
Confirmed this works in 3.1, but fails in latest daily, on SQL Server and SQLite. |
@ajcvickers - What is the generated SQL? Can you post query plan? |
@smitpatel Here's the logs:
|
SingleQueryResultCoordinator is not the error. We are probably trying to materialize null into something which is non-nullable which should have been skipped from materialization. |
Note: This gave incorrect result in 3.1.x by initialize post with default values rather than returning null (the result of DefaultIfEmpty). With #20633 fixed, it now throws error. Right fix would be not to create instance if no rows matched. |
Issues faced:
Due to unintended side effect of breaking working queries due to having a complex expression for entity now, we should postpone this. |
Work-around to original issue
|
i did try the following work-around before raising the bug. this workaround doesn't work :( var query = from blog in db.Blogs
join post in dbPosts on blog.BlogId equals post.BlogId into posts
from xpost in posts.DefaultIfEmpty()
select new Blog
{
Url = blog.Url,
Post = xpost == null ? null : xpost
}; |
@skclusive Did you try the workaround using EF Core 5.0 RC1 or EF Core 3.1.x? |
@ajcvickers i tested with 6.0.0-* nightly. now tried with EF Core RC1 also. same issue. |
i guess this is critical issue. if i use in memory objects. following does work with standard linq query. so i am wondering why #20633 considered bug. var memoryBlogs = new List<Blog>
{
new Blog
{
BlogId = 1,
Url = "http://blogs.msdn.com/adonet"
}
};
var memoryPosts = from p in new List<Post>()
select new Post
{
PostId = p.PostId,
BlogId = p.BlogId,
Content = p.Content
};
var query = from blog in memoryBlogs
join post in memoryPosts on blog.BlogId equals post.BlogId into posts
from xpost in posts.DefaultIfEmpty()
select new Blog
{
Url = blog.Url,
Post = xpost
}; when there is no posts, following does not throw error in the projection. so why throwing error when composed. var posts = from p in db.Posts
select new Post
{
PostId = p.PostId, // we dont consider p null here
BlogId = p.BlogId,
Content = p.Content
};
var post = posts.SingleOrDefault(); // query is success with null value. no error on projection.
Console.WriteLine(post?.PostId); i am composing IQueryable extensively in my code and this recent change breaks my framework. so please consider to address this issue. |
@skclusive - Your query was giving incorrect results in 3.1, The bug fix for #20633 stopped generating incorrect results. In your case it throws exception because the query translation couldn't figure out a way to generate right results. Your query working in LINQ is irrelevant as it worked in 3.1 also and EF Core generated results different than LINQ. var dbPosts = from p in db.Posts
// select p;
select new Post
{
PostId = p.PostId,
BlogId = p.BlogId,
Content = p.Content
};
var query = from blog in db.Blogs
join post in dbPosts on blog.BlogId equals post.BlogId into posts
from xpost in posts.DefaultIfEmpty()
select new Blog
{
Url = blog.Url,
Post = xpost.BlogId == null ? null : xpost
}; Tested that above work-around gives correct result on 5.0 rc2 nightly build. |
@smitpatel got it. thanks. the above mentioned workaround does work in nightly and rc-1. only issue is this comparison produce warning xpost.BlogId == null (comparing int to null). Also will this be documented as i guess some might get this issue frequently. being workaround will this be addressed in future releases? you can close the issue if no further action involved. |
https://stackoverflow.com/a/65207398/1181624 You can also do a cast to a nullable type to make this issue go away. I posted that stack overflow answer before I found this post.
|
Still happens with MetadataGenerator v1.2.0 and the latest EFC packages (5.0.1). However from what I understand, this is now the expected behaviour and we'll have to add explicit "workarounds" to all left-joined queries like the one @shadowfoxish mentioned? |
Related #19095 |
This is very inconvenient, and has been 'punted' for 2 versions now... |
It doesn't work if the target type is nullable but contains non nullable properties. Also there is no point in being forced to handle C# null safety inside a SQL query which doesn't have such concept. This turns what would have been a pretty standard SQL query with a couple of left joins into this mess: query.Select(user => new
{
User = user,
Person = user.Person!,
user.Substitute,
user.Substitute!.OtherUser,
user.Substitute.ExternalUser
})
.Select(record => new UserRecord
{
Id = record.User.Id,
Firstname = record.Person!.Firstname,
Lastname = record.Person.Lastname,
Email = record.Person.Email,
Salutation = record.Person.Salutation,
Substitute = record.Substitute != null
? new SubstituteRecord
{
Type = record.Substitute.Type,
#pragma warning disable S3358
OtherUser = record.OtherUser != null
? new()
{
Id = record.OtherUser.Id,
Email = record.OtherUser.Person!.Email,
Salutation = record.OtherUser.Person.Email,
Firstname = record.OtherUser.Person.Firstname,
Lastname = record.OtherUser.Person.Lastname,
}
: null,
ExternalUser = record.ExternalUser != null
? record.Substitute.ExternalUser
: null
}
: null
}); |
We just upgraded to .Net 6 and this is an embarrassing complete disaster. There's no easy way to determine where this bug occurs other than scouring all 300+ LINQ expressions to see if we're using a leftjoin, and selecting on the result. This isn't even listed as a breaking change on the update page. The error message was completely useless and resulted in around 2 hours of debugging because I couldn't figure out what was going wrong (why would I expect a simple SELECT to be the cause of the error?). Furthermore, this behaviour is completely inconsistent even within the LINQ query itself, and doesn't follow expected C# paradigms. Consider the following query: var userData = await (
from u in db.user
// left join
join s in db.optional_user_settings_row
on u.user_id equals s.user_id
into leftjoin_u from u in leftjoin_u.DefaultIfEmpty()
// s.suspended Doesn't error ???
where u.user_id = UserIDToSearchFor && !s.suspended
select new UserDataResult
{
name = u.display_name,
// s.colourblind_enabled does error ???
colourblind = s.colourblind_enabled,
}
).ToArrayAsync(); This query errors because I know Microsoft doesn't take suggestions, and this is a pipe dream, but the following syntax is what I would actually expect LINQ to work like. var userData = await (
from u in db.user
// left join - new syntax
// `s` is highlighted as POSSIBLY NULL
// and will display a warning in Visual Studio if accessed directly below
left join s in db.optional_user_settings_row
on u.user_id equals s.user_id
// Use null propagating operator since `s` is the actual null value
// and we can EXPLICITLY HANDLE IT instead of automatically doing weird stuff
where u.user_id = UserIDToSearchFor && (s?.suspended ?? false)
select new UserDataResult
{
display_name = u.display_name,
// Use coalescing to decide what the default should be
colourblind_enabled = s?.colourblind_enabled ?? false,
}
).ToArrayAsync(); |
Same problem in EF 7 |
I can't believe that after years, this error is still present. |
I have some issue when I move app from EF 2 to EF 7. .SelectMany(d => d.Posts.DefaultIfEmpty(), (x, y) => new myModel |
This issue is a significant blocker for our transition from EF Core 3.1 to later versions. It fundamentally changes the expected behavior of left joins involving nullable types, which would necessitate extensive modifications in our codebase to introduce null checks to avoid these exceptions. This isn't just a minor inconvenience but a substantial overhaul of existing, stable code. A configuration option to toggle this behavior would be highly beneficial, offering a compromise. This would allow teams who rely on the pre-5.0 behavior to upgrade without rewriting significant portions of their code, while others who prefer the new behavior could opt-in. Without such an option, we're effectively stuck, unable to leverage the advancements in newer EF Core versions. It's crucial to address this issue to facilitate smoother transitions and maintain backward compatibility, especially for complex, enterprise-level applications. |
Hi, Regards |
Really hoping for a fix on this one... |
Please vote it |
when Blog does not have a Post, following query does not work in 5.0.0-preview.8.* or 6.0.0-* nightly. but works in 5.0.0-preview.7.*
Steps to reproduce
I have a repo to reproduce the bug.
https://github.com/skclusive/EFLeftJoinBug
Further technical details
EF Core version:
Database provider: (e.g. Microsoft.EntityFrameworkCore.Sqlite)
Target framework: (e.g. .NET Core 5.0)
Operating system:
IDE: (e.g. Visual Studio Code)
The text was updated successfully, but these errors were encountered: