Design Meeting Notes - July 12, 2012

Model key caching

When discovering a Code First model DbContext has to decide whether to use a cached model or run OnModelCreating and the rest of the pipeline to create a model. In EF5 the key used for this cache lookup is a tuple of derived context type and provider invariant name. However, sometimes the same context type and provider need to be used for multiple models, such as when the using different schemas for multi-tenancy databases. Allowing the cache key to be injected makes this possible without the need to do custom model building and caching.

The implementation of this makes use of the dependency injection work as follows:

  • Key abstractions identified:
    • IDbModelCacheKey
      • (Equals, GetHashCode)
    • IDbModelCacheKeyFactory
      • Create(DbContext)
  • Create default implementations
    • DefaultModelCacheKey etc.
  • “Invert” control
    • Prefer .ctor injection
    • Can also use “poor man’s DI” to aid testing:

      public LazyInternalContext(IDbModelCacheKeyFactory cacheKeyFactory = null)
          _cacheKeyFactory = cacheKeyFactory ?? new DefaultModelCacheKeyFactory();
    • Go directly to the resolver if injection impractical


  • Not all dependencies that can be injected need to be exposed explicitly in DbConfiguration. In this case a property was added but we have now decided to remove it. (Work item 373.)
  • The schema is not included into the cache key by default because we would need to run OnModelCreating to get it, and this has perf implications for large models.
    • We could add API to make the schema available without running OnModelCreating, but on balance it seems like we can instead make people aware of how to do it using the mechanism above without adding surface
    • We should blog on how to do this.
  • We need to document which interfaces/base classes the EF will try to resolve (Work item 374.)

Migrations history table schema changes


  • DbModelBuilder now has a HasDefaultSchema() method to allow the schema that is used in the created model to be changed.
  • Ideally, this should also affect __MigrationHistory table so that
    • it co-located with the other tables in the schema
    • and so that multiple history tables can exist in the same database
  • We then need to be able to migrate the history table along with the rest of the model
  • We probably want to allow further history model configuration in EF6 (table name etc.)

Current implementation:

  • Propagate default schema to history context
  • Include history model metadata in user metadata (transparently)
    • Create/Drop history becomes part of the standard pipeline.
    • Introduce “IsSystem” annotation so we can identify our metadata.
      • Can’t really rely on names


  • Handling default schema changes is tricky:
    • “foo” -> “bar”, (Add|Get)-Migration fails because history is in “foo” but we look in “bar”
      • For explicit migrations we could successively try schemas from the code behind metadata.
      • But auto-migrations don’t really work because the current DB metadata is only in the history table!
        • Use Info Schema?
          • Could still find multiple history tables, which one do we use?
          • By design we don’t reflect into the DB from Migrations but we could change that.
  • Existing apps metadata doesn’t contain history metadata.
    • Use IsSystem in the differ to avoid producing false diffs.
    • When updating, inject history metadata into first user migration (in memory) if not present
      SQL Server system objects cannot be moved
  • Do a table rebuild (SELECT * INTO foo.Bar FROM dbo.Bar)


  • Adding the history table metadata to the user metadata is fine, but we should try to keep this a valid EDMX documentation to ensure we (or others) can easily parse and understand it in the future. (Work item 375.)
  • Is IsSystem enough or do we need to go with something more specific, such as IsHistory
    • IsSystem is okay as long as it works. Depending on what we do with the system-ness of the history table we could change it.
    • We will likely need to use IsSystem in conjunction with c-space names to ensure we can always find the correct metadata.
    • Putting history info in a different container was considered but would require considerable work in other areas, such as the model differ.
  • How do we find the history table after the schema changes?
    • For explicit migrations we will look in the code-behind.
    • For automatic migrations not only can we not find it but we can’t distinguish the case of not finding it from the case of it not being there because the database is new. This could cause Migrations to re-create all the tables in a new schema while all the existing tables still exist (with data) in the old schema.
    • Options:
      • Considered: Make it so that automatic migrations can only be used with the default schema
      • Considered: Provide some kind of API that forces users to declare a schema change explicitly
      • Decision: Make it so that changing the schema doesn’t automatically move the history table as well; you would have to do this with a manual step
  • Should the history table continue to be a system table?
    • Majority in the room believed that it should be
    • Majority believed that we should not add special code to allow this to be changed in EF6 (Current ways of changing it are okay.)
    • Code must continue to work with either a system table or a normal table
      • Migrating the history table to a new schema must account for the table-rebuild this requires

Async immediate LINQ operators

How should async operators like FirstAsync and CountAsync (that  return single values instead of queries) work if the IQueryable is not our IQueryable?

  • If we throw it makes it a bit harder to mock
  • If we don’t throw it could look like these things work with other LINQ providers when they don’t
  • Decision: Throw and make sure that mocking can still be done. Document as necessary.

Last edited Dec 14, 2012 at 5:23 PM by ajcvickers, version 2


No comments yet.