jhalbrecht / EfBlogging

0 stars 0 forks source link

Microsoft.EntityFrameworkCore.Sqlite DbContext SaveChanges() inconsistent behavior #1

Open jhalbrecht opened 7 years ago

jhalbrecht commented 7 years ago

I've implemented a LocalDB wpf app and a sqlite uwp app utilizing the Blog and Post classes from the entity framework documentation the Blog / Post classes of this article "UWP - New Database" for adding core.sqlite to uwp. The classes in the wpf version are linked (not copied) to the uwp app to be sure the syntax was the same between both projects.

The wpf version works as I would expect. a combo box with Blogs as the ItemsSource and a ListView with the ComboBox SelectedItem (SelectedBlog in the vm) as the ListView ItemsSource (SelectedBlog.Posts) . The uwp ef / sqlite core 1.1.0 version works too just after seeding (pictured) but the list of posts in each blog is not retained after dbContext is released even though a SaveChages() was issued. The blogs and posts are in the db but the List <Post> Posts in the blogs is not.

efbloggingcblvasitshouldbe

Recreate the problem by running first with FakeEmUp() uncommented in MainPage ViewModel to seed the db (using bogus) and comment out for 2nd run. The ComboBox will be populated but the ListView will not as there are no posts in the list on the Blog. Again, works fine in the wpf / LocalDB version.

Red are seeds from previous runs of the uwp app. Note that there are no posts in the red circled Blog. My seed method always populates a List<Post> with one or more posts. What you see in red is the result of the dbcontext closing when the app does. There are still Blogs and Posts, however there is no list of posts in the blogs. The circle in the green is the current session. Note there are multiple posts in the blog.

Remember the wpf LocalDB version of this works correctly with the same classes. The problem is with the .net core ef and/or sqlite uwp app.

efblogginguwpredgreenwatchwindown

        private void FakeEmUp()
        {
            var fakeBlogs = FakeBlog.Generator.Generate(4).ToList();
            var c = fakeBlogs.ToList();
            int saveChangesResult = 0;
            try
            {
                foreach (var fakeBlog in fakeBlogs)
                {
                    var foo = bloggingContext.Blogs.Add(fakeBlog);
                    Logger.Log(this, $"foo state added: {foo.State}");
                }
                var peas = bloggingContext.Posts;
                //bloggingContext.Entry(peas).State = EntityState.Modified;
                //bloggingContext.Entry(peas).State = EntityState.Added;
                //bloggingContext.Entry(peas.Entity).CurrentValues.SetValues(peas);
                saveChangesResult = bloggingContext.SaveChanges();
                Logger.Log("FakeEmUp()", $"saveChangesResult: {saveChangesResult}");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }

            var b = bloggingContext.Blogs.Count();
            var p = bloggingContext.Posts.Count();
            var s = b + p;
            Logger.Log(this, $"Blogs {b} + Posts {p} = {s}");
            Logger.Log(this, $"They're equal right? changes in context from count of blogs and posts is {s} saveChangesResult is {saveChangesResult} ");

        }

This is a select query from SQLite/SQL Server Compact Toolbox of Blogs. Note the absence of the List.

addnewblogselectquery

Checkout the dbContext db in this debugging. Look for Posts circled in red. Note that there is a List of posts in the Blog.

efbloggingb4savechanges

Now just after the SaveChanges() you can see that the posts are null rather than a populated list as in the previous image of the debug watch window. Note that I'm not doing the using disposal I switched to a dbcontext that lives the life of the viewmodel, that way I could see the ComboBox/ListView working once before I close the dbContext or app.

efbloggingaftersavechanges

jhalbrecht commented 7 years ago

Found some additional probably better core documentation.

Entity Framework Core

And a generic intro to ef using the Blog / Post models dated Updated: October 23, 2016 by Rowan Miller Entity Framework Code First to a New Database Video With a walkthrough

jhalbrecht commented 7 years ago

Interesting. This newly discovered documentation doesn't use the 'virtual' on the List<Post> Posts or Blog Blog properties. Both pages are for core versions of Entity Framework. I seem to remember that the virtual was for lazy loading.

Removing the 'virtual' I tried the uwp sqlite app hoping it would now save the Blogs with the list of posts. Nope. Adding further to my confusion the LocalDB wpf version of the app with the 'virtual' removed didn't have the list of posts in each blog when I ran the wpf program. And that with existing data. Now I'm wondering about keys or indexes.

I did verify that the LocalDB wpf version showed the blog with the list of posts after I added the virtual back in.

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public virtual List<Post> Posts { get; set; }
    //public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
    //public Blog Blog { get; set; }
}
jhalbrecht commented 7 years ago

OK, whatever :-( I broke it. Been working on it for many days, never saw this. Now I'm getting

PM> Add-Migration Initial No migrations configuration type was found in the assembly 'EfBlogging.Uwp'. (In Visual Studio you can use the Enable-Migrations command from Package Manager Console to add a migrations configuration). PM>

The only thing I can think of that is changed is I was mucking around uninstalling and reinstalling .netCore SDK from the command line in an unrelated project. The NuGets in this project supply all the tooling I need correct?

jhalbrecht commented 7 years ago

Interesting. I rebooted machine and opened the project again. Note this more informative message.

PM> EntityFrameworkCore\Add-Migration Initial
Both Entity Framework Core and Entity Framework 6 are installed. The Entity Framework Core tools are     running. Use 'EntityFramework\Add-Migration' for Entity Framework 6.
To undo this action, use Remove-Migration.
PM> 

Back to working the real problem now.

jhalbrecht commented 7 years ago

Found a couple articles by Arthur Vickers @ ajcvickers Good read. One paragraph kinda kept me hanging, still thinking about it.

All these methods work with the graph of untracked entities reachable from the entity passed to the method. So if you have a Blog which references two Posts and you call Add for the Blog, then the Posts will also be Added.

These methods do not automatically call DetectChanges. This is different from EF6 where these methods would automatically call DetectChanges.

This is exactly what I'm doing in my simple example project

jhalbrecht commented 7 years ago

Found a couple articles by Arthur Vickers @ ajcvickers Good read. One paragraph kinda kept me hanging, still thinking about it.

All these methods work with the graph of untracked entities reachable from the entity passed to the method. So if you have a Blog which references two Posts and you call Add for the Blog, then the Posts will also be Added.

These methods do not automatically call DetectChanges. This is different from EF6 where these methods would automatically call DetectChanges.

This is exactly what I'm doing in my simple example project