OnModelCreating only called once even for new contexts - c#

I have multiple SQL server tables that are the same, but differ in content. In writing a code first EF6 program I am trying to reuse the same db context for each and pass in the table name to the context constructor.
However, while the constructor is being called every time, the OnModelCreating method is only being called once despite the db context being created from new every time. How do I reset this?
I have tried using AsNoTracking and I read along the lines of disabling ModelCaching but couldn't find out how to do this or whether this was the best approach. MSDN even says 'this caching can be disabled by setting the ModelCaching property on the given ModelBuidler[sic],' but it's not there.
This is my DB Context:
public partial class MissingContext : DbContext
{
private string tableName = "";
public MissingContext(string tableName) : base("name=MissingContext")
{
this.tableName = tableName;
}
public virtual DbSet<MissingData> MissingDataSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MissingData>()
.ToTable(this.tableName);
}
}
This is my code to use it:
List<MissingData> missingData=null;
string[] inputTables="TABLEA;TABLEB;TABLEC".Split(';');
foreach (string table in inputTables)
{
logger.Info($"Processing {table}");
missingData = null;
MissingContext db = new MissingContext(table);
var query = from d in db.MissingDataSet.AsNoTracking()
select d;
missingData = query.ToList();
}
In running, table always has the correct TABLEA, TABLEB, TABLEC and it is passed in to the db context constructor, however the OnModelCreating is only called once for the very first loop item so the SQL generated by the query object always selects from TABLEA:
SELECT
[Extent1].[id] AS [id],
[Extent1].[OrganisationName] AS [OrganisationName]
FROM [dbo].[**TABLEA**] AS [Extent1]
*apologies if any code looks wrong, I rename some variables as they are business sensitive.

OnModelCreating will be called only once that's default behaviour.
According to OnModelCreating documentation.
Typically, this method is called only once when the first instance of a derived context is created. The model for that context is then cached and is for all further instances of the context in the app domain. This caching can be disabled by setting the ModelCaching property on the given ModelBuidler, but note that this can seriously degrade performance. More control over caching is provided through use of the DbModelBuilder and DbContextFactory classes directly.

I think the issue here is that per-table is not how contexts are designed to be created. The design of Entity Framework is that (in the most general case), each table will have one class expressing any row in that table. For a different table, it is expected that another class will be defined. Then, you have your derived DbContext.
One DbContext may service arbitrarily many tables, by creating the appropriate DbSet for each of your table classes. If you desire them to work in different contexts for some reason, the design of EF would expect a different class derived from DbContext with it's own DbSets.
A caveat: If you're connecting to merely different databases with the same tables, meaning the columns exactly correspond etc., you can use the same DbContext, supplying the constructor with differing connection strings as appropriate.

Related

Entity Framework map model class to table at run time

