-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
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