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

Seed InMemory database automatically (i.e. without calling EnsureCreated) #11666

Closed
jsacapdev opened this issue Apr 13, 2018 · 15 comments

Comments

@jsacapdev
Copy link
Contributor

@jsacapdev jsacapdev commented Apr 13, 2018

In "Announcing Entity Framework Core 2.1 Preview 2" it states "Data seeding now works with in-memory databases."

For my unit tests, the In-Memory store does not get data populated with the data set up in OnModelCreating. So in the unit test method Seed_It i get nothing returned back. Am I doing something incorrectly?

Steps to reproduce

BloggingContext.cs

namespace TwoOnePreview.Data
{
    using Microsoft.EntityFrameworkCore;

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>().HasData(new Blog(1, "Url1"));

            modelBuilder.Entity<Blog>().HasData(new Blog(2, "Url2"));

            modelBuilder.Entity<Post>().HasData(new Post(1, "Title1", "Content1"){ BlogId = 1 });
            modelBuilder.Entity<Post>().HasData(new Post(2, "Title2", "Content2"){ BlogId = 1 });
            modelBuilder.Entity<Post>().HasData(new Post(3, "Title3", "Content3"){ BlogId = 1 });

            modelBuilder.Entity<Post>().HasData(new Post(4, "Title4", "Content4"){ BlogId = 2 });
        }
    }
	
    public class Blog
    {
        public Blog(int blogId, string url)
        {
            BlogId = blogId;
            Url = url;
        }

        public int BlogId { get; private set; }

        public string Url { get; private set; }

        public virtual ICollection<Post> Posts { get; set; }
    }

    public class Post
    {
        public Post(int postId, string title, string content)
        {
            PostId = postId;
            Title = title;
            Content = content;
        }

        public int PostId { get; private set; }

        public string Title { get; private set; }
        
        public string Content { get; private set; }

        public int BlogId { get; set; }

        public virtual Blog Blog { get; set; }
    }	
}

ValuesController.cs

namespace TwoOnePreview.Api.Controllers
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using TwoOnePreview.Api.ServiceClients;
    using TwoOnePreview.Data;

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly BloggingContext _context;

        private readonly ILogger<BloggingContext> _logger;

        public ValuesController(ILogger<BloggingContext> logger, BloggingContext context)
        {
            _logger = logger;

            _context = context;
        }


        [HttpGet]
        public ActionResult<IEnumerable<Blog>> Get()
        {
            return _context.Blogs;
        }

        [HttpPost]
        [ProducesResponseType(201)]
        public ActionResult<Blog> Post(Blog blog)
        {
            _context.Blogs.Add(blog);
            return CreatedAtAction(nameof(Get), new { id = blog.BlogId }, blog);
        }
    }
}

ValuesControllerTests.cs

namespace TwoOnePreview.Api.Tests
{
    using System;
    using System.Linq;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc.Testing;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using Moq;
    using TwoOnePreview.Api.Controllers;
    using TwoOnePreview.Data;
    using Xunit;

    public class ValuesControllerTests : IClassFixture<TwoOnePreviewApiTestFixture<Startup>>
    {
        public ValuesControllerTests(TwoOnePreviewApiTestFixture<Startup> fixture)
        {
            Client = fixture.CreateDefaultClient();
        }

        public HttpClient Client { get; }

        [Fact]
        public async Task GetById()
        {
            var response = await Client.GetAsync("http://localhost/api/values/5");

            Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
        }

        [Fact]
        public void Seed_It()
        {
            var options = new DbContextOptionsBuilder<BloggingContext>()
                            .UseInMemoryDatabase(databaseName: "Get")
                            .Options;

            using (var context = new BloggingContext(options))
            {
                var controller = new ValuesController(new Mock<ILogger<BloggingContext>>().Object, context);

                var response = controller.Get();
            }
        }
    }
}

Further technical details

EF Core version: 2.1.0-preview2-final
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows Server 2016
IDE: Visual Studio Code
Complete code listing: https://github.com/jsacapdev/asp-net-core-2-1-preview.git

@ajcvickers

This comment has been minimized.

Copy link
Member

@ajcvickers ajcvickers commented Apr 13, 2018

