This is mostly just tests and some cleanup/minor fixes for database creation stuff. Hopefully also contains a fix for the most common "No process is on the other end of the pipe" test failures.
This change includes:
- Some refactoring of the DataStore code to use DI for more Services
- Separation of connection handling from data store handling, including introduction of a connection abstraction
- Initial caching of database mapping by Model
- Updates to SqlServerDataStoreCreator to be dependent on connection but not store
- Simple implementation of DataStoreCreator for in-memory databases
- Wiring up of context.Database methods to database creators
Still working on some of the tests.
This change conceptually separates EntityConfiguration from the service provider and removes methods on EntityConfigurationBuilder that allow services to be specified. However, only in the case where EF is using its own internal DI things that register configuration can also inject some services transparently.
An instance of EntityConfiguration can now optionally be placed in the DI container and injected into the context. It is also possible to create different derived EntityConfigurations in order to configure different contexts, and it is possible to extent EntityConfiguration with additional app-specific properties.
Also cleaned up persistent InMemoryDataStore to be rooted in the IServiceProvider again rather than in the app domain. This works by the InMemoryDatabase being a singleton which is used by the scoped InMemoryDataStore.
Still working on some of the tests.
This change creates a separation between the services and settings for a context. This allows EF to work better with IServiceProvider instances created externally to EF which may change (due to scoping) each time used.
At the low level the steps for services are now:
- Create a ServiceCollection and configure services on it using EF and other extension methods
- Build the provider, either explicitly, or using facilities of MVC, etc.
- Expose a constrctor on the context for IServiceProvider and eitehr register the context type as transient on the DI container or create instances explicitly by passing in the IServiceProvider.
For settings, the OnConfiguring method can be used to set things like connection strings. Alternately, an EntityConfigurationBuilder can be used to create an EntityConfiguration with these kinds of settings. The IServiceProvider should be passed to EntityConfiguration.
When not using any external DI container or services, the EntityConfigurationBuilder can be used to also build the services, which will then be cached appropriately based on the services called. In this case the sugar WithServices method can be used to add providers or other types of service.
Note: Additional testing is needed for some of this code.
- Mostly functioning re-linq based in-memory provider.
- Re-linq based IQueryable/IQueryProvider impl.
- Updated reln. provider to use new in-memory provider.
- Initial LINQ FT infra.
Added MigrationCodeGenerator abstract class, CSharpMigrationCodeGenerator implementation, and dispatch methods for code generators on MigrationOperation classes.
Added Migration abstract class, MigrationBuilder, TableBuilder and ColumnBuilder classes.
Updated relational model and migration model as necessary.
Made ExpressionExtensions public until we decide what to do with shared utilities and resource strings.
Added TypeExtensions and PropertyExtensions to migration utilities.
Added DefaultConstraint struct to better incapsulate the DefaultValue/DefaultSql
Added a bunch of unit tests.
Original values are additional values that can be stored alongside the state entry and updated from or merged with the actual property values when appropriate. The update pipeline also needs to store additional values for properties that are store generated such that they can be used by the update pipeline while changes are being saved. These values cannot be stored directly in the state entries because they should be discarded if the save fails--or when the transaction is rolled back once we have transactions.
These two requirements are now captured into a single general-purpose concept of the sidecar on the state entry. This means that the state manager can be responsible for store value handling such that the data store/update pipeline doesn't need to do anything special--it just propagates values back into the state entry and can likewise read values from the state entry without fear of corrupting the state entry if the transaction is rolled back.
In addition, this change moves original values functionality out of the state entry for better separation of concerns, which in turn means that it is cleaner to change the storage for original values to be something other than just an object array if we determine that doing so is beneficial in terms of memory and/or speed.
The differ creates pairs of matching objects from the model. Using these pairs and the metadata mapping it creates pairs of matching database objects. These pairs are then used to find differences in form of migration operations.
The moves and renames are found by comparing the names in the pairs, the additions are found by eliminating Item2s of pairs from target, the removals are found by eliminating Item1s from source.
The operations are gathered into a collection that allows retrieval by type and are exposed in an order that minimizes conflicts: drop operations without data loss (indexes, constraints), table moves, renames, additions and then column and table drops.
Rename conflicts are handled by inserting a rename operation of the table to be dropped (using a generated name), before the rename operation that is trying to reuse the original name, and then replacing the drop operation with a new one that uses the generated name.
This change introduces an abstraction between the data store and the materializer such that the generic non-boxing ReadField method can be used my the materializer to extract values without boxing. However, the abstraction can also be used to read all values using GetValues. This provides flexibility such that if GetValues is faster it can be used but for cases where GetValues is problematic or slower (e.g. because of type inference in SQLite) then the typed APIs can be used instead.
Note that we still call GetFieldValue<object> in some cases (e.g. when extracting shadow values or composite key values), but we could do similar code-gen as we do for materialization to avoid this.
Adds AcceptChanges to state entries which will:
- Mark modified/added entities as unchanged
- Propagate current values to original values
- Detach deleted entities
AcceptAllChanges calls this for every entity in the state manager and SaveChanges calls AcceptAllChanges.
This change increases the robustness of materialization such that:
- Base types are handled (Fixing Identity blocker issue #76)
- Field matching matches patterns agreed on in Feb 2013 design meeting
- If no field match, then use property setter--handles automatic properties better than trying to match generated field name
- "BackingField" annotation can be used to specify a field name
- FieldMatcher is essentially a convention that can be replaced to match fields differently.
Minor refactorings:
* renaming Column.GenerationStrategy to Column.ValueGenerationStrategy
* encapsulating logic for retrieving store generated columns in an extension method on Table
This method always tries to create the schema. It uses an idempotent
script to create the physical database (so you can add schema to an
existing database) but it always tries to create the entire schema. I
missed this rename from the last commit.
Enable creation of a SQL database from an IModel. Introduced a DataStoreCreator class as this functionality feels separate from DataStore itself (i.e. if we did combine them then SqlServerDataStore would have a dependency on ModelDiffer, MigrationSqlGenerator, etc.)
Needed to make a few changes as part of this:
- MigrationOperationSqlGenerator was building one large script for all operations but not all operations can be run in a script. Introducing a SqlStatement class and returning a set of these instead.
- In the old stack we handled generating the list of statements with a Statement method to add statements to the list, but that made extensibility hard. In these changes we flow the string builder thru the Visit methods so that derived generators can easily tweak the SQL (the SqlServerMigrationSqlGenerator is a good sanity test that this works better). The other nice side effect of this is that MigrationSqlGenerator is no longer stateful.
- The above change required changing the signature of Visit/Accept which meant the general purpose MigrationOperationVisitor would have needed to change signatures or not be used in this case.
- Introduced a SqlStatementExecutor, this needs to be padded out a lot and can be re-used across the stack. BTW to do that we also need to add more concepts (parameters etc.) to SqlStatement.
- Introduced the ability to not create the database when creating test databases (to allow testing creation of the physical database).
This checkin adds support for original value tracking and change detection to state entries.
We need original values for two purposes:
- Use in the update pipeline for concurrency tokens and graph ordering
- Detecting changes when using snapshot change tracking
Also, original values can be stored eagerly (snapshotting when then entity is materialized) or lazily (only when an attempt is made to change the current value).
We attempt to not store original values when not needed and to store them lazily when possible. This is fully possible for entities that implement both INotifyPropertyChanging and INotifyPropertyChanged.
If an entity implements only INotifyPropertyChanged but not INotifyPropertyChanging then only original values for updates are tracked by default but they are tracked eagerly because we don't know that a change is being made until it has already been made.
If an entity doesn't implement INotifyPropertyChanged, then original values for all properties are tracked eagerly so that DetectChanges can compare current and original values.
A flag on the entity type allows eager tracking of all original values for those users who always want original values in order to implement things like rejecting changes to entities.
Adding logic to calculate default names of objects that do not have a
StorageName specified in the IModel.
Made them instance methods on DatabaseBuilder (had to swap some other
methods to be instance as well) so that you could derive from
DatabaseBuilder and change the default naming.
Also removed the Name property from Database since we don't always know
the database name when creating a Database model (i.e. scripting etc.) -
we weren't using the name anywhere.
RelationalDataStore didn't filter out unchanged entries and the default
operation for ModificationOperation was DELETE. This meant we would
delete any unchanged data from the database.
Made BatchExecutor an explicit dependency of RelationalDataStore so that
it could be mocked for unit testing. Had to make a few classes public to
do this, but discussed with Pawel and the plan was that these would be
public in the end anyway.