Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
I have an application that lets a user dynamically query any OData service and return to them the specific columns they requested inside of a grid. After weeks of research, I ended up using
Simple.OData.Client
to query the service. In order to get data back, I have model that defines what needs to be done. Here is what is relevant to my question:
BaseUrl (Service address)
ListName (table/list to query against)
Columns (List)
ODataColumnPath (Path to the data I want)
ColumnDataType (The type of data being returned / to be cast to)
FriendlyName (Something friendly to use)
CalculationType (Enum
None, Count, Sum, Min, Max
)
Now the
ODataColumnPath
can be as simple as "ProductName" or be as complex as "Category/Product/Orders/OrderID" to return a single field or to return many. When it returns many values I do some sort of calculation with it.
Currently I create a
DataTable
by recursively looping (while loop) through all
IEnumerable<IDictionary<string, object>>
s until I get the value(s) that I am looking for. I then use the XML data to create the DataTable columns and then I populate the rows from the loop. This approach works fine but I have to think there is a better way of doing this.
Now when I run the query I am looking for in LinqPad connecting directly to
a Northwind odata service
I get back a
IQueryable<Anonymous>
object.
LinqPad -> Northwind
Products.Select (x => new { x.ProductID, x.ProductName, x.Category.CategoryName })
Request URL
http://demos.telerik.com/kendo-ui/service/Northwind.svc/Products()?$expand=Category&$select=ProductID,ProductName,Category/CategoryName
Using the OData library mentioned above, I get back the same data but as IEnumerable<IDictionary<string, object>>
ODataClient client = new ODataClient(new ODataClientSettings { UrlBase = "http://demos.telerik.com/kendo-ui/service/Northwind.svc/", OnTrace = (a, b) => { string.Format(a, b).Dump("Trace Event"); } });
var data = await client.For("Products").Expand("Category").Select("ProductID,ProductName,Category/CategoryName").FindEntriesAsync().Dump();
Request URL (From trace event)
http://demos.telerik.com/kendo-ui/service/Northwind.svc/Products?$expand=Category&$select=ProductID,ProductName,Category/CategoryName
Now if I specify a strongly typed class, I get back the same as the IQueryable
did (with a little extra work):
var strongly = (await client
.For<Product>()
.Expand(x => x.Category)
.Select(x => new { x.ProductID, x.ProductName, x.Category.CategoryName })
.FindEntriesAsync())
.Select(x => new { x.ProductID, x.ProductName, x.Category.CategoryName })
.Dump();
What I would like to get back is an anonymous list object or a dynamic object. From there I can apply my calculations if needed. Is there a way to dynamically define a class and pass it into the For<T>(...)
static method?
Even though I spent weeks researching this topic to end up using Simple.OData.Client, I am open to using some other method of getting my data.
I ended up finding LatticeUtils AnonymousTypeUtils.cs which creates an anonymous object from a Dictionary to create an anonymous object.
I ended up modifying the first CreateObject
method with the following
public static object CreateObject(IDictionary<string, object> valueDictionary)
Dictionary<string, object> values = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> pair in valueDictionary)
if (pair.Value != null && pair.Value.GetType() == typeof(Dictionary<string, object>))
// Create object and add
object o = CreateObject(pair.Value as IDictionary<string, object>);
values.Add(pair.Key, o);
else if (pair.Value != null && pair.Value.GetType() == typeof(List<IDictionary<string, object>>))
// Get first dictionary entry
IDictionary<string, object> firstDictionary = ((IEnumerable<IDictionary<string, object>>)pair.Value).First();
// Get the base object
object baseObject = CreateObject(firstDictionary);
// Create a new array based off of the base object
Array anonArray = Array.CreateInstance(baseObject.GetType(), 1);
// Return like the others
values.Add(pair.Key, anonArray);
values.Add(pair.Key, pair.Value);
Dictionary<string, Type> typeDictionary = values.ToDictionary(kv => kv.Key, kv => kv.Value != null ? kv.Value.GetType() : typeof(object));
Type anonymousType = CreateType(typeDictionary);
return CreateObject(values, anonymousType);
If you strip out all of the unused methods, comments, and variables like mutable
I ended up with 160ish lines of readable code.
In order to read the data I am still using Simple.OData.Client to get my data but I am serializing the object to JSON, creating the anonymous object, and then deserializing everything back into an IEnumerable
. With this I am able to process 1000 records from my OData service in about 0.35 seconds or so.
// Get Data
ODataClient client = new ODataClient(new ODataClientSettings { UrlBase = "http://demos.telerik.com/kendo-ui/service/Northwind.svc/", OnTrace = (a, b) => { string.Format(a, b).Dump("Trace Event"); } });
IEnumerable<IDictionary<string, object>> data = await client
.For("Products")
.Expand("Category,Order_Details")
.Select("ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued,Category/CategoryName,Order_Details")
.FindEntriesAsync();
// Convert to JSON
string json = JsonConvert.SerializeObject(data);
// Create anonymous type/object
object anonymousObject = AnonymousClassBuilder.CreateObject(data.First());
// Deserialize into type
IEnumerable enumerable = (IEnumerable)JsonConvert.DeserializeObject(json, anonymousObject.GetType().MakeArrayType());
I might end up creating a Fork of Simple.OData.Client and adding this to it so I don't have to serialize the object back to JSON and then back to an object.
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.