这就是我的困境。我正在尝试利用动态 LINQ 来解析搜索过滤器,以从 Azure 表中检索一组记录。目前,我可以使用定义如下的 GenericEntity 对象来获取所有记录:
public class GenericEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
Dictionary<string, object> properties = new Dictionary<string, object>();
/* "Property" property and indexer property omitted here */
}
我可以通过利用 TableServiceContext 对象的 ReadingEntity 事件(称为 OnReadingGenericEvent)来完全填充该记录。以下代码实际上是提取所有记录和希望过滤器(一旦我让它工作)。
public IEnumerable<T> GetTableRecords(string tableName, int numRecords, string filter)
{
ServiceContext.IgnoreMissingProperties = true;
ServiceContext.ReadingEntity -= LogType.GenericEntity.OnReadingGenericEntity;
ServiceContext.ReadingEntity += LogType.GenericEntity.OnReadingGenericEntity;
var result = ServiceContext.CreateQuery<GenericEntity>(tableName).Select(c => c);
if (!string.IsNullOrEmpty(filter))
{
result = result.Where(filter);
}
var query = result.Take(numRecords).AsTableServiceQuery<GenericEntity>();
IEnumerable<GenericEntity> res = query.Execute().ToList();
return res;
}
我定义的所有表都有 TableServiceEntity 派生类型,因此我可以使用反射获取所有属性/类型。在动态 LINQ 查询中使用 GenericEntity 类进行过滤的问题是 GenericEntity 对象没有我尝试过滤的任何属性,因为它们实际上只是字典条目(动态查询错误)。我可以解析出该特定类型的所有属性名称的过滤器,并包装
"Property[" + propName + "]"
每个属性(通过使用类型解析器函数和反射找到)。然而,这似乎有点……矫枉过正。我试图找到一个更优雅的解决方案,但由于我实际上必须在 ServiceContext.CreateQuery<> 中提供一个类型,这使得它变得有些困难。
所以我想我的最终问题是:如何将动态类或泛型类型与此构造一起使用,以便能够利用动态查询进行过滤?这样我就可以从文本框中获取过滤器(例如“item_ID > 1023000”)并动态生成 TableServiceEntity 类型。
我还可以利用其他方法来解决这个问题,但我想既然我开始使用动态 LINQ,不妨也尝试一下动态类。
编辑:所以我已经通过初始选择使用一些反射生成了动态类,但是在将 GenericEntity.Properties 的类型映射到各种关联的表记录类中时遇到了障碍( TableServiceEntity 派生类)及其属性类型。主要问题仍然是我最初必须使用特定的数据类型来创建查询,因此我使用仅包含 KV 对的 GenericEntity 类型。这最终阻止了我进行过滤,因为我无法对对象类型进行比较运算符(>、<、= 等)。
下面是我现在用于映射到动态类的代码:
var properties = newType./* omitted */.GetProperties(
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public);
string newSelect = "new(" + properties.Aggregate("", (seed, reflected) => seed += string.Format(", Properties[\"{0}\"] as {0}", reflected.Name)).Substring(2) + ")";
var result = ServiceContext.CreateQuery<GenericEntity>(tableName).Select(newSelect);
也许我应该修改properties.Aggregate 方法,以在“Properties[...]”部分前面加上reflected.PropertyType?因此,新的选择字符串将如下所示:
string newSelect = "new(" + properties.Aggregate("", (seed, reflected) => seed += string.Format(", ({1})Properties[\"{0}\"] as {0}", reflected.Name, reflected.PropertyType)).Substring(2) + ")";
编辑 2: 所以现在我遇到了很大的障碍。我可以为所有表生成匿名类型来提取我需要的所有值,但是无论我对过滤器做什么,LINQ 都会对我产生影响。我已经在上面说明了原因(对象上没有比较运算符),但我现在一直在努力解决的问题是尝试为动态 LINQ 扩展方法指定一个类型参数以接受新对象类型的架构。那里也不太走运……我会随时通知大家。
So here's my dilemma. I'm trying to utilize Dynamic LINQ to parse a search filter for retrieving a set of records from an Azure table. Currently, I'm able to get all records by using a GenericEntity object defined as below:
public class GenericEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
Dictionary<string, object> properties = new Dictionary<string, object>();
/* "Property" property and indexer property omitted here */
}
I'm able to get this completely populated by utilizing the ReadingEntity event of the TableServiceContext object (called OnReadingGenericEvent). The following code is what actually pulls all the records and hopefully filter (once I get it working).
public IEnumerable<T> GetTableRecords(string tableName, int numRecords, string filter)
{
ServiceContext.IgnoreMissingProperties = true;
ServiceContext.ReadingEntity -= LogType.GenericEntity.OnReadingGenericEntity;
ServiceContext.ReadingEntity += LogType.GenericEntity.OnReadingGenericEntity;
var result = ServiceContext.CreateQuery<GenericEntity>(tableName).Select(c => c);
if (!string.IsNullOrEmpty(filter))
{
result = result.Where(filter);
}
var query = result.Take(numRecords).AsTableServiceQuery<GenericEntity>();
IEnumerable<GenericEntity> res = query.Execute().ToList();
return res;
}
I have TableServiceEntity derived types for all the tables that I have defined, so I can get all properties/types using Reflection. The problem with using the GenericEntity class in the Dynamic LINQ Query for filtering is that the GenericEntity object does NOT have any of the properties that I'm trying to filter by, as they're really just dictionary entries (dynamic query errors out). I can parse out the filter for all the property names of that particular type and wrap
"Property[" + propName + "]"
around each property (found by using a type resolver function and reflection). However, that seems a little... overkill. I'm trying to find a more elegant solution, but since I actually have to provide a type in ServiceContext.CreateQuery<>, it makes it somewhat difficult.
So I guess my ultimate question is this: How can I use dynamic classes or generic types with this construct to be able to utilize dynamic queries for filtering? That way I can just take in the filter from a textbox (such as "item_ID > 1023000") and just have the TableServiceEntity types dynamically generated.
There ARE other ways around this that I can utilize, but I figured since I started using Dynamic LINQ, might as well try Dynamic Classes as well.
Edit: So I've got the dynamic class being generated by the initial select using some reflection, but I'm hitting a roadblock in mapping the types of GenericEntity.Properties into the various associated table record classes (TableServiceEntity derived classes) and their property types. The primary issue is still that I have to initially use a specific datatype to even create the query, so I'm using the GenericEntity type which only contains KV pairs. This is ultimately preventing me from filtering, as I'm not able to do comparison operators (>, <, =, etc.) with object types.
Here's the code I have now to do the mapping into the dynamic class:
var properties = newType./* omitted */.GetProperties(
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public);
string newSelect = "new(" + properties.Aggregate("", (seed, reflected) => seed += string.Format(", Properties[\"{0}\"] as {0}", reflected.Name)).Substring(2) + ")";
var result = ServiceContext.CreateQuery<GenericEntity>(tableName).Select(newSelect);
Maybe I should just modify the properties.Aggregate method to prefix the "Properties[...]" section with the reflected.PropertyType? So the new select string will be made like:
string newSelect = "new(" + properties.Aggregate("", (seed, reflected) => seed += string.Format(", ({1})Properties[\"{0}\"] as {0}", reflected.Name, reflected.PropertyType)).Substring(2) + ")";
Edit 2: So now I've hit quite the roadblock. I can generate the anonymous types for all tables to pull all values I need, but LINQ craps out on my no matter what I do for the filter. I've stated the reason above (no comparison operators on objects), but the issue I've been battling with now is trying to specify a type parameter to the Dynamic LINQ extension method to accept the schema of the new object type. Not much luck there, either... I'll keep you all posted.
发布评论
评论(3)
我创建了一个简单的基于 System.Refection.Emit 的解决方案来创建运行时所需的类。
http://blog.kloud.com.au/ 2012/09/30/a-better-dynamic-tableserviceentity/
I've created a simple System.Refection.Emit based solution to create the class you need at runtime.
http://blog.kloud.com.au/2012/09/30/a-better-dynamic-tableserviceentity/
我遇到了完全相同的问题(使用几乎相同的代码:-))。我怀疑下面的 ADO.NET 类在某种程度上不与动态类型配合,但尚未找到确切的位置。
I have run into exactly the same problem (with almost the same code :-)). I have a suspicion that the ADO.NET classes underneath somehow do not cooperate with dynamic types but haven't found exactly where yet.
所以我找到了一种方法来做到这一点,但它不是很漂亮...
由于我无法在框架本身内真正做我想做的事情,所以我利用了 AzureTableQuery 项目。我几乎只有一个大型 C# 代码字符串,它可以使用我需要的确切对象进行动态编译。如果您查看 AzureTableQuery 项目的代码,您将看到在飞向我们拥有的任何表,它会在我们查询表时遍历并构建我们需要的所有属性和内容。这不是最优雅或最轻量的解决方案,但它仍然有效。
真心希望有更好的方法来做到这一点,但不幸的是它并不像我希望的那么容易。希望有人能够从这次经验中学习,并可能找到更好的解决方案,但我已经有了我需要的东西,所以我已经完成了它的工作(暂时)。
So I've found a way to do this, but it's not very pretty...
Since I can't really do what I want within the framework itself, I utilized a concept used within the AzureTableQuery project. I pretty much just have a large C# code string that gets compiled on the fly with the exact object I need. If you look at the code of the AzureTableQuery project, you'll see that a separate library is compiled on the fly for whatever table we have, that goes through and builds all the properties and stuff we need as we query the table. Not the most elegant or lightweight solution, but it works, nevertheless.
Seriously wish there was a better way to do this, but unfortunately it's not as easy as I had hoped. Hopefully someone will be able to learn from this experience and possibly find a better solution, but I have what I need already so I'm done working on it (for now).