C# EF核心:如何在相关模型的模型中获得最佳平均值
我已经有了一个使用实体框架(EF核心)的Blazor服务器应用程序 我对两个模型使用代码优先的方法,C# EF核心:如何在相关模型的模型中获得最佳平均值,c#,sql-server,entity-framework,entity-framework-core,blazor-server-side,C#,Sql Server,Entity Framework,Entity Framework Core,Blazor Server Side,我已经有了一个使用实体框架(EF核心)的Blazor服务器应用程序 我对两个模型使用代码优先的方法,Entry和Target 每个条目都有一个目标。因此,一个目标可以有多个条目指向它 目标的模型如下所示: public class Target { public string TargetId { get; set; } [Required] public string Name { get; set; } [InverseProperty(&qu
Entry
和Target
每个条目都有一个目标。因此,一个目标可以有多个条目指向它
目标的模型如下所示:
public class Target
{
public string TargetId { get; set; }
[Required]
public string Name { get; set; }
[InverseProperty("Target")]
public List<Entry> Entries { get; set; }
[NotMapped]
public double AverageEntryRating => Entries != null ? Entries.Where(e => e.Rating > 0).Select(e => e.Rating).Average() : 0;
}
public class Entry
{
public string EntryId { get; set; }
public int Rating { get; set; }
[Required]
public string TargetId {get; set; }
[ForeignKey("TargetId")]
public Target Target { get; set; }
}
正如您在我的目标
模型中看到的,我想知道每个目标
的平均评级是多少,基于指向目标
的所有条目的平均值-这就是为什么目标中有这个(未映射
)属性:
public double AverageEntryRating => Entries != null ? Entries.Where(e => e.Rating > 0).Select(e => e.Rating).Average() : 0;
但是(当然)这并不总是有效的,因为不能保证在访问属性时加载目标的条目
我尝试以不同的方式解决它,例如在我的TargetService中有一个方法,在该方法中,我可以传入targetId并给出结果:
public double GetTargetMedianEntryRating(string targetId) {
var median = _context.Entries
.Where(e => e.TargetId == targetId && e.Rating > 0)
.Select(e => e.Rating)
.DefaultIfEmpty()
.Average();
return median;
}
但是当我在一个表中列出我的目标,然后在一个单元格中显示这个值(传入foreach循环的当前targetId)时,我得到了一个并发异常,因为数据库上下文在多个线程中使用(我猜一个线程通过行/目标循环,另一个线程通过获取平均值)。。。所以这让我陷入了新的麻烦
就我个人而言,我更喜欢使用Target
模型上的AverageEntryRating
属性,因为这对我来说很自然,而且像这样访问值也很方便
但是,当我访问此属性时,如何确保加载了条目。或者这不是一个好方法,因为这意味着我必须为所有目标加载条目,这将导致性能下降?如果是的话,什么是获得平均值/中值的好方法?我可以考虑几个选项,这取决于你的情况。可能会有更多的选择,但至少我希望这能给你一些你没有考虑过的选择
具有始终包含所有条目的BaseQuery扩展方法
无论何时查询目标
,都可以确保执行。包括(x=>x.Entries)
。您甚至可以创建一个数据库上下文的扩展方法,名为TargetBaseQuery()
,它包含了您使用它时所需的所有关系。然后,您将确保在访问属性AverageEntryRating
时,将加载每个目标的条目
列表
缺点是性能受到影响,因为每次加载目标时,都需要加载其所有条目。。。这就是你查询的每个目标。
然而,如果您需要让它快速工作,这可能是最简单的。务实的方法是这样做,测量性能影响,如果速度太慢,那么尝试其他方法,而不是进行过早的优化。当然,风险在于,它现在可能运行得很快,但将来可能会严重扩展。所以由你来决定
另一个要考虑的是,不要每次都包括条目,而只有在那些你知道你需要平均值的地方。然而,这可能会成为一个可维护性问题
使用不同的模型和服务方法来计算TargetStats
您可以创建另一个类模型来存储目标的相关数据,但它不会持久化到数据库中。例如:
public class TargetStats
{
public Target Target { get; set; }
public double AverageEntryRating { get; set; }
}
然后,在您的服务中,您可以有这样一种方法(尚未测试,因此它可能无法正常工作,但您知道了):
public List GetTargetStats(){
var targetStats=_context.Target
.Include(x=>x.Entries)
.选择(x=>new TargetStats
{
目标=x,
AverageEntryRatings=x.Entries。其中(e=>e.Rating>0)。选择(e=>e.Rating)。Average(),
})
托利斯先生()
返回targetStats;
}
这样做的唯一好处是,您不必降低所有与目标相关的查询的性能,而只需降低那些需要平均评级的查询的性能。
但这个查询可能仍然很慢。要进一步调整它,您可以编写原始SQL而不是LINQ,或者在数据库中创建一个可以查询的视图
将目标的平均评级存储并更新为一列
为了保持代码干净并在读取时具有良好的性能,最好的方法可能是将平均值作为一列存储在目标表中。这将把计算的性能成本转移到目标或其相关条目的保存/更新上,但由于数据已经可用,读数将非常快。如果阅读的频率比更新的频率高,那么可能值得去做
你可以看看,因为他们谈论了一些不同的性能调整替代方案。我可以考虑几个选项,这取决于你的情况。可能会有更多的选择,但至少我希望这能给你一些你没有考虑过的选择
具有始终包含所有条目的BaseQuery扩展方法
无论何时查询目标
,都可以确保执行。包括(x=>x.Entries)
。您甚至可以创建一个数据库上下文的扩展方法,名为TargetBaseQuery()
,它包含了您使用它时所需的所有关系。然后,您将确保在访问属性AverageEntryRating
时,将加载每个目标的条目
列表
缺点是性能受到影响,因为每次加载目标时,都需要加载其所有条目。。。那就是
public List<TargetStats> GetTargetStats() {
var targetStats = _context.Target
.Include(x => x.Entries)
.Select(x => new TargetStats
{
Target = x,
AverageEntryRatings = x.Entries.Where(e => e.Rating > 0).Select(e => e.Rating).Average(),
})
.ToList()
return targetStats;
}