C# 如何在EF中创建降序时间顺序联接

C# 如何在EF中创建降序时间顺序联接,c#,sql,entity-framework,linq-to-sql,C#,Sql,Entity Framework,Linq To Sql,我试图提高代码和一组EF查询的性能。涉及三张桌子设备是具有设备特定属性的设备列表监视器是可以为设备采样的单个数据元素的列表MonitorSamples存储给定时间戳内每个监视器的单个样本。我试图返回每个设备的每个监视器的最新数据样本列表 下面是返回该数据的未优化方法。我希望将尽可能多的责任推到数据库上,而不是创建这么多背对背的请求。如果只优化查询返回每个设备的最新MonitorSample的一个顺序,这将减少请求数量,但如果我可以将其优化为单个db查询,那将是最好的。我曾考虑过构造一个视图来表示

我试图提高代码和一组EF查询的性能。涉及三张桌子<代码>设备是具有设备特定属性的设备列表<代码>监视器是可以为设备采样的单个数据元素的列表
MonitorSamples
存储给定时间戳内每个监视器的单个样本。我试图返回每个设备的每个监视器的最新数据样本列表

下面是返回该数据的未优化方法。我希望将尽可能多的责任推到数据库上,而不是创建这么多背对背的请求。如果只优化查询返回每个设备的最新
MonitorSample
的一个顺序,这将减少请求数量,但如果我可以将其优化为单个db查询,那将是最好的。我曾考虑过构造一个视图来表示该数据,但如果我能在linq或标准查询中实现,那就更好了。由于时间戳上有一个索引,因此返回单个
MonitorSample
的性能非常好,但是当请求了大量MonitorSample时,性能就会开始大幅下降

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

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;
}
公共类MonitorSampleData
{
公共字符串名称;
公共日期时间;
公共int序列;
公共字符串类型;
公共字符串单元;
公共字符串值;
}
公共类设备数据
{
公共字符串DeviceName;
公共字符串设备类型;
公共i收集监视器;
}
公共部分类设备
{
公共int Id;
公共字符串名称;
公共字符串描述;
公共字符串位置;
公共int?采样器;
公共布尔?同步采样;
公共虚拟设备类型;
公共虚拟设备组;
公共虚拟设备状态;
公共虚拟ICollection监视器;
}
公共部分类监视器
{
公共int Id;
公共字符串名称;
公共布尔启用;
公共int序列;
公共虚拟设备;
公共虚拟监视器UnitType UnitType;
公共虚拟监视器数据类型数据类型;
公共虚拟采集样本;
}
公共部分类监视器示例
{    
公共int Id;
公共系统。日期时间;
公共字符串值;
公共虚拟监视器;
}
公共ICollection GetLatestDeviceData()
{
i收集数据=新列表();
使用(var context=new ApplicationDbContext())
{
var devices=context.GetDevices();
foreach(设备中的var设备)
{
var deviceData=新设备数据();
deviceData.DeviceName=设备名称;
deviceData.DeviceType=device.Type.ShortName;
foreach(设备监视器中的var监视器)
{
var sample=context.GetLatestDataByMonitor(监视器);
if(示例!=null)
{
MonitorSampleData monitorData=新建MonitorSampleData();
monitorData.Name=monitor.Name;
monitorData.Time=样本.Time;
monitorData.Sequence=监视器.Sequence;
monitorData.Type=monitor.DataType.Name;
monitorData.Unit=monitor.UnitType.Name;
monitorData.Value=sample.Value;
deviceData.Monitors.Add(monitorData);
}
}
添加数据(deviceData);
}
}
返回数据;
}
公共监视器示例GetLatestDataByMonitor(监视器监视器)
{
MonitorSample=null;
如果(监视器!=null)
{
样本=(来自监控器样本中的s)
其中s.Monitor.Id==Monitor.Id
orderby s.时间递减
选择s).FirstOrDefault();
}
返回样品;
}

听起来像是标准的投影LINQ查询

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

这样,将使用单个数据库查询检索结果

例如

而不是

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()
的第二个构造,它将强制
内部联接

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

您总是可以创建一个存储过程,然后调用它来获取数据。并不是所有的事情都需要用ef来完成,谢谢你的建议。我将对此进行一次尝试,并在其上运行一个探查器会话,以查看它有多大的不同。因此,我尝试了此操作,但无法让查询返回实际数据。它总是返回空,抛出一个关于数据为空的异常。我将继续进行故障排除,看看能否缩小SQL失败的范围。我错过了
sample!=空
签入原始代码。因此,使用第二个构造,但删除
.DefaultIfEmpty()
行以将其转换为内部联接。除此之外,如果整个查询是在服务器端执行的,则不应该有空异常(请确保您在回答中使用
context.Devices
DbSet访问器)。我提取了查询生成的SQL,并直接在数据库上运行,它正确返回了值。但是,在代码中,它不会被折叠回对象中,因为它会引发异常:
System.Data.SqlTypes.SqlNullValueEx
from sample in monitor.Samples
    .OrderByDescending(s => s.Time)
    .Take(1)