What I can't understand fully is exemplified in a unit test below. Basically I want to know why DbContext overrides collection values currently stored in the database even if Reload() and a new load for the collection has been called.
If I try to call dbContext.Entry(test.TestChildren).Reload(); I get the exception:
System.InvalidOperationException: 'The entity type 'List' was not found. Ensure that the entity type has been added to the model.'
I have also loaded TestChildren explicitly via dbContext.Entry(test).Collection(x => x.TestChildren).Load(); before calling Assert.
https://learn.microsoft.com/en-us/ef/core/querying/related-data/explicit#explicit-loading
I can get it working by creating a new context using (var context = new ApplicationDbContext(dbContextOptions)) and then execute a load on related entities.
However according to Microsoft Docs:
Queries are always executed against the database even if the entities returned in the result already exist in the context.
https://learn.microsoft.com/en-us/ef/core/querying/
The actual values are saved in LocalDb and SQLite file, SQLite in-memory and EF in-memory database looks correct.
https://learn.microsoft.com/en-us/ef/core/testing/sqlite
LocalDb after assert has failed:
EFC 3.1.9 with .NET Core 3.1:
EFC 5.0.2 with .NET 5:
As shown I can get simple properties to load correctly with Reload(). I have tried the solution from @IvanStoev to reload collections but it does not work for me.
https://stackoverflow.com/a/57062852/3850405
I know that most examples have a new using statement per database call but it is normally not a problem to do several calls with one context and Microsoft has several examples with that as well.
My theory is that even though the values are in the database and then fetched DbContext still overrides with the tracked entities because they are probably marked as Modified. This would also explain why EntityEntry.Reload method sets EntityState to Unchanged.
My three questions:
- How can I reload the collection for the entity? The answers I have already tried does not work.
https://stackoverflow.com/a/9084787/3850405 https://stackoverflow.com/a/57062852/3850405
- Is my theory correct or why are values not updated when a new database call is made with - Collection(x => x.TestChildren).Load()?
- Is there anyway to disable the need to use - Reload()behavior or can that have dire consequences? I have seen examples with- dbContext.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;but I have not dared to try it in production systems. I don't want to use Lazy loading.
https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy
https://stackoverflow.com/a/53099150/3850405
I have read a different question about a proper way to replace collection in one to many relationship in Entity Framework but I have not found an answer to my question there.
https://stackoverflow.com/a/31512648/3850405
It is a bit similar to the question on how to refresh an Entity Framework Core DBContext but I would like to know why it happens and there is also no example with a collection.
How to refresh an Entity Framework Core DBContext?
To explain and keep it as simple as possible I have a model very similar to the Getting Started with EF Core model.
ApplicationDbContext:
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(
        DbContextOptions options) : base(options)
    {
    }
    public DbSet<Test> Tests { get; set; }
    public DbSet<TestChild> TestChildren { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}
