Skip to content
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

How to coalesce values when navigating nullable relationships? #33336

Closed
dosolkowski-work opened this issue Mar 15, 2024 · 8 comments
Closed
Assignees
Labels
area-query closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@dosolkowski-work
Copy link

Ask a question

So, we're not allowed to use the null-propagation operator ?. in expression trees--annoying, but I assume there's a good reason. As a result, the documentation says we have to lie using the null-forgiving operator !. to get our code translated to SQL. However, now, as far as C# is concerned, the value can't be null, even though it really can be--which means we can't use the ?? coalesce operator any more to coalesce the null value away! Is there a solution for this? If there was an EF.Functions.Coalesce() function we could use, then at least we could have a way to use COALESCE directly, but since that doesn't exist, there doesn't seem to be a clean solution to this.

Include your code

// Some entity that has an optional reference to another entity
class First {
    public int Id { get; set; }
    public Second? Other { get; set; }
}

// Some other entity
class Second {
    public int Id { get; set; }
    public string Value { get; set; }
}

// Now we try to query...
var query =
    from f in context.Firsts
    select new {
      Id = f.Id,
      Value = f.Other.Value ?? "default" // NOPE: CS0019 Operator '??' can't be applied
      Value = f.Other!.Value ?? "default" // NOPE: Same as above
      Value = f.Other?.Value ?? "default" // NOPE: Can't use '?.' operator
      Value = f.Other != null ? f.Other.Value ? "default" // Only solution I'm aware of, but it's clunky and translates to more complicated CASE expression instead of COALESCE
    };

Include provider and version information

EF Core version: 6.0.27
Database provider: NPGSQL
Target framework: .NET 6.0
Operating system: Windows 10
IDE: Visual Studio 2022 17.9.3

@roji
Copy link
Member

roji commented Mar 17, 2024

I might be missing something, but the below seem to work just fine - I'm not seeing a CS0019:

Value = f.Other.Value ?? "default" // NOPE: CS0019 Operator '??' can't be applied
Value = f.Other!.Value ?? "default" // NOPE: Same as above

I'm also not sure why there would be one - Value is a string (reference type).

@dosolkowski-work
Copy link
Author

@roji Forgot to mention, we have nullable reference types enabled in our .csproj using <Nullable>enable</Nullable> which is probably essential to reproducing the issue. I'm not sure if we could temporarily disable that using the #nullable directive; the documentation implies it affects the entire file.

@roji
Copy link
Member

roji commented Mar 18, 2024

So do I. The first line indeed generates a CS8602 (dereference of a possibly null reference), but the second one with the bang doesn't generate any warnings. See my complete console program below (adapter from your code).

Attempted repro
#nullable enable

await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

// Now we try to query...
var query =
    from f in context.Firsts
    select new {
        Id = f.Id,
        Value = f.Other.Value ?? "default", // warning CS8602: Dereference of a possibly null reference
        Value2 = f.Other!.Value ?? "default" // no warning
    };


public class BlogContext : DbContext
{
    public DbSet<First> Firsts { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public class First {
    public int Id { get; set; }
    public Second? Other { get; set; }
}

public class Second {
    public int Id { get; set; }
    public string Value { get; set; } = "";
}

@dosolkowski-work
Copy link
Author

@roji Okay, narrowing down on the scope of the issue... try changing the type of Value from string to a value type like int. I think value types using Nullable<T> is the problem, it looks like the compiler is more aggressive about refusing to allow null-coalescing when the Nullable<> has been eliminated. The ?. operator would add Nullable<>, but because we can't use that syntax in expression trees, maybe we have to cast it in?

@roji
Copy link
Member

roji commented Mar 18, 2024

Right, you cannot apply the C# coalescing operator to a (non-nullable) value type:

context.Firsts.Where(f => f.Other.Value ?? 9 == 10);

And you indeed can't use the null-coalescing operator to turn it into an int?; this is an unfortunate limitation of expression trees in C#, which I hope will be lifted in the future (this has been discussed various times in the past). In the meantime, you can indeed either explicitly cast to int?, or just use the conditional operator, as people did before the null-coalescing operator was introduced to C# (f.OtherValue == null ? x : y). Those workarounds aren't great, but that's the situation at the moment.

@roji
Copy link
Member

roji commented Mar 19, 2024

I'm going to go ahead and close this as there isn't anything we can do here, except for getting expression tree support to support newer C# features. But please feel free to post back with further questions/suggestions.

@roji roji closed this as not planned Won't fix, can't repro, duplicate, stale Mar 19, 2024
@roji roji added the closed-no-further-action The issue is closed and no further action is planned. label Mar 19, 2024
@dosolkowski-work
Copy link
Author

Maybe the documentation on nullable support in EF Core could note something about this? It's partially related and partially not, because it's the interaction of the nullable reference type in the foreign key reference and the property not being a reference type that makes things tricky. I don't expect anything could actually be done until ?. works in expression trees somehow.

@roji
Copy link
Member

roji commented Mar 19, 2024

We haven't seen complaints about this particular problem - I think the compilation errors generally guide people in the right direction here. But if more people express confusion we can definitely add a note to the docs, absolutely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

3 participants