2

I have some methods that execute arbitrary SQL against a database and serialize that data collection into a list of a concrete type. That data is then serialized into JSON and stored in a cell in a table. Later, I need to come back and deserialize that data back into its original collection so that it can be used.

I'm having some issues figuring out how to take a Type object and create a collection of that type in order to deserialize it. Here is how my code operates:

public async Task ExecuteWidget(Guid runGuid, string widgetName, Type type, string sql,
    IEnumerable<SqlParameter> parameters)
{
    var report = operationsContext.ReportRuns.FirstOrDefault(n => n.RunGuid == runGuid);

    CheckReportStatus(report);

    var param = parameters.ToList();
    var result = edwContext.Database.SqlQuery(type, sql, param.ToArray<object>());
    var query = result.GetQuery(param);
    var data = await result.ToListAsync();
    var widgetData = new ReportRunWidgetData()
    {
        ReportRunId = report?.ReportRunId ?? -1, // This should never be null.
        WidgetName = widgetName,
        WidgetData = new JavaScriptSerializer().Serialize(data),
        Query = query
    };

    operationsContext.ReportRunWidgetDatas.Add(widgetData);
    await operationsContext.SaveChangesAsync();
}

My fetching logic looks something like this:

public object FetchWidgetData(Guid runGuid, string widgetName, Type dataType)
{
    var data = operationsContext.ReportRuns
        .Include("ReportRunWidgetDatas")
        .FirstOrDefault(n => n.RunGuid == runGuid)?
        .ReportRunWidgetDatas.FirstOrDefault(n => n.WidgetName == widgetName)?
        .WidgetData;

    if (data == null) return null;

    var deserialized = new JavaScriptSerializer().Deserialize(data, dataType);

    return deserialized;
}

Now when the ExecuteWidget method is called, the type parameter is populated by the widget's DTO datatype. For example HeadlineWidgetDTO. However, the execute command gets the data back as a List<HeadlineWidgetDTO>. When my FetchWidgetData method is called, the dataType supplied is still HeadlineWidgetDTO, but it actually needs to be of type IEnumerable<HeadlineWidgetDTO> to deserialize properly.

Given just the type for an individual data row, how can I create a Type object that is instead a collection of that type?

JD Davis
  • 3,517
  • 4
  • 28
  • 61
  • 1
    FYI `List` deserialization is supported in Json.NET, which is what you _should_ be using. Microsoft's [own documentation](https://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer(v=vs.110).aspx) tells you to use Json.NET for serializing/deserializing – maccettura Aug 03 '18 at 19:43
  • @maccettura that still doesn't address how to go from a `Type` for `HeadlineWidgetDTO` to `IEnumerable`. I do not believe Json.Net will handle that for me. – JD Davis Aug 03 '18 at 20:04
  • 2
    Is this what you are talking about: https://stackoverflow.com/questions/9140873/how-to-use-activator-to-create-an-instance-of-a-generic-type-and-casting-it-back ? You can use `Type.MakeGenericType` and `Activator.CreateInstance` to create a `List` from a `T` – Flydog57 Aug 03 '18 at 20:13
  • @Flydog57 precisely. If you want to craft that into an answer, I'll go ahead and mark it. – JD Davis Aug 03 '18 at 20:17
  • Also in EF Core you can embed your JSON serializer into a Value Conversion https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions – David Browne - Microsoft Aug 03 '18 at 20:18
  • @JDDavis: I added another way to do this to my answer. – Flydog57 Aug 08 '18 at 16:29

1 Answers1

3

This is mostly a duplicate of How to use Activator to create an instance of a generic Type and casting it back to that type?, however it's hard to tell.

Basically, if you have a type object Type theType, you need to do something like:

 var listType = typeof(List<>);
 var typeParams = new [] {theType};
 var listOfTType = listType.MakeGenericType(typeParams);
 var newListOfT = Activator.CreateInstance(listOfTType);

At that point, you have a variable of type object, but that references an object of type List<WhateverYourTypeIs>. Say, theType is typeof(int), then you will have an object of List<int>. Casting it to something usuable is a whole other question though. If you want to add something to that list, I suspect the best way would be to get a MethodInfo for the Add method and Invoke it.

I thought of another way to do this if the type has a default constructor and isn't too expensive to create. Here's a sample (creating a List<int> - but that's just the way I have it coded):

  var type = typeof(int);
  var dummy = Activator.CreateInstance(type);
  var listOfType = new[] {dummy}.ToList();

When you are finished, the listOfType variable is typed as a List<object> but refers to a List<int>. It's mostly mostly workable - for example, you can call Add(object someObj) on it. You won't get compile type parameter type checking, but you will be able to use it.

Flydog57
  • 6,851
  • 2
  • 17
  • 18