优化循环内的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
。是否有一种方法可以创建过滤器,使其接受参数,而不需要每次都重新创建?
一旦理解了编译器是如何处理匿名方法的,就更容易理解这其中的含义了。
编译器将创建一个新的时间,并使用一个任意的名称。它会给那个类型一个实例方法,名字是任意的。该方法的主体将有效地成为这个匿名方法的主体。
每个封闭的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))
{
// ...