IT:AD:EF/CodeFirst:HowTo:Configure Seed
Summary
Once you've enabled Configure Migrations, you'll quickly come up to the issue of Seeding.
There are a couple of issues to get around first to make it easier to know what to do in various circumstances.
- Seeding verus Data Updates
- Where to put Data Updates (either Seeding, or Migrations)
- When to use handy
AddOrUpdateextension, or custom tests.
Process
Seeding Types
There are different forms of seeding that need to be considered:
- Initialize/Reset Immutable Reference Data (Gender, etc. is never going to change).
- Initialize/Reset Mutable Reference Data (Categories need to be initially setup, and only reset under rare circumstances)
- Initialize/Reset Mutable User Data (one or two Demo Students/Classes, etc. will be needed to demonstrate the app is working, but then any changes made by users should not be erased/reseeded).
The easiest way to handle that is something along the lines of:
var seedingType = _hostSettings.Get<SeedingType>("SeedingType");
if (seedingType >= SeedingType.ResetMutableReferenceData){
dbContext.Categories.AddOrUpdate(x=>x.Name,new Category{Name="Default"});
}
In 99% of the cases, the seeding level should always be Seeding.ResetImmutableReferenceData.
But note that at that level that ensures you always have the necessary Reference Data – but doesn't take into account any new Mutable Records needed (AppSettings stored in the db would be a good example) or Mutable Records that need changing.
That's where Patches/Updates are needed.
Patches/Updates
Another form of data change to consider is Patches/Updates that change data.
A good example might be the updating of an AppSetting value (eg: FaxServerServiceEndpoint).
The reason this is more complex is:
SeedingLevel.ResetMutableReferenceDatawould do this…but would always reset any changes made by SysAdmin. Annoying, therefore not appropriate.- The value might be different per environment (I usually know which environment I'm installing in (DEV,CI,ST,SIT, UAT,COMP,PROD) with
_hostSettings.Get<string>("EnvironmentIdentifier");
So there are two places to consider putting it:
- in Migrations
- in Seeding
Each has Advantages and Disadvantages
In a Migration
* Advantages:
- Makes sense: it's a Migration of Data, not Schema.
- It's linear – ie, if not applied, apply, if already applied, move on and lets never discuss it again.
* Considerations:
- Seeding happens after migrations, so if
SeedingLevelis changed fromSeedingLevel.ResetImmutableReferenceDatatoSeedingLevel.ResetMutableReferenceData:- Seeding data has to align perfectly with what is in the migrations (ie, be Environment aware as well) or it will crush out the updates done by the Migration.
- Migrations has to check
EnvironmentIdentifieras well – and be kept in sync with Seeding.
* Disadvantages:
- Far away from the original Seeding…have to keep an eye on both to ensure things line up in terms of data and order…
- Won't work in the context of a library (Migrations is an App Concept, not a Lib capable technology), so in XActLib, I'm forced to use the Seeding concept described further down.
//Do migration:
if ([]{"UAT","PP"...}.Contains (environmentIdentifier)){
//do an update specific to a specific environment...
}
//or don't check environmentIdentifier, and do an update specific to all environments...
In Seeding
* Advantages:
- Works for XActLib, whereas Migrations wouldn't.
* Considerations:
- You can arrange it so that edge case updates are applied after default case Seeding.
- It's in a well known place, with the rest of the Seeding for that type.
- Then again the Migrations folder is also a WellKnown place.
* Disadvantages:
- Needs an
XAct_DataStoreUpdateLogtable to keep track of what has been applied.
if (!dbLog.Contains(x=>x.Name=="FooUpdate") && (!dbLog.Contains(x.Name=="FooUpdate")&&(x.Enabled==false)){
if ([]{"UAT","PP"...}.Contains (environmentIdentifier)){
//do an update specific to a specific environment...
}
//or don't check environmentIdentifier, and do an update specific to all environments...
}
AddOrUpdate versus Test/Insert
The easiest way to do seeding is to use the AddOrUpdate method:
studentCategories.AddOrUpdate(x=>x.Name, new StudentCategory("Asleep");
never test on the Id (x⇒x.Id), but a variable within the entity. If you test against the Id, and it's autoincremented, it doesn't ever work.
The downside is…AddOrUpdate is slow. Seeding used to happen only after applying a migration, but now it happens when ever you restart the app1). And by the time you've created a dozen or so reference tables…it's not fast.
In which case, consider testing whether you need to update things or not:
if (studentCategories.Count() <10){
...
...
...
}
Or you can also do
if (xactDataStoreUpdateLog.Contains(x=>x.Name=="BaseStudentCategories")){
studentCategories.AddOrUpdate(x=>x.Name,....);
}
and if you later add more:
if (xactDataStoreUpdateLog.Contains(x=>x.Name=="MoreStudentCategories")){
studentCategories.AddOrUpdate(x=>x.Name,....);
}
Instead of x queries, it's only x the first time, and then 1 the next time.