I have my services configured and available via dependency injection.
What I try to do is to get one at runtime (that's why I'm looking non-standard way). That means - not in a constructor, not hardcoded, I need to get a service dynamically exactly when it's needed.
Why? Entity Framework (Core).
Well, version 3.1 doesn't have any means to reset the data context. Yesterday I found out that if I try to remove entry blocked by foreign key constraint - the context breaks irreversibly. The EF tries to save invalid changes each time, despite the first try failed. Using Database.RollbackTransaction doesn't help. Nothing does.
EF has more ugly "features", like default sandbox mode, where the context sees only its own changes, ignoring all external changes to the database. Like someone just thought "databases are primary single user, right?".
Anyway, no way of refreshing / reloading context in EF Core 3.1, failed write to database irreversibly breaks the context.
The way around this is to recreate the context when it needs to be refreshed / reset. That means I need to dispose the old context and create a new one.
I thought that configuring the data context service as Transient would achieve just that. Well - not exactly. I tried to remove an entry I couldn't remove, it failed and the only way I could do any successful write to the database was to reload the Blazor page.
I think of a solution like this:
DataContext.Database.BeginTransaction();
try {
// some writing to the data context
DataContext.Database.CommitTransaction();
} catch {
DataContext.Database.RollbackTransaction();
DataContext.Dispose();
DataContext = GetService<MyDataContext>();
}
So I wonder how to get the service other then store IApplicationBuilder in a static property.
What's wrong with calling the constructor? The constructor requires options (like the connection string), so I would need the configuration service anyway. There are other ugly hacks to get the configuration at runtime, but come on, there must be another, better and just more sane way of doing it.
Related
I need to implement a simple credit system for a web app. So users shall be able to receive credits e.G. by simply paying for them or receive them as a bonus for doing something (like registering, watching an add, etc). On the other hand, the user shall be able to spend these credits for services the web app provides (like promoting their content, enabling VIP status, etc.).
We are building this web app with ASP.net Core 3.1 and Entity Framework Core. So i was thinking about using a TransactionLog in our PostgreSQL database.
But to make it "more fancy" I would like to have these different types of transaction as real types in our model. And that's where I thought table-per-hierarchy comes into play.
We have configured TpH via Fluent API:
// DbSet Properties
public DbSet<AbstractTransaction> CreditTransactions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
// Inside OnModelCreating Method:
modelBuilder.Entity<AbstractTransaction>()
.ToTable("CreditTransactions")
.HasDiscriminator<string>("TransactionType")
.HasValue<RegisterBonus>("RegisterBonus")
.HasValue<SinglePayment>("SinglePayment")
.HasValue<AcivateVIP>("ActivateVIP")
.HasValue<PostPromoted>("PostPromoted");
}
The class diagram basically looks like this:
So far this works nice and easy. But I discovered one drawback: when adding a new transaction type (like NewDebitTransaction) to the class hierarchy without adding the corresponding .HasValue<NewDebitTransaction>("NewDebitTransaction") in the DbContext, EF Core still allows me to add object of type NewDebitTransaction to the Transaction-Table, but stores this entry as an AbstractTransaction type. I was hoping to get some exception telling me, that NewDebitTransaction is not a registered type for the TpH-Mapping or something. Instead EF Core silently downcasts my concrete class to an AbstractTransaction and i can never get it back.
The MSDN-Documentation states, that one can forbid types from the hierarchy, by using .HasBaseType((Type)null) but i don't get how to include this in my scenario.
Currently this is a show stopper for me. Because in the future there will be a lot of new transaction types. And it is only a matter of time until someone forgets to add the corresponding .HasValue() call in the DbContext and we silently store AbstractTransactions in the Database, without knowing what kind of transaction it originally was.
Anybody got an idea on how to prevent EF Core from downcasting my Types to a matching Abstract-Type? Or is TpH the wrong tool for that problem?
I have not found solution for making EF to do this, but you can work around the issue. One option is to use check constraint to prevent database from inserting such values. Something like this:
modelBuilder.Entity<AbstractTransaction>()
.HasCheckConstraint("CK_NOT_AbstractTransaction", "TransactionType != \"AbstractTransaction\"")
...// rest of set up.
Also I would add a unit test (so this kind of errors would be spotted earlier) which will scan assembly for all of AbstractTransaction's inheritors, create an instance of it, save it to database (inmemory one should work), fetch it and check that TransactionType is not "AbstractTransaction". Though this will require to expose the TransactionType property.
I have an Entity Framework Core + ASP.NET Core application and when my application starts up I want to ensure that the database is created, and eventually (once I have migrations) I want to ensure that those are also run.
Initially I put Database.EnsureCreated() into the constructor of my DbContext but that appears to run every time someone hits my application since a new instance of the DbContext is created each time.
I tried to put it into my startup code, but I need an instance of my DbContext to do that and it is unclear how exactly to get one. I am configuring EF as so:
serviceCollection.AddEntityFramework()
.AddSqlServer()
.AddDbContext<Models.MyContext>(options => options.UseSqlServer(...));
I don't see a way to get an instance of the DbContext from the service collection, and I don't see any appropriate singleton to inject a DbContext into so I can do some one-time initialization.
So what is the best place to ensure some code related to my DbContext is called once per application run?
At the time of this writing, there is not a "correct" place to run code on application startup such that it executes within the request scope (see https://github.com/aspnet/Hosting/issues/373).
For now, the workaround is to do the following, but it won't work in more complex multi-application scenarios (see https://github.com/aspnet/EntityFramework/issues/3070#issuecomment-142752126)
public class Startup
{
...
public void Configure(IApplicationBuilder applicationBuilder, ...)
{
...
// NOTE: this must go at the end of Configure
var serviceScopeFactory = applicationBuilder.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
using (var serviceScope = serviceScopeFactory.CreateScope())
{
var dbContext = serviceScope.ServiceProvider.GetService<MyDbContext>();
dbContext.Database.EnsureCreated();
}
}
}
I wonder why you would run to run EnsureCreated as part of your service anyway. Do you really want your webserver to create or update the database schema? Why would the webserver be up and serving request if the database is not up to date?
Do you really trust your migrations so much that they don't ruin data when executed, that you don't want to test the data after running them?
In addition, this will require you to give the webserver database user permissions to change the database schema. This is a vulnerability in itself - someone taking over your webserver will be able to modify your database schema.
I suggest you create the database and apply migrations in a small utility you run yourself, not as part of your web application.
I think zmbq's suggestion is a correct one and there is a way to ensure that migrations are run along with the deployment, so that binaries and database changes are in sync, using Visual Studio's Publish functionality.
When publishing against an IIS instance, one can specify target database connection string to use to also run required migrations:
This will ensure that changes are applied only when needed (not every time application starts) and that application runs using the least required database rights (i.e. database writer, reader etc.) as opposed to rights to alter tables, create indexes etc.
Currently I have an ERP which is a Winforms based client (with SQL Server), which gets delivered and updated on desktops using ClickOnce.
The current version is using Entity Framework 4 (ObjectContext-based) and database first. The way I am doing updates to the client when there's a database schema change is a four step process:
Create an intermediary updated database schema on production with compatible columns (allow null everywhere or have a default value, etc.). Old clients can connect to that database and keep working as if nothing was changed
Update desktop clients to an intermediary version with the updated features which accounts for this intermediary schema but has all "final schema" features
Once all clients are updated and all records are compatible with the "final" schema, make a new update on the schema with the needed database constraints
Update all clients to a final version which is mapped to this final schema (which accounts for database constraints errors, and need those schema changes to work).
I've found this process to be, if a bit cumbersome to us, better for the clients, who can update when they see fit, and don't get interrupted with an update in the middle of their work (which may involve having customers in front of them who don't want to wait for a software update).
Now I have made an almost-complete rewrite of the client (still Winforms), using EF6 and code-first, with migrations.
I've been searching for documentation but can't find anything (seems there's only web programming these days, where generally updates to the database and the web client can be done simultaneously and without interrupting users), but once I apply migrations on production, non-updated clients can no longer work with the database. EF will complain and throw exceptions upon instantiating the context if it's not up to date with the database schema.
Specific question: is there a way to have an EF6 code-first dbcontext to work with a newer migration of the database schema than the one compiled-in, as long as it is compatible? If that's the case, I could just keep doing what I was doing so far.
And an (I guess) oppinion based question if anyone wants to extend on the actual answer: is there any better way to handle this scenario? I'm sure I'm not the only one having this problem, however the keywords needed to Google for documentation are too broad and so far, only web scenarios have come up on my searches.
I'm currently at a stage on the client rewrite where major changes could be allowed, so I don't care if the solution may complicate parts of the code
When an application initializes the model database, etiher by directly calling DbContext.Database.Initialize or instancing the first DbContext, it checks if the model in the application and the model in the database match.
To do so, it calculates the model hash, and compares it with the hash stored in the __MigrationHistory table (or in the EdmMetadata table, if it was updated from EF 4.x). This is done in the System.Data.Entity.Internal.ModelCompatibilityChecker.CompatibleWithModel method, which receives a parameter named throwIfNoMetadata which happens to be false in the internal implementation, so no exception is thrown if there is no metadata.
So, if you make this tables dissapear in some way before the database is initialized, you'll avoid the error. The important point is that you must do this change without using DbContext. If not, the database will try to initialize, and, if this table exists, it will fail. So you can use plain ADO.NET to drop the tables.
Take into account that the metadata tables can be automatically created, for example by applying migrations.
You can also use ctx.Database.CompatibleWithModel(true) to check if the database metadata exists and is compatible or not, to get rid of it. The parameter is precisely the throwIfNoMetadata that I mention above.
Compatibility check in db initializers:
The default DB Initializer is CreateDatabaseIfNotExists, and it does check the model compatibilty with the throwIfNoMetadata set to false. That's why this solution works. However, if you implement your own version of DB Initializer that doesn'd run the check, it shuld work.
public virtual void InitializeDatabase(TContext context)
{
Check.NotNull(context, "context");
var existence = new DatabaseTableChecker().AnyModelTableExists(context.InternalContext);
if (existence == DatabaseExistenceState.Exists)
{
// If there is no metadata either in the model or in the database, then
// we assume that the database matches the model because the common cases for
// these scenarios are database/model first and/or an existing database.
if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false, existenceState: existence))
{
throw Error.DatabaseInitializationStrategy_ModelMismatch(context.GetType().Name);
}
}
else
{
// Either the database doesn't exist, or exists and is considered empty
context.Database.Create(existence);
Seed(context);
context.SaveChanges();
}
}
Is it possible to have entity framework dynamically add/remove tables/columns in its DbContext without recompiling the project ? The use case is EF inside a GUI app and the DB schema may be changed behind the scenes over the lifetime - we don't want the GUI app to be recompiled on every DB change - it should just visually show the new table classes (i.e. Schema) as well as a few controls like type, property name etc.
It's technically not possible if your GUI app works with your database without some detached "api" - real web api, wcf service, etc. Even if you disable automatic migration for project and will manage to make your application think, that database is up-to-date (which can probably happen with some magic, dynamic DLL compilation, etc.), you have a big possibility of getting something funky - changed FK, PK, restrictions, constraints, data types, etc.. This will cause unexpected behavior for Entity Framework, and bring only grief.
Only in this case you can do it without affecting real GUI app - you just re-roll external project and voila - it works, if you properly set up your DTOs and methods. Otherwise, if your API changes uncontrollably, you will get DTO mismatch problems, which you will have to handle with versioning, etc, etc.
It's overall a bad idea to change model backing your application in any casewithout recompilation, since C# is not a dynamic language.
Entity Framework does not do that. It would be custom solution, pretty expensive.
I have an application that loads all the data as expected using EF, however, when it comes to saving, I can't get it to work at all.
I've started off simple, by just using a value from a combobox to alter 1 field in the database. When the value is changed, it executes
this.t.Incident.AssignedTeamID = (int)this.cbTeam.SelectedValue;
I've also confirmed that this changed the EntityState to Modified and that the value is what I expect it to be. Despite this, calling
hdb.SaveChanges();
doesn't save anything back to the database. I know it's probably something simple I'm missing, but I cannot find out what that is at all.
Update:
Adding hdb.context.Attach(this.t.Incident); before using SaveChanges results in an InvalidOperationException stating "An entity object cannot be referenced by multiple instances of IEntityChangeTracker."
If it makes any difference, this is a desktop application, not a web application
Most likely, since you're working with a web app, you have a problem with a disconnected obect context. With all ORMs, you must go through an attach process to update an entity. SaveChanges will never work on both sides of the request/response.
Thank you to everybody who posted here. The answer was quite simple after reading these details.
What I needed to do, as Damien commented on the original question, was to ensure it was all loaded from the same class.
I currently created a private instance of the DB whenever needed, without really thinking. This was fine, it loaded the data as I expected, but meant that I would have around 3 different instances of the database loaded via different classes.
Essentially, I was trying to save the object from a different class with a different instance of the database. Moving the save method back to the class it was created from (presumably like it should have always been) resolved the issue.