In a project using ASP.NET Core 2.0 and Entity Framework, I'm trying to map a known table schema (coded into class MyTableClass) to an unknown table name. This table name is given by the user at run time, so this is done outside of the OnModelCreating method of the Context class. Is there a way to do something like the following pseudocode:
void OnUserEnteredTableNameFromUI(string tableName)
{
var modelBuilder = new ModelBuilder(???); // how?
modelBuilder.Entity<MyTableClass>().ToTable(tableName);
// how to get a ref to DbSet<MyTableClass> myTable from here?
}
Since this is an interesting issue which might help other people that need some dynamic model building, here is how it can be implemented.
Let say we have a custom context with custom table name provided via constructor (as Gert Arnold suggested in the other answer):
public class CustomDbContext : DbContext
{
// …
private string customTableName;
public string CustomTableName => customTableName ?? "DefaultCustomTableName";
}
and we use it inside the OnModelCreating (it should be there, currently there is no other simple way to create model using the predefined convention sets):
modelBuilder.Entity<CustomEntity>().ToTable(CustomTableName);
The only problem is that by default the OnModelCreating is called just once per context type and is cached. Luckily EF Core is built on top of a (replaceable) services architecture. The service interface responsible for model caching is IModelCacheKeyFactory:
Creates keys that uniquely identifies the model for a given context. This is used to store and lookup a cached model for a given context.
It has a single method
object Create(DbContext context)
The returned object GetHashCode / Equals methods are used to identify the passed context instance. The default EF Core service implementation returns an object which compares the type of the context.
In order to make the custom context model working, we need to replace it with a custom service which also compares the custom state (CustomTableName in our case). The implementation could be like this (using C#7.0 value tuples):
class CustomModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context) => new CustomModelCacheKey(context);
}
class CustomModelCacheKey
{
(Type ContextType, string CustomTableName) key;
public CustomModelCacheKey(DbContext context)
{
key.ContextType = context.GetType();
key.CustomTableName = (context as CustomDbContext)?.CustomTableName;
}
public override int GetHashCode() => key.GetHashCode();
public override bool Equals(object obj) => obj is CustomModelCacheKey other && key.Equals(other.key);
}
The only thing remaining is to replace the existing service with the custom. It can be done inside OnConfiguring override:
optionsBuilder.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory>();
And that's all. Anytime you create context with different CustomTableName, EF Core will create a new model and map the CustomEntity to that table.
The same technique can be applied to any context containing custom model affecting state by including all custom state in CustomModelCacheKey.key tuple. Of course it could be implemented w/o value tuples, just with them the GetHashCode and Equals overrides are easier to implement. Actually instead of CustomModelCacheKey the custom service can return directly value tuple containing the context type and custom state member values.
I've seen situations where databases with identical structure but varying table names had been deployed to several sites. In that case, EF only needs the know the table name(s) at application startup.
This can be done by adding a constructor parameter to the context:
private readonly string _userDefinedTableName;
public MyContext(string userDefinedTableName)
{
_userDefinedTableName = userDefinedTableName;
}
Then, in OnModelCreating:
modelBuilder.Entity<MyTableClass>().ToTable(_userDefinedTableName);
However, in your case the name has to change any number of times at runtime. With Entity Framework, that's impossible (well, more exactly, too impractical to really contemplate it). EF compiles and stores model once per context class, because it would be too expensive to do all that for each context instantiation.
That means that OnModelCreating runs not more than once in an application and the first table name remains.
You'll have to find other ways to address table data dynamically, or change the design so the multiple tables can be converted into one fixed table.

Database Class Instance, Fields, Entity Framework

