我有一个地图应用程序,它将在地图上显示表示会员地址处的要素的图钉(例如,会员居住在纬度/经度“X”并与要素“Y”相关联)。 成员可以与多个功能关联,一个功能可以与多个成员关联(多对多)。 在我的 ASP.NET Core/EF Core 应用程序中,我创建了(代码优先)数据库,EF 创建了一个 FeatureMember 表,以使用 Member 和 Feature 表中的 MemberId 和 FeatureId 列来管理多对多关系。 到目前为止,一切正常 - 该应用程序完美地处理多对多关系。
现在,谈谈我遇到的映射问题。 在地图显示页面上,我向用户呈现了一个多选功能列表。 当用户更新地图时,JavaScript 捕获所选FeatureId(隐藏字段)的数组,然后Ajax 调用将该数组发送回Controller 和Repository 类。 一切都很好,直到我尝试构建查询以从数据库获取所需的映射信息。 我在 SSMS 中构建的查询(工作正常)连接到 EF Core 无法识别为实体的 FeatureMember 表。
编译器的错误消息是: CS1061:“MappingContext”不包含“FeatureMember”的定义,并且找不到接受“MappingContext”类型的第一个参数的可访问扩展方法“FeatureMember”(您是否缺少 using 指令或程序集引用?)
SSMS 中运行的 SQL 是:
SELECT
m.MemberMailingName,a.AddressStreetNumber,a.AddressStreetName,a.AddressCity,a.AddressState,a.AddressZip,g.Latitude,g.Longitude,f.FeatureName
FROM
Member m
JOIN MailAddress a ON m.MemberId = a.MemberId
JOIN FeatureMember fm ON m.MemberId = fm.MembersMemberId
JOIN Feature f ON f.FeatureId = fm.FeaturesFeatureId
JOIN Geography g ON g.MailAddressId = a.MailAddressId
WHERE
f.FeatureId IN (1, 2)
ORDER BY f.FeatureId
我的 DbContext 类是:
namespace Mapping.Data
{
public class MappingContext : DbContext
{
public MappingContext(DbContextOptions<MappingContext> options)
: base(options)
{
}
public DbSet<Mapping.Models.Member> Member { get; set; } = default!;
public DbSet<Mapping.Models.Feature> Feature { get; set; } = default!;
public DbSet<Mapping.Models.MailAddress> MailAddress { get; set; } = default!;
public DbSet<Mapping.Models.Geography> Geography { get; set; } = default!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Member>()
.HasOne(a => a.MailAddress)
.WithOne(m => m.Member);
modelBuilder.Entity<Member>()
.HasMany(f => f.Features)
.WithMany(m => m.Members);
modelBuilder.Entity<MailAddress>()
.HasOne(g => g.Geography)
.WithOne(a => a.MailAddress);
}
}
}
如何使用 EF Core/Linq 构建此查询?
EF 旨在接管根据对象模型自动为 Linq 查询生成 SQL 的责任。虽然它确实支持具有显式联接的显式 Linq QL,但使用它只是例外而不是常态。相反,利用对象模型中的导航属性并让 EF 生成合适的查询:
看看这个 SQL 以及我可以从你的实体对象模型中得到的内容:
SELECT
m.MemberMailingName,a.AddressStreetNumber,a.AddressStreetName,a.AddressCity,a.AddressState,a.AddressZip,g.Latitude,g.Longitude,f.FeatureName
FROM
Member m
JOIN MailAddress a ON m.MemberId = a.MemberId
JOIN FeatureMember fm ON m.MemberId = fm.MembersMemberId
JOIN Feature f ON f.FeatureId = fm.FeaturesFeatureId
JOIN Geography g ON g.MailAddressId = a.MailAddressId
WHERE
f.FeatureId IN (1, 2)
ORDER BY f.FeatureId
...你应该从这样的事情中得到相同的结果:
int[] allowedFeatureIds = {1, 2};
var data = await _context.Members
.Where(m => m.Features.Any(f => allowedFeatureIds.Contains(f.Id)))
.SelectMany(m => m.Features.Select(f => new
{
m.MemberMailingName,
m.MailAddress.AddressStreetNumber,
m.MailAddress.AddressStreetName,
m.MailAddress.AddressCity,
m.MailAddress.AddressState,
m.MailAddress.AddressZip,
m.MailAddress.Geography.Latitude,
m.MailAddress.Geography.Longitude,
f.FeatureName
})).ToListAsync();
...假设您只想在代码中使用数据值。如果返回数据,请为这些内容构建 DTO 或视图模型类并填充它而不是匿名类型。