【问题标题】:When should I use navigation properties to query the database?我什么时候应该使用导航属性来查询数据库?
【发布时间】:2020-03-15 08:15:27
【问题描述】:

在我的 Entity Framework 6 应用程序中,我有一张人们的电子邮件地址表:

public class EmailAddress
{
    public int Id { get; set; }

    public int PersonId { get; set; }

    public string EmailAddress { get; set; }

    public virtual Person Person { get; set; }
}

Person 对象也引用了这些电子邮件地址:

public class Person
{
    public int Id { get; set; }
    {...}

    public virtual ICollection<EmailAddress> EmailAddresses { get; set; }
}

如果我想获取一个人的所有电子邮件地址检查该人是否实际存在,以下两种方法是否更有效:

  1. Persons 表上运行Any() 查询,然后在EmailAddresses 表上运行另一个查询,使用PersonId 作为参数:

    public IEnumerable<EmailAddress> GetAddressesByPerson(int personId)
    {
        if (!Context.Persons.Any(x => x.Id == personId))
        {
            throw new Exception("Person not found");
        }
    
        return Context.EmailAddresses.Where(x => x.PersonId == personId).ToList();
    }
    
  2. 获取Person 对象并返回EmailAddresses 导航属性:

    public IEnumerable<EmailAddress> GetAddressesByPerson(int personId)
    {
        var person = Context.Persons.Find(personId);
    
        if (person == null)
        {
            throw new Exception("Person not found")
        }
    
        return person.EmailAddress;
    }
    

【问题讨论】:

    标签: c# entity-framework linq


    【解决方案1】:

    在第一种解决方案的情况下,EF 将生成包含 EXISTS 语句的 sql 查询。然后,如果它退出,您将对数据库执行完全不同的第二次查询。

    如果是第二种解决方案,您将只发送一个select ... from Persons where .. 声明。当您将EmailAddress 设置为导航属性时,如果启用了Lazy Loading,那么EF 将根据personId 生成并执行针对EmailAdress 表的查询。如果Lazy Loading 未启用,则EmailAddress 将为空或为空。

    作为第三个选项,您可以使用 Eager Loading 功能,这将使 EF 生成 join 查询,并一次生成 person 和相关的 EmailAddresses。

    所以,如果大多数情况下您希望拥有正确的personId,那么您可以切换到Eager Loading 模式。 Lazy Loading 主要在场景中很有帮助,当您仅在某些情况下需要获取相关实体时。

    顺便说一句,我建议你turn on logging in EF,查看生成的查询。

    因此,这里是急切加载相关实体的代码示例:

    var person = Context.Persons
                .Include(s ⇒ s.EmailAddresses)
                .FirstOrDefault(x => x.Id == personId);
    

    关键是添加对Include方法的调用并传递导航属性。传递的实体将被急切地加载。在查询结束时,您可以使用任何会立即执行的方法,例如FirstFirstOrDefaultSingleSingleOrDefaultToList 等。 Include不能和Find一起使用,因为后者是DbSet的方法。在您的情况下,最相关的是Single,如果表中没有具有指定id的人,它将自动抛出异常。

    【讨论】:

      【解决方案2】:

      一个选项:

      public IEnumerable<EmailAddress> GetAddressesByPerson(int personId)
      {
          var queryResults = Context.Persons
              .Where(x => x.Id == personId)
              .Select(x => new { EmailAddresses = x.EmailAddresses })
              .Single(); 
      
          return queryResults.EmailAddresses;
      }
      

      上面的查询断言应该返回一个人的电子邮件地址。您可以执行SingleOrDefault,然后检查#null 的结果以自定义错误消息,尽管我倾向于保持异常消息纯粹。然后我们返回选定的集合。因此,如果一个人存在,但没有电子邮件地址,您将收到一个空列表。如果此人不存在,您将获得 Expected 1, found 0 异常。如果 Id 存在多个人(不应该,但是...),您将得到预期 1,发现多个异常。不要使用FirstOrDefault,除非您预计可能有多个,并提供OrderBy 以确保数据顺序是可预测的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-18
        • 2021-12-30
        • 1970-01-01
        相关资源
        最近更新 更多