6

I have an ad-hoc reporting system; I have no compile-time knowledge of the source type of queries or of the required fields. I could write expression trees at runtime using the System.Linq.Expressions.Expression factory methods, and invoke LINQ methods using reflection, but Dynamic LINQ is a simpler solution.

The reporting system is to allow for queries which returns the result of a LEFT JOIN. There are fields in the joined table which are NOT NULL in the database; but because this is a LEFT JOIN, those fields will contain NULL for certain records. The EF6-generated expression falls on this, because the expression projects to a non-nullable value type.

If I was doing this in compile-time LINQ, I would explicitly cast to the nullable type:

enum Color { Red, Green,  Blue }

// using System;
// using static System.Linq.Enumerable;
// using System.Linq;

var range = Range(0, 3).Select(x => (Color)x).AsQueryable();
var qry = range.Select(x => (Color?)x);

Dynamic LINQ supports explicit conversions:

// using static System.Linq.Dynamic.Core

var qry1 = range.Select("int?(it)");

but only a specific set of types can be referenced in the query. If I try to use Color in the query:

var qry2 = range.Select("Color(it)");

I get the following error:

No applicable method 'Color' exists in type 'Color'

and if I try to explicitly cast to Color?:

var qry3 = range.Select("Color?(it)");

I get:

Requested value 'Color' was not found.

How can I do this using the Dynamic LINQ library?

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • Add a item in the enumeration for NONE – jdweng Mar 15 '20 at 12:18
  • @jdweng Ultimately I need this to get back values in a required column from a LEFT JOIN; said values will be `NULL` if there's no corresponding record in the `JOIN`ed table. Also, that wouldn't help for non-enum value types. – Zev Spitz Mar 15 '20 at 13:01
  • are you storing the name of the enumeration in the database or are you storing the integer value in the db? – Saravanan Mar 16 '20 at 13:10
  • @Saravanan The integer value. – Zev Spitz Mar 16 '20 at 15:14
  • Do you **need** to use DLINQ? – picolino Mar 18 '20 at 05:32
  • This is an x-y problem. You want to do _x_ and think you need _y_ to do it. What is _x_ here? There's probably another way to achieve what you want. – Gert Arnold Mar 18 '20 at 08:39
  • @picolino Not really. I could write an expression tree at runtime using the `System.Linq.Expressions.Expression` factory methods to respond to different property types in the source, but I'd much prefer to use Dynamic LINQ. – Zev Spitz Mar 18 '20 at 09:49
  • @GertArnold I have a reporting system which uses Dynamic LINQ against EF6 and an SQL Server database. AFAICT, the pipeline is like this: I pass in a string to Dynamic LINQ -> Dynamic LINQ converts it to an `Expression` -> EF parses the expression tree, generates SQL, and returns results. The problem is, this particular query is returning the results of an ad-hoc `LEFT JOIN` with arbitrary fields; the joined table has some required fields, so the materializing expression on the results tries to convert these fields to a value type, which obviously fails when the database result returns `null`. – Zev Spitz Mar 18 '20 at 10:02

3 Answers3

7

Dynamic LINQ provides a Cast method which can be used as follows:

var range = Enumerable.Range(0,3).Select(x => (Color)x).AsQueryable();
var castDynamic = range.Cast(typeof(Color?)).ToDynamicArray();
castDynamic.Dump();

You can also pass a string with the name of the output type. Note that for nullable types, you need the full name of the type:

string s = typeof(Color?).FullName;
s.Dump();
var castDynamicFromString = range.Cast(s);
castDynamicFromString.Dump();

Cast can also be used within the Dynamic LINQ string expression, either passing in a Type object as a parameter, or by using the name directly:

var castInSelect = range.Select($@"Cast(""{s}""").ToDynamicArray();
castInSelect.Dump();

Output in LINQPad:

enter image description here

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
Stef Heyenrath
  • 9,335
  • 12
  • 66
  • 121
  • You need to provide the full name including the namespace for that Color enum. Or you can try to provide alternate configuration and set the ResolveTypesBySimpleName to true. See https://github.com/StefH/System.Linq.Dynamic.Core/wiki/Configuration on how to to that. – Stef Heyenrath Mar 18 '20 at 11:41
  • Looks nice. Thank you. – Stef Heyenrath Mar 18 '20 at 14:45
3

Try this:

var arg0 = Expression.Parameter(typeof(Color), "x");
var expr = DynamicExpressionParser.ParseLambda(new[] { arg0 }, typeof(Color?), "x");
var qry2 = range.AsQueryable().Select("@0(it)", expr);

Also see https://github.com/StefH/System.Linq.Dynamic.Core/wiki/Dynamic-Expressions#dynamic-lambda-invocation

weichch
  • 9,306
  • 1
  • 13
  • 25
0

I would like to suggest that you can use the following snippet to identify whether the input number is part of the enumeration.

public static bool IsValuePresent<T>(int number) where T : Enum
{
    return !Enum.GetValues(typeof(T)).Cast<int>().Contains(number);
}

The following might be the calls to check if the input value is present in the enumeration

EnumerationConverter.IsValuePresent<Color>(99); //returns False
EnumerationConverter.IsValuePresent<Color>(2); //returns True

Once a False is obtained in the above statements, we can set the value to be null or else, we can do a simple generic type converter from integer to the enumeration like the code given below.

public static T GetAsEnum<T>(int number) where T : Enum
{
    if (Enum.IsDefined(typeof(T), number))
    {
        return (T)Enum.ToObject(typeof(T), number);
    }
    return default(T);
}

Based on the understanding that you have to check for valid enumeration value and then do a conversion, the above solution can be tried, incase of any other aspect, please post here to update.

Saravanan
  • 7,637
  • 5
  • 41
  • 72
  • The problem is not identifying whether the input value is part of the enumeration. The problem (in the main) is that AFAICT the Dynamic LINQ library has no way of saying "convert this to an arbitrary type", only the recognized types. Without Dynamic LINQ, it's perfectly straightforward to do this: `Range(0, 3).Select(x => (Color)x).Select(x => (Color?)x)`, without even a runtime error if the values happen to not match any of the enum values. – Zev Spitz Mar 16 '20 at 15:14
  • Okay, are you trying to use dynamic linq so that you can have this applied to a filter like query to your database – Saravanan Mar 16 '20 at 15:59
  • are you intending to do something like this one: https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression.convert?view=netframework-4.8 – Saravanan Mar 16 '20 at 16:01
  • Yes. I am writing a query with a LEFT JOIN, and some of the rows in the joined table return NULL. Some of the fields in the joined table are typed as non-nullable value types; but when the fields come back in the LEFT JOIN, they can be NULL. When passing in a string to Dynamic LINQ, the expression it creates doesn't account for these NULL values; types recognized by Dynamic LINQ can be "Nullable-ified", but other types such as a custom enum, cannot. I know I could rewrite the generated expression tree and fix it, but I'd rather work within the constrains of Dynamic LINQ if possible. – Zev Spitz Mar 16 '20 at 16:07
  • I've clarified the question. – Zev Spitz Mar 18 '20 at 10:51
  • Note that this answer now has nothing to do with the question (if it ever did). – Zev Spitz Mar 24 '20 at 14:27