重写 Linq to SQL 中的实体缓存/更改跟踪行为

发布于 2024-08-19 21:03:40 字数 1118 浏览 6 评论 0原文

我认为也许原来的问题太冗长了,有太多不必要的细节,所以这是我尝试简化的。

我正在寻找一种方法来执行任何以下的行动。我只需要做其中一项,而不是全部。如果有人知道其中之一的答案,请回复。那么,是否可以在 Linq to SQL 中执行以下任何操作:

  • 通过 ExecuteQueryExecuteMethodCallDataContext 中提取实体没有跟踪这些实体?

  • 调用ExecuteQueryExecuteMethodCall并保证我始终收到从数据库检索到的结果的最新副本,即使这些实体已经被检索并且已经在身份缓存?

  • 指示 Linq to SQL 不要对特定实体类型执行任何更改跟踪 - 但仍然允许其他类型的更改跟踪?

    指示

限制:

  • Refresh 方法是不可能的;实体的数量相当大,这将成为性能灾难。

  • 我不能简单地将 ObjectTrackingEnabled 设置为 false,因为 DataContext 不允许将其设置回 true执行查询后,我确实需要跟踪一些实体。

  • 我也不能扔掉原来的 DataContext 并使用新的;我需要能够在事务中间执行此操作。


这开始成为一个严重的问题,我真的认为默认行为是考虑不周的。如果我执行临时查询或存储过程,我希望收到的结果与所述查询返回的结果完全相同。这才有意义;如果我想要旧的、过时的实体,为什么我要返回数据库来获取它们?

目前,我的解决方法是(a)专门为查询创建一个新的 DataContext 并覆盖事务隔离级别,或者(b)使返回类型为与以下内容相同的“DTO”:实体以各种方式但没有 [Table] 属性,并使用 AutoMapper 将其映射到原始实体。这两个看起来都是可怕的黑客行为。

非常感谢任何人对这个难题提出的任何建议。

I think that perhaps the original question was too long-winded with too many unnecessary details, so this is my attempt to simplify.

I am looking for a means to perform any of the actions below. I only need to do one, not all. If anyone knows the answer to even one of these, please respond. So, is it possible to do any of the following in Linq to SQL:

  • Pull entities out of a DataContext via ExecuteQuery or ExecuteMethodCall without having those entities tracked?

  • Invoke ExecuteQuery or ExecuteMethodCall and guarantee that I always receive fresh copies of the results retrieved from the database, even if those entities had already been retrieved and are already in the identity cache?

  • Instruct Linq to SQL not to perform any change tracking whatsoever on specific entity types - but still allow change tracking for other types?

Restrictions:

  • The Refresh method is out of the question; the number of entities is quite large and this would become a performance disaster.

  • I cannot simply set ObjectTrackingEnabled to false, because the DataContext does not allow setting it back to true after a query has been executed, and I do need some of the entities to be tracked.

  • I also cannot throw away the original DataContext and use a new one; I need to be able to do this in the middle of a transaction.


This is starting to become a serious problem, and I really think that the default behaviour is ill-conceived. If I execute an ad-hoc query or stored procedure, I expect the results I receive to be the exact results that were returned by said query. It only makes sense; if I wanted the old, stale entities, why would I go back to the database to get them?

At the moment, my workaround is to either (a) create a new DataContext specially for the query and override the transaction isolation level, or (b) make the return type a "DTO" that is identical to the entity in every way but without the [Table] attribute, and map it to the original entity using AutoMapper. Both of these seem like horrible hacks.

Would really appreciate any suggestions anyone has on this conundrum.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风吹雨成花 2024-08-26 21:03:40

我已经设法针对这个问题提出了一个可行的长期解决方法。它并不完全理想,但到目前为止使用起来相对轻松,并且远没有其他替代方案那么可怕。

由于这些查询无论如何都是纯 SQL - 它们都是用于内联 SQL 的 ExecuteQuery 或用于存储过程的 ExecuteMethodCall - 我决定直接使用“原始”ADO。 NET,例如当我不希望 DataContext 了解某些实体时。

当然,处理一堆 IDbCommand 实例和来自 IDataReader 的手动映射是很可怕的,所以今天早上我花了几个小时编写了一个库为我完成大部分繁重的工作,为 IDbCommand 公开一个“流畅”(我宽松地使用这个术语)包装器,这是一个使用 MetaModel 的自动 LinqDataReaderMapper 这样我就可以使用现有的实体而无需修改,以及一堆重载的扩展方法可以更快地编写更简单的查询。

在一天结束时,我正在写这样的东西:

