如何在 EF 中创建按时间降序排列的连接

How to create a descending time ordered join in EF

我正在尝试提高代码和一组 EF 查询的性能。涉及三个表。 Devices 是具有设备特定属性的设备列表。 Monitors 是可以为设备采样的单个数据元素的列表。 MonitorSamples 存储给定时间戳的每个监视器的单个样本。我正在尝试 return 每个设备的每个监视器的最新数据示例列表。

以下是 returning 该数据的未优化方法。我想将尽可能多的责任推给数据库,而不是创建那么多背靠背请求。如果只优化一个订单,其中查询 return 是每个设备的最新 MonitorSample,那将减少请求的数量,但如果我可以将其优化为单个数据库查询,那将是最好的.我考虑过构建一个视图来表示该数据,但如果我可以在 linq 或标准查询中完成,那会好得多。由于时间戳上的索引,returning 单个 MonitorSample 的性能很好,但是当请求很多时,性能开始下降很多。

我正在尝试找出创建包含我需要的所有信息的联接的最佳方法,并将其 select 放入我可以使用的新对象中。我在为连接中包含的每个监视器按时间戳排序数据时遇到问题。

public class MonitorSampleData
{
    public string Name;
    public DateTime Time;
    public int Sequence;
    public string Type;
    public string Unit;
    public string Value;
}

public class DeviceData
{
    public string DeviceName;
    public string DeviceType;
    public ICollection<MonitorSampleData> Monitors;
}

public partial class Device
{
    public int Id;
    public string Name;
    public string Description;
    public string Location;
    public int? SampleRate;
    public bool? SynchronizedSampling;
    public virtual DeviceType Type;
    public virtual DeviceGroup Group;
    public virtual DeviceState State;
    public virtual ICollection<Monitor> Monitors;      
}

public partial class Monitor
{
    public int Id;
    public string Name;
    public bool Enabled;
    public int Sequence;
    public virtual Device Device;
    public virtual MonitorUnitType UnitType;
    public virtual MonitorDataType DataType;
    public virtual ICollection<MonitorSample> Samples;     
}

public partial class MonitorSample
{    
    public int Id;
    public System.DateTime Time;
    public string Value;
    public virtual Monitor Monitor;
}

public ICollection<DeviceData> GetLatestDeviceData()
{
    ICollection<DeviceData> data = new List<DeviceData>();
    using (var context = new ApplicationDbContext())
    {
        var devices = context.GetDevices();
        foreach (var device in devices)
        {
            var deviceData = new DeviceData();
            deviceData.DeviceName = device.Name;
            deviceData.DeviceType = device.Type.ShortName;
            foreach (var monitor in device.Monitors)
            {
                var sample = context.GetLatestDataByMonitor(monitor);

                if (sample != null)
                {
                    MonitorSampleData monitorData = new MonitorSampleData();
                    monitorData.Name = monitor.Name;
                    monitorData.Time = sample.Time;
                    monitorData.Sequence = monitor.Sequence;
                    monitorData.Type = monitor.DataType.Name;
                    monitorData.Unit = monitor.UnitType.Name;
                    monitorData.Value = sample.Value;
                    deviceData.Monitors.Add(monitorData);
                }
            }
            data.Add(deviceData);
        }
    }
    return data;
}

public MonitorSample GetLatestDataByMonitor(Monitor monitor)
{
    MonitorSample sample = null;
    if (monitor != null)
    {
        sample = (from s in MonitorSamples
                    where s.Monitor.Id == monitor.Id
                    orderby s.Time descending
                    select s).FirstOrDefault();
    }
    return sample;
}

听起来像标准投影 LINQ 查询。

利用导航属性,将 foreach 替换为 from,将 var sample = 替换为 let sample =,并且不使用自定义方法,而是将所有内容嵌入查询和class 初始化表达式。

这样,将通过单个数据库查询检索结果。

例如

var data =
    (from device in context.Devices
     select new DeviceData
     {
         DeviceName = device.Name,
         DeviceType = device.Type.ShortName,
         Monitors = 
             (from monitor in device.Monitors
              let sample = (from s in monitor.Samples
                            orderby s.Time descending
                            select s).FirstOrDefault()
              select new MonitorSampleData
              {
                  Name = monitor.Name,
                  Time = sample.Time,
                  Sequence = monitor.Sequence,
                  Type = monitor.DataType.Name,
                  Unit = monitor.UnitType.Name,
                  Value = sample.Value
              }).ToList()
     }).ToList();

而不是

let sample = (from s in monitor.Samples
              orderby s.Time descending
              select s).FirstOrDefault()

您可以尝试功能等效的构造

from sample in monitor.Samples
    .DefaultIfEmpty()
    .OrderByDescending(s => s.Time)
    .Take(1)

这可能会变得更好 SQL 翻译。

更新: 我错过了 if (sample != null) 检查您的原始代码。所以真正的 LINQ 等价物是删除了 .DefaultIfEmpty() 的第二个构造,这将强制 INNER JOIN:

from sample in monitor.Samples
    .OrderByDescending(s => s.Time)
    .Take(1)