【问题标题】:Create properties of class and project to them at the runtime在运行时为它们创建类和项目的属性
【发布时间】:2017-12-25 07:29:51
【问题描述】:

我在运行时从存储过程中获取来自DataTable 的列。代码如下所示:

var parkDataTable = new DataTable("tmp");
...
SqlCommand cmd = new SqlCommand("FooStoredProcedure", db.Database.Connection as 
    SqlConnection, transaction.UnderlyingTransaction as SqlTransaction);
cmd.CommandType = CommandType.StoredProcedure;    
dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
parkDataTable.Load(dr);

有趣的是,我的列可以通过在列名称中添加日期来具有各种名称。比如我有一个列名2017-09-01_FactPark,那么下次可以是2017-10-01_FactPark。请看下图的列:

我可以在运行时知道列名和列数。

是否可以在运行时创建一个将上述列作为属性的类,并像往常一样将它们投影到这些属性:

public IQueryable<EmployeeDTO> GetEmployees()
{
    return db.Employees
        .Select(employee => new EmployeeDTO
        {
            ID = employee.ID,                    
            FirstName = employee.PriceNumber,
            LastName = employee.PriceDate                                        
        }).AsQueryable();
}

所以我的目标是创建具有DataTable 属性的类,并将DataTable 列投影到具有属性的类。

是否可以在运行时创建属性并将DataTable 列投影到新创建的类属性?

【问题讨论】:

  • 2017-0901_FactPark, 2017-10-01_FactPark...有人需要规范化...
  • 您不能或不允许? :)
  • @JohnyL 列名由存储过程的SELECT 语句创建,我不允许更改此SELECT 语句。
  • 我认为您在尝试将动态列强制转换为静态定义的属性时走错了路。为什么不将动态字段公开为键/值集合?这将允许您在不重新编译代码的情况下枚举和查找值。 IE。存储过程可以更改,但您的代码不需要。
  • 您打算如何使用查询结果?确保您不能在代码中使用该类,因为属性是在运行时生成的

标签: c# entity-framework linq select datatable


【解决方案1】:

动态构成类形状的一种方法是使用DynamicObject 类。您可以创建自己的动态类,该类将设置和获取属性及其值。

现在,让我们实现我们的动态类:

using System.Dynamic;

class Dynamo : DynamicObject
{
    private Dictionary<string, object> items = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return items.TryGetValue(binder.Name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        items[binder.Name] = value;
        return true;
    }

    public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
    {
        string propName = indexes[0] as string;
        items[propName] = value;
        return true;
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        string propName = indexes[0] as string;
        return items.TryGetValue(propName, out result);
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return items.Keys;
    }
}

我们在这里拥有什么:

  1. items 字典。允许我们存储任何值并通过其名称获取它。
  2. TryGetMember()/TrySetMember() 方法。该组允许我们使用Dot-Property 表示法设置/获取值:Class.Property
  3. TrySetIndex()/TryGetIndex() 方法。该组允许我们使用 Indexer 表示法设置/获取值:Class[varHoldingPropName]Class["propName"]
  4. GetDynamicMemberNames() 方法。允许检索所有属性名称。

为了设置属性名称,您必须使用Indexer 表示法,因为使用Dot-Property 表示法,绑定器将使用您的属性名称作为标识符名称,而Indexer 将评估您的变量:

static void UseDynamicObject()
{

    var colorProperty = "Color";
    var ageProperty = "Age";

    dynamic dynamo = new Dynamo();
    dynamo.colorProperty = "red";
    dynamo[ageProperty] = 20;

    // DOT-PROPERTY NOTATION
    // Error: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
    // 'Dynamo' does not contain a definition for 'Color'.
    Console.WriteLine(dynamo.Color);
    // The property used to retrieve value "red" is "colorProperty" rather "Color".
    Console.WriteLine(dynamo.colorProperty);

    // INDEXER NOTATION
    // We can use either variable or literal name of the property,
    // so these two lines are equivalent.
    Console.WriteLine(dynamo[ageProperty]);
    Console.WriteLine(dynamo["Age"]);

}

应用程序的逻辑如下:您可以从SqlDataReader 获取列名,并将它们用于在您的动态对象上设置它们。

这是使用 SqlDataReader 和我们的动态类的一种可能变体:

static void Main(string[] args)
{

    var data = new List<dynamic>();
    List<string> cols = default;

    using (var conn = new SqlConnection(connStr))
    {
        conn.Open();
        using (var comm = new SqlCommand("SELECT * FROM dbo.Client", conn))
        {
            using (var reader = comm.ExecuteReader())
            {
                // Get columns names
                cols = Enumerable.Range(0, reader.FieldCount)
                       .Select(i => reader.GetName(i)).ToList();
                while (reader.Read())
                {
                    dynamic obj = new Dynamo();
                    cols.ForEach(c => obj[c] = reader[c]);
                    data.Add(obj);
                }
            }
        }
    }


    /* OUTPUT DATA */

    // To get columns names, you can:
    // 1) use ready "cols" variable
    // 2) use "GetDynamicMemberNames()" on first object:
    //    IEnumerable<string> cols = data[0].GetDynamicMemberNames();
    foreach (var obj in data)
    {
        Console.WriteLine(String.Join(", ", cols.Select(c => $"{c} = {obj[c]}")));
    }

    // Output:
    // ClientId = 1, ClientName = Client1
    // ClientId = 2, ClientName = Client2

}

【讨论】:

  • 没有机会测试,但我相信你:)
  • @JohnyL 很好,两个月前我也在想同样的问题,但没有发布它可以使用ExpandoObjects 来完成吗?
  • @Yollo Expando 不允许您使用 Indexer,即您不能这样写:exp["Age"] = 100。错误显示:Cannot apply indexing with [] to an expression of type ExpandoObject
猜你喜欢
  • 2018-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
相关资源
最近更新 更多