@jsacapdev Currently there needs to be a call to context.Database.EnsureCreated() to trigger the seeding to happen. We will discuss whether or not we can make a change to not require this.

@ErikEJ

This comment has been minimized.

Copy link
Contributor

@ErikEJ ErikEJ commented Apr 13, 2018

Keep it...

@ajcvickers

This comment has been minimized.

Copy link
Member

@ajcvickers ajcvickers commented Apr 13, 2018

@ErikEJ More details please...

@ErikEJ

This comment has been minimized.

Copy link
Contributor

@ErikEJ ErikEJ commented Apr 13, 2018

Seems logical that this only happens during migrations being applied

@jsacapdev

This comment has been minimized.

Copy link
Contributor Author

@jsacapdev jsacapdev commented Apr 16, 2018

@ajcvickers thanks for the guidance.

@jsacapdev jsacapdev closed this Apr 16, 2018
@ajcvickers ajcvickers reopened this Apr 16, 2018
@ajcvickers

This comment has been minimized.

Copy link
Member

@ajcvickers ajcvickers commented Apr 16, 2018

Putting this on the backlog for now to consider for later release based on feedback.

@ajcvickers ajcvickers changed the title 2.1 Preview 2 Data HasData() Not Working when Unit Testing for In-Memory Seed InMemory database automatically (i.e. without calling EnsureCreated) Apr 16, 2018
@ajcvickers ajcvickers added this to the Backlog milestone Apr 16, 2018
@anorborg

This comment has been minimized.

Copy link

@anorborg anorborg commented Jun 11, 2018

When should one call context.Database.EnsureCreated when using HasData for seeding scenarios?

@AndriySvyryd

This comment has been minimized.

Copy link
Member

@AndriySvyryd AndriySvyryd commented Jun 11, 2018

@anorborg You can just call it before using the context:

using (var context = new MyDbContext())
{
    context.Database.EnsureCreated();
    ...
}
@gojanpaolo

This comment has been minimized.

Copy link

@gojanpaolo gojanpaolo commented Aug 8, 2018

I vote for explicitly calling context.Database.EnsureCreated.

Imagine having a test which needs the Count() value.

service.AddRangeIfValid(validItems);
Assert.Equal(validItems.Count(), dbContext.Items.Count());

If seeding is implicitly called then this may lead to unexpected results.

@victormarante

This comment has been minimized.

Copy link

@victormarante victormarante commented Sep 26, 2018

Is this still valid? Do you still have call EnsureCreated()?

@ajcvickers

This comment has been minimized.

Copy link
Member

@ajcvickers ajcvickers commented Sep 26, 2018

@JuergenGutsch

This comment has been minimized.

Copy link

@JuergenGutsch JuergenGutsch commented Jan 21, 2019

Why? Doesn't makes sense to me, that EnsureCreated needs to be called explicitly.
I'd like to switch the context to a in-memory DB while running integration tests. This works, but the seeding didn't happen. So I need to add this additional line only to get the integration tests on an in-memory DB running.

@AndriySvyryd

This comment has been minimized.

Copy link
Member

@AndriySvyryd AndriySvyryd commented Jan 22, 2019

@JuergenGutsch Seeding is performed as part of database initialization. To trigger database initialization you need to call EnsureCreated(). Relational providers can also use migrations tools to do this, but otherwise it works the same way.
If you use a shared (named) in-memory database you only need to do this once.

@JuergenGutsch

This comment has been minimized.

Copy link

@JuergenGutsch JuergenGutsch commented Jan 23, 2019

Hi @AndriySvyryd
That means the behavior of the EF interface is dependent on the provider the context runs on? Does this makes sense? Anyway, I see your point. I'm just not sure whether this is good or bad. This seems to be confusing as we can see in this thread or other issues like this.
Currently I added EnsureCreated() to the application to test just to get the integration tests running, even if the DbContext is running on a relational database in production. This would be a pretty bad option. So I need to find a way to call EnsureCreated() from within the test project instead.

@ajcvickers

This comment has been minimized.

Copy link
Member

@ajcvickers ajcvickers commented Jan 24, 2019

We discussed this again as part of planning and agreed that requiring a call to EnsureCreated is desirable here since this type of seeding is inherently associated with the database creation and migration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants
You can’t perform that action at this time.