LINQ,在Model中处理Null

时间:2017-12-10 20:38:13

标签: c# asp.net linq

我试图将AverageRating传递给我的视图。 AverageRating是在Icollection of Review Model中查询项目的结果。评论模型具有评级属性。但是我收到了这条消息:

  
    

System.ArgumentNullException

  

每当执行get时都会发生这种情况,这是可以理解的。但是,当我的代码如下所示时,我如何最好地处理我的模型或其他地方的空异常:

public class MyModel
{

        //Querying this navigation property
        public ICollection<Review> Reviews { get; set; }

        public double? AverageRating
        {
        get
        { 
            //check that this is not null / handle null
            return Math.Round(Reviews.Average(c => c.Rating), 1);
        }

        }

}
public class Review
{
    [Key]
    public int ReviewID { get; set; }
    public int Rating { get; set; }
    public string Comment { get; set; }
    public int CoachID { get; set; }
    public int? StudentID { get; set; }


    public Coach Coach { get; set; }
    public Student Student { get; set; }
}

4 个答案:

答案 0 :(得分:3)

什么是null?

首先,我们假设null表示unknown。 Eric Lippert的Null Is Not Empty为此提供了很好的理由。它可以进一步跟踪SQL的设计和三态逻辑的原理。 null集合不为空,与null int?不为零相同。

但即使你不同意,有两种基本的理论正确使用空值:

1。防止空值

只需调整模型,以便在对象的生命周期内始终阻止空值。通过类型系统(尤其是使用.NET序列化时)并不总是可以实现这一点。这也可能在某些地方产生大量额外的样板代码,因此明智地使用它:

public class Model
{
    // is non-null in any Model instance
    public IReadOnlyList<ModelItem> Items { get; }

    public Model(IEnumerable<ModelItem> items)
    {
        Items = new List<ModelItems>(items); // does not check if items contains null
    }
}

2。传播和处理空值

当您已经拥有null时,最好不要掩盖(这会妨碍维护)。您可以将调用堆栈中的null抛出或返回到其他地方,然后强制处理null或抛出。

public class ModelItem
{
    public double? Value { get; set; }
}

public class Model
{
    public ICollection<ModelItem> Items { get; set; } // for some reason, e.g. serialization, the Items collection can be null

    public double? Average
    {
        get
        {
            if (Items == null)
            {
                // I don't know what items exist => the average is unknown
                return null;
            }

            return Items.Average(i => i?.Value); // note the ?. here to prevent NullReferenceException
       }
    }
}

请注意,与非可空变体不同,Average<Nullable<double>>不会抛出InvalidOperationException空序列,应为非可空类型添加额外检查

另请注意,代码并未尝试将null解析为其他null以外的任何内容。如果您的null在某个地方被处理,它很可能是您的应用程序的业务逻辑的一部分,并且应该驻留在相应的层中(例如,处理与之前版本的模型不向后兼容的代码。 t具有某个属性,将其返回为null)。

但是,如果您的模型类本身假定null集合是一个空集合(出于可读性和维护的原因,我强烈建议不要这样做),应该确实不传播null并且应该在其中处理上课,例如使用合并运算符(??)。

答案 1 :(得分:3)

此实施可能会满足您的需求:

public double? AverageRating
{
    get
    {
        return Reviews?.Average(x => x?.Rating);
    }
}

由于在Reviews之后使用null,它将处理null ?(它将返回Reviews)。

由于在Reviews之后使用null,它会处理?个人x(在计算平均值时会被忽略)。

答案 2 :(得分:1)

您可以使用DefaultIfEmpty为空集设置0值,并从Average计算中排除可能的空值,您应该删除它们;

    public double? AverageRating
    {
        get
        {
            if (Reviews == null)
            {
                return null;
            }
            return Math.Round(Reviews.Where(x => x.Rating.HasValue).Select(x => x.Rating).DefaultIfEmpty(0).Average().Value, 1);
        }
    }

答案 3 :(得分:1)

如果您正在使用C#6.0,则可以使用空传播来帮助指定默认方案。

代码最终会如下所示:

return Math.Round(Reviews?.Average(c => c.Rating) ?? 0.0, 1);

这样可以使用空传播来确保在访问Average扩展方法之前,Review集合不是Null。

如果您有单个项目为NULL,那么您可以使用以下内容扩展lambda内部的检查:

return Math.Round(Reviews?.Average(c => c?.Rating ?? 0.0) ?? 0.0, 1);

这将防止评论为空或评论的项目为空。

这是一个小提琴,展示了它的实际效果:https://dotnetfiddle.net/qBTEyf

如果你需要跳过将NULL转换为0,那么你可以先用Where语句从集合中删除NULL项。

return Math.Round(Reviews?.Where(c => c?.Rating != null).Average(c => c.Rating) ?? 0.0, 1);

这种方式从列表中删除任何空项,然后再将其处理为Average

修改

根据下面的评论,您可以使用DefaultIfEmpty来处理序列本身为空的时间,如下所示:

return Math.Round(Reviews?.DefaultIfEmpty().Average(c => c?.Rating ?? 0.0) ?? 0.0, 1);

调用DefaultIfEmpty将返回IEnumerable一个null元素。然后在Average期间将其过滤掉并返回0。

这也可以与本文中的其他方法结合使用。该小提琴已经使用DefaultIfEmpty

的测试示例进行了更新