【问题标题】:Selecting multiple columns for updating in Linq在 Linq 中选择多个列进行更新
【发布时间】:2021-03-18 11:34:57
【问题描述】:

我有一个包含数千种产品的产品表。我只想更新此表的两列(价格,isAvailable)。那么有没有办法从这个表中只选择这两列?

这是我正在使用的代码。但我不想选择所有列。

     var dbModels = await DbContext.Products
                    .Where(x => x.SellerId == sellerId)
                    .ToListAsync(); 

我试过了

       var db = await DbContext.ProductSkuDetail
                .Where(x => x.SellerId == sellerId)
                .Select(y => new
                {
                    Price = y.Price,
                    IsAvailable = y.IsAvailable
                }).ToListAsync();

但这是只读的。我想更新这些列。

【问题讨论】:

  • 通过使用y => new {,您正在创建一个匿名类(一个包装器)。如果您只删除 Select 并只更新您需要的列会怎样。
  • 谢谢杰罗恩。如果我删除 Select 那么所有的列都会被选中吗?
  • 您是在问如何查询实体并且只返回 2 列吗?还是您已经拥有整个实体并且只想更新 2 列?
  • "我想更新那些列。"从您的代码中不清楚新值的来源。
  • 如果只需要更新几列,最好使用FromSqlRaw甚至Dapper,直接执行UPDATE。 LINQ 不修改数据。 ORM,EF,确实如此。 ORM 适用于 Objects 而不是单个属性。加载对象时,EF 将跟踪哪些属性发生变化并生成相应的 UPDATE 语句。

标签: c# sql entity-framework linq .net-core


【解决方案1】:

是的,有一种方法可以准确指定您想要的列。

,您不能使用该方法更新数据。

使用实体框架DbSet<...>获取数据时,有两种方法:获取表的完整行,或者只获取行的某些属性。

如果您在不使用Select 的情况下执行查询,则使用第一种方法。如果这样做,数据将复制到DbContext.ChangeTracker

DbSet.FindIQueryble.Include 等其他方法也会将获取的数据复制到 ChangeTracker。

如果你使用Select指定你要获取的数据,那么获取的数据不会被复制到ChangeTracker中。

当您调用DbContext.SaveChanges 时,ChangeTracker 用于确定哪些项目已更改或删除,因此需要更新。

ChangeTracker 保留原始获取的数据及其副本。作为更改的结果,您将获得对副本的引用。因此,无论何时更改对副本的引用的属性值,它们都会在 ChangeTracker 中的副本中更改。

当您调用 SaveChanges 时,副本会与 ChangeTracker 中的原始文件进行比较,以检测哪些属性已更改。

为了提高效率,如果您不打算更新获取的数据,明智的做法是确保获取的数据不在 ChangeTracker 中。

使用实体框架获取数据时,请始终使用Select 并仅获取您实际计划使用的属性。如果您打算更改获取的数据,请仅在没有 Select 的情况下进行查询。

更改 = 更新属性,或删除整个行。另外:仅当您计划更新获取的数据时才使用查找和包含。

您想更新获取的行

因此您必须获取完整的行:不要使用 Select,获取完整的行。

如果您想通过主键获取项目,请考虑使用DbSet.Find。这有一个小的优化,如果它已经在 ChangeTracker 中,则不会再次获取数据。

考虑为此编写 SQL

通常您不必定期更新数千个项目。但是,如果您必须经常这样做,请考虑使用 sql 进行更新:

using (var dbContext = new MyDbContext(...))
{
    const string sqlText = @"Update products
        SET Price = @Price, IsAvailable = @IsAvailable....
        Where SellerId = @SellerId;";
    var parameters = new object[]
    {
        new SqlParameter("@SellerId", sellerId),
        new SqlParameter("@Price", newPrice),
        new SqlParameter("@IsAvailable", newAvailability),
    };
    dbContext.DataBase.ExecuteSqlCommand(sqlText, parameters);
}

(在我的例子中你必须检查SQL命令的有效性。由于我使用实体框架,我的SQL有点生疏。)

顺便说一句:虽然这种方法效率很高,但你会失去实体框架的优势:实际数据库与表结构的解耦:你的表和列的名称会渗透到这个语句。

我的建议是只使用直接 SQL 来提高效率:如果您必须经常更新。您的 DbContext 隐藏了数据库的内部布局,因此请将此方法作为 DbContext 的一部分

public void UpdatePrice(int sellerId, bool IsAvailable, decimal newPrice)
{
    const string sqlText = ...
    var params = ...
    this.Database.ExecuteSqlCommand(sqlText, params);
}

唉,每次更新都必须调用一次,没有 SQL 命令可以在一个 SQL 命令中更新数千个不同价格的商品 }

【讨论】:

    【解决方案2】:

    您必须在匿名类型中包含主键:

    var models = await context.Products
        .Where(p => p.SellerId == sellerId)
        .Select(p => new
        {
            Id = p.Id, // primary key
            Price = p.Price,
            IsAvailable = p.IsAvailable
        })
        .ToListAsync();
    

    然后,当您需要将数据保存回数据库时,您需要创建具有相同主键的实体并将它们附加到上下文中。

    foreach (var x in models)
    {
        var product = new Product
        {
            Id = x.Id,
            Price = newPrice, // get new price somehow
            IsAvailable = false // get new availability somehow
        };
        context.Attach(product);
    
        var entry = context.Entry(product);
        entry.Property("Price").IsModified = true;
        entry.Property("IsAvailable").IsModified = true;
    }
    
    await context.SaveChangesAsync();
    

    更多关于Attach的信息

    【讨论】:

    • 这会导致一个半构建的无效对象。为此,所有其他属性都必须可以为空并且没有验证
    • Attach 使实体处于Unchanged 状态。这不得产生任何更新。我错过了什么吗?
    • 我相信“可能”缺少的唯一行是entry.State = EntityState.Modified;。否则,代码本身似乎直接来自 docs.microsoft.com 本身。还缺少禁用验证检查的行,因为其余属性为空/默认值可能会引发验证错误。
    【解决方案3】:

    EF 不适合批处理操作。

    它适用于对象,而不是表、记录或字段。如果要更新或删除对象,则需要先阅读它(它们)。这允许 EF 更改跟踪器跟踪字段值或整个对象状态的更改,并生成适当的 SQL。

    对于批处理操作,请考虑使用原始 SQL 查询、Dapper 等轻量级库或Entity Framework Plus 等第三方包。

    【讨论】:

      【解决方案4】:

      您可以使用 ExecuteSqlCommandAsync 命令编写将在您的 SQL Server 中执行的查询。

      await dbContext
      .Database
      .ExecuteSqlCommandAsync("UPDATE Products SET Price = {0}, IsAvailable = {1} WHERE SelleriId = {2}", new object[] { priceValue, isAvailableValue, sellerId });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-08-07
        • 2021-12-11
        • 1970-01-01
        • 1970-01-01
        • 2013-05-24
        • 2015-11-11
        • 2020-01-23
        相关资源
        最近更新 更多