question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

InMemory database throwing NullReferenceException when using a nullable value-converted property in a query

See original GitHub issue

When upgrading our application to EF Core 5, we encountered problems when using the InMemory database on an entity class that contains a (non-required) property which is value-converted from string to a custom data class.

We managed to reduce our production code to a very simplified program that exhibits the problem.

Any help with this is greatly appreciated!

Reproduction code

The following program works with EF Core 3.1 but breaks with EF Core 5.0 and InMemoryDb:

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

public static class Program
{
    static void Main(string[] args)
    {
        // Use SQL server -- this works
        var sqlContext = new FooDbContext(new DbContextOptionsBuilder<FooDbContext>()
            .UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EfCoreIssueTestDb").Options);
        Test(sqlContext);

        // Use InMemoryDB -- this breaks
        var inMemoryContext = new FooDbContext(new DbContextOptionsBuilder<FooDbContext>().UseInMemoryDatabase("EfCoreIssueTestDb").Options);
        Test(inMemoryContext);
    }

    private static void Test(FooDbContext context)
    {
        Console.WriteLine($"Testing with database provider {context.Database.ProviderName}");
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        context.Device.Add(new Device { NetworkId = null });
        context.Device.Add(new Device { NetworkId = new NetworkId("x") });
        context.Device.Add(new Device { NetworkId = new NetworkId("y") });
        context.Device.Add(new Device { NetworkId = null });
        context.Device.Add(new Device { NetworkId = null });
        context.SaveChanges();

        Console.WriteLine($"We have {context.Device.Count(_ => _.NetworkId != null)} Devices with a NetworkId.");
        Console.WriteLine($"We have {context.Device.Count(_ => _.NetworkId == null)} Devices without a NetworkId.");
        Console.WriteLine();
    }
}

public class FooDbContext : DbContext
{
    public FooDbContext(DbContextOptions<FooDbContext> options) : base(options)
    {
    }

    public DbSet<Device> Device { get; set; }

    protected override void OnModelCreating(ModelBuilder model)
    {
        base.OnModelCreating(model);

        model.Entity<Device>(entity =>
        {
            entity.HasKey(_ => _.Id);

            entity.Property(_ => _.NetworkId)
                .HasConversion(new ValueConverter<NetworkId, string>(
                    bar => bar != null ? bar.ToString() : null,
                    str => str != null ? new NetworkId(str) : null));
        });
    }
}

public class Device
{
    public int Id { get; set; }

    public NetworkId NetworkId { get; set; }
}

public class NetworkId
{
    private readonly string _eui;

    public NetworkId(string eui) => _eui = eui;

    public override string ToString() => _eui;

    private bool Equals(NetworkId other) => other != null && Equals(_eui, other._eui);

    public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is NetworkId other && Equals(other));

    public override int GetHashCode() => _eui != null ? _eui.GetHashCode() : 0;

    public static NetworkId FromString(string s) => s != null ? new NetworkId(s) : null;

    public static bool operator ==(NetworkId left, NetworkId right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(NetworkId left, NetworkId right)
    {
        return !Equals(left, right);
    }

    public static explicit operator string(NetworkId networkId)
    {
        return networkId?.ToString();
    }

    public static explicit operator NetworkId(string s)
    {
        return FromString(s);
    }
}

Project file:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
      <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.9" />
      <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.9" />
      <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.9" />
    </ItemGroup>
</Project>

Output with EF Core version 5.0.9

Testing with database provider Microsoft.EntityFrameworkCore.SqlServer
We have 2 Devices with a NetworkId.
We have 3 Devices without a NetworkId.

Testing with database provider Microsoft.EntityFrameworkCore.InMemory
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method(Closure , ValueBuffer )
   at System.Linq.Enumerable.WhereListIterator`1.GetCount(Boolean onlyIfCheap)
   at lambda_method(Closure )
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryQueryExpression.ResultEnumerable.GetEnumerator()
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNextHelper()
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.Count[TSource](IQueryable`1 source, Expression`1 predicate)
   at Program.Test(FooDbContext context) in C:\dev\hagleitner\scratch\EFCoreIssue\Program.cs:line 33
   at Program.Main(String[] args) in C:\dev\hagleitner\scratch\EFCoreIssue\Program.cs:line 17

Program output with EF Core version 3.1.18

Testing with database provider Microsoft.EntityFrameworkCore.SqlServer
We have 2 Devices with a NetworkId.
We have 3 Devices without a NetworkId.

Testing with database provider Microsoft.EntityFrameworkCore.InMemory
We have 2 Devices with a NetworkId.
We have 3 Devices without a NetworkId.

Further observations

  • The problem goes away when removing the public override bool Equals(object obj) method from the NetworkId class.

  • However, stepping through with a debugger reveals that neither of the Equals method throw.

  • Interestingly, the problem still persists when changing the overridden Equals method to public override bool Equals(object obj) => base.Equals(obj)

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
smitpatelcommented, Aug 13, 2021

https://github.com/dotnet/efcore/blob/eb9a2b8ac70dc1736e49302d19d76ad3186b4744/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs#L215-L221

My bet is that we shouldn’t pass binaryExpression.Method arg if there is a property on left/right which has value converter but no value comparer as it would be using wrong methodInfo to compare even after conversion.

0reactions
klafacommented, Aug 18, 2021

Thanks @ajcvickers for providing the workaround! This enabled us to finish our upgrade to EF Core 5.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to debug and fix 'Nullable object must have a value ...
There can be two answers. Firstly, this may be probably because your query is returning NULL and the model is not accepting null....
Read more >
Breaking changes in EF Core 6.0
If all nullable properties contain a null value in database then an object instance won't be created in the query causing nested dependent's ......
Read more >
Handle null values in query expressions (LINQ in C#)
If a source collection is null or contains an element whose value is null , and your query doesn't handle null values, a...
Read more >
Null Reference Exceptions
Null Reference Exceptions. A NullReferenceException happens when you try to access a reference variable that isn't referencing any object.
Read more >
B.3.4.3 Problems with NULL Values
The concept of the NULL value is a common source of confusion for newcomers to SQL, who often think that NULL is the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found