Create a table if it does not exist? - c#

Using Entity Framework Core, is there a way to create the table if it does not yet exist? Exception will throw even if EnsureCreated is called in the context:
DbSet<Ticker> Ticker { get; set }
Database.EnsureCreated();
Ticker.Add(...);
dbctx.SaveChanges(); <== exception
Results in exception:
System.Data.SqlClient.SqlException: Invalid object name 'Ticker'
Is there a way to create the table Ticker before data is inserted?
== EDIT==
This questions is not to create/migrate the entire database, the database always exist and most of its tables also exists, but some of the tables may not. So I just need create one or two tables in runtime.

In Entity framework Core (on version 2.2.4) you can use the following code in your DbContext to create tables in your database if they don't exist:
try
{
var databaseCreator = (Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator);
databaseCreator.CreateTables();
}
catch (System.Data.SqlClient.SqlException)
{
//A SqlException will be thrown if tables already exist. So simply ignore it.
}
Database.EnsureCreated() doesn't create the schema (so your tables) when the database already exists. That's the reason why you get that exception.
You can check that method's documentation.
PS: Make sure you catch the right exception if it changes in the new versions of Entity framework Core.

My guess is that your context is wrongly defined. Maybe you forgot to add the DbSet to your context implementation?
Below code is working perfectly, and I honestly prefer to EnsureCreated() in the constructor of the actual DBContext implementation.
internal class AZSQLDbContext : DbContext
{
public AZSQLDbContext() {
this.Database.EnsureCreated();
}
internal DbSet<TaskExecutionInformation> TaskExecutionInformation { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var dbUser = "your-user";
var dbPW = "your-pw";
optionsBuilder.UseSqlServer(
$"Server=tcp:sample-sql.database.windows.net,1433;Initial Catalog=sample-db;Persist Security Info=False;User ID={dbUser};Password={dbPW};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
}
}
TaskExecutionInformation is just a PoCo and could be anything. See below though if you need a bit of guidance.
public class TaskExecutionInformation
{
public Guid Id { get; set; }
public string Status { get; set; }
public int Duration { get; set; }
}

In my case there was 2 applications using same database and those could create its own code-first tables, if they were missing.
So my solution for that is following extension method used in startup on dbcontext:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Extensions
{
internal static class DbContextExtensions
{
internal static void EnsureCreatingMissingTables<TDbContext>(this TDbContext dbContext) where TDbContext : DbContext
{
var type = typeof(TDbContext);
var dbSetType = typeof(DbSet<>);
var dbPropertyNames = type.GetProperties().Where(p => p.PropertyType.Name == dbSetType.Name)
.Select(p => p.Name).ToArray();
foreach (var entityName in dbPropertyNames)
{
CheckTableExistsAndCreateIfMissing(dbContext, entityName);
}
}
private static void CheckTableExistsAndCreateIfMissing(DbContext dbContext, string entityName)
{
var defaultSchema = dbContext.Model.GetDefaultSchema();
var tableName = string.IsNullOrWhiteSpace(defaultSchema) ? $"[{entityName}]" : $"[{defaultSchema}].[{entityName}]";
try
{
_ = dbContext.Database.ExecuteSqlRaw($"SELECT TOP(1) * FROM {tableName}"); //Throws on missing table
}
catch (Exception)
{
var scriptStart = $"CREATE TABLE {tableName}";
const string scriptEnd = "GO";
var script = dbContext.Database.GenerateCreateScript();
var tableScript = script.Split(scriptStart).Last().Split(scriptEnd);
var first = $"{scriptStart} {tableScript.First()}";
dbContext.Database.ExecuteSqlRaw(first);
Log.Information($"Database table: '{tableName}' was created.");
}
}
}
}

You have a few options here. The simplest is to do:
MyContext.Database.CreateIfNotExists();
Or, do it initialization style, by putting this in your context's constructor:
Database.SetInitializer<MyContext>(new CreateDatabaseIfNotExists<MyContext>());
Both of these however require you to drop your schema manually every time you have modified your model and need to re-create the database. If you don't want to do that, you can use the following initialization instead:
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
This will check your model against the database every time you run your program, and automatically drop and re-create the database if the model has been modified.
EDIT:
If you don't want to drop the database, and simply update it, then you can use the following initialization:
Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext, Config>());

Related

Entity Framework Core get data from stored procedure and then convert into view model without DbSet

