何时应使用导航属性查询数据库?

3
在我的 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. Run an Any() query on the Persons table and then another query on the EmailAddresses table, using PersonId as a parameter:

    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. Get the Person object and return the EmailAddresses navigation property:

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

1
如果选择第一种解决方案,EF会生成包含EXISTS语句的SQL查询。然后,如果存在,您将对数据库执行完全不同的第二个查询。
如果选择第二种解决方案,则只需发送select ... from Persons where ..语句。并且,由于您设置了EmailAddress作为导航属性,如果启用了延迟加载,则EF将基于personId生成并执行与EmailAdress表相关的查询。如果没有启用延迟加载,那么EmailAddress将为null或为空。
作为第三个选项,您可以使用Eager Loading功能,该功能会让EF生成join查询,并一次性获取person和相关的EmailAddress
因此,如果大多数情况下您期望具有正确的personId,则可以切换到Eager Loading模式。Lazy Loading在大多数情况下有助于在某些情况下仅获取相关实体。
顺便提一下,我建议您打开EF日志记录,以查看生成的查询。
因此,这是加载相关实体的代码示例:
var person = Context.Persons
            .Include(s ⇒ s.EmailAddresses)
            .FirstOrDefault(x => x.Id == personId);

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

1
一种选择:

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 来自定义错误消息,但我倾向于保持异常消息纯净。然后我们返回所选的集合。因此,如果一个人存在,但没有电子邮件地址,你将收到一个空列表。如果这个人不存在,你将得到一个预期1,发现0的异常。如果Id对应多个人(不应该,但是...),你将得到一个预期1,发现多个异常。除非你希望有多个可能性,并提供一个OrderBy来确保数据顺序可预测,否则不要使用FirstOrDefault

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接