回答原问题
我正在使用以下查询来根据需要过滤我的 BranchId。
var query = await _dbContext.TargetItems
.Include(i => i.BranchTargetItem)
.ThenInclude(i => i.BranchTarget)
.Where(x=> x.BranchTargetItem.Any(x=> x.BranchTarget.Any(x=> x.BranchId.Equals("9417"))))
.ToListAsync(cancellationToken);
但我想将左连接转换为内连接 [...]
要解决您最初的问题,一个简单的解决方案是颠倒您的包含顺序:
var branchTargets = context.BranchTargets
.Include(bt => bt.BranchTargetItem)
.ThenInclude(bti => bti.TargetItem)
.Where(bt => bt.BranchId == "9417")
.ToList();
这将使用INNER JOINs 并返回BranchTarget 实体,您可以从那里通过branchTargets[0].BranchTargetItem.TargetItem 访问您的TargetItem 实体:
SELECT [p].[BT_BRANCH_TARGET_ITEM_ID], [p].[BT_BRANCH_ID ], [p].[BT_ID], [p0].[BTI_ID], [p0].[BTI_ITEM_TARGET_ITEM_ID], [p1].[TI_ID]
FROM [PERF_BRANCH_TARGETS ] AS [p]
INNER JOIN [PERF_BRANCH_TARGET_ITEMS] AS [p0] ON [p].[BT_ID] = [p0].[BTI_ID]
INNER JOIN [PERF_TARGET_ITEMS] AS [p1] ON [p0].[BTI_ITEM_TARGET_ITEM_ID] = [p1].[TI_ID]
WHERE [p].[BT_BRANCH_ID ] = N'9417'
如果您不希望查询返回 BranchTarget 作为根实体,您可以执行以下操作:
var targetItems = context.BranchTargets
.Include(bt => bt.BranchTargetItem)
.ThenInclude(bti => bti.TargetItem)
.Where(bt => bt.BranchId == "9417")
.AsEnumerable()
.Select(bt => bt.BranchTargetItem.TargetItem)
.ToList();
这是一个完整的示例控制台项目,演示了这种方法:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class TargetItem
{
public int TargetItemId { get; set; }
public ICollection<BranchTargetItem> BranchTargetItem { get; set; }
}
public class BranchTargetItem
{
public int BranchTargetItemId { get; set; }
public int ItemTargetItemId { get; set; }
public TargetItem TargetItem { get; set; }
public ICollection<BranchTarget> BranchTarget { get; set; }
}
public class BranchTarget
{
public int BranchTargetId { get; set; }
public string BranchId { get; set; }
public int BranchTargetItemId { get; set; }
public BranchTargetItem BranchTargetItem { get; set; }
}
public class Context : DbContext
{
public DbSet<TargetItem> TargetItems { get; set; }
public DbSet<BranchTargetItem> BranchTargetItems { get; set; }
public DbSet<BranchTarget> BranchTargets { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63636238")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TargetItem>(
entity =>
{
entity.ToTable("PERF_TARGET_ITEMS");
entity.Property(e => e.TargetItemId)
.HasColumnName("TI_ID");
entity.HasMany(x => x.BranchTargetItem)
.WithOne(x => x.TargetItem)
.HasForeignKey(x => x.ItemTargetItemId);
entity.HasData(
new TargetItem {TargetItemId = 1},
new TargetItem {TargetItemId = 2},
new TargetItem {TargetItemId = 3});
});
modelBuilder.Entity<BranchTargetItem>(
entity =>
{
entity.ToTable("PERF_BRANCH_TARGET_ITEMS");
entity.Property(e => e.BranchTargetItemId)
.HasColumnName("BTI_ID");
entity.Property(e => e.ItemTargetItemId)
.HasColumnName("BTI_ITEM_TARGET_ITEM_ID");
entity.HasMany(x => x.BranchTarget)
.WithOne(x => x.BranchTargetItem)
.HasForeignKey(x => x.BranchTargetItemId);
entity.HasData(
new BranchTargetItem {BranchTargetItemId = 11, ItemTargetItemId = 1},
new BranchTargetItem {BranchTargetItemId = 22, ItemTargetItemId = 2},
new BranchTargetItem {BranchTargetItemId = 33, ItemTargetItemId = 3},
new BranchTargetItem {BranchTargetItemId = 41, ItemTargetItemId = 1});
});
modelBuilder.Entity<BranchTarget>(
entity =>
{
entity.ToTable("PERF_BRANCH_TARGETS ");
entity.Property(e => e.BranchTargetItemId)
.HasColumnName("BT_ID");
entity.Property(e => e.BranchId)
.HasColumnName("BT_BRANCH_ID ");
entity.Property(e => e.BranchTargetId)
.HasColumnName("BT_BRANCH_TARGET_ITEM_ID");
entity.HasData(
new BranchTarget {BranchTargetId = 100, BranchId = "9417", BranchTargetItemId = 11},
new BranchTarget {BranchTargetId = 200, BranchId = "9417", BranchTargetItemId = 41},
new BranchTarget {BranchTargetId = 300, BranchId = "1234", BranchTargetItemId = 22});
});
}
}
internal static class Program
{
private static void Main()
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
// Your original query:
//
// var result = context.TargetItems
// .Include(ti => ti.BranchTargetItem)
// .ThenInclude(bti => bti.BranchTarget)
// .Where(ti => ti.BranchTargetItem.Any(bti => bti.BranchTarget.Any(bt => bt.BranchId.Equals("9417"))))
// .ToList();
//
// Debug.Assert(result.Count == 2);
// Debug.Assert(result[0].BranchTargetItem != null);
// Debug.Assert(result[0].BranchTargetItem.Count == 2);
// Debug.Assert(result[0].BranchTargetItem.First().BranchTargetItemId == 11);
// Debug.Assert(result[0].BranchTargetItem.First().BranchTarget != null);
// Debug.Assert(result[0].BranchTargetItem.First().BranchTarget.Count == 1);
var result = context.BranchTargets
.Include(bt => bt.BranchTargetItem)
.ThenInclude(bti => bti.TargetItem)
.Where(bt => bt.BranchId == "9417")
.AsEnumerable()
.Select(bt => bt.BranchTargetItem.TargetItem)
.OrderBy(bt => bt.TargetItemId)
.ToList();
Debug.Assert(result.Count == 2);
Debug.Assert(result[0].BranchTargetItem != null);
Debug.Assert(result[0].BranchTargetItem.Count == 2);
Debug.Assert(result[0].BranchTargetItem.First().BranchTargetItemId == 11);
Debug.Assert(result[0].BranchTargetItem.First().BranchTarget != null);
Debug.Assert(result[0].BranchTargetItem.First().BranchTarget.Count == 1);
}
}
}
回答有关递归导航属性的后续问题
使用传统方法,您通常有以下选择:
- 如果您知道要查询递归结构的深度,只需添加额外的
.ThenInclude(ti => ti.TargetItems) 子句。
- 遍历返回的项目并执行其他查询。
- 只需急切加载所有
TargetItem 实体。
以下完全工作的示例项目演示了几种方法:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class TargetItem
{
public int TargetItemId { get; set; }
public int? ParentTargetItemId { get; set; }
public TargetItem ParentTargetItem { get; set; }
public ICollection<BranchTargetItem> BranchTargetItem { get; set; }
public ICollection<TargetItem> TargetItems { get; set; }
}
public class BranchTargetItem
{
public int BranchTargetItemId { get; set; }
public int ItemTargetItemId { get; set; }
public TargetItem TargetItem { get; set; }
public ICollection<BranchTarget> BranchTarget { get; set; }
}
public class BranchTarget
{
public int BranchTargetId { get; set; }
public string BranchId { get; set; }
public int BranchTargetItemId { get; set; }
public BranchTargetItem BranchTargetItem { get; set; }
}
public class Context : DbContext
{
public DbSet<TargetItem> TargetItems { get; set; }
public DbSet<BranchTargetItem> BranchTargetItems { get; set; }
public DbSet<BranchTarget> BranchTargets { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63636238_01")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TargetItem>(
entity =>
{
entity.ToTable("PERF_TARGET_ITEMS");
entity.Property(e => e.TargetItemId)
.HasColumnName("TI_ID");
entity.HasMany(x => x.BranchTargetItem)
.WithOne(x => x.TargetItem)
.HasForeignKey(x => x.ItemTargetItemId);
entity.HasMany(x => x.TargetItems)
.WithOne(x => x.ParentTargetItem)
.HasForeignKey(x => x.ParentTargetItemId);
entity.HasData(
new TargetItem {TargetItemId = 1, ParentTargetItemId = null},
new TargetItem {TargetItemId = 2, ParentTargetItemId = 1},
new TargetItem {TargetItemId = 3, ParentTargetItemId = 1},
new TargetItem {TargetItemId = 4, ParentTargetItemId = 2});
});
modelBuilder.Entity<BranchTargetItem>(
entity =>
{
entity.ToTable("PERF_BRANCH_TARGET_ITEMS");
entity.Property(e => e.BranchTargetItemId)
.HasColumnName("BTI_ID");
entity.Property(e => e.ItemTargetItemId)
.HasColumnName("BTI_ITEM_TARGET_ITEM_ID");
entity.HasMany(x => x.BranchTarget)
.WithOne(x => x.BranchTargetItem)
.HasForeignKey(x => x.BranchTargetItemId);
entity.HasData(
new BranchTargetItem {BranchTargetItemId = 11, ItemTargetItemId = 1},
new BranchTargetItem {BranchTargetItemId = 22, ItemTargetItemId = 2},
new BranchTargetItem {BranchTargetItemId = 33, ItemTargetItemId = 3},
new BranchTargetItem {BranchTargetItemId = 41, ItemTargetItemId = 1});
});
modelBuilder.Entity<BranchTarget>(
entity =>
{
entity.ToTable("PERF_BRANCH_TARGETS ");
entity.Property(e => e.BranchTargetItemId)
.HasColumnName("BT_ID");
entity.Property(e => e.BranchId)
.HasColumnName("BT_BRANCH_ID ");
entity.Property(e => e.BranchTargetId)
.HasColumnName("BT_BRANCH_TARGET_ITEM_ID");
entity.HasData(
new BranchTarget {BranchTargetId = 100, BranchId = "9417", BranchTargetItemId = 11},
new BranchTarget {BranchTargetId = 200, BranchId = "9417", BranchTargetItemId = 41},
new BranchTarget {BranchTargetId = 300, BranchId = "1234", BranchTargetItemId = 22});
});
}
}
internal static class Program
{
private static void Main()
{
SetupDatabase();
Approach1();
Approach2();
Approach3();
}
private static void Approach1()
{
using var context = new Context();
var targetItems = GetTargetItems(context);
var handled = new HashSet<TargetItem>();
var queue = new Queue<TargetItem>(targetItems);
while (queue.TryDequeue(out var targetItem))
{
handled.Add(targetItem);
context.Entry(targetItem)
.Collection(ti => ti.TargetItems)
.Load();
foreach (var childTargetItem in targetItem.TargetItems)
{
if (!handled.Contains(childTargetItem))
{
queue.Enqueue(childTargetItem);
}
}
}
AssertTargetItems(targetItems);
}
private static void Approach2()
{
using var context = new Context();
var targetItems = GetTargetItems(context);
var handled = new HashSet<TargetItem>();
var queue = new Queue<TargetItem>(targetItems);
while (queue.TryDequeue(out var targetItem))
{
handled.Add(targetItem);
var childTargetItems = context.TargetItems
.Where(ti => ti.ParentTargetItemId == targetItem.TargetItemId)
.ToList();
foreach (var childTargetItem in childTargetItems)
{
if (!handled.Contains(childTargetItem))
{
queue.Enqueue(childTargetItem);
}
}
}
AssertTargetItems(targetItems);
}
private static void Approach3()
{
using var context = new Context();
var targetItems = GetTargetItems(context);
context.TargetItems.Load();
AssertTargetItems(targetItems);
}
private static void AssertTargetItems(List<TargetItem> targetItems)
{
Debug.Assert(targetItems.Count == 2);
Debug.Assert(targetItems[0].BranchTargetItem != null);
Debug.Assert(targetItems[0].BranchTargetItem.Count == 2);
Debug.Assert(targetItems[0].BranchTargetItem.First().BranchTargetItemId == 11);
Debug.Assert(targetItems[0].BranchTargetItem.First().BranchTarget != null);
Debug.Assert(targetItems[0].BranchTargetItem.First().BranchTarget.Count == 1);
Debug.Assert(targetItems[0].TargetItemId == 1);
Debug.Assert(targetItems[0].TargetItems != null);
Debug.Assert(targetItems[0].TargetItems.Count == 2);
Debug.Assert(targetItems[0].TargetItems.First().TargetItems != null);
Debug.Assert(targetItems[0].TargetItems.First().TargetItems.Count == 1);
}
private static List<TargetItem> GetTargetItems(Context context)
=> context.BranchTargets
.Include(bt => bt.BranchTargetItem)
.ThenInclude(bti => bti.TargetItem)
.Where(bt => bt.BranchId == "9417")
.AsEnumerable()
.Select(bt => bt.BranchTargetItem.TargetItem)
.OrderBy(bt => bt.TargetItemId)
.ToList();
private static void SetupDatabase()
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
}
}