为 Entity Framework、WCF OData 和动态客户端消费在运行时动态生成未知类






4.97/5 (24投票s)
一个几乎完整的未知类型被跨 EF、WCF 使用并在客户端消费的示例
引言
我有幸从事了我或大多数开发人员都不想从事的最复杂的事情之一。我以前也做过一些类似的项目,但都没有达到这次需求的要求程度。
阅读本文时,请注意,我不会提供各种测试和性能计算的实际构造,而是提供如何动态生成类、存储数据、通过 WCF 公开数据以及在客户端进行消费的最终结论。这是一个非常精简的解决方案,并且还在扩展中,因为我仍在进行中,并且仅用于提供一些关于思考过程的见解。
背景
简报/需求
应用程序可能接收或不接收包含某些构造的文件。如果不存在文件,用户必须能够提前进行配置。数据必须存储以供比较和查看。它必须快速且能够处理大量数据。数据必须可访问 REST (JSON),并且您必须能够通过浏览器查询数据。
关注点
- 一些数据
- REST
- 必须快速
- 大量数据
将受影响的组件
- 动态数据存储
- 动态 Web 服务
- 客户端动态数据处理
好的,基本上我需要“抓一些空气”并用它做点什么。现在这应该不难(带点讽刺意味)。
调查
- 数据库
第一个也是直接的方法是重新查看数据库的 EAV(实体属性值)设计。我知道大多数开发人员(数据库管理员)会把枪指着我的头,因为这应该是最后的手段,我知道。但为了您的兴趣,您可以将元信息存储在表中,然后按行存储数据。
我们取了一个包含约 80,000 条记录和 35 个字段的文件作为测试基础,并将数据移植到一个普通的“宽”表和一个 EAV 类型表中,然后复制数据 4 次。省略空值后,resulting in just over 250,000 records in the “wide” table and over 7,900,000 in the EAV value table。(“宽”表中超过 250,000 条记录,EAV 值表中超过 7,900,000 条记录。)
该死的,我惊讶于从 EAV 结构中检索如此大量数据有多慢,难怪数据库管理员不喜欢这个想法。
好的,EAV 被淘汰了,现在轮到传统的“宽”表了。
现在,请记住,我们永远不会知道接收到的表结构!!!!
- 类/对象使用
接下来是关于我如何使用数据以及如何为代码使用填充数据。我研究了一下 Expando,老实说不多,因为它在我看来,非常类似于拥有一个 EAV 结构,每个对象都有一个 Dictionary<string, object>。如果它对数据库来说很糟糕,那么在代码中它会不会一样糟糕?时间有限,我甚至不想去测试,因为我偶然发现了一篇关于如何使用 ILGenerator 在运行时创建动态类的文章。(包含代码)
好处是你可以构建一个类并使用 ILSpy 查看该类,然后构建一个构造来在运行时构建等效的代码,并比较结果。
我的结论是,从存储的元信息生成类和属性,并添加相关的属性,使其具有数据库和 WCF 可感知性,并为了万无一失,添加一个实现 INotifyPropertyChanged 接口的基类,并带有一个 ID(键)属性。
另一个巨大的好处是,如果将 ILGenerator 代码放入一个单独的程序集中,它就可以在服务器端和客户端“即时”生成类。
下一个大问题是,我作为开发人员想做什么?
嗯,其实有几个,而且都是我们通常在大多数应用程序开发中使用的所有好东西。
- 我想尽可能多地使用 Entity Framework,既然微软已经给了我们一个方便的 ORM,为什么还要自己编写呢?
- 我想为 Entity Framework 使用 WCF Data Services,因为它结合得很好,而且我无需编写各种 OperationContract 来公开,我只需用 Data Services 公开数据即可。
最重要的是,我将无法这样做,因为数据表和类将在运行时完成。
- 我想使用 Linq 来查询数据服务或非常接近它。
现在的问题是我为什么要这样做。嗯,答案很简单,每个开发人员都知道我提到的一些或所有构造,所以万一我被公交车撞倒了,别人必须能够继续工作,而无需学习一些糟糕的构造。
说了这么多,我们至少可以制定一些基本规则(现在这些规则可以随着系统的发展和约束的建立而改变,但在本例中不行)
- 您不能凭空创造任何东西,所以需要提供一些信息。
- 我们将需要 2 个构造,一个用于元信息,一个用于“动态”内容。
- 起初,我们不会处理任何关系构造。
- 一旦添加了数据,我们将不允许修改表。
好了,心中有了明确的图景,希望您也有,我将埋头苦干,慢慢地将这一切整合在一起。
提出的解决方案
首先,元信息。
我们将使用一些 EAV 结构,但仅用于存储有关我们将生成和使用的表的信息。我们需要一个 Template 表,在本例中是目标表名,一个 Attribute 表,它告诉我们字段名是什么,以及一个 TemplateAttributes 表,它链接 Template 和 Attribute 表。添加此表是因为一个字段可能属于多个表,具有不同的字段属性。还有一个额外的 Type 查找表,以后可以用来在稍后阶段创建 .Net 和 SQL 类型。
在此示例中,我基于 CSV 文件中的信息,因此 Idx 列用于文件中的索引,以便以后使用。DisplayName 将用于在网格上显示不同的名称,通过一个帮助类构建网格,但这些都不会用于此示例。
表中其余的字段都应该被使用。
至少我们有东西可以开始工作了。
构建运行时类
现在我们准备好使用 ILGenerator 来构建我们的类了。
如前所述,我们将使用一个基类来实现 INotifyPropertyChanged 并公开一个 Id 属性。在这种情况下,我为基类创建了一个抽象类,以防以后需要它,并强制未来的实现遵守某些约束。
public abstract class DynamicEntity : INotifyPropertyChanged
{
public abstract int Id { get; set; }
public abstract event PropertyChangedEventHandler PropertyChanged;
public abstract void OnPropertyChanged(string propertyName);
}
public class BaseDynamicEntity : DynamicEntity
{
private int _id;
[DataMember, Key]
public override int Id
{
get
{
return _id;
}
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public override event PropertyChangedEventHandler PropertyChanged;
public override void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
现在我们可以开始构建类了,因为它们包含在引用中,所以没有讨论各种构建器的使用。
非常重要
对于程序集引用和 WCF Data Service,运行时生成的类必须引用相同的程序集,否则会出错。
我使用 Microsoft 提供的 WCF Data Services(来自 Nuget),对于此项目,您需要安装和使用。
主类中有各种重载和非泛型方法,但对于本文,以下方法将有效。这些方法是公共的,带有返回类型,因此您可以扩展、编写自己的方法并单独运行每个方法,看看它们是什么样子的。
public class DynamicClassFactory
{
private AppDomain _appDomain;
private AssemblyBuilder _assemblyBuilder;
private ModuleBuilder _moduleBuilder;
private TypeBuilder _typeBuilder;
private string _assemblyName;
public DynamicClassFactory() : this("Dynamic.Objects")
{
}
public DynamicClassFactory(string assemblyName)
{
_appDomain = Thread.GetDomain();
_assemblyName = assemblyName;
}
/// <summary>
/// This is the normal entry point and just return the Type generated at runtime
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <param name="properties"></param>
/// <returns></returns>
public Type CreateDynamicType<T>(string name, Dictionary<string, Type> properties) where T : DynamicEntity
{
var tb = CreateDynamicTypeBuilder<T>(name, properties);
return tb.CreateType();
}
/// <summary>
/// Exposes a TypeBuilder that can be returned and created outside of the class
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <param name="properties"></param>
/// <returns></returns>
public TypeBuilder CreateDynamicTypeBuilder<T>(string name, Dictionary<string, Type> properties)
where T : DynamicEntity
{
if (_assemblyBuilder == null)
_assemblyBuilder = _appDomain.DefineDynamicAssembly(new AssemblyName(_assemblyName),
AssemblyBuilderAccess.RunAndSave);
//vital to ensure the namespace of the assembly is the same as the module name, else IL inspectors will fail
if (_moduleBuilder == null)
_moduleBuilder = _assemblyBuilder.DefineDynamicModule(_assemblyName + ".dll");
//typeof(T) is for the base class, can be omitted if not needed
_typeBuilder = _moduleBuilder.DefineType(_assemblyName + "." + name, TypeAttributes.Public
| TypeAttributes.Class
| TypeAttributes.AutoClass
| TypeAttributes.AnsiClass
| TypeAttributes.Serializable
| TypeAttributes.BeforeFieldInit, typeof(T));
//various class based attributes for WCF and EF
AddDataContractAttribute();
AddTableAttribute(name);
AddDataServiceKeyAttribute();
//if there is a property on the base class and also in the dictionary, remove them from the dictionary
var pis = typeof(T).GetProperties();
foreach (var pi in pis)
{
properties.Remove(pi.Name);
}
//get the OnPropertyChanged method from the base class
var propertyChangedMethod = typeof(T).GetMethod("OnPropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
CreateProperties(_typeBuilder, properties, propertyChangedMethod);
return _typeBuilder;
}
public void AddDataContractAttribute()
{
Type attrType = typeof(DataContractAttribute);
_typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(attrType.GetConstructor(Type.EmptyTypes),
new object[] { }));
}
public void AddTableAttribute(string name)
{
Type attrType = typeof(TableAttribute);
_typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(attrType.GetConstructor(new[] { typeof(string) }),
new object[] { name }));
}
public void AddDataServiceKeyAttribute()
{
Type attrType = typeof(DataServiceKeyAttribute);
_typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(attrType.GetConstructor(new[] { typeof(string) }),
new object[] { "Id" }));
}
public void CreateProperties(TypeBuilder typeBuilder, Dictionary<string, Type> properties, MethodInfo raisePropertyChanged)
{
properties.ToList().ForEach(p => CreateFieldForType(p.Value, p.Key, raisePropertyChanged));
}
private void CreateFieldForType(Type type, String name, MethodInfo raisePropertyChanged)
{
FieldBuilder fieldBuilder = _typeBuilder.DefineField("_" + name.ToLowerInvariant(), type, FieldAttributes.Private);
PropertyBuilder propertyBuilder = _typeBuilder.DefineProperty(name, PropertyAttributes.HasDefault, type, null);
//add the various WCF and EF attributes to the property
AddDataMemberAttribute(propertyBuilder);
AddColumnAttribute(propertyBuilder);
MethodAttributes getterAndSetterAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;// | MethodAttributes.Virtual;
//creates the Get Method for the property
propertyBuilder.SetGetMethod(CreateGetMethod(getterAndSetterAttributes, name, type, fieldBuilder));
//creates the Set Method for the property and also adds the invocation of the property change
propertyBuilder.SetSetMethod(CreateSetMethod(getterAndSetterAttributes, name, type, fieldBuilder, raisePropertyChanged));
}
private void AddDataMemberAttribute(PropertyBuilder propertyBuilder)
{
Type attrType = typeof(DataMemberAttribute);
var attr = new CustomAttributeBuilder(attrType.GetConstructor(Type.EmptyTypes), new object[] { });
propertyBuilder.SetCustomAttribute(attr);
}
private void AddColumnAttribute(PropertyBuilder propertyBuilder)
{
Type attrType = typeof(ColumnAttribute);
var attr = new CustomAttributeBuilder(attrType.GetConstructor(Type.EmptyTypes), new object[] { });
propertyBuilder.SetCustomAttribute(attr);
}
private MethodBuilder CreateGetMethod(MethodAttributes attr, string name, Type type, FieldBuilder fieldBuilder)
{
var getMethodBuilder = _typeBuilder.DefineMethod("get_" + name, attr, type, Type.EmptyTypes);
var getMethodILGenerator = getMethodBuilder.GetILGenerator();
getMethodILGenerator.Emit(OpCodes.Ldarg_0);
getMethodILGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
getMethodILGenerator.Emit(OpCodes.Ret);
return getMethodBuilder;
}
private MethodBuilder CreateSetMethod(MethodAttributes attr, string name, Type type, FieldBuilder fieldBuilder, MethodInfo raisePropertyChanged)
{
var setMethodBuilder = _typeBuilder.DefineMethod("set_" + name, attr, null, new Type[] { type });
var setMethodILGenerator = setMethodBuilder.GetILGenerator();
setMethodILGenerator.Emit(OpCodes.Ldarg_0);
setMethodILGenerator.Emit(OpCodes.Ldarg_1);
setMethodILGenerator.Emit(OpCodes.Stfld, fieldBuilder);
if (raisePropertyChanged != null)
{
setMethodILGenerator.Emit(OpCodes.Ldarg_0);
setMethodILGenerator.Emit(OpCodes.Ldstr, name);
setMethodILGenerator.EmitCall(OpCodes.Call, raisePropertyChanged, null);
}
setMethodILGenerator.Emit(OpCodes.Ret);
return setMethodBuilder;
}
public void SaveAssembly()
{
_assemblyBuilder.Save(_assemblyBuilder.GetName().Name + ".dll");
}
}
现在我们已经完成了元信息以及在客户端和服务器端创建类的能力。
实体框架
接下来,我们需要读取元信息并为动态表添加第二个模型。这样做的原因是,元信息可以用 Model First 方法来完成,并且使用 EF 6.0,我们包含所有表。
这还将自动从 NuGet 添加 EntityFramework 6.1.1。
接下来,我们需要为读取未来动态表添加数据库上下文,为此,我们不能使用向导,也不能使用 DbSet<T>,因为我们永远不会提前知道这些类。
那么这意味着什么呢?我不得不告诉您,我们失去了 EF 的 ChangeTracker 功能,并且直到有方法添加它之前,它将不存在。这还不算世界末日!!!!
另一个问题是,我们没有用于将类映射到数据库的架构。得益于一位名叫 maxbeaudoin 的人编写的奇迹般的程序集,我们能够“神奇地”生成模型。所以获取 MagicDbModelBuilder,将其添加为引用,并在 using 下添加。
创建一个派生自 DbContext 的类,添加一个方法以从外部向上下文添加类型,并重写 OnModelCreating。在这些早期阶段,“Id”是硬编码的,但您可以查找 DataServiceKeyAttribute 并动态检查 HasKey 选项。由于抽象类设置了一些约束,我在硬编码“Id”键检查时是安全的。
public partial class DynamicDbContext : DbContext
{
public DynamicDbContext()
: base("name=DynamicDbContext")
{
Database.SetInitializer(new NullDatabaseInitializer<DynamicDbContext>());
}
public void AddTable(Type type)
{
_tables.Add(type.Name, type);
}
private Dictionary<string, Type> _tables = new Dictionary<string, Type>();
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var entityMethod = modelBuilder.GetType().GetMethod("Entity");
foreach (var table in _tables)
{
entityMethod.MakeGenericMethod(table.Value).Invoke(modelBuilder, new object[] { });
foreach (var pi in (table.Value).GetProperties())
{
if (pi.Name == "Id")
modelBuilder.Entity(table.Value).HasKey(typeof(int), "Id");
else
modelBuilder.Entity(table.Value).StringProperty(pi.Name);
}
}
}
}
非常重要
由于我们有两个模型,它们的构造不同,所以有两个连接字符串,一个包含 ssdl 信息,另一个是纯粹的传统数据库连接字符串,没有任何关于架构的信息。
WCF Data Service (服务器端)
与大多数开发人员不同,我不会将代码文件附加到我的 svc 文件,因为代码文件可以被制作成可移植的,也可以用于 asmx 服务,而且有很多关于此的文章。
现在我们需要 2 个类文件,再次是一个用于公开元信息,另一个用于动态表。对于这个示例,我将将其包含在 DynamicServerSide 程序集中,尽管它可以放在其他任何地方。
由于我还没有坐下来弄清楚 DataService<T> 的变化,我仍然在使用 EntityFrameworkDataService<T> 的预发布版本。这可以通过 NuGet 下载,只需确保设置为“Include PreRelease”(包含预发布),然后查找“WCF Data Services EntityFramework Provider”。
此外,我关心它的可用性,所以安全被省略了。
首先,我们有 MetadataService
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public partial class DynamicMetadataService : EntityFrameworkDataService<DynamicEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.AcceptProjectionRequests = true;
config.UseVerboseErrors = true;
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
}
其次,DynamicODataService
我特意将其设置为 OData,因为 ?$format=json 将自动根据简报公开 REST-JSON 格式(哇!)。
我还包括了一个简单的 CreateTable WebInvoke POST 方法,因此在将元数据保存到主数据库后,您可以告诉数据库实际创建表,没什么花哨的。需要注意的一点是,由于存在 2 个 EF 模型,您需要创建一个数据库连接来读取元数据并创建表。这并不是真正的缺点,因为您可以创建另一个数据库来只存储所有这些“动态”表,并将其与您的主数据库分开。
另一个需要注意的关键部分是 CreateDataSource() 的重写,这允许我们实际读取元信息并在运行时创建类。
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class DynamicODataService : EntityFrameworkDataService<DynamicDbContext>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceActionAccessRule("*", ServiceActionRights.Invoke);
config.DataServiceBehavior.AcceptProjectionRequests = true;
config.UseVerboseErrors = true;
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
protected override DynamicDbContext CreateDataSource()
{
var result = base.CreateDataSource();
var dcf = new DynamicClassFactory();
var context = new DynamicEntities();
var templates = (from t in context.DynamicTemplates.Include("DynamicTemplateAttributes").Include("DynamicTemplateAttributes.DynamicAttribute")
select t);
foreach (var dynamicTemplate in templates)
{
var type = CreateType(dcf, dynamicTemplate.Name, dynamicTemplate.DynamicTemplateAttributes);
result.AddTable(type);
}
return result;
}
private Type CreateType(DynamicClassFactory dcf, string name, ICollection<DynamicTemplateAttribute> dynamicAttributes)
{
var props = dynamicAttributes.ToDictionary(da => da.DynamicAttribute.Name, da => typeof(string));
var t = dcf.CreateDynamicType<BaseDynamicEntity>(name, props);
return t;
}
[WebInvoke(Method = "POST")]
public void CreateTable(string template)
{
var context = new DynamicEntities();
var qry = (from dt in context.DynamicTemplates.Include("DynamicTemplateAttributes")
.Include("DynamicTemplateAttributes.DynamicAttribute")
select dt).FirstOrDefault(dt => dt.Name == template);
if (qry == null)
throw new ArgumentException(string.Format("The template {0} does not exist", template));
var ct = new StringBuilder();
ct.AppendFormat("CREATE TABLE {0} (Id int IDENTITY(1,1) NOT NULL, ", qry.Name);
foreach (var dta in qry.DynamicTemplateAttributes)
{
ct.AppendFormat("{0} nvarchar(255) NULL, ", dta.DynamicAttribute.Name);
}
ct.AppendFormat("CONSTRAINT [PK_{0}] PRIMARY KEY CLUSTERED", qry.Name);
ct.AppendFormat("(Id ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF," +
"ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]");
var ts = ct.ToString();
CurrentDataSource.Database.ExecuteSqlCommand(ts);
}
}
现在添加一个 ASP.Net Web 项目,并确保对其应用 NuGet EntityFramework Provider,添加对 DynamicServerSide 项目的引用,并添加一个 svc 文件。在 svc 标记中添加以下内容:
<%@ ServiceHost Language="C#"
Factory="System.ServiceModel.Activation.WebServiceHostFactory, System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
Service="DynamicServerSide.DynamicODataService" %>
为 DynamicMetadataService 添加另一个 svc 文件并添加其相关的标记。
<%@ ServiceHost Language="C#"
Factory="System.ServiceModel.Activation.WebServiceHostFactory, System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
Service="DynamicServerSide.DynamicMetadataService" %>
出于简便性,我将表保留在同一个数据库中,以下是连接字符串:
<connectionStrings>
<add name="DynamicEntities" connectionString="metadata=res://*/DynamicModel.csdl|res://*/DynamicModel.ssdl|res://*/DynamicModel.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=Dynamic;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="DynamicDbContext" connectionString="data source=.;initial catalog=Dynamic;integrated security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
</connectionStrings>
下面是实际的元数据服务:
现在是激动人心的部分,你能猜到吗……
希望您猜到了,是的,我们实际上通过 WCF Data Service 公开了一个“未知动态生成的”强类型对象。这很棒,我们现在可以实际抓住浏览器并在地址栏中运行查询……大声疾呼吧!
这是服务和数据:
最后是 JSON 格式,通过键字段 3 检索数据:
{ "odata.metadata": "https://:60086/DynamicODataService.svc/$metadata#SomeTables/@Element", "Id": 3, "Firstname":"My","Surname":"Mother","Gender":"Female"}
现在这真的很棒
最后,客户端
我们如何通过客户端进行消费?本质上,您可以添加对已部署 Web 服务的引用,请记住,在设计时添加它实际上不会创建类,您必须运行服务来创建数据源。但是我们想动态地完成,而无需更改客户端代码。嗯,这需要大量工作。
首先,我们显然需要添加对元数据的服务引用,只需将其添加为普通 WCF 服务引用即可。
其次,我们必须手动为动态 OData 服务构建客户端。
添加对 DynamicClassGenerator 项目的引用,以便我们可以在客户端生成类型。
非常重要
注意 ResolveTypeFromName 和 ResolveNameFromType,这将实际将本地类型更改为服务器类型,反之亦然,并且不会抛出类型未找到的错误。另外两个关键方法是 LoadTypes 和 GetServiceQuery。LoadTypes 将实际创建类型,GetServiceQuery 用于构建服务器的查询指令。请注意,在 LoadTypes 中,生成的程序集被保存,如何保存取决于您的业务/部署逻辑。您可以基本上将其保存,并在服务器端没有更改时在运行时加载它。
public partial class ProxyODataService : DataServiceContext
{
public ProxyODataService(Uri serviceRoot)
: base(serviceRoot, global::System.Data.Services.Common.DataServiceProtocolVersion.V3)
{
this.ResolveName = new global::System.Func<global::System.Type, string>(this.ResolveNameFromType);
this.ResolveType = new global::System.Func<string, global::System.Type>(this.ResolveTypeFromName);
this.OnContextCreated();
}
partial void OnContextCreated();
protected string ResolveNameFromType(global::System.Type clientType)
{
if (clientType.Namespace.Equals("Dynamic.Objects", global::System.StringComparison.Ordinal))
{
return string.Concat("DynamicServerSide.", clientType.Name);
}
return null;
}
protected global::System.Type ResolveTypeFromName(string typeName)
{
global::System.Type resolvedType = this.DefaultResolveType(typeName, "DynamicServerSide", "Dynamic.Objects");
if ((resolvedType != null))
{
return resolvedType;
}
return null;
}
public void LoadTypes(IEnumerable<DynamicTemplate> templates)
{
var dcf = new DynamicClassFactory();
Types = new Dictionary<string, Type>();
foreach (var dynamicTemplate in templates)
{
var type = CreateType(dcf, dynamicTemplate.Name, dynamicTemplate.DynamicTemplateAttributes);
Types.Add(type.Name, type);
}
dcf.SaveAssembly();
}
public DataServiceQuery GetServiceQuery(string name)
{
var odd = typeof(ProxyODataService).GetMethod("CreateQuery");
var mi = odd.MakeGenericMethod(Types[name]);
var qr = mi.Invoke(this, new[] { name + "s" }) as DataServiceQuery; //assumes plural
return qr;
}
private static Type CreateType(DynamicClassFactory dcf, string name, DataServiceCollection<DynamicTemplateAttribute> dynamicAttributes)
{
var props = dynamicAttributes.ToDictionary(da => da.DynamicAttribute.Name, da => typeof(string));
var type = dcf.CreateDynamicType<BaseDynamicEntity>(name, props);
return type;
}
public Dictionary<string, Type> Types { get; private set; }
}
要从客户端使用服务,我们必须使用我偶然发现的另一个库,名为 Dynamic Linq Library 版本 1.1.13,可从 Microsoft 的 Nathan Arnott 处的 Nuget 获取。这使我们能够通过传递字符串来调用 WCF 服务,从而获得各种额外功能。
对于 WPF/Silverlight,我们有一些优势,绑定变得非常简单,所以对于网格,您只需要调用 Web 服务并取回一个动态的可查询集合。
我附加了一个小型 ViewModel,没什么花哨的,也不遵循原则(命令等),只是用于托管数据并将其绑定到 Grid。但客户端的使用很简单:
var meta = new DynamicEntities(new Uri("https://:60086/DynamicMetadataService.svc"));
var templates = (from t in meta.DynamicTemplates.Expand("DynamicTemplateAttributes/DynamicAttribute")
select t);
var odata = new ProxyODataService(new Uri("https://:60086/DynamicODataService.svc"));
odata.LoadTypes(templates);
var qry = odata.GetServiceQuery("SomeTable");
ViewModel.Data = new ObservableCollection<dynamic>(qry.AsQueryable().ToDynamicArray());
这是一个示例结果:
您也可以运行类似 qry.Where("Firstname=\"Igor\"") 这样的语句,您本来可以有预定义的查找策略或过滤类型条件。
我已能够将更改的属性保存回数据库,但这涉及到手动处理上下文中的对象状态,例如:odata.ChangeState(entity, EntityStates.Modified); 通过 odata.SaveChanges();
之所以这样做,是因为我们丢失了 ChangeTracker,可能需要自己实现一些东西。至少类具有属性更改通知,我可以对其进行挂钩以进行任何进一步的连接。
附加工作,现在将包括动态创建编辑屏幕等,但这可以存储在元数据等中,至少数据传输基本上得到了解决。
在下载中,您会找到代码项目和数据库,它是为 sql server 2014 准备的,但我包含了脚本(表和数据),您可以运行并自己测试,只需创建一个名为 Dynamic 的数据库,否则您需要更改连接字符串。
请注意,我测试了重新创建数据库和代码,一切正常!
构建解决方案将确保示例从 NuGet 中获取程序集。确保从 github 中获取。
附注:如果您想问我为什么没有使用 No-SQL 数据库。嗯,我没有时间,也没有时间去研究它,特别是编写各种服务和钩子,除非它开箱即用。
任何评论都将受到赞赏,特别是关于一些微调等,以及/或如何让丢失的组件工作。
参考文献
这些可能使用过,也可能没有使用过,但它们提供了洞察力,并且可能仍然会被纳入代码库,我可能忘记添加一两个,但我会在找到它们时添加。
https://codeproject.org.cn/Articles/121568/Dynamic-Type-Using-Reflection-Emit
https://codeproject.org.cn/Articles/13337/Introduction-to-Creating-Dynamic-Types-with-Reflec
http://msdn.microsoft.com/en-us/library/3y322t50.aspx
https://codeproject.org.cn/Articles/19513/Dynamic-But-Fast-The-Tale-of-Three-Monkeys-A-Wolf
https://grahammurray.wordpress.com/tag/reflection-emit/
http://www.codewrecks.com/blog/index.php/2008/08/04/implement-inotifypropertychanged-with-dynamic-code-generation/
http://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
https://codeproject.org.cn/Articles/131587/Building-Linq-Expressions-Dynamically
http://corememorydump.blogspot.com/2012/02/wcf-data-services-reflection-custom.html
http://blogs.msdn.com/b/alexj/archive/2010/01/04/creating-a-data-service-provider-part-1-intro.aspx
http://stackoverflow.com/questions/12525528/share-poco-types-between-wcf-data-service-and-client-generated-by-add-service-re
https://nuget.net.cn/packages/System.Linq.Dynamic.Library/1.1.13
http://dynamiclinq.azurewebsites.net/
http://www.remondo.net/repository-pattern-example-csharp/
http://blog.tonysneed.com/2013/11/18/trackable-entities-versus-self-tracking-entities/
以及最重要的 MagicDbModelBuilder (对我来说)
https://github.com/maxbeaudoin/MagicDbModelBuilder
关注点
这付出了相当大的努力,因此我想与我的同行们分享。