C# 导航属性的总和为Linq到SQL

C# 导航属性的总和为Linq到SQL,c#,entity-framework-6,C#,Entity Framework 6,我试图找到一个解决方案,如何使用Linq to SQL在数据库服务器上执行TotalConversions=>Statistics.Sum(Sum=>Sum.Conversions) 当前代码的问题是,统计信息是ICollection(IEnumerable),而不是IQueryable和SUM函数从数据库本地获取所有记录,并且仅在对结果求和时才获取。这不是我们的解决方案,因为统计数据包含数千条记录 public class User : Entity { public int Id {

我试图找到一个解决方案,如何使用Linq to SQL在数据库服务器上执行
TotalConversions=>Statistics.Sum(Sum=>Sum.Conversions)

当前代码的问题是,
统计信息是ICollection
(IEnumerable),而不是IQueryable和SUM函数从数据库本地获取所有记录,并且仅在对结果求和时才获取。这不是我们的解决方案,因为统计数据包含数千条记录

public class User : Entity
{
   public int Id { get; set; }
   public virtual ICollection<Statistic> Statistics { get; set; }
   [NotMapped]
   public int TotalConversions => Statistics.Sum(sum => sum.Conversions);
}
它工作得很好,但另一个问题是这样的属性不能在linq查询中使用

[NotMapped]
public int TotalConversions
{
    get
    {
        if (_totalConversions == null)
        {
            var databaseContext = GetDbContextFromEntity(this);
            _totalConversions = databaseContext.Statistic.Where(s => s.UserId == Id).Sum(s => s.Conversions);
        }
        return (int)_totalConversions;
    }
}
_context.User.Where(w=>w.Id==1 && w.Id == 2).OrderBy(o=>o.TotalConversions)
如何在
Model
中的计算属性中执行
SUM
,该属性将在数据库服务器上执行,也可以在select查询中使用。如果有
EF
,这可能吗?

这个问题。 当您将属性标记为
[NotMapped]
时,您告诉EF此属性不应存在于数据库级别上。它仅在代码中可用(即数据在内存中时,而不是在数据库中时)

将此属性标记为
[NotMapped]
本质上会阻止您使用
TotalConversions
属性使用数据库操作(order by)

我在这里看到两种解决方案。一个简单,另一个不那么简单


Simple:在没有属性的情况下使用
OrderBy
中的计算。
在第二个示例中,您已经基本上做到了这一点,但是您可以更简洁地做到这一点,避免使用自定义属性

您要做的是:

_context.User.Where(w=>w.Id==1 && w.Id == 2).OrderBy(o => o.TotalConversions)
正如我提到的,你不能这样做。您可以做的是:

Sum()。实际上,您已将请求从“基于此.Net属性对集合进行排序”(这是不可能的)更改为“基于此SQL兼容评估对集合进行排序”


然而,我假设您正在尝试使用自定义属性,以避免在整个代码库中复制/粘贴相同的计算。如果这是你的目标,我完全同意你的意图,这是一个足够好的理由去尝试更复杂的选择

更复杂:将所需的
OrderBy
参数定义为自定义静态
User
属性。 您可以参数化
OrderBy
参数。首先,
OrderBy
方法是一种具有两种通用类型的通用方法:

IOrderedQueryable<A> OrderBy<A,B>(Expression<Func<A,B>> expression) { }
可以这样定义表达式属性:

[NotMapped]
public int TotalConversions => Statistics.Sum(sum => sum.Conversions);
[NotMapped]
public static Expression<Func<User,int>> TotalConversionsLambda = (user => user.Statistics.Sum(sum => sum.Conversions));
对于编译器(和EF),这相当于来自更简单方法的解决方案,因此将以相同的方式工作。但是,这还有另外一个好处,即定义lambda一次(干式),而不是在代码库中的任何地方复制/粘贴它


解释。 参数化表达式可能有点令人困惑。至少在我开始使用它们时,情况就是这样。因此,也许有一个更简单的解释

