使DAL可扩展的模式/想法/经验

时间:2012-07-19 10:03:49

标签: .net entity-framework architecture data-access-layer asp.net-web-api

我一直在设计一个使用多租户(SaaS)服务器 - 客户端架构设计应用程序的相当艰巨的任务。我已经考虑过服务器的内部架构了一段时间了,这就是我所拥有的:

我想让服务器在WCF或MVC4中实现WebAPI。

服务器将具有基于插件的架构,使用MEF作为协调器。由于应用程序有一些要求根据租户要求更改业务逻辑,我正在考虑这样做。更具体地说,我将有一个类/功能,我用MEF导出,在类中,我将能够为其托管它的WebAPI路由添加元数据属性。显然,插件可能需要在某些情况下扩展,甚至可能被其他插件使用,在这种情况下,他们没有路由。

此时服务器的主要问题是,我在某种程度上考虑了插件架构,插件应该能够处理插件所需的所有内容。所以在持久性方面,如果开发了一个新的插件,我应该能够在插件中编写一些代码,然后由DAL执行,以便为插件的数据提供持久性。我不希望插件在任何情况下都经过DAL并触摸数据库,因为多租户和访问的约束需要来自某个地方。从本质上讲,我失去了如何在我的插件中定义一些持久性的能力,如果说DAL在EF上运行的话。我想过将“CreatePersistentStorage(tenant)”这样的东西放到插件的界面中,通过创建表来完全控制插件。我相信Orchard会做这样的事情,但会以更加强大的方式。

我需要帮助我如何实现可以在插件中扩展的DAL。 DAL必须控制最终创建,因此插件的数据表是在正确的租户下创建的。

也许EF不是要走的路?关于您认为值得赞赏的评论/经验。你也会在这个时候去WCF WebAPI或ASP.NET WebAPI吗?

祝你好运, 拉里

1 个答案:

答案 0 :(得分:0)

好的,这就是我目前所拥有的,它提供了多租户,它提供了插件 - 清洁/优雅而不是那么多。

public class Context : DbContext
{
    private static ConcurrentDictionary<string, DbCompiledModel> modelCache = 
               new ConcurrentDictionary<string, DbCompiledModel>();

    private Context(DbCompiledModel model) : base(model)
    {
    }

    public static Context Create(string tenantSchema)
    {
        var compiledModel = modelCache.GetOrAdd(
            tenantSchema,
            t =>
                {
                    var providerInfo = 
                        new DbProviderInfo("System.Data.SqlClient", "2008");
                    var modelBuilder = new DbModelBuilder();

                    modelBuilder.Conventions.Remove<IncludeMetadataConvention>();

                    foreach (var plugin in ServiceLocator
                        .Current
                        .GetAllInstances<IPlugin>().ToList())
                    {
                        plugin.SetupPersistence(modelBuilder,tenantSchema);
                    }

                    var model = modelBuilder.Build(providerInfo);
                    return model.Compile();
                });

        return new Context(compiledModel);
    }

    /// <summary>
    /// Creates the database and/or tables for a new tenant
    /// </summary>
    public static void ProvisionTenant(string tenantSchema)
    {
        using (var ctx = Create(tenantSchema))
        {
            if (!ctx.Database.Exists())
            {
                ctx.Database.Create();
            }
            else
            {
                try
                {
                    var createScript = ((IObjectContextAdapter)ctx)
                        .ObjectContext
                        .CreateDatabaseScript();
                    ctx.Database.ExecuteSqlCommand(createScript);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Tenant already exists.");        
                }
            }
        }
    }
}

以下是插件的外观:

[Export(typeof(IPlugin))]
public class Class2 : IPlugin
{

    public int Id { get; set; }
    public string Name { get; set; }
    public string Lol { get; set; }

    public void SetupPersistence(DbModelBuilder modelBuilder, string tenantSchema)
    {
        modelBuilder.Entity<Class2>().ToTable("Keke", tenantSchema);
        modelBuilder.Entity<Class2>().HasKey(i => i.Id);
    }
}

以下是您可以使用这些内容的方法:

    Database.SetInitializer<Context>(null);

    Context.ProvisionTenant("personal");
    Context.ProvisionTenant("work");

    using (var db = Context.Create("personal"))
    {
        db.Set<Class2>().Add(new Class2 { Name = "Keke" });
        db.SaveChanges();
        foreach (var d in db.Set<Class2>())
        {
            Console.WriteLine(d.Name);
        }

        Console.Read();
    }

我正在使用CommonServiceLocator来保留我的MEF参考。

现在,这有几个问题:

  • 一个插件可以控制表名,还可以将模式名设置为表 - 我不知道如何在API端控制它。我可以将整个EF api包装在另一个类中,但是为了使一个函数像我想要的那样工作似乎很麻烦。
  • 关键是要有一个插件可扩展的数据库层,并且可扩展性,是为将来发生的变化做好准备,目前我的实现在变更部分不足,因为没有处理插件内部迁移的方法。
  • 太可怕了。

如何处理迁移,或者如何从上下文中显式定义插件的表名,我们对任何输入都表示赞赏。