我正在尝试使用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
完全相同吗?
答案 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);
此示例使用整数,但我们可以针对任何类型执行此操作(string
,bool
,...应该很明显)。这也适用于参考类型:
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 ;为你做出决定。