0

This is a bit of a multi-faceted question, so let me just dive into the code with some explanation at the bottom.

Example Sample Data:

List<Encounter> input
Id, Facility, HospitalService, Field3, Field4, Field5, ...
 1,  1,       A,               ...,    ...,    ...
 2,  2,       A,               ...,    ...,    ...
 3,  1,       B,               ...,    ...,    ...
 4,  2,       B,               ...,    ...,    ...
 5,  1,       A,               ...,    ...,    ...
 5,  2,       A,               ...,    ...,    ...

What I want to do is query my data object and e.g. return back the distinct fields, e.g.

distinct Facility is 1, 2
distinct HospitalService is A, B
distinct pair is 1A, 2A, 1B, 2B

However, one gotcha is I want to return back a strongly typed object, and in this case, the same object as the input, in this case, the Encounter object, with all the other fields with empty or default values, e.g.

List<Encounter> output, with only fields of interest populated
Id, Facility, HospitalService, Field3, Field4, Field5, ...
 0,  1,       A,               "",     "",     ""
 0,  2,       A,               "",     "",     ""
 0,  1,       B,               "",     "",     ""
 0,  2,       B,               "",     "",     ""

With Standard LINQ, I can do this and it works.

    List<Encounter> sampleData = CreateSampleData();
    List<Encounter> rawResultFromStandardLinq =
        sampleData
            .GroupBy(e => new {e.Facility, e.HospitalService})
            .Select(e => new Encounter() { Facility = e.Key.Facility, HospitalService = e.Key.HospitalService})
            .ToList();

Question #1: In the above example, it is not dynamic. I had to know which object to create with the new keyword. Further, I had to know which fields to select/project. How can I do this dynamically? How can I project an anonymous type to a strong type?

e.g. I thought I could do something like this to use json serialization. This works, but I presume it would be very slow.

    var rawResultAsAnonymousType =
        sampleData
            .GroupBy(e => new { e.Facility, e.HospitalService })
            .Select(e => new { e.Key.Facility, e.Key.HospitalService }) 
            .ToList();

    string json = JsonConvert.SerializeObject(rawResultAsAnonymousType);
    var encountersFromJson = JsonConvert.DeserializeObject<List<Encounter>>(json);

Question #2: The next problem we have is that we want the query to be dynamic. i.e. we want to expose an interface that will let the client query the data to get whatever they want. For that purpose, we have turned to Dynamic LINQ.

Can someone help me to get this to work?

[Update: I can now do this for multiple columns]
            var rawResultFromDynamicLinq4 =
                DynamicQueryable
                    .GroupBy(_sampleData.AsQueryable(), @"new (Facility, HospitalService)", "it")
                    .Select("new (it.Key.Facility, it.Key.HospitalService)")
                    ;

[Before, I was trying to do this]
        var rawResultFromDynamicLinq =
            sampleData
                .GroupByMany("Facility", "HospitalService")
                //.Select(.... how do I get my object back?)
            ;

Some explanation:

Why are we doing it like this? It's largely irrelevant to the technical question, but if you must know, I work in healthcare and we are using the FHIR standards to query data, so we have to use the defined FHIR models. This means we can't just return back a List that contains distinct values of a particular field (e.g. for the client to create dropdown values for filtering the data).

Raymond
  • 3,382
  • 5
  • 43
  • 67

3 Answers3

3

How can I project an anonymous type to a strong type?

Well, an anonymous type is a strong type - you just don't know the name of the type at compile time (hence "anonymous"). So sure, you can project to a different type and use tools like AutoMapper to map the anonymous type to a different type but you still have to know the fields at compile-time.

You might be able to do something like:

    sampleData
        .GroupBy(e => new {e.Facility, e.HospitalService})
        .Select(g => g.First())
        .ToList();

but it's not clear if that's exactly what you're looking for (and you still need to know the "grouping" fields at compile-time).

How can I do this dynamically?

What do you mean by "dynamically"? You mean have something that automatically sets the properties based on the destination type?

Can someone help me to get this to work?

Again, if you just want the first item that matches each grouping condition you can do

var rawResultFromDynamicLinq =
    sampleData
        .GroupByMany("Facility", "HospitalService")
        .Select(g -> g.First())
    ;
D Stanley
  • 149,601
  • 11
  • 178
  • 240
1

