Change entity map to another "unknown" table at runtime - c#

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 ;)

Related

Adding database generated IDs to seed data in Entity Framework Core

I have an Entity Framework Core code-first database and a .Net 5 application. I have seeded some initial values into a table using the .HasData method.
Here is the gist of my RailcarEvent entity class:
public class RailcarEvent
{
public Guid RailcarEventId { get; set; }
public string Identifier { get; set; }
}
I created a helper class that contains the IDs for some of these RailcarEvents. The main advantage here is that since these Guids are declared in a static class, I can use RailcarEventIdentifiers kind of like an enum.
public static class RailcarEventIdentifiers
{
public static readonly Guid ArriveDest = new Guid("49DB8ECD-1D26-46A1-BD39-AF358EDEB17D");
public static readonly Guid ArriveInTransit = new Guid("8F3D8BE4-E745-406D-B361-1A81B9A5C410");
public static readonly List<RailcarEvent> All = new()
{
new RailcarEvent
{
RailcarEventId = ArriveDest,
Identifier = "Arrive Dest"
},
new RailcarEvent
{
RailcarEventId = ArriveInTransit,
Identifier = "Arrive In-Transit"
}
};
}
For example, this allows me to compare property myRailcar.RailcarEventId to RailcarEventIdentifiers.ArriveDest in my code.
I have seeded the RailcarEvent table in my DbContext like so:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<RailcarEvent>().HasData(RailcarEventIdentifiers.All);;
}
My application is also able to dynamically add entries to the RailcarEvent entity if a new event is detected. For this entity, it happens infrequently enough that I am okay with checking the table once in a while to see what the new events are, then manually add them to the code with the Guid from the table.
I want the ability to reference these new application-created RailcarEvents in the same way as the others in my code, but when I add the application-created RailcarEvents to my RailcarEventIdentifiers.All list and attempt a migration, I get conflicting key errors saying that the data already exists in the database (and it does). I don't care that it already exists and would just like to ignore this error and continue.
Is there a way to create a migration that will not cause these conflicting key errors?
Is there a better way to track these static IDs? I'd be open to hearing that as well.

Create a table if it does not exist?

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>());

An Entity with identical table data

Before I elaborate the problem, I'm well aware the database isn't designed conventionally. Sadly, I can't change this particular database due to how it is integrated, so I've got a potential solution but that won't be implemented for several months. In the mean time I need to work around the following:
The problem is I need to build an Entity, this would represent our Accounts. But the problem, our database implements the following structure:
Invoiced Table
Non-Invoiced Table
My Entity, represents the exact same data on those tables, same column names, duplicate under all conditions, except one is invoiced while the other represents non-invoiced customers. But since it isn't one table, with a Flag to indicate invoiced versus non-invoiced, how can my Entity link to both of those tables?
Since both tables represent separate names, I can't use the [Table("...")] or the auto mapping capabilities. I hate asking such a question, but I can't find any documentation on how to handle such an issue.
You could use table-per-concrete class inheritance then define the table names on the derived types:
public abstract class Account
{
// common entity code here
...
}
public class InvoicedAccount : Account {}
public class NonInvoicedAccount: Account {}
public YourContext : DbContext
{
public DbSet<InvoicedAccount> InvoicedAccounts { get; set; }
public DbSet<NonInvoicedAccount> NonInvoicedAccounts { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<InvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "InvoicedAccountTable" );
} );
modelBuilder.Entity<NonInvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "NonInvoicedAccountTable" );
} );
}
}

Emit DbContext.OnModelCreating on every context creation

