views:

104

answers:

2

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.

A: 

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.

maartenba
Yeah, what I've found is that the Dynamic LINQ queries will try to run against the class you specify, so for me it was the GenericEntity type. If I could either dynamically determine the correct table record type (I have at least 8) and create a query based on that type, then I might be able to do it.Come to think of it, when creating the initial query, I might just be able to do a Convert.ChangeType in the CreateQuery selector function, that way I get the type I need when I perform the filter... I'll test it out and let you know the results.
SPFiredrake
A: 

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).

SPFiredrake