实体框架核心-极慢的性能

时间:2020-02-17 20:31:40

标签: sql-server performance entity-framework linq asp.net-core

我有以下实体(我将显示正在使用的属性,因为我不想使其超出所需的大小):

属性::其中一个属性可以是另一个属性的子项,并且与GeoLocation具有1-1关系,并且可以具有多个MultimediaOperation < / p>

public partial class Property
{
    public Property()
    {
        InverseParent = new HashSet<Property>();
        Multimedia = new HashSet<Multimedia>();
        Operation = new HashSet<Operation>();
    }

    public long Id { get; set; }
    public string GeneratedTitle { get; set; }
    public string Url { get; set; }
    public DateTime? DatePublished { get; set; }
    public byte StatusCode { get; set; }
    public byte Domain { get; set; }
    public long? ParentId { get; set; }

    public virtual Property Parent { get; set; }
    public virtual GeoLocation GeoLocation { get; set; }
    public virtual ICollection<Property> InverseParent { get; set; }
    public virtual ICollection<Multimedia> Multimedia { get; set; }
    public virtual ICollection<Operation> Operation { get; set; }
}

地理定位:如上所述,它与Property

具有1-1关系
public partial class GeoLocation
{
    public int Id { get; set; }
    public double? Latitude { get; set; }
    public double? Longitude { get; set; }
    public long? PropertyId { get; set; }

    public virtual Property Property { get; set; }
}

多媒体:它可以为一个Property容纳多个大小不同的图像。此处的详细信息是Order指定要在客户端应用程序中显示的图像的顺序,但并非总是以1开头。在某些情况下,Property具有{{1 }}以3或x开头的文件。

Multimedia

OPERATIONS::定义public partial class Multimedia { public long Id { get; set; } public long? Order { get; set; } public string Resize360x266 { get; set; } public long? PropertyId { get; set; } public virtual Property Property { get; set; } } 可以进行的所有操作,并使用Property将该操作命名。 (租金,出售等)

OperationType

价格:定义每个操作的价格和货币类型。 (即:对于X金额的美元货币,物业可以有租金选项-public partial class Operation { public Operation() { Price = new HashSet<Price>(); } public long Id { get; set; } public long? OperationTypeId { get; set; } public long? PropertyId { get; set; } public virtual OperationType OperationType { get; set; } public virtual Property Property { get; set; } public virtual ICollection<Price> Price { get; set; } } public partial class OperationType { public OperationType() { Operation = new HashSet<Operation>(); } public long Id { get; set; } public string Name { get; set; } public virtual ICollection<Operation> Operation { get; set; } } -但如果使用其他货币类型,则可以为同一Operation注册另一个价格)

Operation

说,我想获取所有记录(实际上大约是40K-50K),但仅用于少数几个属性。如前所述,public partial class Price { public long Id { get; set; } public float? Amount { get; set; } public string CurrencyCode { get; set; } public long? OperationId { get; set; } public virtual Operation Operation { get; set; } } 表可以为每个Multimedia保留很多记录,但是我只需要第一个具有较小Property值并按Order排序的记录。之后,我需要将结果转换为MapMarker对象,如下所示:

DatePublished

为了实现这一目标,我做了以下事情:

public class MapMarker : EstateBase
{
    public long Price { get; set; }
    public int Category { get; set; }
    public List<Tuple<string, string, string>> Prices { get; set; }
}

