Design Meeting Notes - March 7, 2013
Custom migrations usability
We recently accepted a pull request to allow new Migrations operations to be added. This seems to work well when adding completely new operations, but doesn’t work very well when attempting to extend existing operations. The conclusion from this is that
extending existing operations is much better done by overriding the handling of the existing operation and either:
- Using anonymous arguments to provide additional information
- Inheriting from the existing operation class and then adding special-case handling in the override
Adding new operations should only be done for complete new operations, not extensions of existing operations.
The intention of the WrapService method provided for OnLockingConfiguration is to allow an existing service to be wrapped by a new service. However, the new service does not need to wrap the existing service—it could instead just replace it. Therefore we
will change the name to ReplaceService to reflect this more general applicability.
Database initializers and shared databases
EF includes two initializers that in certain situations will drop a database and recreate it. These are:
Note that neither of these are set by default so an application must opt-in to this functionality before any database will be dropped.
Prior to v6 EF only supported a single metadata table per database—be that EdmMetdata or __MigrationHistory—and each metadata table held information for only a single DbContext. It is now possible to have the same database used by multiple contexts in two
- Using HasDefaultSchema for each different context will result in multiple __MigrationHistory tables, one for each schema
- Using the ContextKey column to store metadata for multiple contexts in a single __MigrationHistory table
The question that arises out of this is should the semantics of “dropping a database” change because of this to be more about the current model/context rather than the entire database?
- Throw if one of these initializers is used with a shared database and direct people to use Migrations
- This would prevent people from using these initializers without really thinking about what “drop database” means
- The problem here is that we cannot come up with a reliable, provider-agnostic way to determine if the database is shared for the case where there may be multiple history tables in different schemas
- Attempt to use the data in the history table with the Migrations provider to drop only appropriate tables rather than the whole database
- The problem with this is that we can’t always do it and even when we can it may be fragile. For example:
- We can’t do it if the metadata is in EdmMetadata. We could just have the old behavior here, but that adds complexity and concept count to the solution. We could throw, but that would break the upgrade path and also be a breaking change.
- We can’t do it if there is no Migrations provider. We could throw, but that basically makes these initializers not usable when you don’t have a Migrations provider. We could just have the old behavior here, but that adds complexity and concept count to
the solution. It also means that for the multiple schema cases without a Migrations provider (which is supported) the initializers would have different semantics than when a Migrations provider is present.
- Table/constraint dropping would have to be done in the correct order and we would need to understand the constraints to drop—this has been a source of error in the past
- This change would also mean that “drop database” isn’t really a “drop database” anymore, which seems confusing.
- Continue to drop the entire database if one of these initializers is used
- This would mean that we would continue to do “what it says on the can” without any complex conditionals or semantics.
We will continue to do what we do now given that this will probably create the least confusion and also given that the first two options don’t really seem to work very well.