65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (24投票s)

2014年11月8日

CPOL

14分钟阅读

viewsIcon

134607

downloadIcon

1948

一个几乎完整的未知类型被跨 EF、WCF 使用并在客户端消费的示例

引言

我有幸从事了我或大多数开发人员都不想从事的最复杂的事情之一。我以前也做过一些类似的项目,但都没有达到这次需求的要求程度。

阅读本文时,请注意,我不会提供各种测试和性能计算的实际构造,而是提供如何动态生成类、存储数据、通过 WCF 公开数据以及在客户端进行消费的最终结论。这是一个非常精简的解决方案,并且还在扩展中,因为我仍在进行中,并且仅用于提供一些关于思考过程的见解。

背景

简报/需求

应用程序可能接收或不接收包含某些构造的文件。如果不存在文件,用户必须能够提前进行配置。数据必须存储以供比较和查看。它必须快速且能够处理大量数据。数据必须可访问 REST (JSON),并且您必须能够通过浏览器查询数据。

关注点

  1. 一些数据
  2. REST
  3. 必须快速
  4. 大量数据

将受影响的组件

  1. 动态数据存储
  2. 动态 Web 服务
  3. 客户端动态数据处理

好的,基本上我需要“抓一些空气”并用它做点什么。现在这应该不难(带点讽刺意味)。

调查

  1. 数据库

第一个也是直接的方法是重新查看数据库的 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 被淘汰了,现在轮到传统的“宽”表了。

现在,请记住,我们永远不会知道接收到的表结构!!!!

 

  1. 类/对象使用

接下来是关于我如何使用数据以及如何为代码使用填充数据。我研究了一下 Expando,老实说不多,因为它在我看来,非常类似于拥有一个 EAV 结构,每个对象都有一个 Dictionary<string, object>。如果它对数据库来说很糟糕,那么在代码中它会不会一样糟糕?时间有限,我甚至不想去测试,因为我偶然发现了一篇关于如何使用 ILGenerator 在运行时创建动态类的文章。(包含代码)

好处是你可以构建一个类并使用 ILSpy 查看该类,然后构建一个构造来在运行时构建等效的代码,并比较结果。

我的结论是,从存储的元信息生成类和属性,并添加相关的属性,使其具有数据库和 WCF 可感知性,并为了万无一失,添加一个实现 INotifyPropertyChanged 接口的基类,并带有一个 ID(键)属性。

另一个巨大的好处是,如果将 ILGenerator 代码放入一个单独的程序集中,它就可以在服务器端和客户端“即时”生成类。

下一个大问题是,我作为开发人员想做什么?

嗯,其实有几个,而且都是我们通常在大多数应用程序开发中使用的所有好东西。

  1. 我想尽可能多地使用 Entity Framework,既然微软已经给了我们一个方便的 ORM,为什么还要自己编写呢?
  2. 我想为 Entity Framework 使用 WCF Data Services,因为它结合得很好,而且我无需编写各种 OperationContract 来公开,我只需用 Data Services 公开数据即可。

最重要的是,我将无法这样做,因为数据表和类将在运行时完成。

  1. 我想使用 Linq 来查询数据服务或非常接近它。

现在的问题是我为什么要这样做。嗯,答案很简单,每个开发人员都知道我提到的一些或所有构造,所以万一我被公交车撞倒了,别人必须能够继续工作,而无需学习一些糟糕的构造。

说了这么多,我们至少可以制定一些基本规则(现在这些规则可以随着系统的发展和约束的建立而改变,但在本例中不行)

  1. 您不能凭空创造任何东西,所以需要提供一些信息。
  2. 我们将需要 2 个构造,一个用于元信息,一个用于“动态”内容。
  3. 起初,我们不会处理任何关系构造。
  4. 一旦添加了数据,我们将不允许修改表。

好了,心中有了明确的图景,希望您也有,我将埋头苦干,慢慢地将这一切整合在一起。

提出的解决方案

首先,元信息。

我们将使用一些 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=&quot;data source=.;initial catalog=Dynamic;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" 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

关注点

这付出了相当大的努力,因此我想与我的同行们分享。

© . All rights reserved.