在Entity Framework Core 6中,如何更新一个宽表而不将所有列返回到内存中?

huangapple go评论76阅读模式
英文:

In Entity Framework Core 6, how to update a wide table without returning all columns to memory?

问题

在Microsoft Entity Framework Core 6.0中,在选择性选择之后使用.Find()会提高性能吗?

这里的情况是:我们系统中的主要实体(表)有100多列。通常只需要加载2~5列以执行所需的业务逻辑。业务逻辑可能需要或不需要将某些内容保存到该实体中。

问题是哪种方式更快:

// 将所有100列加载到内存中
var targetRow = _context.MainTable.FirstOrDefault( /* where条件 */);

/* 使用实体中的4个值执行业务逻辑 /
if( /
需要保存 /)
{
targetRow.Status = /
新值 */
_context.SaveChanges()
}

还是只返回所需的列并在需要保存时使用.Find()更快:

// 只加载5列到内存中
var targetRow = _context.MainTable.Where( /* where条件 */).Select( r => new { r.id, r.col1, r.col2, r.col3, r.col4, r.col5);

/* 使用实体中的4个值执行业务逻辑 /
if( /
需要保存 /)
{
var trackedEntity = _context.MainTable.Find(targetRow.id);
trackedEntity.Status = /
新值 */
_context.SaveChanges()
}

虽然第二种方式似乎需要两次与数据库的往返,但Microsoft关于Find()的说明如下:

通过给定的主键值查找实体。如果上下文正在跟踪具有给定主键值的实体,则立即返回而不会向数据库发出请求。否则,将向数据库发出查询以查找具有给定主键值的实体,并将找到的实体连接到上下文并返回。如果没有找到实体,则返回null。

问题1:在第二个示例中,Find()需要在Where().Select()中加载足够的数据以立即返回,还是必须首先进行另一次往返?

问题2:如果.Find()需要另一个完整的往返,是否有办法在这个示例中使用EF来更新行,而不必加载所有100列?

英文:

In Microsoft Entity Framework Core 6.0, will using a .Find() after a selective select improve performance?

Here is the situation: the main entity (table) in our system has over 100 columns. Often only 2~5 columns need to be loaded to preform the needed business logic. The business logic might or might now require saving something to that entity.

The question is which is faster:

// Load All 100 columns into memory    
var targetRow = _context.MainTable.FirstOrDefault( /* where condition */);

/*  Preform business logic using 4 values from Entity */
if( /* save is needed */)
{
    targetRow.Status = /* New Value */
    _context.SaveChanges()
}

Or is it faster to return only the needed columns and use .Find() if the Save is called for:

// Load only 5 columns into memory    
var targetRow = _context.MainTable.Where( /* where condition */).Select( r => new { r.id, r.col1, r.col2, r.col3, r.col4, r.col5);

/*  Preform business logic using 4 values from Entity */
if( /* save is needed */)
{
    var trackedEntity = _context.MainTable.Find(targetRow.id);
    trackedEntity.Status = /* New Value */
    _context.SaveChanges()
}

While the second looks like it takes two round trips to the DB, Microsoft says this about Find():

Finds an entity with the given primary key values. If an entity with the given primary key values is being tracked by the context, then it is returned immediately without making a request to the database. Otherwise, a query is made to the database for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found, then null is returned.

Question #1: in the second example does EF Core load enough data in the Where().Select() for the Find() to return immediately, or must it make another round trip first?

Question #2: if .Find() does require another full round trip, is there any way to use EF to update the row in this example without loading all 100 columns?

答案1

得分: 0

首先,有几点说明:

  1. 最快的方式是使用存储过程或第三方库,它们可以将您的逻辑(如果可能的话)映射成单个查询。
  2. 您应该自己测量您的设置(我们对数据结构、数据大小、负载配置等一无所知)(理论上您不应该获取不必要的数据,但实际上我曾经看到过在应用程序中没有实质性差异的情况)。

至于问题 - 选择匿名对象不会导致跟踪实体,因为没有实体,即:

var targetRow = _context.MainTable
    .Where( /* where 条件 */)
    .Select( r => new { r.id, r.col1, r.col2, r.col3, r.col4, r.col5})
    .ToList();

var trackedCount = ctx.ChangeTracker.Entries().Count();

trackedCount 应该为零(如果上下文中没有发出其他请求)。因此,Find 需要进行一次往返。这在跟踪和自定义投影中有详细说明。

您可以做的一件事是使用 Attach。根据我的实验,EF 也不会跟踪“手动”创建的实例(我无法找到覆盖此内容的文档),因此您可以/需要做如下操作:

var targetRow = _context.MainTable
    .Where( /* where 条件 */)
    .Select( r => new MainTable { Id = r.id, ...}) // 正确构造
    .ToList();
// ...
if(toUpdate)
{
   _context.MainTable.Attach(targetRow);
   targetRow.Something = ....;
   _context.SaveChanges();
}

阅读更多:

英文:

First of all several notes:

  1. The fastest way would be using either some kind of stored procedure or 3rd party library which will map (if possible) your logic into a single query
  2. You should measure your setup (we don't know anything about data structure, data size, load profiles etc.) yourself (in theory you should not fetch unnecessary data but in practice I have seen situations when there were no meaningful difference for the app).

As for the question - selecting an anonymous object will not result in tracking the entity because there is no entity, i.e.:

var targetRow = _context.MainTable
    .Where( /* where condition */)
    .Select( r => new { r.id, r.col1, r.col2, r.col3, r.col4, r.col5})
    .ToList();

var trackedCount = ctx.ChangeTracker.Entries().Count();

trackedCount should be zero (if not other requests were made on the context). So Find will need to make a roundtrip. This is covered in the Tracking and custom projections.

One thing you can do is to use Attach. Based on my experiments EF does not track the "manually" created instances either (was not able to find the docs covering this), so you can/need do something like:

var targetRow = _context.MainTable
    .Where( /* where condition */)
    .Select( r => new MainTable { Id = r.id, ...}) // construct correctly
    .ToList();
// ...
if(toUpdate)
{
   _context.MainTable.Attach(targetRow);
   targetRow.Something = ....;
   _context.SaveChanges();
}

Read more:

huangapple
  • 本文由 发表于 2023年6月26日 07:19:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76552770.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定