3

I regularly find myself coding things like

int? min = someQueryable
    .Where(x => someCondition)
    .Select(x => (int?)x.someNonNullableIntProperty)
    .Min();

It allows me to get null instead of "System.ArgumentNullException: value cannot be null" when nothing matches the condition. (And I do not want to get 0 instead of null.)

I would like to avoid the cast. Is there any other way I have missed?

The hard part is, it should be of sufficiently common use to be natively understood (or ignored) by linq providers. ( and for my specific needs.)

Otherwise I would have to add some custom AsNullable extensions to linq-to-nh provider (which requires some work), and I do not know if EF (6) allows to do this.

Why do I wish avoiding cast? Casting may go unnoticed during refactoring, and then may cause bugs. And most of the cast I see in code are due to careless developers, not trying to memorize they can have the non-null value from .Value on nullable types, by example. (There is also the ternary case like someCondition ? someIntProperty : (int?)null but the "Elvis" operator ?. will probably allow to avoid most of them ; and we can also write default(int?), althougth it is a bit lengthy. ?. could maybe even be used for my example query, but it would not be a general solution.)

Trying new Nullable<int>(x.someNonNullableIntProperty) as suggested here fails with a NotSupportedException (with NH, not tested with EF). Anyway, it would not suit me either. In case of some later type change of the property, it may go unnoticed too due to implicit conversion. (And trying new Nullable(x.someNonNullableIntProperty) does not compile, generic type argument inferring does not work well with constructors.)

Trying x.someNonNullableIntProperty as int? (which is still a cast, but less tolerant about types mismatch in this case, see this here) fails with an ArgumentException (NH tested again, Expression of type 'System.Int32' cannot be used as a 'System.Nullable`1[System.Int32]' return type (translated)).

Frédéric
  • 9,364
  • 3
  • 62
  • 112
  • If it's a simple method syntax top level `Select` like in the example, I think something can be done. But in general you can't do that. – Ivan Stoev Mar 23 '16 at 17:41

2 Answers2

-1

I tried this once, but for IEnumerable, and came up with

    public static T? max<T>(IEnumerable<T> values) where T: struct, IComparable<T>
    {
        T? result = null;
        foreach (var v in values)
            if (!result.HasValue || (v.CompareTo(result.Value) > 0))
                result = v;
        return result;
    }

To handle IQueryable, you'd need to extend your data access library. In case of NHibernate, the mechanism is called HqlGenerator. See this answer for links.

Community
  • 1
  • 1
devio
  • 36,858
  • 7
  • 80
  • 143
  • Rather than redefining min, max and all others, I would define some `AsNullable` and extend linq-to-nh with it as I have already done it [here](/a/35492489/1178314) for another subject. But I think we really should have some .Net built-in way to get a nullable from any non nullable `struct` expression without having to explicitly mention its underlying type. It looks like it is a lacking feature. – Frédéric Mar 24 '16 at 06:39
  • [Done it](/a/36211132/1178314). But not accepting either, it does not address EF case. – Frédéric Mar 24 '16 at 22:45
-1

For Entity Framework, I have given up. It looks as too much effort for me. (See here.)

For NHibernate, I have done the AsNullable extension I was thinking of.

This is following the same logic as my other extensions here and here.

First, define an AsNullable extension:

public static class NullableExtensions
{
    public static T? AsNullable<T>(this T value) where T : struct
    {
        // Allow runtime use.
        // Not useful for linq-to-nhibernate, could be:
        // throw NotSupportedException();
        return value;
    }
}

Then, implements its HQL translation (originally based on NHibernate compare implementation, then quite simplified, see edits):

public class AsNullableGenerator : BaseHqlGeneratorForMethod
{
    public AsNullableGenerator()
    {
        SupportedMethods = new[]
        {
            ReflectionHelper.GetMethodDefinition(() => NullableExtensions.AsNullable(0))
        };
    }

    public override HqlTreeNode BuildHql(MethodInfo method,
        Expression targetObject,
        ReadOnlyCollection<Expression> arguments,
        HqlTreeBuilder treeBuilder,
        IHqlExpressionVisitor visitor)
    {
        // Just have to transmit the argument "as is", HQL does not need a specific call
        // for null conversion.
        return visitor.Visit(arguments[0]).AsExpression();
    }
}

Extend the default linq2NH registry with your generator:

public class ExtendedLinqToHqlGeneratorsRegistry :
    DefaultLinqToHqlGeneratorsRegistry
{
    public ExtendedLinqToHqlGeneratorsRegistry()
        : base()
    {
        this.Merge(new AsNullableGenerator());
    }
}

Now configure NH to use your new registry. With hibernate.cfg.xml, add following property node under session-factory node:

<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>

Or using code:

using NHibernate.Cfg;
// ...

var cfg = new Configuration();
cfg.LinqToHqlGeneratorsRegistry<ExtendedLinqToHqlGeneratorsRegistry>();
// And build the session factory using this configuration.

Now we can rewrite the query.

int? min = someQueryable
    .Where(x => someCondition)
    .Select(x => x.someNonNullableIntProperty.AsNullable())
    .Min();
Frédéric
  • 9,364
  • 3
  • 62
  • 112