,但是结果要花费14分钟以上,因此一分钟内可以多次调用此方法。我想对其进行优化,以便在更短的时间内返回结果。我曾尝试删除public async Task<IEnumerable<MapMarker>> GetGeolocatedPropertiesAsync(int quantity) { var properties = await GetAllProperties().AsNoTracking() .Include(g => g.GeoLocation) .Include(m => m.Multimedia) .Include(p => p.Operation).ThenInclude(o => o.Price) .Include(p => p.Operation).ThenInclude(o => o.OperationType) .Where(p => p.GeoLocation != null && !string.IsNullOrEmpty(p.GeoLocation.Address) && p.GeoLocation.Longitude != null && p.GeoLocation.Latitude != null && p.StatusCode == (byte)StatusCode.Online && p.Operation.Count > 0) .OrderByDescending(p => p.ModificationDate) .Take(quantity) .Select(p => new { p.Id, p.Url, p.GeneratedTitle, p.GeoLocation.Address, p.GeoLocation.Latitude, p.GeoLocation.Longitude, p.Domain, p.Operation, p.Multimedia.OrderBy(m => m.Order).FirstOrDefault().Resize360x266 }) .ToListAsync(); var mapMarkers = new List<MapMarker>(); try { foreach (var property in properties) { var mapMarker = new MapMarker(); mapMarker.Id = property.Id.ToString(); mapMarker.Url = property.Url; mapMarker.Title = property.GeneratedTitle ?? string.Empty; mapMarker.Address = property.Address ?? string.Empty; mapMarker.Latitude = property.Latitude.ToString() ?? string.Empty; mapMarker.Longitude = property.Longitude.ToString() ?? string.Empty; mapMarker.Domain = ((Domain)Enum.ToObject(typeof(Domain), property.Domain)).ToString(); mapMarker.Image = property.Resize360x266 ?? string.Empty; mapMarker.Prices = new List<Tuple<string, string, string>>(); foreach (var operation in property.Operation) { foreach (var price in operation.Price) { var singlePrice = new Tuple<string, string, string>(operation.OperationType.Name, price.CurrencyCode, price.Amount.ToString()); mapMarker.Prices.Add(singlePrice); } } mapMarkers.Add(mapMarker); } } catch (Exception ex) { throw; } return mapMarkers; } ,但是在ToListAsync()循环中也要花费很多时间,这很有意义。

那么,您认为我在这里能做什么? 预先感谢。

更新: 这是foreach方法,我忘了包括这个方法。

GetAllProperties()

以及实体框架针对SQL Server进行的SQL查询:

private IQueryable<Property> GetAllProperties()
{
    return _dbContext.Property.AsQueryable();
}

更新2:如@Igor所述,这是执行计划结果的链接: https://www.brentozar.com/pastetheplan/?id=BJNz9KdQI

1 个答案:

答案 0 :(得分:1)

好,几件事应该有所帮助。 #1 .Include().Select()通常应互斥。

您正在选择:

p.Id,
p.Url,
p.GeneratedTitle,
p.GeoLocation.Address,
p.GeoLocation.Latitude,
p.GeoLocation.Longitude,
p.Domain,
p.Operation,
p.Multimedia.OrderBy(m => m.Order).FirstOrDefault().Resize360x266

,但是随后在您的foreach循环中访问Price和OperationType实体。

编辑更新了用于收集操作的示例。 (哇)

相反,我建议:

p.Id,
p.Url,
p.GeneratedTitle,
p.GeoLocation.Address,
p.GeoLocation.Latitude,
p.GeoLocation.Longitude,
p.Domain,
Operations = p.Operation.Select( o => new 
{
   OperationTypeName = o.OperationType.Name,
   o.Price.Amount,
   o.Price.CurrencyCode
}).ToList(),
p.Multimedia.OrderBy(m => m.Order).FirstOrDefault().Resize360x266

然后调整您的foreach逻辑以使用返回的属性,而不是返回的实体和相关实体值。

使用类似的图像字段(MultiMedia)加载40-50k条记录可能总是有问题。为什么需要一次加载全部 50k?

这看起来会在地图上放置标记。这样的解决方案应该考虑至少应用半径过滤器,以使标记位于地图上给定中心点的合理半径内,或者如果加载较大区域(缩小地图)以计算区域并按区域过滤数据或获取计算该区域中的位置并以100个左右的批次批量加载/渲染位置,而不是潜在地等待 all 个位置加载。要考虑的事情。