I have a function in the repository, GetForms, the purpose of the function is to call a stored procedure and return rows with data. Everything is working fine until now.
Function
public IEnumerable<FormBO> GetForms()
{
var id = "1"
var Query= _context.FormBO.FromSqlRaw("dbo.SP_Core #pin_ID={0}", id)
.AsNoTracking().ToList(); //3K line of sp
return Query;
}
Model
public class FormBO
{
[Key]
public int? ID { get; set; }
public int? secondid { get; set; }
......
}
DbContext
Added this code, so context thinks it is a table in the database and, I don't have to do more stuff
public virtual DbSet<FormBO> FormBO { get; set; }
The problem
Whenever we scaffold the database and the db context, it regenerates all the files and code, so it removes the
public virtual DbSet<FormBO> FormBO { get; set; }
And we have to add this line manually is there any way I can change the logic, so I don't have to add this code (DBset<FormBO>) to DbContext every time a dba updates the database...
What I found
I found that if I change the model to ".Database" and FromSqlRaw to ExecuteSqlRaw, but it is just returning the count as int not a list of rows.
public IEnumerable<FormBO> GetForms()
{
var id = "1"
var Query = _context.Database.ExecuteSqlRaw("dbo.SP_Core #pin_ID={0}", id)
.AsNoTracking().ToList(); //3K line of sp
return Query;
}
If it is possible it automatically add the DBSet to context whenever we update the code which I don't think we will able to do.
or
Get the query result without the dbset model and then I will use foreach loop to add it in FormBO model it is just 10 rows
Since the table doesn't actually exist in the database, the built in scaffolding process won't attempt to create it.
However you could probably replace the IScaffoldingModelFactory service, with an implementation that extends RelationalScaffoldingModelFactory, and use the code-first fluent api to define meta data for tables that don't really exist.
You could probably use this type of approach to define types for all table values in the database. Since EF Core 5 is adding support for table values, maybe they'll do it for you, but I haven't tested that.
public class MyModelFactory : RelationalScaffoldingModelFactory
{
public MyModelFactory(
IOperationReporter reporter,
ICandidateNamingService candidateNamingService,
IPluralizer pluralizer,
ICSharpUtilities cSharpUtilities,
IScaffoldingTypeMapper scaffoldingTypeMapper,
LoggingDefinitions loggingDefinitions)
: base(reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, loggingDefinitions)
{
}
protected override ModelBuilder VisitDatabaseModel(ModelBuilder modelBuilder, DatabaseModel databaseModel)
{
modelBuilder.Entity<FormBO>(entity =>
{
// ...
});
return base.VisitDatabaseModel(modelBuilder, databaseModel);
}
}
services.AddDbContextPool<ContextType>(o =>
{
o.ReplaceService<IScaffoldingModelFactory, MyModelFactory>();
// ...
});
Of course there's an easy answer too. The scaffolded context is a partial class. Just define your other DbSet in another source file.

Why doesn't Entity Framework recognise an existing record in the database?

I have created a database system for a library, and when a user registers the program checks if they already have an account, but this is always returning false even when I can see in my DBMS that the record exists.
public static bool UserExists(string email)
{
using (var context = new LibraryDbContext())
{
if (context.Users.Any(d => d.Email == email))
{
return true;
}
else
{
return false;
}
}
}
Also, this is my Dbcontext class
public class LibraryDbContext : DbContext
{
public LibraryDbContext()
: base()
{
}
public DbSet<Book> Books { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<ReturnsLog> Returns { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("LibraryProjectDbo");
}
}
Typically issues like this when connecting to an existing database for the first time are due to EF being pointed at a database you don't expect. (I.e. creating one via Code First rather than connecting to your existing DB, or creating tables you don't expect.)
When using database first, you should always disable the Code First initialization:
public LibraryDbContext()
{
Database.SetInitializer<LibraryDbContext>(null);
}
This will generate an exception if the DbContext goes and tries to create a schema which will help direct you to the correction.
By default EF will be looking for a connection string called "LibraryDbContext" in your web.confing/.exe.config file in the runtime location. When using connection strings I generally like to be explicit with the connection string name to reflect a Database rather than the DbContext. Often I will utilize multiple contexts to split up behaviour in a system that all point to the same database. So for example I'd call the main database connection string something like "AppConnection" and pass that through the base constructor.
public LibraryDbContext()
: base ("AppConnection")
{
Database.SetInitializer<LibraryDbContext>(null);
}
Alternatively to test your connection out you can pass a Connection String itself to the base constructor.

Is there any way to directly query the in memory database used by Entity Framework Core?

