-
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
Invalid ThenInclude() Nullable Reference Type Warning #17212
Comments
Not a bug. Nullable reference type warnings are compiler generated. What the warning say is true that you are potentially dereferencing a null. |
This leaves me with questions about how much suppressing the warning is acceptable. I am building a project targeting 3.0, and I decided to try out the NRT feature too as I think it is really valuable for preventing problems before they happen.
There's a part of me that thinks if I am using a library class that I shouldn't have to suppress warnings to use it in the intended manner, otherwise I feel like it adds friction to using NRT with EF. Is there some attribute or something in the implementation of NRT that can mark a function's intention to be null safe at runtime? Context.Alpha.Include(a => a.Beta)
#nullable disable
.ThenInclude(b => b.Delta)
#nullable restore
.ToListAsync() |
Another similar case |
The wider issue here is that these are just expression trees. This code is never executed, so does it even make sense to flag nullability issues here? As Smit's case makes very clear, the SQL that is actually executed doesn't have the same nullability semantics anyway. For the Include case, the call to Include is just telling EF to create a certain type of query--clearly the executing SQL is very different. |
The main reason I marked this particular thing as an issue is that all of my other suppression in this case is inside the context and associated model classes (where I have a non-nullable string property that doesn't have a sensible default initialization). This is the only place that I have to suppress null warnings outside of the context and model classes. |
@smitpatel The weird thing is that if I mark the {
...
public DbSet<Alpha>? Alpha { get; set; }
public DbSet<Beta>? Beta { get; set; }
public DbSet<Delta>? Delta { get; set; }
}
...
//warning
context.Alpha.Where(a => a.Beta.ID > 3)
//fine
.Include(a => a.Beta)
//warning
.ThenInclude(b => b.Delta); |
I suspect what is triggering the first warning is the access to The call to The call to Most of these warnings in query methods are superfluous because as @smitpatel and @ajcvickers mentioned before, EF Core in most cases translate the expressions to SQL (which has different null semantics) rather than executing them in memory. Do you have an example of your previous point "Adding a relationship with fluid API when one of the keys is nullable/optional". I assume the warning occurs when configuring the keys, am I right? |
I am starting to wonder if it would be worth annotating the extension methods on We could do it right away for some of the extension methods defined by EF Core, but for LINQ operators in the BCL it requires more thinking: We don't know that all IQueryable providers translate to something that propagates nulls like SQL. Ideally there would be a way for the EF Core provider in use in the DbContext to influence this, but that seems unfeasible. Another option that perhaps exists (I don't really know) is to have providers somehow carry "warning suppressions" for specific methods, so if you just have that provider installed then the LINQ operators won't warn. @roji any thoughts about this? |
tl;dr IMHO DbSets should definitely be non-nullable, and possibly also navigation properties themselves (or use null-forgiving for the last). DbSets on the context are a great example of late-initialized properties - they are 100% guaranteed to never be null by EF Core (DbContext's constructor initializes them), but the compiler doesn't know this. The best practice for this kind of case is to keep them non-nullable and to force-initialize to null with the null-forgiving operator: The case of required properties without a sensible default is similar but a bit weaker - there is no airtight guarantee that they're always initialized, but assuming the user properly materialized an entity from the database (including with the proper Includes), they would never observe a null. For these cases it's also fine to force-initialize to null, although a non-nullable property wrapping a nullable field and throwing InvalidOperationException for nulls is a bit tighter (but more verbose). Navigation properties within queries is obviously more tricky... For nullable non-collection navigations, it seems like sprinkling the null-forgiving operator is OK (better than warning suppression because more precise): Where(od => od.Order!.Customer!.City == "Seattle") It's not perfect, but the user (unlike the compiler) knows the query is translated by EF and that null semantics will be taken care of - so it's their responsibility to communicate that. A more extreme but possibly better solution, is to make the navigation non-nullable, and to tell EF via other means that the relationship isn't required (via (Note: collection navigations should obviously be non-nullable - not having a related entity is expressed by having an empty list rather than a null.)
We are pretty confident (or at least hopeful) that LINQ async operators will make their way into the BCL at some point, so I'd be wary doing anything like this. It seems fundamentally problematic (probably impossible) to distinguish between invocations of operators on EF (where we want to suppress) and outside EF (where we don't)...
I'm not aware of anything like this, especially not in a way that would suppress the warnings only when applied to EF DbSets. We'd almost need a "reverse analyzer", which suppresses warnings based on analysis instead of generating them... This might not be a bad idea but it doesn't exist AFAIK (and would be hard to make a case for). We should definitely document the above best practices (once we agree on them), a specific doc page on nullability may make sense. |
Are you thinking about LINQ to Objects operators or async operators for
That is why I was thinking that we could do this right away on the extension methods that EF Core defines for
Yes, I was thinking exactly about this. Anti-analyzers.
I get what you are saying, and maybe there isn't much we can do now, but it is a lot of paper cuts.
Definitively, we should. I think you previous comment is a good starting point for the docs section. |
Ah right, I misunderstood. For these we may actually be able to annotate with the new attributes. I'll take a quick look at this later today. But this would still leave the standard LINQ operators (e.g. Where) where we can't really do anything... |
From a quick look I think we can't do anything about Include/ThenInclude. The generic type definition of the property - including its nullability - flows from the Include's selector to the ThenInclude's selector, and there's no method or property where we could put a |
@divega The warning comes up when I do a One of my mistakes before creating this issue was my own lack of awareness of the null-forgiveness operator For my two cents, I agree that using I definitely feel like a EF & NRT doc page outlining best practices would be most helpful. |
I learned from this guide from Microsoft that we can auto-include navigational properties. I used that to auto-include the grandchild navigation property whenever the child entity is queried from the db. Is that a reasonable workaround? |
I completely agree with @roji -- both that I'd use !:
And the EF team should probably rewrite the ThenInclude declaration so it accepts a nullable reference. |
ThenInclude does accept a nullable reference - or rather, it accepts whatever type flows from the previous Include (which is a nullable Beta in the above case). The problem is when you access a property on that nullable reference - the compiler issues a warning, since as far as it's concerned, |
The solution here is most likely a diagnostics suppressor that identifies EF LINQ queries, and suppresses nullability warnings within them - that's covered by #21663. Note that statically identifying EF LINQ queries connects with AOT work we're planning, where those queries are pre-compiled (see #22086 for examples of these). |
Duplicate of #21663 |
I am trying to use EF Core with the new C# 8.0 nullable reference types, and I am trying to find the right pattern to use. This leads me to a few issues, but the most concrete one is a warning when I try to use ThenInclude with a property that is nullable.
However, based on #9038 it appears that
ThenInclude()
should not dereference the null if theInclude()
object is null. Again, I'm not sure if this is a bug or just ignorance of the pattern on my part.Steps to reproduce
https://gist.github.com/kateract/3b07b479c5d284d8a5b859cb99299371
Further technical details
EF Core version: 3.0.0-preview8.19405.11
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows
IDE: Visual Studio Professional 2019 16.3.0 Preview 1.0
The text was updated successfully, but these errors were encountered: