改进LINQ查询,使其在数据库中完全执行

时间:2014-06-07 18:03:34

标签: c# linq entity-framework linq-to-entities

我有一个Address模型(简化)......

public class Address
{
    public int AddressId { get; set; }
    public string City { get; set; }
}

...和DbContext派生类,其中包含一组Addresses

public DbSet<Address> Addresses { get; set; }

然后我有这个查询应该检索一个或不address_context是我的数据库上下文类的实例):

public Address GetAddress(string city, int addressId)
{
    Address address = null;

    // this is a database query
    var addresses = _context.Addresses.Where(a => a.City == city).ToList();

    // the rest queries in memory
    if (addresses.Count <= 1)
        address = addresses.FirstOrDefault();
    else
    {
        address = addresses.FirstOrDefault(a => a.AddressId == addressId);
        if (address == null)
            address = addresses.FirstOrDefault();
    }

    return address;
}

查询有点奇怪。逻辑很简单:

  • 如果数据库表中只有一个(或没有)地址,请求的city将此地址作为结果。
  • 如果有多个地址,请求的city更喜欢具有给定addressId的地址。如果结果地址都没有,addressId只取第一个。

令人不安的是.ToList()调用可能会将大量地址加载到我不感兴趣的内存中。最后,我只过滤了内存中加载的一个地址作为最终结果。

有没有办法重写这个查询(使用LINQ-to-Entities),以便它在数据库中完全运行并只返回一个或没有地址(使用单个数据库往返)?

1 个答案:

答案 0 :(得分:5)

您的逻辑可以被解释为更喜欢具有给定ID的地址,如果没有匹配jut pick any。您的查询不会强制执行该案件的任何订单。

var addressesInCity = _context.Addresses.Where(a => a.City == city);
var addrByID = addressesInCity.Where(a => a.AddressId == addressId);
var anyAddr = addressesInCity.Take(1);

你可以用两个查询来写这个:

addrByID.FirstOrDefault() ?? anyAddr.FirstOrDefault();

您可以将这些组合成一个查询:

addrByID.Select(a => new { Priority = 1, a })
.Concat(anyAddr.Select(a => new { Priority = 2, a }))
.OrderBy(x => x.Priority)
.Take(1)
.Select(x => x.a)
.FirstOrDefault();

这样可以保存往返,SQL Server可以通过常量了解排序。它将有效运行。 必然比第一种形式更有效但不会更糟糕。

请注意,UNION (ALL)返回的结果顺序未定义。我们需要通过引入Priority字段来强制执行订单。