I'm using the in-memory database provider to do some tests of a .Net Core 2.2 application. I need to make one of the tables in the application inaccessible - either by renaming it or dropping it. Is there any way to run that kind of query against the in memory DB directly? I've tried to get the connection using:
context.Database.GetDbConnection();
but that throws an error
Relational-specific methods can only be used when the context is using a relational database provider.
I am aware I can destroy the whole database with:
context.Database.EnsureDeleted();
but I need to retain it and only destroy or rename the one table.
The InMemory provider isn't backed by a relational database, and has a different set of operations that won't support your case.
You can instead use the SQLite in-memory mode (doc), then create an interceptor that intercepts the issued EF commands, and either suppress the creation of the table, or suppress other queries targeted at that table.
public class MyInterceptor : DbCommandInterceptor {
public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result) {
if (ContainsBannedTable(command)) {
//suppress the operation
result = InterceptionResult<int>.SuppressWithResult(0);
}
return base.NonQueryExecuting(command, eventData, result);
}
private bool ContainsBannedTable(DbCommand command) {
//or custom logic
return command.CommandText.Contains("ToDeleteEntity");
}
}
The following will throw an exception (Microsoft.EntityFrameworkCore.DbUpdateException... SqliteException: SQLite Error 1: 'no such table: ToDeleteEntity') when trying to access the undesired table.
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<MyContext>()
.UseSqlite(connection)
.AddInterceptors(new MyInterceptor())
.Options
;
var context = new MyContext(options);
context.Database.EnsureCreated();
context.AllowedEntity.Add(new AllowedEntity { Id = 1 });
context.SaveChanges();
Console.WriteLine(context.AllowedEntity.FirstOrDefault()?.Id); //1 - ok
context.ToDeleteEntity.Add(new ToDeleteEntity { Id = 1 });
//will throw an exception
context.SaveChanges();
Console.WriteLine(context.ToDeleteEntity.FirstOrDefault()?.Id);
//close the connection and clean up
//...
public class MyContext : DbContext {
public MyContext(DbContextOptions options) : base(options) {
}
public DbSet<AllowedEntity> AllowedEntity { get; set; }
public DbSet<ToDeleteEntity> ToDeleteEntity { get; set; }
}
public class ToDeleteEntity {
public int Id { get; set; }
}
public class AllowedEntity {
public int Id { get; set; }
}

EF6 entity with DatabaseGeneratedOption.Identity Guid Id force insert my Id value

I am trying to use EF to export/import the existing database of a DbContext. In this context, there are several entities with Guid Id properties with DatabaseGeneratedOption.Identity defined by the ModelBuilder. When I re-import the entities, I want to use the Id value from the serialized object, but it always generates a new Id value when I save the changes. Is there any way to force EF to use my Id value in this case? I know DatabaseGeneratedOption.None will allow me to do it, but then I will always be responsible for generating the Id. I know there segmentation issues of the index that occur without using sequential Guids, so I do not want to do this.
Am I out of luck or has anyone found a trick?
Update: we have decided to simply change all Guid Id from DatabaseGeneratedOption.Identity to DatabaseGenerationOption.None and provide the Id ourselves. Although this leads to index fragmentation, we do not expect this to be a problem with the smaller size of our tables.
You can achieve what you want by defining two contexts that derive from a base context. One context defines its keys with DatabaseGeneratedOption.Identity, the other one with DatabaseGeneratedOption.None. The first one will be your regular application's context.
This is possible by virtue of Guid primary keys not being real identity columns. They're just columns with a default constraint, so they can be inserted without a value, or with a value without having to set identity_insert on.
To demonstrate that this works I used a very simple class:
public class Planet
{
public Guid ID { get; set; }
public string Name { get; set; }
}
The base context:
public abstract class BaseContext : DbContext
{
private readonly DatabaseGeneratedOption _databaseGeneratedOption;
protected BaseContext(string conString, DatabaseGeneratedOption databaseGeneratedOption)
: base(conString)
{
this._databaseGeneratedOption = databaseGeneratedOption;
}
public DbSet<Planet> Planets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Planet>().HasKey(p => p.ID);
modelBuilder.Entity<Planet>().Property(p => p.ID)
.HasDatabaseGeneratedOption(this._databaseGeneratedOption);
base.OnModelCreating(modelBuilder);
}
}
The context subclasses:
public class GenerateKeyContext : BaseContext
{
public GenerateKeyContext(string conString)
: base(conString, DatabaseGeneratedOption.Identity)
{ }
}
public class InsertKeyContext : BaseContext
{
public InsertKeyContext(string conString)
: base(conString, DatabaseGeneratedOption.None)
{ }
}
I first run some code to create and seed the source database:
var db1 = #"Server=(localDB)\MSSQLLocalDB;Integrated Security=true;Database=GuidGen";
var db2 = #"Server=(localDB)\MSSQLLocalDB;Integrated Security=true;Database=GuidInsert";
// Set initializers:
// 1. just for testing.
Database.SetInitializer(new DropCreateDatabaseAlways<GenerateKeyContext>());
// 2. prevent model check.
Database.SetInitializer<InsertKeyContext>(null);
using (var context = new GenerateKeyContext(db1))
{
var earth = new Planet { Name = "Earth", };
var mars = new Planet { Name = "Mars", };
context.Planets.Add(earth);
context.Planets.Add(mars);
context.SaveChanges();
}
And a target database:
using (var context = new GenerateKeyContext(db2))
{
context.Database.Initialize(true);
}
Finally this is the code that does the actual job:
var planets = new List<UserQuery.Planet>();
using (var context = new GenerateKeyContext(db1))
{
planets = context.Planets.AsNoTracking().ToList();
}
using (var context = new InsertKeyContext(db2))
{
context.Planets.AddRange(planets);
context.SaveChanges();
}
Now in both databases you'll see two records with identical key values.
You might wonder: why can't I use one context class, and construct it either with or without the Identity option? That's because EF builds the EDM model only once for a context type and stores it in the AppDomain. So the option you use first would determine which model EF will use for your context class.

