EF 4.1 Multi-Tenant With Code First
来源:互联网 发布:网络电视接口类型 编辑:程序博客网 时间:2024/05/07 19:00
After a long break while we got EF 4.1 (Magic Unicorn Edition) polished up and shipped it felt like time to get some content up on here again. We’ve released some good introductory content for EF 4.1 on the MSDN Data Developer Center so I thought I’d dive into something a little more advanced.
The default Code First workflow that is seen in most walkthroughs gently encourages you to write your model configuration code in a way that takes advantage of inbuilt model caching. This is a good thing because the pipeline that processes conventions, data annotations and fluent API calls is pretty expensive. The model for a derived context type is cached at the AppDomain level and is built on the assumption that the context is always used to access the same set of tables. This is fine for most folks but if you have a multi-tenant database then you may have code executing in a single AppDomain that wants to use the same Code First context to access different groups of tables representing multiple instances of the same model.
The Model
The model we will be working with is very simple:
public class Person{ public int Id { get; set; } public string Name { get; set; } public virtual ICollection<ContactInfo> ContactInfo { get; set; }}public class ContactInfo{ public int Id { get; set; } public string Details { get; set; } public int PersonId { get; set; } public virtual Person Person { get; set; }}
We are going to use different database schemas to distinguish each tenant. An example database with two tenants looks something like this:
The API
The public surface of our API is shown below, we have a derived context and two static methods that allow us to provision a new tenant and create a context to access a given tenant.
public class ContextContext : DbContext{ public DbSet<Person> People { get; set; } public DbSet<ContactInfo> ContactInfo { get; set; } /// <summary> /// Creates a context that will access the specified tenant /// </summary> public static ContextContext Create(string tenantSchema, DbConnection connection) { // TODO: Implement } /// <summary> /// Creates the database and/or tables for a new tenant /// </summary> public static void ProvisionTenent(string tenantSchema, DbConnection connection) { // TODO: Implement }}
The Implementation
Here is a complete code listing of our context, the implementation is explained in the next section.
public class ContactContext : DbContext{ private ContactContext(DbConnection connection, DbCompiledModel model) : base(connection, model, contextOwnsConnection: false) { } public DbSet<Person> People { get; set; } public DbSet<ContactInfo> ContactInfo { get; set; } private static ConcurrentDictionary<Tuple<string, string>, DbCompiledModel> modelCache= new ConcurrentDictionary<Tuple<string, string>, DbCompiledModel>(); /// <summary> /// Creates a context that will access the specified tenant /// </summary> public static ContactContext Create(string tenantSchema, DbConnection connection) { var compiledModel = modelCache.GetOrAdd( Tuple.Create(connection.ConnectionString, tenantSchema), t => { var builder = new DbModelBuilder(); builder.Conventions.Remove<IncludeMetadataConvention>(); builder.Entity<Person>().ToTable("Person", tenantSchema); builder.Entity<ContactInfo>().ToTable("ContactInfo", tenantSchema); var model = builder.Build(connection); return model.Compile(); }); return new ContactContext(connection, compiledModel); } /// <summary> /// Creates the database and/or tables for a new tenant /// </summary> public static void ProvisionTenant(string tenantSchema, DbConnection connection) { using (var ctx = Create(tenantSchema, connection)) { if (!ctx.Database.Exists()) { ctx.Database.Create(); } else { var createScript = ((IObjectContextAdapter)ctx).ObjectContext.CreateDatabaseScript(); ctx.Database.ExecuteSqlCommand(createScript); } } }}
The Explanation
What exactly is that code doing?
- Constructor
- We’ve made the constructor private to force consumers to use the Create factory method.
- We are using the base constructor that accepts a compiled model and connection, this bypasses the usual OnModelCreating workflow and forces the context to run with the specified model and connection.
- The contextOwnsConnection flag indicates whether or not the connection should be disposed when the context is disposed. You’ll see in the sample usage that I used the same connection instance to access multiple tenants so I don’t want this behavior.
- Static modelCache Field
- We already know that model creation is expensive so don’t want to recreate the model for every context instance. This field will be used to cache models based on the connection string and tenant schema.
- Obviously the connection string based caching is a little flaky, feel free to substitute Server/Database name or whatever works in your scenario.
- Static Create Method
- This method houses the primary logic that allows us to target multiple tenants.
- We check to see if there is a model already created to access the tenant on this connection string. If not we use the DbModelBuilder –> DbModel –> DbCompiledModel workflow to create a model. This workflow is usually handled internally by DbContext.
- If you look at the API on DbModel you may be wondering what you can achieve by us having separated DbModel and DbCompiledModel… the answer in EF 4.1 is ‘absolutely nothing’! However, the longer term plan is to have DbModel be a mutable object model giving you much finer grain control once you are done with DbModelBuilder.
- You’ll notice I am removing ‘IncludeMetadataConvention’ from the conventions, this removes the EdmMetadata table that Code First includes by default.
- Provider information is required to build the final model because the database part will vary depending on the database provider being used. There is an overload of DbModel.Build that will accept raw provider information but we are using a handy overload that will work out these things based on the DbConnection.
- You’ll notice (probably with some dismay) we need to write code to configure the table schema for each entity. Admittedly there aren’t many magic unicorns grazing around this code… in future versions of EF we will be able to replace this with a much cleaner custom convention.
- Static ProvisionTenant Method
- This is pretty self explanatory, if the database doesn’t exist then we create it along with the schema for the new tenant. If the database does exist then we pull out the create script and run it against the existing database.
The Usage
Does it work? Of course…
using (var connection = new SqlConnection(@"Server=./SQLEXPRESS;Database=Contacts;Trusted_Connection=True;")){ ContactContext.ProvisionTenant("personal", connection); ContactContext.ProvisionTenant("work", connection); using (var ctx = ContactContext.Create("personal", connection)) { ctx.People.Add(new Person { Name = "Rowan Miller" }); ctx.SaveChanges(); }}
The Problem
Database schemas work fine as a way to differentiate each tenant. Another common practice is to use table prefixes and this hits a bit of an issue. Code First generates names for things such as foreign key constraints and there is no way to change these names. The generated names are often based on the entity type names and you end up with conflicts between two tenants. Unfortunately the only solution at this stage is to manipulate the generated sql script before executing it in the ProvisionTenant method… admittedly that is a pretty ugly solution.
The Summary
You can use Code First to access a multi-tenant database that uses database schemas to differentiate between each tenant. Table prefixes are possible but requires some ugly code.
- EF 4.1 Multi-Tenant With Code First
- EF 4.1 Code First Walkthrough
- ef-->code first-->Migrations
- ef code first例子
- EF Code first
- EF(code First)
- EF Code First 常用命令
- EF code first attribute
- EF Code First
- EF code First
- EF Code First 初体验
- EF Code First 初体验
- EF Code-First中的事务
- EF Code First 数据表关联
- mvc EF code first 学习
- EF code-first 数据迁移
- EF Code First数据库连接配置
- EF框架—Code-First
- ZZ一个人的命运决定于晚上8点到10点之间...
- spring日志
- SAP学习资源汇总
- ObjectInputStream与ObjectOutputStream 报错 EOFException
- 论中国世界文化遗产的保护与开发
- EF 4.1 Multi-Tenant With Code First
- 论中国世界文化遗产的保护与开发
- QQ登陆功能测试用例
- 论中国世界文化遗产的保护与开发
- SQL Server 2005 企业版没有 Management Studio管理工具
- SAP 技术架构的发展历程
- 60款很酷的 jQuery 幻灯片演示和下载
- HashCode知识总结(以set存储对象为例进行说明)
- 【转】UMD文件结构(文件解析)