Skip to content

Improve logging around context model caching #11608

@SimonCropp

Description

@SimonCropp

So when mapping a view there is a workaround that can be taken.

  • when creating/installing the db dont map the view (to avoid EF creating a table named by the view)
  • when using the db map the view

This can be done by passing a bool into the custom DbContext, so the same context can be used from the installation context and the usage context

public class MyDataContext : DbContext
{
    bool mapViews;
    public DbSet<MyView> MyViews { get; set; }
    public DbSet<MyTable> MyTables { get; set; }

    public MyDataContext(DbContextOptions<MyDataContext> options, bool mapViews) : base(options)
    {
        this.mapViews = mapViews;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MyTable>();

        // only map View when not in install mode.
        // otherwise EF incorrectly creates a table named after the view
        // and the view creation scripts will fail with a name conflict
        if (mapViews)
        {
            modelBuilder.Entity<MyView>(entity => { entity.HasKey(e => e.Id); });
        }
        else
        {
            modelBuilder.Ignore<MyView>();
        }
    }
}

However when instantiating two different instances of said context, one with mapViews: false and one with mapViews: true, the entity mapping from the first context seems to be re-used in the second context.

[Fact]
public void ReGenSqlExpress()
{
    using (var context = new MyDataContext(GetOptions(), mapViews: false))
    {
        context.Database.EnsureCreated();
    }

    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var command = connection.CreateCommand())
        {
            command.CommandText = @"
IF (NOT EXISTS (SELECT 1 FROM sys.views WHERE name = 'MyViews'))
BEGIN
EXECUTE('CREATE VIEW MyViews AS SELECT * FROM MyTables');
END";
            command.ExecuteNonQuery();
        }
    }

    using (var context = new MyDataContext(GetOptions(), mapViews: true))
    {
        var myTable = new MyTable
        {
            Title = DateTime.Now.ToString()
        };
        context.MyTables.Add(myTable);
        context.SaveChanges();
        var myViews = context.MyViews.ToList();
        Debug.WriteLine(myViews);
    }
}

This results in a InvalidOperationException : Cannot create a DbSet for 'MyView' because this type is not included in the model for the context.

note that, after the first run to create the db, if you comment out the following

using (var context = new MyDataContext(GetOptions(), mapViews: false))
{
    context.Database.EnsureCreated();
}

The usage of the view will work

full repro: https://github.com/SimonCropp/EfViewMappingRepro

Targeting SQL Server 2016 on windows 2012R2

repro'd with the following nuget combos

  • Microsoft.EntityFrameworkCore 2.0.2 + Microsoft.EntityFrameworkCore.SqlServer 2.0.2
  • Microsoft.EntityFrameworkCore 2.1.0-preview2-final + Microsoft.EntityFrameworkCore.SqlServer 2.1.0-preview2-final

also verified on both netcoreapp2 and net461

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions