在linq查询中将非可空表达式转换为可为空而无需硬键入/转换

时间:2016-03-23 17:29:45

标签: c# linq linq-to-entities nullable linq-to-nhibernate

我经常发现自己编写了像

这样的内容
usernameInput

当没有任何条件匹配时,它允许我得到null而不是“System.ArgumentNullException:value不能为null”。 (而且我不想让int? min = someQueryable .Where(x => someCondition) .Select(x => (int?)x.someNonNullableIntProperty) .Min(); 代替0。)

我想避免演员。这是我错过的其他任何方式吗?

困难的部分是,linq提供者应该对其进行本地理解(或忽略)。 (用于满足我的特定需求。)

否则我将不得不向linq-to-nh提供者添加一些自定义null扩展(这需要一些工作),我不知道EF(6)是否允许这样做。

为什么我希望避免演员?重构过程中可能会忽略转换,然后可能会导致错误。 我在代码中看到的大部分演员都是由于粗心的开发人员,而不是试图记住他们可以在可空类型上使用AsNullable的非空值,例如。 (还有像.Value这样的三元案例,但“猫王”操作符someCondition ? someIntProperty : (int?)null可能会避免大部分操作;我们也可以编写?.,尽管它有点冗长。default(int?)甚至可以用于我的示例查询,但它不是一般解决方案。)

尝试?. as suggested here失败,new Nullable<int>(x.someNonNullableIntProperty)(使用NH,未使用EF测试)。无论如何,它也不适合我。如果稍后对属性进行类型更改,则由于隐式转换,它可能也会被忽略。 (并且尝试NotSupportedException不能编译,泛型类型参数推断对构造函数不起作用。)

尝试new Nullable(x.someNonNullableIntProperty)(仍然是演员,但在这种情况下对类型不匹配的容忍度较低,请参阅this here)失败并显示x.someNonNullableIntProperty as int?(NH再次测试, Expression类型'System.Int32'不能用作'System.Nullable`1 [System.Int32]'返回类型(已翻译))。

2 个答案:

答案 0 :(得分:-1)

I tried this once,但IEnumerable,并提出了

    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;
    }

要处理IQueryable,您需要扩展数据访问库。在NHibernate的情况下,该机制称为HqlGenerator。有关链接,请参阅this answer

答案 1 :(得分:-1)

对于实体框架,我放弃了。对我来说这看起来太费劲了。 (见here。)

对于NHibernate,我已经完成了我想到的AsNullable扩展。

这与我的其他扩展程序herehere遵循相同的逻辑。

首先,定义AsNullable扩展名:

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;
    }
}

然后,实现其HQL翻译(最初基于NHibernate compare implementation,然后非常简化,请参阅编辑):

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();
    }
}

使用您的生成器扩展默认的linq2NH注册表:

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

现在配置NH以使用新的注册表。使用hibernate.cfg.xml,在property node:

下添加以下session-factory节点
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>

或者使用代码:  

using NHibernate.Cfg;
// ...

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

现在我们可以重写查询。

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