var results = context.Connection
    .Command("SELECT Column1, Column2 FROM Table " +
             "WHERE FilterColumn1 = @Param1 AND FilterColumn2 = @Param2")
    .Parameters(
        p => p.Name("Param1").Value(someValue),
        p => p.Name("Param2").Value(someOtherValue))
    .ExecuteReader()
    .MapWith(context.Mapping).To<MyEntity>();

或者只是这样:

var results = context
    .ExecuteQueryRaw<MyEntity>(CommandType.StoredProcedure, "SomeProc",
        new { Param1 = someValue, Param2 = someOtherValue });

我不会发布它的整个代码 - 它很长,我认为它只是一个大的 tl;dr 此时 - 但主要思想是,它们都给我返回一个 IEnumerable,并且因为它们是直接从 IDataReader 复制的,它们本质上是独立的实体 - DataContext 没有直接了解它们,因此既不能拦截结果也不能在事后跟踪它们。

因此,问题部分解决了,尽管如果我能让 DataContext 自行运行,效果会更好。

I've managed to come up with a viable longer-term workaround for this issue. It's not perfectly ideal, but it's been relatively painless to employ so far and is far less scary than the alternatives.

Since these queries are pure SQL anyway - they're all ExecuteQuery for inline SQL or ExecuteMethodCall for stored procedures - I've decided to just drop down to "raw" ADO.NET for instances when I don't want the DataContext to know about certain entities.

Of course, it would be horrible to have to deal with a bunch of IDbCommand instances and manual mappings from IDataReader, so I spent a few hours this morning coding up a library to do most of the heavy lifting for me, exposing a "fluent" (I use the term loosely) wrapper for the IDbCommand, an automatic LinqDataReaderMapper which uses the MetaModel so I can use my existing entities without modifications, and a bunch of overloaded extension methods to make it quicker to write the simpler queries.

At the end of the day, I'm writing something like this:

var results = context.Connection
    .Command("SELECT Column1, Column2 FROM Table " +
             "WHERE FilterColumn1 = @Param1 AND FilterColumn2 = @Param2")
    .Parameters(
        p => p.Name("Param1").Value(someValue),
        p => p.Name("Param2").Value(someOtherValue))
    .ExecuteReader()
    .MapWith(context.Mapping).To<MyEntity>();

Or just this:

var results = context
    .ExecuteQueryRaw<MyEntity>(CommandType.StoredProcedure, "SomeProc",
        new { Param1 = someValue, Param2 = someOtherValue });

I'm not going to post the entire code for it - it's pretty long and I think it would just be a big tl;dr at this point - but the main idea is that these both give me back an IEnumerable<MyEntity>, and since they're being copied directly from an IDataReader, they are essentially detached entities - the DataContext has no direct knowledge of them and can therefore neither intercept the results nor track them after the fact.

So, problem partially solved, although it would still be better if I could just get the DataContext to behave itself.

十雾 2024-08-26 21:03:40

如果您为 LINQ 查询的结果构造一个新对象,则不会跟踪它的更改。因此,您可以使用现有的 DataContext 来加载您知道不需要更新的值,如下所示(代码来自 此处):

using (NorthwindDataContext context = new NorthwindDataContext())
{
  var a = from c in context.Categories
  select new Category
  {
    CategoryID = c.CategoryID,
    CategoryName = c.CategoryName,
    Description = c.Description
  };
}

我认为这基本上就是第二个示例中的内容在你的回答中,我只是想确认这是可行的,而且不是一个坏主意。

另外,我的理解是,在任何情况下都不应该使用单个庞大的 DataContext; DataContext 实际上是一个工作单元级别的集合,而不是整个世界。因此,对只读类型数据和可更新数据使用单独的 DataContext 是非常有意义的(我想,除非您无法提前预测哪个是哪个)。

If you construct a new object for your results from a LINQ query, it will not be change tracked. So you could use your existing DataContext to load values you know you won't need to update, like so (code from here):

using (NorthwindDataContext context = new NorthwindDataContext())
{
  var a = from c in context.Categories
  select new Category
  {
    CategoryID = c.CategoryID,
    CategoryName = c.CategoryName,
    Description = c.Description
  };
}

I think this is essentially what you have in the second example in your answer, I just want to affirm that that works and isn't a bad idea.

Also, my understanding is that you shouldn't be using a single massive DataContext in any case; a DataContext is really a Unit-of-Work level collection, not the whole world. So using separate DataContexts for your read-only type data and your updateable data makes perfect sense (unless you can't predict which is which in advance, I suppose).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文