【发布时间】:2020-03-09 01:28:59
【问题描述】:
我想创建一个允许单独程序集(插件)在运行时向对象添加属性的系统。插件是插件,可以随时添加/删除。
基础实体
public class FooDto
{
public int Id { get; set; }
public string Description { get; set; }
...
}
此数据直接来自使用 EF Core 的 linq 投影的数据库,如下所示:
DbContext.Foos.Select(foo => new FooDto {
Id = foo.Id,
Description = foo.Description,
...
});
插件
插件可以创建以前不存在的新表和关系。假设有一个插件创建了一个名为“Bar”的表,我们想将 Bar 的描述添加到 FooDto。
public class PluginFoo : Foo
{
public Bar Bar { get; set; }
}
它会在某处定义这样的选择表达式:
pluginFoo => new PluginFooDto {
BarDescription = pluginFoo.Bar.Description
}
我可以让每个插件完全独立运行并触发自己的数据库查询,但我想尝试将它们全部组合成一个查询。
基本上,插件的表达式将共享的唯一内容是表达式参数将属于相同的基类。实际使用的类可能是包含插件使用的附加数据的派生类(如 PluginFoo 所示)。
理论上,可以创建一个结合基本 Select 和新 select 表达式的 SQL 语句。
我的问题来了,真的可以创建这样的系统吗?
我的思考过程:
- 我不会进行选择,而是使用一个名为“ProjectToAndExtend”的自定义扩展方法或其他我挂钩所有额外数据的方法。
- 基本 DTO 将实现一个接口,该接口具有一个名为“ExtendedValues”的属性。这可以是 IDictionary/object/dynamic。
- 每个插件都会定义一个返回“Expression>”的方法
- 在“ProjectToAndExtend”中,我会查看每个插件表达式并获取所有属性/投影,并创建一个包含所有插件表达式组合的运行时(代理?)类。
- 然后,我将创建一个新表达式,该表达式将使用所有提供的插件投影的组合投影到运行时类。
- 然后,我会将这个新表达式添加到原始选择的“ExtendedValues”属性中。
现在我对表达式构建和运行时类创建的了解有限。我最想知道这样的事情是否可能?
如果可能的话,我不希望任何人真正给我一个这样的工作示例。我只是想避免花费数小时来学习 Expressions/Reflection。Emit 只是为了发现这种系统完全不可能。
如果有人对如何做到这一点有任何更好/不同的想法,我很乐意倾听。
提前致谢!
编辑:
为了这个问题,我们假设数据库已经为所有插件提供了完整的架构。这个问题不是关于修改架构的步骤,而是关于查询数据。
我认为了解项目如何查询其数据可能会有所帮助,因此这里有一个 DbContext 设置示例。每个插件都有自己独立的上下文,只处理它使用的数据。
基础项目
public class BaseDbContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
}
在插件项目中,
public class PluginDbContext : DbContext
{
public DbSet<PluginFoo> Foos { get; set; }
}
2 个 DbContext 指向完全相同的数据库和表,但它们在架构上具有不同的范围(Foo 不知道 Bar,但 PluginFoo 知道)。
对于我的问题;在数据库中所有架构都正确的情况下,是否可以将 Bar 的数据附加到来自DbSet<foo> 的选择表达式中?
如果 Ef Core 无法做到这一点,是否可以直接使用 Linq-Sql?
【问题讨论】:
-
如果数据来自数据库,您希望插件如何“创建”额外的列?
-
@DavidG 每个插件都有自己的迁移,这些迁移将在安装/添加时运行。从技术上讲,当插件被删除时,这些额外的列将保留,但一旦它被卸载,数据就不再需要发送到客户端。
-
EF 有一个静态模型,因此“添加”任何内容、修改数据库都需要一些 EF 迁移过程。并且删除插件不会删除我猜的存储数据。这个问题太复杂了。在运行时创建类型是可能的,但这些类型也只能由运行时创建的代码或反射使用,如数据绑定。创建动态创建类型的 DbSet
?我认为这行不通。特别是因为数据库是持久的,但类型不是。即使是旧的 Database-First 方法,也是使用 C# 代码生成来创建 DbSet。 -
@Holger 这很可能无法做到。我认为这将取决于 Ef Core 如何使用表达式来构建 sql。我已经对问题添加了一个编辑,希望能澄清一点。我不太关心在这样的插件系统中是否可以修改数据库架构,在最坏的情况下,我们可以根据客户拥有的插件手动设置数据库架构。我最关心的是我们是否可以有通用代码来处理它的查询。
-
作为一个想法,对于少量数据,我一直在使用 XML-Columns 来处理这种情况。它们可以在不影响数据结构的情况下进行扩展。一些用户可以在 xml 中有更多行,而另一些则更少。如果您的插件不需要整个表,您可以使用属性列表、键值对以及可以存储所有内容的通用工具让生活变得更轻松。查询编译时未知的表是相对简单的事情,但您不支持 Entire EF-Relationship /Foreignkey/Migration。您可以使用 ObjectQuery 将其读取到动态类或使用 SqlReader 读取到表。
标签: c# entity-framework linq dynamic reflection.emit