我试图将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; }
}
答案 0 :(得分:3)
首先,我们假设null
表示unknown
。 Eric Lippert的Null Is Not Empty为此提供了很好的理由。它可以进一步跟踪SQL的设计和三态逻辑的原理。 null
集合不为空,与null
int?
不为零相同。
但即使你不同意,有两种基本的理论正确使用空值:
只需调整模型,以便在对象的生命周期内始终阻止空值。通过类型系统(尤其是使用.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
}
}
当您已经拥有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
。