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

Exception using LazyLoading When sharing same .Net Type among multiple owned types #11945

Closed
jornvl90 opened this issue May 9, 2018 · 4 comments
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@jornvl90
Copy link

jornvl90 commented May 9, 2018

Describe what is not working as expected.

I am using postgres and I have lazy loading enabled. I have one class with 2 owned properties which are from the same .NET type (analogue to this: https://docs.microsoft.com/en-us/ef/core/modeling/owned-entities#sharing-the-same-net-type-among-multiple-owned-types). Doing Count() on the set results in not being ably to Skip(...).Take(...)

If you are seeing an exception, include the full exceptions details (message and stack trace).

Exception message: Object reference not set to an instance of an object.
Stack trace: at lambda_method(Closure , ValueBuffer )
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalMixedEntityEntry..ctor(IStateManager stateManager, IEntityType entityType, Object entity, ValueBuffer& valueBuffer)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryFactory.NewInternalEntityEntry(IStateManager stateManager, IEntityType entityType, Object entity, ValueBuffer& valueBuffer)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTrackingFromQuery(IEntityType baseEntityType, Object entity, ValueBuffer& valueBuffer, ISet`1 handledForeignKeys)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.StartTracking(Object entity, IEntityType entityType)
   at lambda_method(Closure , QueryContext , Blog , Object[] )
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._Include[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Action`3 fixup)
   at lambda_method(Closure , TransparentIdentifier`2 )
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.<_TrackEntities>d__17`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Linq.Enumerable.WhereEnumerableIterator`1.ToArray()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.OrderedEnumerable`1.ToArray()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.OrderedEnumerable`1.ToList(Int32 minIdx, Int32 maxIdx)
   at System.Linq.OrderedPartition`1.ToList()
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at ConsoleApp1.Program.Main(String[] args) in C:\Users\Jorn Van Loock\source\repos\ConsoleApp1\ConsoleApp1\Program.cs:line 82

Steps to reproduce

namespace ConsoleApp1
{
    public class Blog
    {
        public int Id { get; set; }
        public virtual Person Writer { get; set; }
        public virtual Person Reader { get; set; }
        public int Type { get; set; }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class BloggingContext : DbContext
    {

        private string connectionString = $"Host=localhost;Port=15432;Database=testDB;Username=postgres;Password=postgres";
        public BloggingContext()
        {
            Database.EnsureCreated();
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {

            optionsBuilder
                .UseLazyLoadingProxies()
                .UseNpgsql(connectionString);
            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(e => {
                e.OwnsOne(x => x.Writer);
                e.OwnsOne(x => x.Reader);
            });
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Insert
            using (var db = new BloggingContext())
            {
                Blog blog = new Blog()
                {
                    Type = 1,
                    Writer = new Person()
                    {
                        FirstName = "firstNameWriter",
                        LastName = "lastNameWriter"
                    },
                    Reader = new Person()
                    {
                        FirstName = "firstNameReader",
                        LastName = "lastNameReader"
                        
                    }
                };

                db.Set<Blog>().Add(blog);
                db.SaveChanges();
            }

            // Get
            using (var db = new BloggingContext())
            {
                Func<Blog, bool> predicate = (blog) => blog.Type == 1;
                IOrderedEnumerable<Blog> blogs = db.Set<Blog>().Where(predicate).OrderBy(p => p.Id);
                int count = blogs.Count(); // THIS LINE CAUSES THE ISSUE
                blogs.OrderBy(p => p.Id).Skip(0).Take(5).ToList();
                Console.Write("Done");
            }

        }
    }
}

Further technical details

EF Core version: 2.1.0-rc1
Database Provider: Npgsql 2.1.0-rc1
Operating system: Win10
IDE: Visual Studio 2017 15.6.6

@ajcvickers
Copy link
Contributor

Notes for triage: There are multiple things going on here--I wrote tests for what I found--see below.

  • The first issue is that when an opaque predicate is used, then I don't see a client-eval warning, even though it is obviously being client-evaled.
  • Also, when using an opaque predicate, the Count() query tracks entities even though it should not do because the query doesn't return any entities.
  • Even without an opaque predicate, if a query that generates proxy instances and includes owned types is executed twice, then the second time the query is executed:
    • An attempt is made to track the entities again, even though they are already tracked from the first query
    • The second call ToStartTrackingFromQuery has an empty ValueBuffer, even though the entity has shadow property values--this is what causes the null-ref exception to be thrown

Test code for LazyLoadProxyTestBase:

private static void VerifyBlogs(List<Blog> blogs)
{
    Assert.Equal(3, blogs.Count);

    for (var i = 0; i < 3; i++)
    {
        Assert.Equal($"firstNameReader{i}", blogs[i].Reader.FirstName);
        Assert.Equal($"lastNameReader{i}", blogs[i].Reader.LastName);

        Assert.Equal($"firstNameWriter{i}", blogs[i].Writer.FirstName);
        Assert.Equal($"lastNameWriter{i}", blogs[i].Writer.LastName);

        Assert.Equal($"127.0.0.{i + 1}", blogs[i].Host.HostName);
    }
}

[Fact]
public virtual void Lazy_loading_finds_correct_entity_type_with_multiple_queries()
{
    using (var context = CreateContext(lazyLoadingEnabled: true))
    {
        var blogs = context.Set<Blog>().Where(_ => true);

        VerifyBlogs(blogs.ToList().OrderBy(e => e.Host.HostName).ToList());
        Assert.Equal(12, context.ChangeTracker.Entries().Count());

        VerifyBlogs(blogs.ToList().OrderBy(e => e.Host.HostName).ToList());
        Assert.Equal(12, context.ChangeTracker.Entries().Count());
    }
}

[Fact]
public virtual void Lazy_loading_finds_correct_entity_type_with_opaque_predicate_and_multiple_queries()
{
    using (var context = CreateContext(lazyLoadingEnabled: true))
    {
        Func<Blog, bool> opaquePredicate = _ => true;

        var blogs = context.Set<Blog>().Where(opaquePredicate);

        VerifyBlogs(blogs.ToList().OrderBy(e => e.Host.HostName).ToList());
        Assert.Equal(12, context.ChangeTracker.Entries().Count());

        VerifyBlogs(blogs.ToList().OrderBy(e => e.Host.HostName).ToList());
        Assert.Equal(12, context.ChangeTracker.Entries().Count());
    }
}

[Fact]
public virtual void Lazy_loading_finds_correct_entity_type_with_multiple_queries_using_Count()
{
    using (var context = CreateContext(lazyLoadingEnabled: true))
    {
        var blogs = context.Set<Blog>().Where(_ => true);

        Assert.Equal(3, blogs.Count());
        Assert.Empty(context.ChangeTracker.Entries());

        Assert.Equal(3, blogs.Count());
        Assert.Empty(context.ChangeTracker.Entries());
    }
}

[Fact]
public virtual void Lazy_loading_finds_correct_entity_type_with_opaque_predicate_and_multiple_queries_using_Count()
{
    using (var context = CreateContext(lazyLoadingEnabled: true))
    {
        Func<Blog, bool> opaquePredicate = _ => true;

        var blogs = context.Set<Blog>().Where(opaquePredicate);

        Assert.Equal(3, blogs.Count());
        Assert.Empty(context.ChangeTracker.Entries());

        Assert.Equal(3, blogs.Count());
        Assert.Empty(context.ChangeTracker.Entries());
    }
}

@divega divega added this to the 2.2.0 milestone May 10, 2018
@AmilaDotDev
Copy link

I'm having the same issue.

{System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method(Closure , QueryContext , Person , Object[] )
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._Include[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Action`3 fixup)
   at lambda_method(Closure , TransparentIdentifier`2 )
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
   at lambda_method(Closure )
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ResultEnumerable`1.GetEnumerator()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.<_TrackEntities>d__17`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass15_1`1.<CompileQueryCore>b__0(QueryContext qc)
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
   at OwnedTypes.NotWorking.Program.Main(String[] args) in ...\OwnedTypes\OwnedTypes.NotWorking\Program.cs:line 26}

Please see my sample code @ https://github.com/amilarox1/OwnedTypes

Further technical details
EF Core version: 2.1.0-rc1
Database Provider: SqlServer 2.1.0-rc1
Operating system: Win10
IDE: Visual Studio 2017 15.6.6

@ajcvickers
Copy link
Contributor

@amilarox1 Your case has a completely different stack trace, indicating that it is not the same root cause. Can you please file a new issue?

@ajcvickers ajcvickers removed this from the 2.2.0 milestone May 11, 2018
@AmilaDotDev
Copy link

Created new issue #11972

@ajcvickers ajcvickers modified the milestones: 2.2.0, 2.1.0 May 11, 2018
@ajcvickers ajcvickers assigned ajcvickers and unassigned anpete May 12, 2018
@ajcvickers ajcvickers changed the title Lazy loading & Lambda Expression Exception using LazyLoading When sharing same .Net Type among multiple owned types May 12, 2018
@ajcvickers ajcvickers added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. shiproom-approved labels May 12, 2018
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

5 participants