是否可以保存传递给方法的预编译委托?
我有一个方法,它接受委托并在 DbContext.Local 实体上运行它,如果为 null,则尝试在数据库中查找它,如下所示。
public static T FirstOrDefaultInLocalOrDb<T>(this DbSet<T> myTable, Func<T, string, bool> criteria, string input) where T : class
{
var output = myTable.Local.Where(o => criteria((T)o, input)).FirstOrDefault();
if (output == null)
{
Expression<Func<T, bool>> predicate = (u) => criteria(u, input);
output = myTable.Where(predicate.Compile()).FirstOrDefault();
}
return output;
}
99% 的情况下它会在本地实体中找到它,而不需要去数据库。
在我程序的另一部分中,这一行调用此方法 1000 次,每次都使用唯一的 HomeId。
var currHouse = db.Houses.FirstOrDefaultInLocalOrDb2(delegate(House h, string value) { return h.AllHomesID == value; }, HomeId);
我已经做了一些性能测试,我意识到这个方法运行非常慢,我认为这是因为它每次运行时都必须编译委托。
这个方法也在其他地方使用,所以我必须像这样保持它的通用性,但我想知道,因为该行本质上每次都会将相同的委托传递给它,只是使用不同的输入值,是否可以以某种方式预-编译委托,这样方法就不需要每次都编译委托了?
更新
我编写了该方法的非通用版本,我认为这就是我试图让我的原始方法以通用方式执行的操作。这种方法似乎运行得更快。使用 System.Diagnostic.Stopwatch,旧方法的运行时间约为 100 毫秒,而此方法的运行时间约为 7 毫秒。
public static House FirstOrDefaultAllHomesIdInLocalOrDb(this DbSet<House> myHouseTable, string allHomesId)
{
var output = myHouseTable.Local.Where(o => o.AllHomesID == allHomesId).FirstOrDefault();
if (output == null)
{
output = myHouseTable.Where(o => o.AllHomesID == allHomesId).FirstOrDefault();
}
return output;
}
I have a method that takes a delegate and runs it on the DbContext.Local entity and if null, tries to find it in the database, as specified below.
public static T FirstOrDefaultInLocalOrDb<T>(this DbSet<T> myTable, Func<T, string, bool> criteria, string input) where T : class
{
var output = myTable.Local.Where(o => criteria((T)o, input)).FirstOrDefault();
if (output == null)
{
Expression<Func<T, bool>> predicate = (u) => criteria(u, input);
output = myTable.Where(predicate.Compile()).FirstOrDefault();
}
return output;
}
99% of the time it's finding it in the Local entity and does not need to go to DB.
In another part of my program, this line is calling this method 1000s of times, each time with a unique HomeId.
var currHouse = db.Houses.FirstOrDefaultInLocalOrDb2(delegate(House h, string value) { return h.AllHomesID == value; }, HomeId);
I've done some performance testing and I've realized that this method runs very slowly and I think it's because it has to compile the delegate every time it runs.
This method is used in other places too so I have to keep it generic like so, but I was wondering, as that line is essentially passing the same delegate to it each time, just with a different input value, is it possible to somehow pre-compile the delegate, so that the method doesn't need to compile the delegate every time?
UPDATE
I wrote this non-generic version of the method, which I think is what I'm trying to get my original method to do, just in a generic way. This method seemed to run much faster. Using System.Diagnostic.Stopwatch, the old method ran at about 100ms, whilst this one ran at about 7ms.
public static House FirstOrDefaultAllHomesIdInLocalOrDb(this DbSet<House> myHouseTable, string allHomesId)
{
var output = myHouseTable.Local.Where(o => o.AllHomesID == allHomesId).FirstOrDefault();
if (output == null)
{
output = myHouseTable.Where(o => o.AllHomesID == allHomesId).FirstOrDefault();
}
return output;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这里最大的问题是您将
criteria
声明为Func
,而不是Expression
。这意味着查询必须将
myTable.Local
的所有行检索到您的应用程序中,然后针对它们运行您的委托。换句话说,如果您将类型更改为
Expression<...>
而不是Func<...>
,则 SQL 执行将被执行 使用这些条件,和“TOP 1”或实际数据库引擎所需的任何语法。相反,您基本上是在执行
select * from myTable
,然后在第一个上停止。根据实体框架的执行方式,它可能会在从数据库获取行时生成行,但我对此表示怀疑,因为并非所有数据库引擎都支持多个打开的游标。
因此,在这种情况下,如果表有 100 万行,并且有 500.000 行匹配,但您只需要第一行,那么您仍然会检索 500.000 行并丢弃其中的最后 499.999 行。
The biggest problem here is that you've declared your
criteria
as aFunc
, and not as anExpression
.This means that the query has to retrieve all the rows of
myTable.Local
into your application, and then run your delegate against them.In other words, if you had changed the type to
Expression<...>
instead ofFunc<...>
, the SQL executing would be executed with those criteria, and the "TOP 1" or whatever syntax your actual database engine requires.Instead, you're basically executing
select * from myTable
, and then stopping on the first one.Depending on how entity framework does it, it might yield the rows as it gets them from the database, but I doubt it, as not all database engines supports having multiple open cursors.
So in this case, if the table has 1 million rows, and 500.000 match, but you only want the first, you're still retrieving 500.000 rows and discarding the last 499.999 of those.