Im new to C# and Entity Framework and I have a question about fields and initialization of a database class.
I have received some code in a program from my teacher that has a connection to a MySQL database through Entity Framework.
So far we have seen examples where inside methods for adding stuff to the database you first create an instance of it. An example of what we have seen so far:
using (var db = new p4_databaseEntities())
{
cp_car carEntity = new cp_car();
carEntity.make = car.make;
carEntity.model = car.model;
carEntity.year = car.year;
db.cp_car.Add(carEntity); //
db.SaveChanges();
MessageBox.Show("A Car has been added");
}
this.Close();
cp_car is a table in the database and a class in the program.
db is the current instance of the database.
Now, in the code I have received, this is not done this way. Its done in a different matter. Btw the program is a windows forms.
In the first form window, inside the constructor, he has created a new instance of the database and he calls upon a method called init
from the another class called LinqQueries. The code for that:
public Form1()
{
InitializeComponent();
p4_databaseEntities db = new p4_databaseEntities();
LinqQueries.Init(db);
this.CenterToScreen();
}
How the LinqQueries class looks:
private static p4_databaseEntities _db;
public static void Init(p4_databaseEntities db)
{
_db = db;
}
As I understand he created a new instance of the database in the constructor, where he also called on the init method. This method then defined the db object as _db. Now every single method he makes for adding or removing data from the database he is using _db and not db.
My question is does this mean that the init method assigns the static field of type p4_databaseEntities (name of database class) as an object? Is the value of the _db then an object? Is it a reference to an object? And also i noticed he uses the same field over and over again when making changes to the database which led me to believe it may be an active object that doesn’t die through out the programs lifespan?
If anyone could clarify this It would be greatly appreciated. Excuse any errors or wrong statements I have made please correct me if im wrong in any way. Im new to C# and Entity Framework.
Thanks beforehand
You are a bit inaccurate in your descriptions. This attributes to your confusion.
A DbContext is not a database, it represents the connection to the database. If you construct a DbContext object, you get access to the tables in the database via the Entities described in the DbSet.
Although it seems that a DbSet represents a table in the database, it does not. For instance an entity that you access via a DbSet can have an ICollection as member, which contains items that are not part of the table, but are items in a different table. Accessing the items in this ICollection causes an SQL Join to be performed, without you having to type the join
Hence a DbSet object in the DbConnection does not represent database table, it represents the access to the properties that can be accessed using the DbSet object, inclusive the properties of all objects in the database that are in tables that have a relation with the DbSet object.
Your first code with the using statement is the normal way entity framework should be used.
To hide the design of the database, quite often a separate class is introduced that is the only one that should use the DbContext. All users of the database should communicate using the seperate class.
This allows changing the internals of the database without having to change the code that uses the database.
This is what probably was meant as the purpose of the LinqQueries class. Instead of calling functions of the DbContext directly, users should call the (probably static) functions of LinqQueries. This way the internal structure of the database can change without having to change the callers of the LinqQueries functions.
What in fact is happening is that LinqQueries is meant to communicate with only one DbContext. LinqQueries does not decide which DbContext is used. Proper functioning depends heavily on exactly one user of LinqQueries who should create a DbContext and Init LinqQueries. This one user should also know when no one needs the LinqQueries anymore, because he has to Dispose() the DbContext.
This design if fairly error prone. Supposing the designer makes very grood products, his products will be used by a lot of users (meaning software, not operators). How do you assert that exactly one user will call the Init function?
If you really want that all users use the same Dbcontext, why not let the constructor of LinqQueries create this DbContext. In fact the design is similar to the singleton design pattern, so why not create LinqQueries as a singleton.
The limitation that all users of LinqQuery should use the same one and only DbContext limits the use of the LinqQuery class unnecessarily.
If users of the LinqQuery class could pass a DbContext to the constructor, then users could decide which database should be used by this particular LinqQueries object. This is quite handy when creating unit tests: instead of using the original database, the testable code could be used with a database with specific test values.
All in all the goal of the designer is unclear. IMHO it is a bad design and you are right that it is not clear what happens.
class LinqQueries
{
// default constructor: use the default DbContext
public LinqQueries()
{
this.DbContext = new p4_databaseEntities();
}
// special constructor: user provided dbContext
public LinqQueries(p4_databaseEntities dbContext)
{
this.dbContext = dbContext;
}
private readonly p4_databaseEntities dbContext;
public IEnumerable<...> GetMyElements(...)
{
return this.dbContext....;
}
}
This way, every creator of the LinqQueries would exactly know what to do either use the default p4_databaseEntities or create your own Dbcontext and dispose it when not needed anymore
using (var mydbContext = new p4_databaseEntities())
{
LinqQueries linqQueries = new LinqQueries(myDbContext);
// note that this constructor is very lightWeight!
var result = linqQueries.MyQeury(...);
}
This method is really safe. Any errors made by me, does not influence the code of any other user of the LinqQuery class.

Entity Framework Code First returning same data for different tables mapped to same class

