我正在努力了解如何在ASP.NET Web API v2项目中使用Entity Framework 6和SQL Server构造SQL查询。
我的表结构:Computers
表是我的软件已清单的所有计算机的根表。它包含列ComputerID
,该列被其他表用作外键。
通过使用以下查询,我可以检索包括我拥有的所有信息的计算机:
Computer computer = ComputersDbContext.Computers
... more includes....
.Include("Computer_Win_Installed_Software")
... more includes....
.Where(a => a.ComputerId == computerId)
.First(t => t.TenantId == tenantId);
如您所见,我还有另一个名为Computer_Win_Installed_Software
的表,该表存储系统上安装的所有软件。看起来像这样:
IdentityKey | ComputerID (FK) | Softwarename | EntryTimestamp
------------+-----------------+------------------+--------------
1 | 1 | Some Software | 1547241345
2 | 1 | Another Software | 1547241345
EntryTimestamp
是Unix时间戳记,它对于每个清单运行都是唯一的,并且对该运行中发现的所有软件都相同。现在,如果系统再次进行盘点,则表将如下所示:
IdentityKey | ComputerID (FK) | Softwarename | EntryTimestamp
------------+-----------------+--------------------+---------------
1 | 1 | Some Software | 1547241345
2 | 1 | Another Software | 1547241345
3 | 1 | Some Software | 1886454564
4 | 1 | Another Software | 1886454564
5 | 1 | Even More Software | 1886454564
我想保留历史数据,所以我需要保留旧条目。
我的问题是,我从上面进行的EF查询将在结果对象中返回所有这些条目。
如何更改查询以使其仅返回:
IdentityKey | ComputerID (FK) | Softwarename | EntryTimestamp
------------+-----------------+--------------------+---------------
3 | 1 | Some Software | 1886454564
4 | 1 | Another Software | 1886454564
5 | 1 | Even More Software | 1886454564
我考虑使用2个查询:
EntryTimestamp
的最大值第二个查询:类似这样的内容:
Computer computer = ComputersDbContext.Computers
.Include("Computer_Win_Installed_Software")
.Where(a => a.ComputerId == computerId)
.Where(b => b.Computer_Win_Installed_Software.EntryTimestamp == EntryTimestamp)
.First(t => t.TenantId == tenantId);
但是Intellisense立即对我尖叫:D
我还考虑过只选择列MAX()
的{{1}};但我什至不能在查询代码中使用EntryTimestamp
。
当我写:b.Computer_Win_Installed_Software.EntryTimestamp
时,它没有列出任何列作为可用选项。
我认为这是因为EF中的.Where(b => b.Computer_Win_Installed_Software
类的类型为Computer_Win_Installed_Software
。对于其他所有与ICollection<Computer_Win_Installed_Software>
表具有一对多关系的表,这也是同样的问题。
另一个表与Computers
表具有1到1的关系,在这里我可以选择所有列。
我很困惑。
是的,我做了google,但是找不到任何可以帮助我的东西。去这里的正确方法是什么?
编辑:添加的模型
DBContext:
Computers
计算机的EF模型:
public class DbEntities : DbContext
{
public virtual DbSet<Agent> Agents { get; set; }
public virtual DbSet<Computer_Win_Installed_Software> ComputerWinInstalledSoftware { get; set; }
public DbSet<Computer> Computers { get; set; }
}
Computer_Win_Installed_Software的EF模型:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ProjectName
{
using System;
using System.Collections.Generic;
public partial class Computer
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Computer()
{
this.Computer_Win_Installed_Software = new HashSet<Computer_Win_Installed_Software>();
}
public int ComputerId { get; set; }
public int TenantId { get; set; }
public virtual Agent Agent { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Computer_Win_Installed_Software> Computer_Win_Installed_Software { get; set; }
}
}
答案 0 :(得分:1)
因此,对于给定的Computer
,ICollection<Computer_Win_Installed_Software> Computer_Win_Installed_Software
将始终包括所有EntryTimestampts
的所有相关行。您无法(轻松)filter包含的实体。
因此,只需返回过滤的实体:
var computer = ComputersDbContext.Computers
.Where(a => a.ComputerId == computerId)
.First(t => t.TenantId == tenantId);
var cwis = computer
.Computer_Win_Installed_Software
.Where(b => b.EntryTimestamp == EntryTimestamp);
当然,我不确定这是您实际想要做的,您可能有一个XY Problem。
您可以尝试不包括相关对象,而只是查询它们吗?
var computer = ComputersDbContext.Computers
.Where(a => a.ComputerId == computerId)
.First(t => t.TenantId == tenantId);
.Where(b => b.Computer_Win_Installed_Software.EntryTimestamp == EntryTimestamp);
答案 1 :(得分:0)
请尝试:
var computer = db.ComputerWinInstalledSoftware
.Include("Computer")
.First(c => c.Computer.ComputerId == computerId
&& c.Computer.TenantId == tenantId
&& c.EntryTimestamp == EntryTimestamp ).Computer;
答案 2 :(得分:0)
尝试一下:
Computer computer = context.Computers
.Include(c => c.Computer_Win_Installed_Software.Join(
c.Computer_Win_Installed_Software.Where(a => a.ComputerId == computerId && a.Computer.TenantId == tenantId)
.GroupBy(s => s.Softwarename)
.Select(g => new { SoftwareName = g.Key, MaxDate = g.Max(r => r.EntryTimestamp) })
,o => o.EntryTimestamp
,i => i.MaxDate
, (o,i) => o))
.First(a => a.ComputerId == computerId && a.TenantId == tenantId);
在您的实体定义的任何位置都看不到TenantId
列,因此请确保在其中放置正确的字段。
这是在最大日期之前加入记录。
更新:我做了一个小更正。它应该按软件名称分组。根据您的喜好更改分组,以便正确识别每个软件。
答案 3 :(得分:0)
好的。经过大量研究,我发现有几篇文章指出您不能在包含陈述中进行过滤,这是一种全有或全无的交易。最后的过滤始终会产生奇怪的结果。
来源: Entity Framework - Selective Condition on Included Navigation Property
Does Include load all related entities or the specified ones?
我能够找到2个解决方案: DynamicSelectExtensions: https://github.com/thiscode/DynamicSelectExtensions
似乎有EntityFramework +(EF +)。我目前在Stackoverflow上看到过开发人员的帖子,尽管目前找不到。他的库似乎可以执行我想做的事情,但是正常的EF不能,而且似乎永远也不会,因为对此功能的要求可以追溯到几年前。
感谢大家尝试帮助我并投入您的时间。现在,我将进行多个查询,也许以后再回来,希望有更多的经验:D