Change entity map to another "unknown" table at runtime

I have to write a C# application that works with a SQL server database created and mantained by an old application. The application creates new tables each year and the "year property" is in the table name. The number of tables it creates may vary depending of the number of "sections" that the user has created inside the application. So, I have to work with tables like Cwx_DRyz (quite self explanatory...), where "wx" can be the section, and "yz" would be the year. An example of group of table could be:
C01_DR07
C01_DR08
C01_DR09
C02_DR08
C02_DR09
C03_DR06
C04_DR12
And all of those tables could represent, for example, clients. They would be clients from different sections and years, but clients with the same structure.
My question is: Can I have a Client entity to handle all those tables and change the mapping from one to another at runtime? The title says "unknown" because I don't know the tables before runtime.
The most similar question I have found is Entity Framework map multiple tables to one entity and the answer is to use the "Table Per Concrete Type Inheritance", but it is not useful for my case.
PS: EF version 4.3.1 and VS2010
EDIT: The tables don't have primary keys... Most of them have columns that are supossed to have unique values (integer or string).
If you use "code first" you could create a mapping as you want. This also works with existing databases when the mapping you have created match the database.
So whenever you create a context you can build the string (tablename) you want to map to.
Some codesamples for "code first" and how you could start:
The DbContext:
public DbSet<YourEntity> YourEntities { get; set; }
...
// this is called when the db gets created and does the configuration for you => maybe not needed in your case
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
ConfigurationRegistrar configurationRegistrar = modelBuilder.Configurations;
new GeneralEntitiesConfiguration(configurationRegistrar);
}
GeneralEntitiesConfiguration is a class im using to handle the configurations, nothing more than a helper which looks like:
public class GeneralEntitiesConfiguration
{
public GeneralEntitiesConfiguration(ConfigurationRegistrar configurationRegistrar)
{
configurationRegistrar.Add(new YourEntityConfiguration());
//and additional configurations for each entity, just to splitt it a bit and have it more read and maintenance able
}
}
YourEntityConfiguration is a class where i have all the configurations for this entity:
public class YourEntityConfiguration : EntityTypeConfiguration<YourEntity>
{
public YourEntityConfiguration ()
{
ToTable("WhatEverYouLike"); // here you can do any magic to map this entity to a table, just make sure that your properties are mapped to the correct colums
Property(entity => entity.Id).HasColumnName("YouColumnName");
//and here you also have to do the other configurations
}
}
At the application startup (or before you initialize your context the first time) you have to initialize the database. Therefore you can use an initializer which checks the database and handles differences. Build in there are things like "DropCreateDatabaseAlways" or "DropCreateDatabaseIfModelChanges" => you would need to create your own which just ignores any differences. In my sample i have create one which just throws an exception when the model differs (i wanted to handle model changes with scipts for the first try):
//before using the context the first time i'm calling, you can ignore the connection string
DbContextInitializer.Init(conString);
public static class DbContextInitializer
{
public static void Init (string connectionString)
{
Database.SetInitializer(new CreateDbThrowExceptionIfModelDiffersInitializer<SMDbContext>());
using(var dbContenxt = new MyDbContext(connectionString))
{
try
{
dbContenxt.Database.Initialize(true);
}
catch(DatabaseModelDiffersException diffException)
{
// some magic...
}
catch(Exception ex)
{
// TODO: log
throw;
}
}
}
public class CreateDbThrowExceptionIfModelDiffersInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
public void InitializeDatabase(TContext context)
{
using (new TransactionScope(TransactionScopeOption.Suppress))
{
if (!context.Database.Exists())
context.Database.Create();
}
if (!context.Database.CompatibleWithModel(true))
{
throw new DatabaseModelDiffersException("Database Model differs!");
}
}
protected virtual void Seed(TContext context)
{
// create data if you like
}
}
// just an exception i'm using for later useage
public class DatabaseModelDiffersException : Exception
{
public DatabaseModelDiffersException(string msg) : base(msg)
{}
}
}
Hope you have got an idea of you can handle dynamic table names with entity framework!
If there are more questions just ask ;)

Categories