Answer to #2: There really is not such thing as dynamic linq; however, there are some third party libraries to do it. Also built in to .Net there are 2 options. 1st there is the ability to execute dynamic SQL (this is a bad idea). The 2nd option is Expression Trees. This could work for question 1 but the type can't be dynamic.

     // Add a using directive for System.Linq.Expressions. 

        string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
                           "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
                           "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
                           "Blue Yonder Airlines", "Trey Research", "The Phone Company",
                           "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

        // The IQueryable data to query.
        IQueryable<String> queryableData = companies.AsQueryable<string>();

        // Compose the expression tree that represents the parameter to the predicate.
        ParameterExpression pe = Expression.Parameter(typeof(string), "company");

        // ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
        // Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
        Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
        Expression right = Expression.Constant("coho winery");
        Expression e1 = Expression.Equal(left, right);

        // Create an expression tree that represents the expression 'company.Length > 16'.
        left = Expression.Property(pe, typeof(string).GetProperty("Length"));
        right = Expression.Constant(16, typeof(int));
        Expression e2 = Expression.GreaterThan(left, right);

        // Combine the expression trees to create an expression tree that represents the 
        // expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
        Expression predicateBody = Expression.OrElse(e1, e2);

        // Create an expression tree that represents the expression 
        // 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
        MethodCallExpression whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { queryableData.ElementType },
            queryableData.Expression,
            Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
        // ***** End Where ***** 

        // ***** OrderBy(company => company) ***** 
        // Create an expression tree that represents the expression 
        // 'whereCallExpression.OrderBy(company => company)'
        MethodCallExpression orderByCallExpression = Expression.Call(
            typeof(Queryable),
            "OrderBy",
            new Type[] { queryableData.ElementType, queryableData.ElementType },
            whereCallExpression,
            Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
        // ***** End OrderBy ***** 

        // Create an executable query from the expression tree.
        IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);

        // Enumerate the results. 
        foreach (string company in results)
            Console.WriteLine(company);

        /*  This code produces the following output:

            Blue Yonder Airlines
            City Power & Light
            Coho Winery
            Consolidated Messenger
            Graphic Design Institute
            Humongous Insurance
            Lucerne Publishing
            Northwind Traders
            The Phone Company
            Wide World Importers
        */

https://msdn.microsoft.com/en-us/library/bb882637.aspx

tdbeckett
  • 618
  • 8
  • 19
  • And Scott Gu has a blog: http://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library – Raymond Sep 11 '15 at 17:00
0

This is possible with some modifications to System.Linq.Dynamic.Library

See changed code here :https://gist.github.com/de1e6c5e758e15cc9154.git and here https://gist.github.com/d166f17cd672b696b916.git

You can now use this like:

var sampleData = new List<Encounter>
{
    new Encounter {Id = "1", Language = "1", VersionId = "A"},
    new Encounter {Id = "2", Language = "2", VersionId = "A"},
    new Encounter {Id = "3", Language = "1", VersionId = "B"},
    new Encounter {Id = "4", Language = "2", VersionId = "B"},
    new Encounter {Id = "5", Language = "1", VersionId = "A"},
    new Encounter {Id = "6", Language = "2", VersionId = "A"}
};

List<Encounter> fromStandardLinq = sampleData
        .GroupBy(e => new { e.Language, e.VersionId })
        .Select(e => new Encounter { Id = "0", Language = e.Key.Language, VersionId = e.Key.VersionId })
        .ToList();

Console.WriteLine("fromStandardLinq:");
foreach (var en in fromStandardLinq)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}

var fromDynamicLinq1a = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select<Encounter>("new (\"0\" as Id, it.Key.Language, it.Key.VersionId)")
        ;

Console.WriteLine("fromDynamicLinq1a:");
foreach (Encounter en in fromDynamicLinq1a)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}

var fromDynamicLinq1b = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select(new { Id = "9", Language = "9", VersionId = "9" }, "new (\"0\" as Id, it.Key.Language, it.Key.VersionId)")
        .Select(x => x)
        ;

Console.WriteLine("fromDynamicLinq1b:");
foreach (dynamic en in fromDynamicLinq1b)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}

Console.WriteLine("fromDynamicLinq2a:");
var rawResultFromDynamicLinq2a = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select(typeof(Encounter), "new (\"0\" as Id,it.Key.Language, it.Key.VersionId)")
        ;

foreach (Encounter en in rawResultFromDynamicLinq2a)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}

Console.WriteLine("fromDynamicLinq2b:");
var rawResultFromDynamicLinq2b = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select(typeof(Encounter), new { Id = "9", Language = "9", VersionId = "9" }, "new (\"0\" as Id,it.Key.Language, it.Key.VersionId)")
        ;

foreach (Encounter en in rawResultFromDynamicLinq2b)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}
Stef Heyenrath
  • 9,335
  • 12
  • 66
  • 121