11

我正在寻找有关如何在模块化非单片应用程序设计中使用 ORM(在本例中为 EF5)的建议,其中包含核心部分和第 3 方模块,其中核心没有直接引用第 3 方模块和模块仅引用核心/公共表和类。

为了论证,一个足够接近的类比是 DNN。

代码优先:

在 CodeFirst 中,我使用的方法是通过反射来构建 Db 的模型:在 Core 的 DbContext 的 DbInitialation 阶段,我使用反射在任何用 IDbInitializer(自定义包含 Execute() 方法的合约)来定义 dll 的结构。每个 dll 都将它所知道的信息添加到 DbModel 中。任何后续播种也在同一个 wa 中处理(搜索特定的 IDbSeeder 合约并执行它)。

优点:*该方法目前有效。* 可以在所有存储库中使用相同的核心 DbContext,只要每个 repo 使用 dbContext.GetSet(),而不是期望它是 dbContext 的属性。没什么大不了的。缺点: * 它仅在启动时有效(即,添加新模块需要 AppPool 刷新)。* CodeFirst 非常适合 POC。但是在 EF5 中,它对于企业工作来说还不够成熟(我已经等不及要添加 EF6 for StoredProcs 和其他功能了)。* 我的 DBA 讨厌 CodeFirst,至少对于 Core,希望尽可能地使用存储过程优化该部分......我们是一个团队,所以我必须设法找到一种方法来取悦他,如果可以的话找到一种方法...

数据库优先:

DbModel 阶段似乎发生在 DbContext 的构造函数之前(从嵌入式 *.edmx 资源文件中读取)。DbInitialization 永远不会被调用(因为模型被认为是完整的),所以我不能添加比 Core 知道的更多的表。

如果我不能像使用 CodeFirst 那样动态地向模型添加元素,这意味着 * 核心 DbContext 的模型必须了解 Db 中的每个表——核心和每个第 3 方模块。使应用程序单体化和高度耦合,打败了我想要实现的目标。* 或者每个第 3 方必须创建自己的 DbContext,导入 Core 表,导致 * 版本控制问题(当 Core 的 *.edmx 更新时,模块不更新 *.edmx 等) * 在不同的内存上下文中到处重复 = 困难跟踪并发问题。

在这一点上,在我看来,CodeFirst 方法是使用 EF 实现模块化软件的唯一方法。但希望其他人知道如何让 DatabaseFirst 大放异彩——有没有办法将 DbSet '附加'到从嵌入式 *.edmx 文件创建的模型中?

还是有其他想法?

4

2 回答 2

4

我会考虑使用一种plugin架构,因为这是您对应用程序本身的总体设计。

您可以通过执行以下操作来完成此基础知识(请注意,此示例使用StructureMap- 这是StructureMap 文档的链接):

  1. 创建一个接口,您的DbContext对象可以从中派生。

    public interface IPluginContext {
        IDictionary<String, DbSet> DataSets { get; }
    }
    
  2. 在您的依赖注入设置中(使用 StructureMap) - 执行以下操作:

    Scan(s => {
        s.AssembliesFromApplicationBaseDirectory();
        s.AddAllTypesOf<IPluginContext>();
        s.WithDefaultConventions();
    });
    For<IEnumerable<IPluginContext>>().Use(x =>
        x.GetAllInstances<IPluginContext>()
    );
    
  3. 对于您的每个插件,要么更改{plugin}.Context.tt文件,要么添加一个partial class文件,使DbContext生成的文件派生自IPluginContext.

    public partial class FooContext : IPluginContext { }
    
  4. 更改{plugin}.Context.tt每个插件的文件以公开如下内容:

    public IDictionary<String, DbSet> DataSets {
        get {
            // Here is where you would have the .tt file output a reference
            // to each property, keyed on its property name as the Key - 
            // in the form of an IDictionary.
        }
    }
    
  5. 您现在可以执行以下操作:

    // This could be inside a service class, your main Data Context, or wherever
    // else it becomes convenient to call.
    public DbSet DataSet(String name) {
        var plugins = ObjectFactory.GetInstance<IEnumerable<IPluginContext>>();
        var dataSet = plugins.FirstOrDefault(p =>
            p.DataSets.Any(ds => ds.Key.Equals(name))
        );
    
        return dataSet;
    }
    

如果语法不完美,请原谅我 - 我在帖子中执行此操作,而不是在编译器中。

最终结果使您可以灵活地执行以下操作:

    // Inside an MVC controller...
    public JsonResult GetPluginByTypeName(String typeName) {
        var dataSet = container.DataSet(typeName);
        if (dataSet != null) {
            return Json(dataSet.Select());
        } else {
            return Json("Unable to locate that object type.");
        }
    }

显然,从长远来看 - 您会希望控件被反转,其中插件是实际绑定到架构中的插件,而不是服务器期望类型。但是,您可以使用这种延迟加载来完成同样的事情 - 主应用程序公开一个所有插件都绑定到的端点。

那将是这样的:

public interface IPlugin : IDisposable {
    void EnsureDatabase();
    void Initialize();
}

您现在可以将此接口公开给任何将为您的架构(DNN 风格)创建插件的应用程序开发人员 - 您的StructureMap配置工作如下:

Scan(s => {
    s.AssembliesFromApplicationBaseDirectory(); // Upload all plugin DLLs here
    // NOTE: Remember that this gives people access to your system!!! 
    //       Given what you are developing, though, I am assuming you
    //       already get that.
    s.AddAllTypesOf<IPlugin>();
    s.WithDefaultConventions();
});
For<IEnumerable<IPlugin>>().Use(x => x.GetAllInstances<IPlugin>());

现在,当您初始化应用程序时,您可以执行以下操作:

// Global.asax
public static IEnumerable<IPlugin> plugins = 
    ObjectFactory.GetInstance<IEnumerable<IPlugin>>();

public void Application_Start() {
    foreach(IPlugin plugin in plugins) {
        plugin.EnsureDatabase();
        plugin.Initialize();
    }
}

您的每个IPlugin对象现在都可以包含自己的数据库上下文,管理安装(如果需要)自己的数据库实例/表的过程,并优雅地处理自己。

显然这不是一个完整的解决方案——但我希望它能让你朝着一个有用的方向开始。:) 如果我可以帮助澄清这里的任何内容,请告诉我。

于 2012-11-08T00:24:18.123 回答
1

虽然这是一种 CodeFirst 方法,而不是最干净的解决方案,但即使在启动后强制初始化运行又如何,如此处发布的答案所示:强制代码优先以始终初始化不存在的数据库?(我对 CodeFirst 一无所知,但鉴于这是一个月前还没有帖子,值得一试)

于 2012-11-07T21:23:44.750 回答