请注意,我们可以交换文字值:

DoSomething(5);
对于包含值的变量:

int myValue = 5;
DoSomething(myValue);
本例使用整数,但我们可以对任何类型执行此操作(
string
bool
,…应该是显而易见的)。这也适用于参考类型:

DoSomething(new User() { Name = "John Doe" });
与:

User john = new User() { Name = "John Doe" };
DoSomething(john);
Expression<Func<Foo,Bar>> myValue = (foo => foo.BarValue);
DoSomething(myValue);
表达式
有点复杂(由于其复杂的泛型类型和lambda表示法),但其工作原理完全相同:

DoSomething(foo => foo.BarValue);
与:

User john = new User() { Name = "John Doe" };
DoSomething(john);
Expression<Func<Foo,Bar>> myValue = (foo => foo.BarValue);
DoSomething(myValue);
老实说,与简单地定义一个独立于表达式属性工作的属性相比,我确实认为表达式的重复编译最终可能会对性能造成更大的伤害:

[NotMapped]
public static Expression<Func<User,int>> TotalConversionsLambda = (user => user.Statistics.Sum(sum => sum.Conversions));

[NotMapped]
public int TotalConversions
{
    get
    {
        return this.Statistics.Sum(sum => sum.Conversions);
    }
}
[未映射]
公共静态表达式TotalConversionsLambda=(user=>user.Statistics.Sum(Sum=>Sum.Conversions));
[未映射]
公共整数转换
{
得到
{
返回this.Statistics.Sum(Sum=>Sum.Conversions);
}
}
选择权在你。如果你想(学究般地)坚持干燥,你可以使用第一个;但这样做的性能成本最终可能对你的伤害大于坚持干货对你的好处


我没有编译表达式的性能成本的确切数字,也不知道您的优先级(性能高于性能?性能高于性能?),因此我无法为您做出决定。

如果在最后一段代码的where和OrderBy()子句之间添加.ToList(),您将能够在Linq中使用TotalConversions函数。。ToList()之后的任何原因现在都将是Linq toEntities@uk2k05order by应该在数据库内部执行,如果您添加ToList(),查询将成为可枚举的,并且之后的所有操作都将在本地执行,现在将其镜像到~200k条记录上?!只需跳过Where,假设查询只有OrderBy\u context.User.OrderBy(o=>o.TotalConversions)。这将不起作用,因为Linq to SQL提供程序不会将TotalConversion转换为SQL。使用您的建议_context.User.ToList().OrderBy(o=>o.TotalConversions)所有排序都将在本地执行。很快-这是不可能的(不幸)。@IvanStoev是的,我提供的解决方案不可能实现,但我99%确定可以使用带编译的谓词。问题不在于实现,而在于
[NotMapped]
属性。一旦标记了这样的属性,就不能在L2E查询中使用它。如果您不标记它,EF将在db表中查找列。“小添加”部分将无法按预期工作,因为统计信息是导航属性,因此它将要么将整个集合加载到内存中(如果启用延迟加载),要么返回0(甚至可能引发异常)@Evk:这个小加法是基于OP自己的初始代码:
public int TotalConversions=>Statistics.Sum(Sum=>Sum.Conversions)
。您所说的是正确的,但该评论对OP的代码同样正确。在任何情况下(我的或他的)
[NotMapped]
public static Expression<Func<User,int>> TotalConversionsLambda = (user => user.Statistics.Sum(sum => sum.Conversions));

[NotMapped]
public int TotalConversions
{
    get
    {
        return TotalConversionsLambda.Compile().Invoke(this);
    }
}
[NotMapped]
public static Expression<Func<User,int>> TotalConversionsLambda = (user => user.Statistics.Sum(sum => sum.Conversions));

[NotMapped]
public int TotalConversions
{
    get
    {
        return this.Statistics.Sum(sum => sum.Conversions);
    }
}