分享

EF Merge Options and Compiled Queries

 ThinkTank_引擎 2015-03-11

最近出现了一些有关已编译的查询和它们是如何合并选项的问题。我看着它意识到我没有完全理解它所有的工作,于是穿过大厅,一边聊天与开发者做了最上的代码的这一部分的工作之一,花了细节。他是能够给我一个更完整的故事,和我想要把它弄出来,诚挚的祝福: 祝你 — — 尤其是因为这一领域的 EF 只是不是我们想要的一样干净。这是为将来的版本中改进名单上肯定另一件事 (* 后 *.Net 4),但现在至少我希望能帮助你理解的东西是如何工作的所以你可以想想怎么去了,直到我们可以使它更好。

第 1 部分: 合并选项

< T > ObjectQuery MergeOption 枚举属性效果显著,对 EF 处理一个查询的方式。默认值是 MergeOption.AppendOnly,而且它有点神奇身份分辨率图案,是大多数 Orm 的默认值 — — 那就是,新的实体,则查询检索附加到 ObjectContext,和如果具有相同键作为一个传入的实体的实体已经附加到上下文,则该对象返回,而不是传入的实体。其他合并选项的两个主要用于解决冲突: MergeOption.PreserveChanges 将更新的上下文具有实体的数据库中的当前数据以使你有在上下文中所做的更改将保存到数据库和替换无论是原始值,而 MergeOption.OverwriteChanges 将更新当前值语境具有这样贵实体被更改以匹配数据库的当前状态。最有趣的一个合并选项,虽然,是 MergeOption.NoTracking,本质上说,"只是给我任何实体来从数据库没有试图附加或身份解决他们针对上下文"。不难理解,NoTracking 选项是其中发展最快。有点不太明显的是 NoTracking 在许多情况下也会产生不同的查询比其他人,因为它是最弱的最简单、 最精简的版本魔法如此之类的自动检索 EntityKey 属性的实体相关与独立协会不会发生与 NoTracking 查询 — — 只有与其他合并选项。

为了今天这篇文章,但是,让我们家上 MergeOptions 这是一个特定的查询实例不是的 ObjectContext 属性的属性的另一个方面。所以,例如,我可以与 EF 使用 LINQ 和创建两个单独的查询,从同一个实体集检索数据,但是有不同的合并选项,使事情清楚我会那样做的查询实例上获取和设置 MergeOption 属性的查询。不幸的是,既然 IQueryable 是一个固定的接口,用于 LINQ,和它是什么返回 LINQ 语法的时候,我通常不得不投到 ObjectQuery 我 LINQ 查询,可以订的合并选项。其结果是看起来像这样的代码:

// executing a query directly from the context will get the default AppendOnly option
var query1 = from c inctx.Customers
             where c.Country == "UK"
             select c;
var customer1 = query1.First();
Debug.Assert(customer1.EntityState == EntityState.Unchanged);
 
// if I want a different merge option, then I set the MergeOption on the query instance
var query2 = from c inctx.Customers
             where c.Country == "UK"
             select c;
((ObjectQuery)query2).MergeOption = MergeOption.NoTracking;
var customer2 = query2.First();
Debug.Assert(!Object.ReferenceEquals(customer1, customer2));
Debug.Assert(customer2.EntityState == EntityState.Detached);

有时你会看到一种模式是合并选项 ObjectQuery (或 ObjectSet 在 EF4 的情况下) 上设置上下文,然后写一个查询使用该查询的人。这实际上是,对一个点,因为在上下文上的 ObjectQuery 属性 getter 将缓存实例的 ObjectQuery (至少为默认代码生成器 — — 如果有人使用一个自定义的模板或手写的上下文,然后所有的赌注是关闭)。所以很重要,记住这是查询实例不上下文的属性。让我来告诉你如何你可以得到绊倒了如果你不小心:

var query1 = from c inctx.Customers
             where c.Country == "UK"
             select c;
ctx.Customers.MergeOption = MergeOption.NoTracking;
var customer1 = query1.First();
Debug.Assert(customer1.EntityState == EntityState.Unchanged);

在上面的代码中,你有什么期待?将该断言火灾或不吗?什么关于这段代码:

ctx.Customers.MergeOption = MergeOption.NoTracking;
var query2 = from c inctx.Customers
             where c.Country == "UK"
             select c;
var customer2 = query2.First();
Debug.Assert(customer2.EntityState == EntityState.Detached);

事实证明,既不主张将火,因为在第一种情况下,合并选项并不设置,直到创建查询和合并选项是来自查询没有上下文,query1 只是使用默认合并选项后所以检索的实体连接,并最终以未更改状态。在第二种情况下,合并选项被设置,所以该查询基于从属性返回该客户查询和继承其合并选项与客户进行检索时从数据库,它使用 NoTracking 和实体最终离的结果后,会创建的查询。

如果你看看客户属性的生成代码的上下文,您将看到 ObjectQuery 缓存的作用:

publicObjectSet<Customer> Customers
{
    get
    {
        if((_Customers == null))
        {
            _Customers = base.CreateObjectSet<Customer>("Customers");
        }
        return_Customers;
    }
}
privateObjectSet<Customer> _Customers;

如果转而写了这段代码,只是始终返回调用 CreateObjectSet (如果我亲笔写的 ObjectContext 为一个简单的 POCO 例子或者别的,我有时带的快捷方式) 的结果,然后两个上面的查询会使用默认合并选项因为上面一行设置 MergeOption 将在新创建的 ObjectSet,然后扔掉了,因为没有人抱有参考和它下面的行创建 query2 会使用不同的新创建的 ObjectSet。

