查找非多对多关系的实体

Find entities not in many-to-many relation

我可能看错了,但我在 EF Core 3.1 中有一个基本的多对多代码优先设置,其中 Department <-> DepartmentDay <-> Day。

modelBuilder.Entity<DepartmentDay>(entity =>
{
    entity.HasKey(dd => new { dd.DepartmentId, dd.DayId });
    entity.HasOne(dp => dp.Day)
        .WithMany(p => p.DepartmentDays)
        .HasForeignKey(d => d.DayId);
    entity.HasOne(dp => dp.Department)
        .WithMany(p => p.DepartmentDays)
        .HasForeignKey(d => d.DepartmentId);
});

第一个问题:这种关系是可选的,我可以有几天不与部门联系吗?我需要这个,因为这与营业时间有关,并且希望有影响所有部门的通用日期,而不必与所有部门建立特定连接。但正如我一开始所说,我可能会以错误的方式看待这个问题。

第二个问题:如果第一个问题是真实有效的设置,我如何在 Linq 查询中获取那些未连接到部门的日子?

我目前拥有的是(编辑:将 allDays 从 Hashset 更改为 List。)

var allDays = await _context.Days.ToListAsync();
var allDepartmentDays = _context.DepartmentDays.Select(dd => dd.DayId).ToHashSet();
var genericDays = allDays.Where(d => !allDepartmentDays.Contains(d.Id));

还是在这里使用原始查询来提高性能更好?

SELECT Id
FROM Day
WHERE Id NOT IN (SELECT DayId FROM DepartmentDay)

编辑 2:包括整个数据模型

public class Department
{
    public int Id { get; set; }
    public int DepartmentNr { get; set; }
    public string Service { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
    public string Postal { get; set; }
    public string City { get; set; }
    public string Url { get; set; }
    public string MapUrl { get; set; }
    public DateTime Created { get; set; }
    public string CreatedBy { get; set; }
    public DateTime? Updated { get; set; }
    public string UpdatedBy { get; set; }
    public ICollection<DepartmentPeriod> DepartmentPeriods { get; set; }
    public ICollection<DepartmentDay> DepartmentDays { get; set; }
}

public class DepartmentDay
{
    public int DepartmentId { get; set; }
    public int DayId { get; set; }
    public Department Department { get; set; }
    public Day.Models.Day Day { get; set; }
}

public class Day
{
    public int Id { get; set; }
    public int PeriodId { get; set; }
    public string Service { get; set; }
    public string City { get; set; }
    public DateTime? Date { get; set; }
    public DayOfWeek? DayOfWeek { get; set; }
    public DateTime? OpenTime { get; set; }
    public DateTime? CloseTime { get; set; }
    public bool IsClosed { get; set; }
    public string Description { get; set; }
    public DateTime Created { get; set; }
    public string CreatedBy { get; set; }
    public DateTime? Updated { get; set; }
    public string UpdatedBy { get; set; }

    public ICollection<DepartmentDay> DepartmentDays { get; set; }
    public virtual Period.Models.Period Period { get; set; }
}

modelBuilder.Entity<Day>(entity =>
{
    entity.HasOne(d => d.Period)
        .WithMany(p => p.Days)
        .HasForeignKey(d => d.PeriodId);          
});

还有一个关系没有包含在初始问题中,我的第一个问题的答案是Department 1-M DepartmentPeriod M-1 Period 1-1 Day。所以 Day table 中的某些日子与 DepartmentDay 无关,但仅与 Period 无关,对吗?

第一个问题:它是可选的吗?没有你的数据类型真的是一个棘手的问题,但假设你有正确的导航属性,你只需要指定导航属性,如果它们的键没有命名为 {DataType}Id,所以如果你这样命名它们就不是必需的,否则您必须指定要使用哪些外键字段以及哪些是键。如果您不指定关系,entity framework 将为您生成多对多关系的表。

第二个问题: 在性能方面,如果您能够编写一个好的查询,SQL 查询将始终优于您建议的查询,甚至

select a.id from [day] a left join departmentday b on a.id = b.dayid where b.dayid is null

即便如此,也可以表现得更好一些, 然而,就可测试性而言,sql 是一个问题,因为我们想要 运行 我们实体 dbcontext 的 InMemory 模型,然后你的 sql 通常至少不能执行。

所以问题是,如果您真的需要额外的性能,并且是否有可能编写一个 linq 查询,通过使其成为同一表达式的一部分而不是两个或更多表达式,从而有效地使 linq 引擎编写类似的查询.

在您的情况下,我可能会遗漏目前未提供的数据模型中的某些内容,但您似乎可以像这样获得您想要的内容:

_context.Days.Include(d => d.DepartmentDays).Where(!d.DepartmentDays.Any());

更新: 查看模型,似乎缺少一些模型工作,您正在寻找的投影可以按照建议的方式完成。

模型创建的更改/添加:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Department>()
        .HasKey(k => k.Id);

    modelBuilder.Entity<DepartmentDay>()
        .HasKey(k => new { k.DayId, k.Department });

    modelBuilder.Entity<Day>()            
        .HasOne(d => d.Period)
        .WithMany(p => p.Days)
        .HasPrincipalKey(k => k.PeriodId)
        .HasForeignKey(d => d.Id);
   
    //Notice your departmentid is not alone a foreign key in the relationship table, as departments can have one row per day
    modelBuilder.Entity<DepartmentDay>()
        .HasOne(a => a.Department)
        .WithMany(b => b.DepartmentDays)
        .HasPrincipalKey(p => p.Id)
        .HasForeignKey(f => new { f.DepartmentId, f.DayId });

    base.OnModelCreating(modelBuilder);
}

并且在上下文中我们可以使用以下方式进行此类查询:

public List<Day> GetDaysWithoutAnyDepartmentDays()
{
    return Days
        .Include(i => i.DepartmentDays)
        .Where(x => !x.DepartmentDays.Any()
        ).ToList();
}