i am fairly new in EF and learning EF code first. i am looking for a knowledge to map exisiting sql server view with EF code first. i have map my view with POCO but getting the below error.
when i try to fetch data from view then got the below error thrown
Additional information: The model backing the 'TestDBContext' context
has changed since the database was created. Consider using Code First
Migrations to update the database
my full code as follow
public class TestDBContext : DbContext
{
public TestDBContext()
: base("name=TestDBContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new vwCustomerConfiguration());
}
public DbSet<vwCustomer> vwCustomer { get; set; }
}
public class vwCustomerConfiguration : EntityTypeConfiguration<vwCustomer>
{
public vwCustomerConfiguration()
{
this.HasKey(t => t.CustomerID);
this.ToTable("vwCustomer");
}
}
public class vwCustomer
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
}
this way i am trying to load data.
using (var db = new TestDBContext())
{
var listMyViews = db.vwCustomer.ToList();
}
guide me what i am missing in code for which error is throwing. thanks
UPDATE1
When i issue Add-Migration "My_vwCustomer" then i saw new migration code added as below one. it seems there is no migration is pending.
public partial class My_vwCustomer : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.vwCustomers",
c => new
{
CustomerID = c.Int(nullable: false, identity: true),
FirstName = c.String(),
})
.PrimaryKey(t => t.CustomerID);
}
public override void Down()
{
DropTable("dbo.vwCustomers");
}
}
OP's Feedback :
When i generate the view with ADO.Net Entity model wizard then
everything works fine.
You can do it as shown below.
Note : I have picked the 1 to 4 from this post.
Create a POCO class for the view; for example FooView
Add the DbSet property in the DbContext class
Use a FooViewConfiguration file to set a different name for the view
(using ToTable("Foo"); in the constructor) or to set particular
properties
public class FooViewConfiguration : EntityTypeConfiguration<FooView>
{
public FooViewConfiguration()
{
this.HasKey(t => t.Id);
this.ToTable("myView");
}
}
Add the FooViewConfiguration file to the modelBuilder, for example
ovveriding the OnModelCreating method of the Context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooViewConfiguration ());
}
According to the above configuration,now your table is
this.ToTable("myView");.In other words myView.
Here is the EF query to retrieve all the data on the myView table.
var listMyViews = yourDbContext.myView.ToList()
Your projection may be like this :
var query = yourDbContext.myView
.Select(v=> new
{
ID = v.ID,
EmpName = v.EmpName,
Salary = v.Salary
}).ToList();
Configure view as a table and use this custom generator to prevent migration generation for tables marked as views
public class SkipViewGenerator: CSharpMigrationCodeGenerator
{
protected override void Generate(CreateTableOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
protected override void Generate(RenameTableOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
protected override void Generate(MoveTableOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
protected override void Generate(DropTableOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
protected override void Generate(AddColumnOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Table))
base.Generate(operation, writer);
}
protected override void Generate(DropColumnOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Table))
base.Generate(operation, writer);
}
protected override void Generate(DropPrimaryKeyOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Table))
base.Generate(operation, writer);
}
protected override void Generate(AlterColumnOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Table))
base.Generate(operation, writer);
}
protected override void Generate(AddPrimaryKeyOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Table))
base.Generate(operation, writer);
}
protected override void Generate(AlterTableOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
protected override void Generate(CreateIndexOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
protected override void Generate(DropIndexOperation operation, IndentedTextWriter writer)
{
if (!IsView(operation.Name))
base.Generate(operation, writer);
}
private bool IsView(string tableNameWithSchemaName)
{
var tableName = DatabaseName.Parse(tableNameWithSchemaName).Name;
var schemaName = DatabaseName.Parse(tableNameWithSchemaName).Schema;
return schemaName.Contains("View");
}
}
Usage example
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
CodeGenerator = new SkipViewGenerator();
}
protected override void Seed(MyDbContextcontext)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data.
}
}
DatabaseName.Parse implementatnion you can get from Github sources
Related
I am currently writing some integrations test which affect the database just in a different schema.
I am following this guide
https://www.thinktecture.com/en/entity-framework-core/isolation-of-integration-tests-in-2-1/
My problem is with this abstract class used for creating DbContext?
public abstract class IntegrationTestsBase<T> : IDisposable
where T : DbContext
{
private readonly string _schema;
private readonly string _historyTableName;
private readonly DbContextOptions<T> _options;
protected T DbContext { get; }
protected IntegrationTestsBase()
{
_schema = Guid.NewGuid().ToString("N");
_historyTableName = "__EFMigrationsHistory";
_options = CreateOptions();
DbContext = CreateContext();
DbContext.Database.Migrate();
}
protected abstract T CreateContext(DbContextOptions<T> options,
IDbContextSchema schema);
protected T CreateContext()
{
return CreateContext(_options, new DbContextSchema(_schema));
}
private DbContextOptions<T> CreateOptions()
{
return new DbContextOptionsBuilder<T>()
.UseNpgsql($"Server=(local);Database=Demo;...",
builder => builder.MigrationsHistoryTable(_historyTableName, _schema))
.ReplaceService<IMigrationsAssembly, DbSchemaAwareMigrationAssembly>()
.ReplaceService<IModelCacheKeyFactory, DbSchemaAwareModelCacheKeyFactory>()
.Options;
}
public void Dispose()
{
DbContext.GetService<IMigrator>().Migrate("0");
DbContext.Database.ExecuteSqlCommand(
(string)$"DROP TABLE [{_schema}].[{_historyTableName}]");
DbContext.Database.ExecuteSqlCommand((string)$"DROP SCHEMA [{_schema}]");
DbContext?.Dispose();
}
}
DbContext is being created, but once the migrations is being called i get an error stating that the schema, does not exist?
which I am not sure why because i in my ensure the in my configuration
public class SchemaContext : DbContext, IDbContextSchema
{
public virtual DbSet<Schema>? SchemaModel { get; set; }
public SchemaContext()
{
}
public SchemaContext(DbContextOptions<SchemaContext> options, IDbContextSchema schema = null)
: base(options)
{
Schema = schema.Schema;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(Schema);
//base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
public string Schema { get; }
}
I would assume that modelBuilder.HasDefaultSchema(Schema); would have created the schema?
which does not seem to be case, hence migration fails? what am I missing? why is the schema not being created in the actual database?
In your could you are configure Context with method - UseSqlServer which is used only for MS SQL, you have to use Npgsql.EntityFrameworkCore.PostgreSql package and configure you context with UseNpgsql extension method:
.UseNpgsql("Host=localhost;Port=5432;Database=my_db;Username=postgres;Password=password");
I am developing a .NET CORE MVC 2.1 Web Application with a DbContext declared in a DLL (EF Core 2.1).
I would like to configure the context using IServiceCollection.AddContext<GladContext> but if I do not ALSO configures it DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) I am told that No database provider has been configured for this DbContext despite having a constructor taking a DbContextOptions<GladContext>
public GladContext(DbContextOptions<GladContext> options, IGladConnectionStringProvider connectionStringProvider) : base(options)
{
_connectionStringProvider = connectionStringProvider;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
GladOptionsBuilderHelper.ConfigureDefaultOptionsBuilder(optionsBuilder, _connectionStringProvider.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
The IGladConnectionStringProvider is my current workaround and that is acceptable if it wasn't because I now need to configure both DbContextOptionsBuilder and DbContextOptionsBuilder<GladContext>
public static class GladOptionsBuilderHelper
{
public const string GladMigrationsHistory = "__GladMigrationsHistory";
public static DbContextOptionsBuilder<GladContext> CreateDefaultTypedOptionsBuilder(string connectionString)
{
var optionsBuilder = new DbContextOptionsBuilder<GladContext>();
optionsBuilder
.UseSqlServer(connectionString, options =>
{
options.EnableRetryOnFailure();
options.MigrationsHistoryTable(GladMigrationsHistory, EntityBase.SchemaName);
})
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
return optionsBuilder;
}
public static void ConfigureDefaultOptionsBuilder(DbContextOptionsBuilder optionsBuilder, string connectionString)
{
optionsBuilder
.UseSqlServer(connectionString, options =>
{
options.EnableRetryOnFailure();
options.MigrationsHistoryTable(GladMigrationsHistory, EntityBase.SchemaName);
})
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
}
The DbContextOptionsBuilder<GladContext> is used in IDesignTimeDbContextFactory<GladContext>
Can you can tell me how to use AddContext to configure GladContext or how to construct a DbContextOptionsBuilder from a DbContextOptionsBuilder<GladContext> or the other way around?
The configuration part of it is an override of the IServiceCollection.AddDbContext().
So, when you call AddDbContext, just add your options into the parentheses like so:
var connectionString = "CONNECTION-STRING-HERE";
services.AddDbContext<MyContext>(o => o
.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(true)
.EnableSensitiveDataLogging(true));
public class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
}
Background - I am using Entity framework code version 2.1.4-rtm-31024
check out the CODE LISTING 1 - the problem (according to Ms Build Engine 15.9) is that GetAllMakes calls .ToList, but no 'ToList' method exists for a DbSet of VehicleMake. (check out Code Listing 2) to see the implementation of _vehicleContext.VehicleMakes
Why do I get a compile error? this makes no sense to me since I can call VehicleMakes.ToList() elsewhere in the code (no compiler error) no problem at all - see listing 3 for an example.
CODE LISTING 1
using System.Collections.Generic;
namespace CarPriceComparison.Models
{
public class VehicleRepository : IVehicleRepository
{
private VehicleContext _vehicleContext;
public VehicleRepository(VehicleContext dbContext_)
{
_vehicleContext = dbContext_;
}
public IEnumerable<VehicleMake> GetAllMakes()
{
return _vehicleContext.VehicleMakes.ToList();
}
}
}
CODE LISTING 2
namespace CarPriceComparison.Models
{
public class VehicleContext : DbContext
{
private IConfigurationRoot _config;
public VehicleContext(IConfigurationRoot config_, DbContextOptions
options_) : base(options_)
{
_config = config_;
}
public DbSet<VehicleMake> VehicleMakes {get; set;}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["ConnectionStrings:VehicleContextConnection"]);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<VehicleModel>()
.HasOne(p => p.Make)
.WithMany(b => b.Models)
.HasForeignKey(p => p.VehicleMakeForeignKey)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
}
}
CODE LISTING 3
private VehicleContext _vehicleContext;
private IMailService _mailService;
private IConfigurationRoot _config;
public HomeController(IMailService mailService_, IConfigurationRoot
config_, VehicleContext vehicleContext_)
{
_vehicleContext = vehicleContext_;
_mailService = mailService_;
_config = config_;
}
public IActionResult Index()
{
var vehicleData = _vehicleContext.VehicleMakes.ToList();
return View();
}
I think you missing an using statement.
using System.Linq;
Using ASP.NET Core MVC and Entity Framework 6, I want to seed my code-first database with data from a CSV file that I have placed in wwwroot\data
I am trying to access the WebRootPath value in the class that performs the seed but cannot get it to work. I understand the solution is based on Dependency Injection though being very new to ASP.NET Core and Dependency Injection I haven't got this to work.
Startup.cs - Standard code, DbContext setup found via another SO question.
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext
services.AddScoped(p =>
{
var connectionString = Configuration["Data:ProjectDbContext:ConnectionString"];
return new ProjectDbContext(connectionString);
});
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
ProjectDbContext.cs
[DbConfigurationType(typeof(DbConfig))]
public class ProjectDbContext : DbContext
{
static ProjectDbContext ()
{
Database.SetInitializer(new ProjectInitializer());
}
public ProjectDbContext (string connectionName) : base(connectionName)
{
}
public DbSet<SomeEntity> SomeEntity{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
}
ProjectInitializer.cs
public class ProjectInitializer : DropCreateDatabaseAlways<ProjectDbContext>
{
private readonly IHostingEnvironment _appEnvironment;
public ProjectInitializer(IHostingEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
}
public override void InitializeDatabase(ProjectDbContext context)
{
base.InitializeDatabase(context);
}
protected override void Seed(ProjectDbContext db)
{
string dataPath = Path.Combine(_appEnvironment.WebRootPath, "data");
string contracts = Path.Combine(dataPath, "Data.csv");
// Parse file, create objects
db.SaveChanges();
}
}
I have changed my classes as follows, it works though I'm not sure it is entirely correct:
Startup.cs
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
HostingEnvironment = env;
}
public IHostingEnvironment HostingEnvironment { get; }
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext
services.AddScoped(p =>
{
var connectionString = Configuration["Data:ProjectDbContext:ConnectionString"];
return new ProjectDbContext(connectionString, HostingEnvironment);
});
// Add framework services.
services.AddMvc();
}
ProjectDbContext.cs
[DbConfigurationType(typeof(DbConfig))]
public class ProjectDbContext : DbContext
{
public ProjectDbContext(string connectionName, IHostingEnvironment appEnvironment) : base(connectionName)
{
Database.SetInitializer(new ProjectInitializer(appEnvironment));
}
public DbSet<SomeEntity> SomeEntity { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
}
ProjectInitializer.cs
private readonly IHostingEnvironment _appEnvironment;
public ProjectInitializer(IHostingEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
}
public override void InitializeDatabase(ProjectDbContext context)
{
base.InitializeDatabase(context);
}
protected override void Seed(ProjectDbContext db)
{
string dataPath = Path.Combine(_appEnvironment.WebRootPath, "data");
string contracts = Path.Combine(dataPath, "Data.csv");
// Parse file, create objects
db.SaveChanges();
}
I have the following code:
public DataInitializer() {
Database.SetInitializer<DataContext>(null);
try {
using (var context = new DataContext()) {
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
}
catch (Exception ex) {
throw new InvalidOperationException("The Data database could not be initialized", ex);
}
}
and
public class DataContext : DbContext
{
public DataContext() : base("xxdata") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<Application> Applications { get; set; }
}
The code gets executed when I step through it but it seems like no database file is created (SQL Server Express).
Is there something obvious that I am doing wrong?
Create an inherited class as below;
public class DataContextInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
//Seed data if you want
}
}
and add this to your Global.asax
protected void Application_Start()
{
//Add your database initializer code
Database.SetInitializer(new DataContextInitializer());
}