作为Linq to SQL的导航属性的SUM

时间:2017-11-13 12:10:04

标签: c# entity-framework-6

我正在尝试使用Linq to SQL找到如何在数据库服务器上执行TotalConversions => Statistics.Sum(sum => sum.Conversions)的解决方案。

当前代码的问题是Statistics is ICollection(IEnumerable)而不是IQueryable和SUM函数从本地获取数据库中的所有记录,并且仅在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);
}

我们尝试的另一个解决方案是访问模型中的DbContext并像这样执行Linq to SQL查询

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

它工作正常,但另一个问题是这样的属性不能像这样在linq查询中使用

_context.User.Where(w=>w.Id==1 && w.Id == 2).OrderBy(o=>o.TotalConversions)

如何在SUM内的计算属性中执行Model,该属性将在数据库服务器上执行,也可以在选择查询中使用。这可能与EF完全相同吗?

1 个答案:

答案 0 :(得分:1)

问题。

[NotMapped]
public int TotalConversions => Statistics.Sum(sum => sum.Conversions);

当您将属性标记为[NotMapped]时,您告诉EF该属性不应存在于数据库级。它只能在您的代码中使用(即,当数据在内存中时,而不是在数据库中时)。

将此属性标记为[NotMapped]固有地阻止您使用TotalConversions属性使用数据库操作(order by)。

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

简单:使用OrderBy中没有属性的计算。

您已经在第二个示例中完成了这项工作,但您可以更简洁地完成此操作,避免使用自定义属性。

你想要做的是:

_context.User.Where(w=>w.Id==1 && w.Id == 2).OrderBy(o => o.TotalConversions)

正如我所提到的,你不能这样做。您可以做的是:

_context.User.Where(w=>w.Id==1 && w.Id == 2).OrderBy(o => o.Statistics.Sum(sum => sum.Conversions))

Sum()具有SQL等价物,因此Linq2SQL能够转换它。基本上,您已经更改了来自&#34的请求;根据此.Net属性订购集合&#34; (这是不可能的)&#34;根据 SQL兼容的评估&#34;来订购集合。

但是,我认为您尝试使用自定义属性,以避免在整个代码库中复制/粘贴相同的计算。如果这是你的目标,我完全同意你的意图,这是尝试更复杂的替代方案的充分理由。

更复杂:将您需要的OrderBy参数定义为自定义静态User属性。

您可以参数化OrderBy参数。首先,OrderBy方法是一种具有两种泛型类型的通用方法:

IOrderedQueryable<A> OrderBy<A,B>(Expression<Func<A,B>> expression) { }

注意:为了示例,此签名被简化,以显示OrderBy方法的基本输入和输出。

是您的实体类型,在您当前的情况下为User

B 是排序参数的类型。您按整数排序,因此B为int 注意:几乎总是,B可以由编译器推断(基于您使用的lambda方法),并且不需要显式声明。但是,对于此解决方案,您需要知道其类型。

这意味着对于您当前的情况,我们知道OrderBy将采用Expression<Func<User,int>>类型的参数。您可以通过将鼠标悬停在Orderby上来确认,IntelliSense会告诉您当前定义的通用参数的类型。

而不是像这样定义int属性:

[NotMapped]
public int TotalConversions => Statistics.Sum(sum => sum.Conversions);

您可以像这样定义表达式属性:

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

请注意,我将其设为静态,因为此lambda与特定User对象无关,我们希望在不依赖现有User的情况下访问它。 这是一个关键的区别。 int属性基本上表示&#34;这就是你获得这个User &#34;的总转换次数,而表达式属性说&#34;这是方法,用于检索任何给定User &#34;的总转化次数。我们不是定义,而是定义方法,它将为我们提供所需的值。

这意味着您可以在致电OrderBy时使用此预定义的lambda:

_context.User.Where(w=>w.Id==1 && w.Id == 2).OrderBy(User.TotalConversionsLambda)

对于编译器(和EF),这相当于更简单方法的解决方案,因此将以相同的方式工作。但是,这有一个额外的好处,就是定义lambda 一次(DRY),而不是在代码库中的任何地方复制/粘贴它。

解释。

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

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

DoSomething(5);

表示包含值的变量:

int myValue = 5;
DoSomething(myValue);

此示例使用整数,但我们可以针对任何类型执行此操作(stringbool,...应该很明显)。这也适用于参考类型:

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

对战:

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

Expression<>有点复杂(由于其复杂的泛型类型和lambda表示法),但它的工作方式完全相同:

DoSomething(foo => foo.BarValue);

对战:

Expression<Func<Foo,Bar>> myValue = (foo => foo.BarValue);
DoSomething(myValue);

这就是它的全部内容。类型更复杂,但基本原理完全相同。

编辑 - 小添加

您可以创建其他int媒体资源,以便在代码中使用

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

选择权在你手中。如果你想(迂腐地)坚持DRY,你可以使用第一个;但这样做的性能成本可能最终会伤害到你,而不是坚持DRY会让你受益。

我没有关于编制表达式的性能成本的确切数字,而且我不知道您的优先级在哪里(性能优于DRY?干掉性能?),所以我可以&#39 ;为你做出决定。