Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: How to return a nullable version of a fully qualified type #643

Closed
jbhelm opened this issue Nov 7, 2022 · 12 comments
Closed

Question: How to return a nullable version of a fully qualified type #643

jbhelm opened this issue Nov 7, 2022 · 12 comments
Assignees
Labels

Comments

@jbhelm
Copy link

jbhelm commented Nov 7, 2022

I am attempting to return a nullable version of a non-system value type (in this case an enum). According to the docs {type}?(expr) can be used to convert "Between the nullable and non-nullable forms of any value type". While this seems to work for system primitive types (Int32, etc), it does not work for fully qualified nullable types. Note that fully qualifying nullable system types (such as System.Int32?) does not work either. I've tried various combinations of {type}?(expr) and As() with quoted and unquoted type name, the question marks inside and outside the quotes, as well as adding the DynamicLinqType attribute to the enum, but everything results in an error (see examples below). What is the proper way to return a nullable version of a non-system value type?

using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Dynamic.Core.CustomTypeProviders;

namespace DynamicLinqBugExample1
{
    internal class Program
    {
        [DynamicLinqType]
        public enum ExampleEnum
        {
            Value1,
            Value2,
        };

        private static readonly ParsingConfig DefaultParsingConfig = new() { 
            AllowNewToEvaluateAnyType = true,
        };

        static void Main(string[] args)
        {
            var data = new[]
            {
                new { Value = ExampleEnum.Value1 },
                new { Value = ExampleEnum.Value2 },
            };

            data.AsQueryable().Select(DefaultParsingConfig, $"({typeof(ExampleEnum).FullName}?)(Value)"); // System.Linq.Dynamic.Core.Exceptions.ParseException: 'Enum value 'ExampleEnum' is not defined in enum type 'DynamicLinqBugExample1.Program''
            data.AsQueryable().Select(DefaultParsingConfig, $"{typeof(ExampleEnum).FullName}?(Value)"); // System.Linq.Dynamic.Core.Exceptions.ParseException: 'Enum value 'ExampleEnum' is not defined in enum type 'DynamicLinqBugExample1.Program''
            data.AsQueryable().Select(DefaultParsingConfig, $"\"{typeof(ExampleEnum).FullName}?\"(Value)");  // System.Linq.Dynamic.Core.Exceptions.ParseException: 'Syntax error'
            data.AsQueryable().Select(DefaultParsingConfig, $"\"{typeof(ExampleEnum).FullName}\"?(Value)");  // System.Linq.Dynamic.Core.Exceptions.ParseException: '':' expected'
            data.AsQueryable().Select(DefaultParsingConfig, $"As(Value, \"{typeof(ExampleEnum).FullName}?\")");  // System.Linq.Dynamic.Core.Exceptions.ParseException: 'Type 'DynamicLinqBugExample1.Program+ExampleEnum?' not found'
            data.AsQueryable().Select(DefaultParsingConfig, $"As(Value, \"{typeof(ExampleEnum).FullName}\"?)");  // System.Linq.Dynamic.Core.Exceptions.ParseException: 'Expression expected'

        }
    }
}

This example is extremely simple for illustrative purposes; in my case these are much more complex dynamic LINQ strings being generated on the fly to execute against Linq to Entities on an Azure SQL database. Because it's executing on a database, it can return nullable values for properties that the model indicate are not nullable (due to outer joins, for example).

Related, but more of a feature request, would be a way to get a nullable version of an expression without needing to specify the type. Generally we don't know the actual type of the expression we're building, so we must go through a lot of code to determine that so we could pass it to {type}?(expr) -- but the dynamic LINQ library is also already doing all that as well to determine the underlying type. A method of getting a nullable version of whatever type the expression produces would be extremely helpful, something like ?(expr) or nullable(expr).

Thank you -- this is a very useful library!

@StefH
Copy link
Collaborator

StefH commented Nov 9, 2022

Hello @jbhelm. A quick remark on your code examples: the DefaultParsingConfig should be the first parameter for the Select-method.

@StefH StefH added the question label Nov 9, 2022
@StefH StefH self-assigned this Nov 9, 2022
@jbhelm
Copy link
Author

jbhelm commented Nov 9, 2022

Hello @jbhelm. A quick remark on your code examples: the DefaultParsingConfig should be the first parameter for the Select-method.

Yes of course, thanks! I've updated the example.

@StefH
Copy link
Collaborator

StefH commented Nov 17, 2022

1️⃣
This does work indeed : "int?(Value)". But "ExampleEnum?(Value)" does not.
I'll take a look if this can be fixed for enums.

2️⃣
It would be nice if this "As(Value, \"ExampleEnum\")" could work.
However, this throws exception:
System.ArgumentException: The type used in TypeAs Expression must be of reference or nullable type, ExampleEnum is neither (Parameter 'type')
I'll take a look if I can change the parser code so that in this case, an expression like "As(Value, \"ExampleEnum?\")" could maybe be handled...

@StefH
Copy link
Collaborator

StefH commented Nov 17, 2022

#647

@StefH
Copy link
Collaborator

StefH commented Nov 17, 2022

@StefH
Copy link
Collaborator

StefH commented Nov 19, 2022

@jbhelm
Should I create a preview version for you to test this As cast solution?

@jbhelm
Copy link
Author

jbhelm commented Nov 19, 2022

@StefH Thanks for the fast turn around on this! Yes, if you create a preview I can try it out for our particular use case next week.

@StefH
Copy link
Collaborator

StefH commented Nov 19, 2022

@jbhelm
Preview version (1.2.24-preview-01) can be found here:

https://www.myget.org/F/system-linq-dynamic-core/api/v3/index.json

@jbhelm
Copy link
Author

jbhelm commented Nov 21, 2022

@StefH -- I gave this a try, thanks for publishing the preview.

While it works well for LINQ to Objects, it does not work for LINQ to Entities, which is my use case. My queries can return nullable versions of value types that are not nullable in the model due to outer joins occurring on the database. Attempting to use the As(value, "Example.MyEnum?") syntax with LINQ to Entities results in an error similar to:

The 'TypeAs' expression with an input of type 'Example.MyEnum' and a check of type 'System.Nullable`1[[Example.MyEnum, ...]]' is not supported. Only entity types and complex types are supported in LINQ to Entities queries.

Note that the same thing occurs with system value types such as DateTime:

The 'TypeAs' expression with an input of type 'System.DateTime' and a check of type 'System.Nullable`1[[System.DateTime, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]' is not supported. Only entity types and complex types are supported in LINQ to Entities queries.

In the case of the system types, the nullable cast operator of DateTime?(value) can be used as expected, however, as discussed above, it does not work for fully-qualified types.

@StefH
Copy link
Collaborator

StefH commented Nov 26, 2022

@jbhelm
Conclusions:

  1. Casting to Simple Enum will only work if you provide a custom CustomTypeProvider OR when you annotate with DynamicLinqType
  2. Casting a Simple Enum to nullable will work just like casting a int to nullable if you follow rule 1
  3. Casting to Full Type Enum will not work yet, I need to think on that, parsing code needs to be updated. Maybe to a solution could be to support quotes like "\"{typeof(ExampleEnum).FullName}\"

You I'll create a new issue for item 3 and close this one and merge the PR to master.

@jbhelm
Copy link
Author

jbhelm commented Nov 26, 2022

@StefH
Apologies for my delay in responding. I did get this to work in my scenario by using the DynamicLinqType attribute and the simple enum name. I did try the DynamicLinqType attribute previously, but I must have been attempting to still use the fully qualified name.

Thank you for your time on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants