Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # 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
# for free to join this conversation on GitHub. Already have an account? # 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