添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
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.