优化循环内的LINQ查询

本文关键字:LINQ 查询 循环 优化 | 更新日期: 2023-09-27 17:50:57

我有一段代码,确保客户的地址与UI中的修改同步:

var customerAddresses = customer.CustomerAddresses.Select(x => x.Address);
// add address to customer if it does not already exist
foreach (var addressModel in model.Addresses)
{
    // make sure an address matches all properties
    Func<Address, bool> addressFilter = x => x.Id == addressModel.Id &&
             x.Street.Equals(addressModel.Street, StringComparison.OrdinalIgnoreCase) &&
             x.City.Equals(addressModel.City, StringComparison.OrdinalIgnoreCase) &&
             x.Province.Equals(addressModel.Province, StringComparison.OrdinalIgnoreCase) &&
             x.PostalCode.Equals(addressModel.PostalCode, StringComparison.OrdinalIgnoreCase);
    // check if customer already has this address
    if (!customerAddresses.Any(addressFilter))
    {
        // check if address already exists in database
        var address = this.DbContext.Addresses.SingleOrDefault(addressFilter);
        // add address if it does not exist
        if (address == null)
        {
            address = this.DbContext.Addresses.Add(new Address
            {
                Street = addressModel.Street,
                City = addressModel.City,
                Province = addressModel.Province,
                PostalCode = addressModel.PostalCode
            });
        }
    }
    this.DbContext.CustomerAddresses.Add(new InsuredAddress
    {
        Customer = customer,
        Address = address,
        IsPreferred = addressModel.IsPreferred
    });             
}

然而,我担心每次在循环中都创建Func<Address, bool> addressFilter。是否有一种方法可以创建过滤器,使其接受参数,而不需要每次都重新创建?

优化循环内的LINQ查询

一旦理解了编译器是如何处理匿名方法的,就更容易理解这其中的含义了。

编译器将创建一个新的时间,并使用一个任意的名称。它会给那个类型一个实例方法,名字是任意的。该方法的主体将有效地成为这个匿名方法的主体。

每个封闭的over变量都有一个实例字段

该类型的新实例将在方法中创建,封闭的over变量将被替换为对该闭包类字段的访问。对匿名方法的调用将被替换为对这个新类中的方法的调用。

因此,根据这个转换,您应该能够看到,匿名方法只被编译一次,而不管它在另一个方法中的定义范围。


说了这么多,你真的不应该因为完全不相关的原因这样组织你的程序。首先,您定义的是Func而不是Expression<Func>,因此过滤器将无法转换为在数据库上执行的查询,相反,您将拉下整个Addresses两次(一次当您调用Any时,一次当您调用SingleOrDefault时,为模型中的每个地址。这真的不好。我的意思是,至少,您应该使用Expression来定义谓词,以便过滤可以在数据库方面完成,并省略对Any的调用,以便您每个循环只做一个查询,但老实说,您不应该执行多个查询在所有。你应该做的是连接两个表,这样你就可以在一个大查询中获得所有的信息,而不是在一个循环中执行任何查询。

关你什么事?

不要误解我的意思——这只是生成了一些对象。实时问题将是数据库的执行时间。所以,除非你能在这里分析一个问题,否则你很可能在这里完全处于过早的优化中。

你可以用列表(customerAddresses)和地址模型(addressModel)作为参数创建一个新的方法,返回一个布尔值来代替"Any"。

private bool CheckSync(var customerAddresses, var addressModel)
{
    foreach(var item in customerAddresses)
    {
        if(item.Id != addressModel.Id 
        || !item.Street.Equals(addressModel.Street, StringComparison.OrdinalIgnoreCase)
        || !item.City.Equals(addressModel.City, StringComparison.OrdinalIgnoreCase)
        || !item.Province.Equals(addressModel.Province, StringComparison.OrdinalIgnoreCase)
        || !item.PostalCode.Equals(addressModel.PostalCode, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }
    }
    return true;
}

调用这个方法而不是"Any":

// check if customer already has this address
if (!CheckSync(customerAddresses, addressFilter))
{
    // ...