I have a class Timer which I want to use with different tables that have the same structure so I am passing in the table name.
public class TimerContext : DbContext
{
public DbSet<Timer> Timers { get; set; }
private readonly string _tableName;
public TimerContext(string tableName) : base("name=fooDb")
{
_tableName = tableName;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Timer>().ToTable(_tableName);
base.OnModelCreating(modelBuilder);
}
}
However when I pass in two different table names they return the same data. prevTimers contains the exact same data as currTimers. How do I get the unique data from each table? Why do I get the same data for two different tables?
var currTimers = new TimerContext(currentTimerTableName).Timers.ToList();
var prevTimers = new TimerContext(previousTimerTableName).Timers.ToList();
EF will call the OnModelCreating method to create an in-memory copy of the model as soon as it is needed. Once it has done this, this copy will be used. In your case, the code to generate prevTimers uses this in-memory model which is mapped to the current timers table. If you place a breakpoint on the OnModelCreating method, you should see that it is only called one time.
All that said, it is possible to dig deep and interrogate the in-memory model (you have to use the old school context type, ObjectContext vs. DbContext). Using some code from Rowan Miller here, you can find which table is mapped to each entity set. Using this code, each item in the tables variable has a read / write Table property that contains the database table name. I haven't tried setting this but it certainly seems plausible. Of course, you would need to alter the model somewhere else outside of the OnModelCreating method, say in the constructor, so that the code will fire each time an instance of the context is created.
* UPDATE *
Because I am always interested in learning new things, I couldn't leave this alone and threw together a test application. Unfortunately, it looks like you cannot set the property (despite it being a read / write property) as it throws an InvalidOperationException stating that the item is read-only. Perhaps there is another way but I have not found it...yet.
* UPDATE *
The solution is actually much simpler that what I had first mentioned. A couple of the constructors of the DbContext class accepts an instance of the DbCompiledModel class as one of its parameters. Using the DbModelBuilder class, you can build the same code that you would normally put in the OnModelCreating method. You can call the Build method of this class to create an instance of the DbModel class. You can call the Compile method of this class to create an instance of the DbCompiledModel class. The only real trick is that the Build method requires some additional information (I used an instance of the DbProviderInfo class but I think you could also use an actual connection but that would probably incur a hit to the database). I have tested this and this one does indeed work as desired.
Something like...
DbModelBuilder builder = null;
builder = new DbModelBuilder();
builder.Entity<TestEntity>().ToTable(tableName);
DbModel model1 = null;
model1 = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2012"));
builder.Entity<TestEntity>().ToTable(anotherTableName);
DbModel model2 = null;
model2 = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2012"));
DbCompiledModel compiledModel1 = null;
DbCompiledModel compiledModel2 = null;
compiledMdoel1 = model1.Compile();
compiledMdoel2 = model2.Compile();
TestContext context1 = null;
TestContext context2 = null;
context1 = new TestContext(compiledModel1);
context2 = new TestContext(compiledModel2);
Of course, the constructor of the TestContext class would have to pass the compiled model instance to the base constructor.
cannot add comment, but I just want to add one thing that happened to me
my code was like this:
DbModelBuilder builder = new DbModelBuilder();
this.OnModelCreating(builder);
var model = builder.Build(this.Database.Connection);
I was thought that when i pass the current DbConnection object to this method, it will somehow "inherit" of all connection settings, but seems I was wrong.
After debugging a while, i just realize it generate some weird connection string for me, which always results in cannot find database issue.
So my solution is, when instantiate the "TestContext" (as in Jason Richmeier's answer), pass nameOrConnectionString as first parameter and the compiled model as second, and it solved my issue.
I wonder, since EF keep a in-memory copy of certain model, is manually create another one will just create a new copy in memory? And if my code need to do this a lot of times, it is going to keep creating new models in memory and finally ends with a memory overflow?

Entitiy Framework, use same model for two tables with same layout but different table names

I have two tables that have the same layout -
Report Table
ID
ReportCol1
ReportCol2
In another database I have
Reporting Table
ID
ReportCol1
ReportCol2
I want to use a single entity model called Report to load the data from both of these tables.
In my context class I have
public DbSet<Report> Report{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ReportMap());
}
In my call to the first database Report table I get the results as expected.
I change the connection string to point to the second database, but I can't change the name of the table in the table mapping.
I don't want to use stored procs for the reason outlined in my comment.
What can I do, short of the tables names in the database(that is not an option).
Have you tried this fluent API modelBuilder.Entity<Report>().ToTable("Reporting"); ? You may need to write this so it conditionally does this based on which database you are connecting to. You may need to have your configuration allow you to say "DatabaseA uses this mapping and connection string", and "DatabaseB uses this other mapping and conenctions string", and rather than changing the connection string, you specify which database by some name/key, and your app looks up that name to determine which mapping code to run.
if(dbMappingconfig == DbMapping.A)//some enum you create
{
modelBuilder.Entity<Report>().ToTable("Reporting");
}
If your goal is to be able to pass these entities to other methods like DisplayReport(Report r) so that you don't have to duplicate code, you could have both Reporting and Report classes implement a IReport interface.
EF also supports inheritance hierarchies, so you could have them inherit from the same class, BUT I havfe a strong feeling that will not work across databases.
If the OnModelCreating doesn't rerun, it's probably already cached. Put modelBuilder.CacheForContextType = false; in there so it doesn't cache it in future, and to clear the current cache I think you can just do a Clean+Rebuild. This will come at the price of rebuilding the model everytime instead of reusing a cache. What you'd really want is use the cache up until the connection string changes. I don't know of anyway to manually clear the cache, but there might be a way. You can manage the model building yourself:
DbModelBuilder builder = new DbModelBuilder();
// Setup configurations
DbModel model = builder.Build(connection);
DbCompiledModel compiledModel = model.Compile();
DbContext context = new DbContext(connection, compiledModel);
But that will introduce additional complexities since you will need to manage the caching yourself.
While searching on this, I came across this that looks like they are trying to accomplish the same thing, as well as having gone down the same page, see Final section in question: How to map an Entity framework model to a table name dynamically
Are you able to create the same named view in each database and map to that instead of a variable table name?
I have 2 copies of tables with different names in my solution and deal with that by having 2 contexts and 2 sets of map files (generated text templates)