到目前为止是你和我一起吗?将该信息放在次要地位,并让我们看看如何编译的查询工作。

第 2 部分: 已编译的查询

已编译查询的想法是减少与执行某个特定查询第一次通过确保你付出的成本只有一次,如果你想要多次执行该查询相关的成本。它的工作的方式是你提前创建 LINQ 查询和调用编译方法来回来了一个特别的委托,你可以使用以后执行该查询。有三个步骤。首先,您声明为静态,并将它初始化为返回调用编译的方法的委托。

staticFunc<NorthwindEntities, string, IQueryable<Customer>> compiledQuery =
    CompiledQuery.Compile((NorthwindEntities ctx, stringcountry) =>
        (from c inctx.Customers
         where c.Country == country
         select c));

准备好执行该查询时,你调用该委托,以取回 ObjectQuery 在上下文和参数中传递。

var query = compiledQuery(ctx, "UK");

然后,可以像任何其他使用此查询:

foreach(var customer inquery)
{
    // do some stuff
}

看来,虽然,有几个非常意想不到的事情,关于方式已编译的查询工作。再次,我只能说我很抱歉这是 EF,那我们会在以后的版本中,来研究它的这样一个棘手的部分和同时如果你知道它是如何工作的你可以至少弄清楚如何让您的应用程序功能:

  1. 直到实际执行该查询的第一次发生了没有真正的工作。CompiledQuery.Compile 并不像一种准备方法当您调用准备了繁重的任务发生的地方。当调用委托返回 ObjectQuery,甚至不会发生艰苦的工作。它只发生在该查询的执行方法调用或更通常可以查询所列举。不过后第一次执行,, 所有的辛勤工作被缓存以便以后再执行速度更快。
  2. 如果您创建一个新的查询,基于已编译查询,它将工作 — — 你只是不会得到任何好处的编译。任何更改将发送到服务器的查询会产生一个新的查询,它不预编译。调用。作用是: 或。Count () 或。Any (),例如,将更改该查询。你需要保持精确的查询相同。这是一种方法来完成调用。所以你会得到编译为违背了数据库的部分,然后执行其他步骤在内存中的完成数据库查询后,将由 LINQ to 对象执行已编译查询 AsEnumerable() 方法,然后您调用任何其他方法。

第三次出现意外的行为值得一整段......

第 3 部分: 结合合并选项和已编译的查询

真正棘手的事情时,你结合已编译的查询和合并选项。因为合并选项是 ObjectQuery 实例的属性,并与已编译查询实际上并未得到该实例直到您调用该委托,你不能指定合并选项,最初调用编译方法时,或当你调用该委托。然而,合并选项没有影响实际生成的查询中,所以在时间时第一次执行查询 (即当所有感兴趣的工作发生了,记住) 合并选项被锁定。当第一次执行该查询时,EF 检查 ObjectQuery 属性传递到委托的上下文上的合并选项。那第一次执行后,虽然,合并选项将相同的查询而不考虑合并选项在用于任何后续的执行上下文的 ObjectQuery 上设置任何后续执行。

所以,如果你以下面的代码 (使用相同的已编译的查询委托第 2 以上部分中的示例中创建):

using(var ctx1 = new NorthwindEntities())
{
    ctx1.Customers.MergeOption = MergeOption.NoTracking;
    var query1 = compiledQuery(ctx1, "UK");
    var customer1 = query1.AsEnumerable().First();
    Debug.Assert(customer1.EntityState == EntityState.Detached);
}

然后编译的查询将使用没有跟踪合并选项,所以该断言不会失败。如果你遵循,这段代码...

using(var ctx2 = new NorthwindEntities())
{
    var query2 = compiledQuery(ctx2, "France");
    var customer2 = query2.AsEnumerable().First();
    Debug.Assert(customer2.EntityState == EntityState.Detached);
}

第二次执行也将使用 NoTracking 合并选项,即使在其上下文上的 ObjectQuery 有仅附加作为其合并选项。

让事情有点复杂,保持介意意外的行为 #2,这意味着如果我漏掉了 AsEnumerable 调用,然后执行查询会将使用新的查询,而不是重用已编译的查询,所以第二次执行不会锁定在合并选项,相反会拿起从查询上下文上的合并选项。因此,如果第二次执行代码看起来像这样:

using(var ctx2 = new NorthwindEntities())
{
    var query2 = compiledQuery(ctx2, "France");
    var customer2 = query2.First();
    Debug.Assert(customer2.EntityState == EntityState.Detached);
}

然后该断言将失败,因为合并选项将使用仅附加和实体状态未更改,而不是离会结束。

摘要

我能说什么呢?已编译的查询是棘手的,当您将它们合并与合并选项就更棘手,但性能好处可以是巨大的所以它值得学习关于他们的工作。请记住这三种可能的意外行为:

  1. 没有真正的工作直到实际执行该查询的第一次发生。
  2. 如果您创建一个新的查询,基于已编译查询,它将工作 — — 你只是不会得到任何好处的编译。
  3. 使用与已编译查询的合并选项是由 ObjectQuery 在第一次执行该查询的时间用于已编译查询作为基础上指定的合并选项决定的。

现在,回到试图找到时间来完成下一阶段的 D3 过帐。:-)

-Danny

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多