Test model:
public class Test
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public List<TestChild> TestChildren { get; set; } = new List<TestChild>();
}
public class TestChild
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public int TestId { get; set; }
    public Test Test { get; set; }
}
UnitTest1:
using System;
using Xunit;
using EFCoreTest;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System.Linq;
using System.Data.Common;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace XUnitTestEFCore
{
    public static class Extensions
    {
        public static void Reload(this CollectionEntry source)
        {
            if (source.CurrentValue != null)
            {
                foreach (var item in source.CurrentValue)
                    source.EntityEntry.Context.Entry(item).State = EntityState.Detached;
                source.CurrentValue = null;
            }
            source.IsLoaded = false;
            source.Load();
        }
    }
    public class UnitTest1
    {
        [Fact]
        public void TestSqliteFile()
        {
            var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                .UseSqlite("Data Source=test.db")
                .Options;
            var dbContext = new ApplicationDbContext(dbContextOptions);
            TestSingleProprtyMethod(dbContext, dbContextOptions);
            TestCollectionMethod(dbContext, dbContextOptions);
        }
        [Fact]
        public void TestSqliteInMemory()
        {
            var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                .UseSqlite(CreateInMemoryDatabase())
                .Options;
            var connection = RelationalOptionsExtension.Extract(dbContextOptions).Connection;
            var dbContext = new ApplicationDbContext(dbContextOptions);
            TestCollectionMethod(dbContext, dbContextOptions);
        }
        [Fact]
        public void TestEFInMemoryDatabase()
        {
            var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                             .UseInMemoryDatabase(Guid.NewGuid().ToString())
                             .Options;
            var dbContext = new ApplicationDbContext(dbContextOptions);
            TestCollectionMethod(dbContext, dbContextOptions);
        }
        [Fact]
        public void TestLocalDb()
        {
            var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                             .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=XUnitTestEFCore;Trusted_Connection=True;MultipleActiveResultSets=true")
                             .Options;
            var dbContext = new ApplicationDbContext(dbContextOptions);
            TestCollectionMethod(dbContext, dbContextOptions);
        }
        private static DbConnection CreateInMemoryDatabase()
        {
            var connection = new SqliteConnection("Data Source=:memory:");
            connection.Open();
            return connection;
        }
        private void TestCollectionMethod(ApplicationDbContext dbContext, DbContextOptions<ApplicationDbContext> dbContextOptions)
        {
            dbContext = new ApplicationDbContext(dbContextOptions);
            Seed(dbContext);
            //Works - Nothing in Db
            Assert.Null(dbContext.Tests.FirstOrDefault());
            dbContext.SaveChanges();
            //Works - First item in Db
            Assert.NotNull(dbContext.Tests.FirstOrDefault());
            //Works - TestChildren are correctly added
            Assert.Equal(2, dbContext.Tests.FirstOrDefault().TestChildren.Count);
            using (var context = new ApplicationDbContext(dbContextOptions))
            {
                var usingTest = context.Tests.FirstOrDefault();
                context.Entry(usingTest)
                    .Collection(x => x.TestChildren)
                    .Load();
                //Works
                Assert.Equal(2, usingTest.TestChildren.Count);
            }
            var test = dbContext.Tests.FirstOrDefault();
            test.TestChildren = test.TestChildren.Where(x => x.Id == 1).ToList();
            var count = dbContext.Entry(test)
                .Collection(x => x.TestChildren)
                .Query()
                .Count();
            //Works - Two items
            Assert.Equal(2, count);
            //Works - Two items
            Assert.Equal(2, dbContext.TestChildren.Where(x => x.TestId == 1).Count());
            using (var context = new ApplicationDbContext(dbContextOptions))
            {
                //Works - Two items
                Assert.Equal(2, context.TestChildren.Where(x => x.TestId == 1).Count());
            }
            using (var context = new ApplicationDbContext(dbContextOptions))
            {
                var usingTest = context.Tests.FirstOrDefault();
                context.Entry(usingTest)
                    .Collection(x => x.TestChildren)
                    .Load();
                //Works - Two items
                Assert.Equal(2, usingTest.TestChildren.Count);
            }
            dbContext.Entry(test).Reload();
            dbContext.Entry(test)
                .Collection(x => x.TestChildren)
                .Load();
            //Source - https://stackoverflow.com/a/57062852/3850405
            dbContext.Entry(test).Collection(x => x.TestChildren).Reload();
            //dbContext.Entry(test.TestChildren).Reload(); Causes exception System.InvalidOperationException: 'The entity type 'List<TestChild>' was not found. Ensure that the entity type has been added to the model.'
            //Only one TestChild left even though no save has been performed, test is Reloaded and TestChildren has been explicitly loaded
            Assert.Equal(2, test.TestChildren.Count);
        }
        private void TestSingleProprtyMethod(ApplicationDbContext dbContext, DbContextOptions<ApplicationDbContext> dbContextOptions)
        {
            Seed(dbContext);
            Assert.Null(dbContext.Tests.FirstOrDefault());
            dbContext.SaveChanges();
            Assert.NotNull(dbContext.Tests.FirstOrDefault());
            var test2 = dbContext.Tests.FirstOrDefault();
            test2.Name = "test2";
            //Will be test2
            Assert.NotEqual("test1", dbContext.Tests.FirstOrDefault().Name);
            dbContext.Entry(test2).Reload();
            Assert.Equal("test1", dbContext.Tests.FirstOrDefault().Name);
            using (var context = new ApplicationDbContext(dbContextOptions))
            {
                Assert.Equal("test1", context.Tests.FirstOrDefault().Name);
            }
        }
        private void Seed(ApplicationDbContext dbContext)
        {
            dbContext.Database.EnsureDeleted();
            dbContext.Database.EnsureCreated();
            var test1 = new Test()
            {
                Name = "test1"
            };
            var testChild1 = new TestChild()
            {
                Name = "testChild1"
            };
            var testChild2 = new TestChild()
            {
                Name = "testChild2"
            };
            test1.TestChildren.Add(testChild1);
            test1.TestChildren.Add(testChild2);
            dbContext.Tests.Add(test1);
        }
    }
}



 
    