Entity Framework 4 TPH inheritance, how to change one type into another?

I have found some information regarding this but not enough for me to understand what the best practice for is for this scenario. I have your typicaly TPH setup with an abstract base class "Firm". I have several children "Small Firm", "Big Firm" etc inheriting from Firm. In reality I actually have different realistic classifications for firms but I am trying to keep it simple in this example. In the database as per TPH I have a single Firm table with a FirmTypeId column (int) that differentiates between all these types. Everything works great except I have a requirement to allow a user to change one type of firm into another. For example a user might have made a mistake when adding the firm, and would like to change it from Big Firm to Small Firm. Because entity framework does not allow exposing the discriminating database column to be exposed as a property, I don't believe there is a way to change one type into another via EF. Please correct me if I am wrong. The way I see it I have two options:
Don't use TPH. Simply have a Firm Entity and go back to using .Where(FirmTypeId == something) to differentiate between the types.
Execute SQL directly using context.ExecuteStoreCommand to update the FirmTypeId column of the database.
I've seen a post where people suggest that One of the tenets of OOP is that instances cannot change their type. Although that makes perfect sense to me, I just don't seem to be able to connect the dots. If we were to follow this rule, then the only time to use any kind of inheritance (TPH/TPT) is when one is sure that one type would never be converted into another. So a Small Firm will never become a Big Firm. I see suggestions that composition should be used instead. Even though it doesn't make sense to me (meaning I don't see how a Firm has a Big Firm, to me a Big Firm is a Firm), I can see how composition can be modeled in EF if the data is in multiple tables. However in a situation where I have a single table in the database it seems it's TPH or what I've described in #1 and #2 above.
I've ran into this problem in our project, where we have core DBContext and some "pluggable" modules with their own DBContexts, in which "module user" inherits "core (base) user". Hope that's understandable.
We also needed the ability to change (let's call it) User to Customer (and if needed also to another "inherited" Users at the same time, so that user can use all those modules.
Because of that we tried using TPT inheritance, instead of TPH - but TPH would work somehow too.
One way is to use custom stored procedure as suggested by many people...
Another way that came to my mind is to send custom insert/update query to DB. In TPT it would be:
private static bool UserToCustomer(User u, Customer c)
{
try
{
string sqlcommand = "INSERT INTO [dbo].[Customers] ([Id], [Email]) VALUES (" + u.Id + ", '" + c.Email + "')";
var sqlconn = new SqlConnection(ConfigurationManager.ConnectionStrings["DBContext"].ConnectionString);
sqlconn.Open();
var sql = new SqlCommand(sqlcommand, sqlconn);
var rows = sql.ExecuteNonQuery();
sqlconn.Close();
return rows == 1;
}
catch (Exception)
{
return false;
}
}
In this scenario Customer inherits User and has only string Email.
When using TPH the query would only change from INSERT ... VALUES ... to UPDATE ... SET ... WHERE [Id] = .... Dont forget to change Discriminator column too.
After next call dbcontext.Users.OfType<Customer> there is our original user, "converted" to customer.
Bottomline: I also tried solution from another question here, which included detaching original entity (user) from ObjectStateManager and making new entity (customer) state modified, then saving dbcontext.SaveChanges(). That didn't work for me (neither TPH nor TPT). Either because using separate DBContexts per module, or because EntityFramework 6(.1) ignores this.
It can be found here.
Yes, you got it all right. EF inheritance does not support this scenario. The best way to change a Firm type for an existing Firm is to use a stored procedure.
Please take a look at this post for more info:
Changing Inherited Types in Entity Framework
Unless you explicitly want to use the polymorphic functionality of the relational inheritance, then why not look at a splitting strategy?
http://msdn.microsoft.com/en-us/data/ff657841.aspx
EDIT: APOLOGIES, THIS IS AN EF 6.x ANSWER
I'm posting example code for completeness. In this scenario, I have a base Thing class. Then, sub-classes: ActiveThing and DeletedThing
My OData ThingsController, has a main GetThings which I intend to only expose ActiveThings, but, it's GetThing(ThingId) can still return either type of object. The Delete action performs a conversion from ActiveThing to DeletedThing much in the way requested by the OP, and much in the manner described in other answers. I'm using inline SQL (parameterized)
public class myDbModel:DbContext
{
public myDbModel(): base("name=ThingDb"){}
public DbSet<Thing> Things { get; set; } //db table
public DbSet<ActiveThing> ActiveThings { get; set; } // now my ThingsController 'GetThings' pulls from this
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//TPH (table-per-hierarchy):
modelBuilder.Entity<Ross.Biz.ThingStatusLocation.Thing>()
.Map<Ross.Biz.ThingStatusLocation.ActiveThing>(thg => thg.Requires("Discriminator").HasValue("A"))
.Map<Ross.Biz.ThingStatusLocation.DeletedThing>(thg => thg.Requires("Discriminator").HasValue("D"));
}
}
Here's my updated ThingsController.cs
public class ThingsController : ODataController
{
private myDbModel db = new myDbModel();
/// <summary>
/// Only exposes ActiveThings (not DeletedThings)
/// </summary>
/// <returns></returns>
[EnableQuery]
public IQueryable<Thing> GetThings()
{
return db.ActiveThings;
}
public async Task<IHttpActionResult> Delete([FromODataUri] long key)
{
using (var context = new myDbModel())
{
using (var transaction = context.Database.BeginTransaction())
{
Thing thing = await db.Things.FindAsync(key);
if (thing == null || thing is DeletedThing) // love the simple expressiveness here
{
return NotFound();//was already deleted previously, so return NotFound status code
}
//soft delete: converts ActiveThing to DeletedThing via direct query to DB
context.Database.ExecuteSqlCommand(
"UPDATE Things SET Discriminator='D', DeletedOn=#NowDate WHERE Id=#ThingId",
new SqlParameter("#ThingId", key),
new SqlParameter("#NowDate", DateTimeOffset.Now)
);
context.ThingTransactionHistory.Add(new Ross.Biz.ThingStatusLocation.ThingTransactionHistory
{
ThingId = thing.Id,
TransactionTime = DateTimeOffset.Now,
TransactionCode = "DEL",
UpdateUser = User.Identity.Name,
UpdateValue = "MARKED DELETED"
});
context.SaveChanges();
transaction.Commit();
}
}
return StatusCode(HttpStatusCode.NoContent);
}
}

Categories