I use entity framework code first to work with my database.
I have several tables with different names but same structure, and this tables dynamically appears in database. How could I map EntityFramework to one of that tables at run-time and use data from just like I work this over entities of DbContext?
What I've done to make it work:
For example, my class what describes structure of dynamically created table is SetElement.
Here is my context:
public class DataContext : DbContext
{
public DataContext()
: base("RepositoryConnectionString") { }
string setElementsTableId; // the name of table that need to be dynamicly mapped to
// Enforce model recreating
public DataContext(string setElementsTableId)
: this()
{
this.setElementsTableId = setElementsTableId;
}
/* some other entities */
public DbSet<Entities.SetElement> SetElements { get; set; } // dynamicly mapped entity
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* come configurations */
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId)); // map SetElements property to dynamicly created table
}
}
}
How I use this:
public static void AddSetElements(ICollection<SetElement> setElements, string tableId)
{
using (ctx = new DataContext(tableId)) // configere DataContext to map tableId table for entity SetElements
try
{
var num = ctx.SetElements.Count();
ctx.SetElements.AddRange(setElements);
ctx.SaveChanges();
}
catch (Exception e)
{
}
}
I have also some methods to get, udtate and remove data from dynamicly created tables that are same to AddSetElements.
All works just as I wish but only if AddSetElements runs first, because at the first datacontext creating DbContext.OnModelCreating runs and configure all mappings. But next instance creation doesn't call DbContext.OnModelCreating.
So, my question is: how to call DbContext.OnModelCreating everytime of creating an instance of DataContext then I use DataContext(string setElementsTableId) to create it?
I know, my question is similar to 'dynamic table mapping in EF' but I found nothing in the results.
By the way. If you know another way to solve my problem, you are welcome.
There is a built-in feature which may address your issue : `IDbModelCacheKey ; the implementation of which is to be registered in your configuration.
The point is to generate a different key for your different contexts.
I would go for something like :
First, the configuration
public class EntityFrameworkConfiguration: DbConfiguration
{
public EntityFrameworkConfiguration()
{
this.SetModelCacheKey(ctx => new EntityModelCacheKey((ctx.GetType().FullName + ctx.Database.Connection.ConnectionString).GetHashCode()));
}
}
Then the implementation of the IDbModelCacheKey
public class EntityModelCacheKey : IDbModelCacheKey
{
private readonly int _hashCode;
public EntityModelCacheKey(int hashCode)
{
_hashCode = hashCode;
}
public override bool Equals(object other)
{
if (other == null) return false;
return other.GetHashCode() == _hashCode;
}
public override int GetHashCode()
{
return _hashCode;
}
}
Finally, your DataContext
public class DataContext : DbContext
{
string setElementsTableId;
// use the setElementsTableId as extended property of the
// connection string to generate a custom key
public DataContext(string setElementsTableId)
: base(ConfigurationManager.ConnectionStrings["RepositoryConnectionString"]
+ "; Extended Properties=\"setElementsTableId=" + setElementsTableId + "\"")
{
this.setElementsTableId = setElementsTableId;
}
public DbSet<Entities.SetElement> SetElements { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId));
}
}
}
I hope this will be of some help
Look like nobody knows answer...
Otherwise, one man told me that my question is meaningless because of storage data in several tables will not give any achievement. More better to add indexes to database, partitioning table or something else. In other words this is Database Management System problem. But if some one knows answer I'll be very pleasured to hear something about EF hack.

EF5 Code First: Detect if it creates a database so I can run ALTER statements

I am new with Entity Framework 5. Our team is using Code First workflow.
Before I'll start with my main question, let me first show you what I have tried (the ultimate comment of all time :D).
public class MyDBContext : CDBContext
{
public MyDBContext() : base(connString) { }
public MyDBContext(string connStr) : base(connStr) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// removes some conventions
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
// ........
// model configurations which contains mappings
modelBuilder.Configurations.Add(new AccountConfiguration());
// ........
// calls base OnModelCreating
base.OnModelCreating(modelBuilder);
}
// list of all Entity
public DbSet<Account> Account { get; set; }
}
MyDBContext is the class I have created that inherits from CBDContext that contains override methods and which also inherits from DBContext. One of the problems I have encountered is that entity framework doesn't handle field uniqueness. I have already read the article on Configuring/Mapping Properties and Types with the Fluent API on their site and I can't find any configuration to set a property into unique.
So what I did in order to set the field unique is to manually run several ALTER sql statements during creation,
using (MyDBContext _context = new MyDBContext(connString))
{
if (_context.Database.CreateIfNotExists())
{
_context.Database.ExecuteSqlCommand("ALTER TABLE Account ADD CONSTRAINT UQ_Account_AccountNumber UNIQUE(AccountNumber)");
_context.Database.ExecuteSqlCommand("ALTER TABLE Account ADD CONSTRAINT UQ_Account_GUID UNIQUE(GUID)");
// .... more on this in the following lines ...
}
}
My Questions:
Am I right that entity framework don't have any configuration or data annotations to set the field unique?
Is there a way to detect or know during runtime if EF creates a database or not so I can move or hide this statement if (_context.Database.CreateIfNotExists()) somewhere to an available method that can be overriden?
What I really want is to remove if (_context.Database.CreateIfNotExists()) from the using statemnt and put it somewhere else or inside MyDBContext so my code will look like this,
using (MyDBContext _context = new MyDBContext(connString))
{
Account _acc = new Account()
// ...Account properties ...
_context.Account.Add(_acc);
_context.SaveChanges();
}
Thanks.
You should take a look at Code First Migrations, more specific at the Data Motion / Custom SQL and later sections - this is might the way to achieve your desired result. Your migration class can look like this:
public partial class AddUniqueConstrains : DbMigration
{
public override void Up()
{
Sql("ALTER TABLE Account ADD CONSTRAINT UQ_Account_AccountNumber UNIQUE(AccountNumber)");
Sql("ALTER TABLE Account ADD CONSTRAINT UQ_Account_GUID UNIQUE(GUID)");
}
public override void Down()
{
Sql("ALTER TABLE Account DROP CONSTRAINT UQ_Account_AccountNumber UNIQUE");
Sql("ALTER TABLE Account DROP CONSTRAINT UQ_Account_GUID");
}
}
You can also explore other options described in answers to this question: Unique Constraint in Entity Framework Code First
If you don't use (or cannot use) EF migrations you can use custom initializer as mentioned in this answer. The custom initializer will execute a Seed method after creating the database = only once when database doesn't exist. If you need to incrementally develop the database initializer itself will not help you (that is what migrations are for).

Categories