C# 优化 Linq 查询:用 where 更新

C# Optimizing Linq query: Update with where

我将如何优化以下查询?我们正在审查数据库中的 entity framework 个查询,并努力学习。

        using (var context = new DataDbContext())
        {
            var query = (from u in content.Parents
                         where u.Children.Any(y = y.Age > 13)
                         select u);

            foreach (var parent in query.ToList())
            {
                foreach (var children in owner.Children)
                {
                    children.IsTeenager= true;
                }
            }
            context.SaveChanges();
        }

优化不多,可以减少代码。逻辑有点奇怪

using (var context = new DataDbContext())
{
    foreach (var child in content.Parents.Where(o => o.Children.Any(x => x.Age > 13)).SelectMany(o => o.Children))
    {
        children.IsTeenager= true;
    }

    context.SaveChanges();
}

using (var context = new DataDbContext())
{
    var parents = content.Parents.Where(o => o.Children.Any(x => x.Age > 13));
    foreach (var child in parents.SelectMany(o => o.Children))
    {
        children.IsTeenager= true;
    }

    context.SaveChanges();
}

没有什么好方法可以简化您在这里所做的事情。 EF 倾向于将您推向从数据存储中读取一堆数据、在本地更新实体然后将这些更改写回的路径。这显然很慢。但是,您在这里要做的是获取所有子项并设置 IsTeenager 属性 这样您甚至都不关心 Parent 对象,您可以简单地这样做:

var children = context.Children.Where(c => c.Age < 13);

foreach(var child in children)
{
    child.IsTeenager = true;
}

context.SaveChanges();

当然,使用原始 SQL 可以更简单地完成此操作。例如:

context.Database.ExecuteSqlCommand("UPDATE Children SET IsTeenager = 1 WHERE Age < 13");

我之前在你的评论中看到了以下内容(已被删除):

it was a job interview question I had few months ago, not even sure

如果我在求职面试中问某人这个问题,我希望他们会告诉我以下内容:


  1. Age 和 IsTeenager 应该取决于每个单独的实体,而不是通过集合计算。
  2. 您应该确保保存此人/child 的出生日期而不是年龄。否则,您不知道实体的年龄何时发生变化,因为年龄仅在系统中捕获实体的日期和时间有效。
  3. IsTeeneger 不应持久化,应根据出生日期值派生。如果它只在 c# 代码中检查过并且您不必对其进行查询,那么将其设为仅模型 getter 字段,即 not 映射到 Sql.如果您需要对其进行查询,请将其作为计算列作为数据库架构。 Age!
  4. 也应该这样做
  5. Parent 和 Child 可以更好地建模为具有递归 one-to-many 关系 Children 的 Person。这样你也可以做 grand parents who have children who have children.

仅限模型

public class PersonModel
{
    public DateTime BirthDate { get; set; }
    public int Age
    {
        get
        {
            var today = DateTime.Today;
            // Calculate the age.
            var age = today.Year - BirthDate.Year;
            // Go back to the year the person was born in case of a leap year
            if (BirthDate > today.AddYears(-age)) age--;
            return age;
        }
    }
    public bool IsTeenager
    {
        get
        {
            return Age >= 13 && Age < 20;
        }
    }
}

映射到计算列

public class PersonModel
{
    public DateTime BirthDate { get; set; }
    public int Age { get; set; } // should be computed and mapped from sql
    public bool IsTeenager { get; set; } // should be computed and mapped from sql
}
(from u in content.Parents
  where u.Children.Any(y = y.Age > 13)
  select u);

这似乎是一个错误。如果 Parent 有一个 child 是 14 而另一个是 9 两者都会将 IsTeenager 设置为 true。相反,如果他们有一个 13 岁的 child,那么 child 就不会将 Teenager 设置为 true。

如果可能的话,我会 IsTeenager 计算 属性 而不是存储值

public bool IsTeenager => Age >= 13 && Age <= 19

这样我就再也不用调用查询了。

如果失败,我会将查询更改为

content.Parents.SelectMany(o => o.Children).Where(o => o.Age >= 13 && o.Age <= 19)

可能我可以完全跳过间接寻址。

content.Children.Where(o => o.Age >= 13 && o.Age <= 19)

取决于数据库的结构。我真的需要通过 parents 吗?

除非我知道 IsTeenager 在查询 运行 时总是为假,否则我可以过滤那些不需要它的设置:

content.Parents.SelectMany(o => o.Children).Where(o => !o.IsTeenager && o.Age >= 13 && o.Age <= 19)

content.Children.Where(o => !o.IsTeenager && o.Age >= 13 && o.Age <= 19)

当你想使用 Entity Framework 更新数据时,你必须先将它检索到内存中(通过预加载或延迟加载)然后修改它。
有时它会导致 N + 1 问题。这意味着对数据库服务器进行了 N+1 次调用。如果您使用某些基于云的托管服务,例如 Azure 或 AWS,那么您需要按请求付费。所以这在经济上也是昂贵的。

对于您的情况,我更愿意在 Entity Framework 中执行此操作,但使用存储过程。使用了普通查询,但我不建议您以这种方式进行查询。

使用存储过程将使您无需将数据带入内存,只需调用 1 次数据库即可获得结果。