EF Core 错误的连接语句
EF Core wrong join statement
我在 EF Core 2.2
中有以下模型 类
public class User
{
[Key]
public long Id { get; set; }
[ForeignKey("Post")]
public long? PostId { get; set; }
public virtual Post Post { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public long UserId { get; set; }
public virtual User User { get; set; }
}
我检查了与SSMS的关系,没有问题。
但是当我使用
dbContext.Posts.Include(p => p.User);
EF Core 生成以下连接语句
FROM Posts [p]
LEFT JOIN Users [p.Users] ON [p].[Id] = [p.Users].[PostId]
我包括来自 Post 的用户,并希望它如下所示
FROM Posts [p]
LEFT JOIN Users [p.Users] ON [p].[UserId] = [p.Users].[Id]
模型有什么问题?
假设我想在用户模型中保存最后 PostId
。
是否有一个属性可以告诉 ef core 在加入模型时使用哪个属性?
User 和 Post 之间的关系是错误的。这应该是您的模型:
public class User
{
[Key]
public long Id { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public long UserId { get; set; }
public virtual User User { get; set; }
}
也就是一对多的关系:一个用户可以有多个post,一个post可以只有一个用户。
从其他回复的讨论来看,您似乎希望用户包含帖子,但同时也让用户跟踪对最新 post 的引用。 EF 可以对此进行映射,但是您可能需要明确说明这些关系。
例如:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
public virtual Post LatestPost { get; set; }
}
[Table("Posts")]
public class Post
{
[Key]
public int PostId { get; set; }
public string PostText { get; set; }
public DateTime PostedAt { get; set; }
public virtual User User { get; set; }
}
然后进行配置以确保 EF 正确连接用户和 post 之间的关系:
// EF6
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasMany(x => x.Posts)
.WithRequired(x => x.User)
.Map(x=>x.MapKey("UserId"));
HasOptional(x => x.LatestPost)
.WithMany()
.Map(x=>x.MapKey("LatestPostId"));
}
}
// EFCore
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasMany(x => x.Posts)
.WithOne(x => x.User)
.HasForeignKey("UserId");
HasOne(x => x.LatestPost)
.WithMany()
.IsRequired(false)
.HasForeignKey("LatestPostId");
}
}
您也可以使用 modelBuilder 引用在 OnModelCreating 事件中完成此操作。请注意,我没有在我的实体中声明 FK 属性。这也是一个选项,但我通常建议不要声明 FK 以避免引用与 FK 更新问题。我将 LatestPost FK 命名为 LatestPostId 只是为了更准确地揭示它的用途。如果您愿意,它可以映射到 "PostId"。
现在假设我要添加一个新的 post,我想将它关联到用户,并将其指定为该用户的 LatestPost:
using (var context = new SomethingDbContext())
{
var user = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 1);
var newPost = new Post { PostText = "Test", User = user };
user.Posts.Add(newPost);
user.LatestPost = newPost;
context.SaveChanges();
}
您可以更新 "latest" post 引用,方法是加载用户并将 LatestPost 引用设置为所需的 post。
但是您应该考虑这种结构存在的风险。问题是没有办法可靠地(在数据级别)强制执行用户中的 LatestPost 引用实际上引用了与该用户关联的 post。例如,如果我有一个指向特定 post 的最新 post,那么我会从用户的 Posts 集合中删除那个 post 引用,这可能会导致 FK 约束错误,或者只是解除关联来自用户的 post,但用户最新的 post 仍然指向该记录。我还可以将另一个用户的 post 分配给该用户的最新 post 参考。即
using (var context = new SomethingDbContext())
{
var user1 = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 1);
var user1 = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 2);
var newPost = new Post { PostText = "Test", User = user1 };
user1.Posts.Add(newPost);
user1.LatestPost = newPost;
user2.LatestPost = newPost;
context.SaveChanges();
}
那就太好了。用户 2 的 "LatestPostId" 将设置为此新的 post,即使此 post 的 UserId 仅引用用户 1。
处理 Latest post 之类的东西时,更好的解决方案是 不 对模式进行反规范化以适应它。相反,在实体中使用未映射的属性以获得最新 post,或者更好的是,在需要时依靠投影来检索此数据。在这两种情况下,您都将从用户 table
中删除 LatestPostId
未映射属性:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
[NotMapped]
public Post LatestPost
{
get { return Posts.OrderByDescending(x => x.PostedAt).FirstOrDefault(); }
}
}
未映射 属性 方法的警告是,如果您想访问此 属性,您需要记住在用户上预先加载帖子,否则您将遇到延迟加载。您也不能在发送到 EF (EF6) 的 Linq 表达式中使用此 属性,尽管它们可能与 EFCore 一起使用,但如果表达式提前转换到内存中,则可能会出现性能问题。 EF 将无法将 LatestPost 转换为 SQL,因为架构中没有键。
投影:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}
那么如果你想检索一个用户并且它是最新的 post:
var userAndPost = context.Users.Where(x => x.UserId == userId)
.Select(x => new { User = x, LatestPost = x.Posts.OrderByDescending(PostedAt).FirstOrDefault()} ).Single();
使用 Select
的投影可以检索感兴趣的实体,或者更好的是,只需 return 来自这些实体的字段到扁平视图模型或 DTO 中以发送到 UI 等。这导致对数据库的查询更有效。使用 Select
检索您无需担心通过 Include
预先加载的详细信息,如果正确完成,将避免延迟加载的陷阱。
关于如何创建模型这个问题你已经有足够多的答案了,我只会强调你的问题 class
你在用户模型中有 craete postid 作为 FK,因此它总是将它视为用户和 post 之间的关系,你必须删除它并设计类似于什么的东西史蒂夫说
我在 EF Core 2.2
中有以下模型 类public class User
{
[Key]
public long Id { get; set; }
[ForeignKey("Post")]
public long? PostId { get; set; }
public virtual Post Post { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public long UserId { get; set; }
public virtual User User { get; set; }
}
我检查了与SSMS的关系,没有问题。
但是当我使用
dbContext.Posts.Include(p => p.User);
EF Core 生成以下连接语句
FROM Posts [p]
LEFT JOIN Users [p.Users] ON [p].[Id] = [p.Users].[PostId]
我包括来自 Post 的用户,并希望它如下所示
FROM Posts [p]
LEFT JOIN Users [p.Users] ON [p].[UserId] = [p.Users].[Id]
模型有什么问题?
假设我想在用户模型中保存最后 PostId
。
是否有一个属性可以告诉 ef core 在加入模型时使用哪个属性?
User 和 Post 之间的关系是错误的。这应该是您的模型:
public class User
{
[Key]
public long Id { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public long UserId { get; set; }
public virtual User User { get; set; }
}
也就是一对多的关系:一个用户可以有多个post,一个post可以只有一个用户。
从其他回复的讨论来看,您似乎希望用户包含帖子,但同时也让用户跟踪对最新 post 的引用。 EF 可以对此进行映射,但是您可能需要明确说明这些关系。
例如:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
public virtual Post LatestPost { get; set; }
}
[Table("Posts")]
public class Post
{
[Key]
public int PostId { get; set; }
public string PostText { get; set; }
public DateTime PostedAt { get; set; }
public virtual User User { get; set; }
}
然后进行配置以确保 EF 正确连接用户和 post 之间的关系:
// EF6
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasMany(x => x.Posts)
.WithRequired(x => x.User)
.Map(x=>x.MapKey("UserId"));
HasOptional(x => x.LatestPost)
.WithMany()
.Map(x=>x.MapKey("LatestPostId"));
}
}
// EFCore
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasMany(x => x.Posts)
.WithOne(x => x.User)
.HasForeignKey("UserId");
HasOne(x => x.LatestPost)
.WithMany()
.IsRequired(false)
.HasForeignKey("LatestPostId");
}
}
您也可以使用 modelBuilder 引用在 OnModelCreating 事件中完成此操作。请注意,我没有在我的实体中声明 FK 属性。这也是一个选项,但我通常建议不要声明 FK 以避免引用与 FK 更新问题。我将 LatestPost FK 命名为 LatestPostId 只是为了更准确地揭示它的用途。如果您愿意,它可以映射到 "PostId"。
现在假设我要添加一个新的 post,我想将它关联到用户,并将其指定为该用户的 LatestPost:
using (var context = new SomethingDbContext())
{
var user = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 1);
var newPost = new Post { PostText = "Test", User = user };
user.Posts.Add(newPost);
user.LatestPost = newPost;
context.SaveChanges();
}
您可以更新 "latest" post 引用,方法是加载用户并将 LatestPost 引用设置为所需的 post。
但是您应该考虑这种结构存在的风险。问题是没有办法可靠地(在数据级别)强制执行用户中的 LatestPost 引用实际上引用了与该用户关联的 post。例如,如果我有一个指向特定 post 的最新 post,那么我会从用户的 Posts 集合中删除那个 post 引用,这可能会导致 FK 约束错误,或者只是解除关联来自用户的 post,但用户最新的 post 仍然指向该记录。我还可以将另一个用户的 post 分配给该用户的最新 post 参考。即
using (var context = new SomethingDbContext())
{
var user1 = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 1);
var user1 = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 2);
var newPost = new Post { PostText = "Test", User = user1 };
user1.Posts.Add(newPost);
user1.LatestPost = newPost;
user2.LatestPost = newPost;
context.SaveChanges();
}
那就太好了。用户 2 的 "LatestPostId" 将设置为此新的 post,即使此 post 的 UserId 仅引用用户 1。
处理 Latest post 之类的东西时,更好的解决方案是 不 对模式进行反规范化以适应它。相反,在实体中使用未映射的属性以获得最新 post,或者更好的是,在需要时依靠投影来检索此数据。在这两种情况下,您都将从用户 table
中删除 LatestPostId未映射属性:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
[NotMapped]
public Post LatestPost
{
get { return Posts.OrderByDescending(x => x.PostedAt).FirstOrDefault(); }
}
}
未映射 属性 方法的警告是,如果您想访问此 属性,您需要记住在用户上预先加载帖子,否则您将遇到延迟加载。您也不能在发送到 EF (EF6) 的 Linq 表达式中使用此 属性,尽管它们可能与 EFCore 一起使用,但如果表达式提前转换到内存中,则可能会出现性能问题。 EF 将无法将 LatestPost 转换为 SQL,因为架构中没有键。
投影:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}
那么如果你想检索一个用户并且它是最新的 post:
var userAndPost = context.Users.Where(x => x.UserId == userId)
.Select(x => new { User = x, LatestPost = x.Posts.OrderByDescending(PostedAt).FirstOrDefault()} ).Single();
使用 Select
的投影可以检索感兴趣的实体,或者更好的是,只需 return 来自这些实体的字段到扁平视图模型或 DTO 中以发送到 UI 等。这导致对数据库的查询更有效。使用 Select
检索您无需担心通过 Include
预先加载的详细信息,如果正确完成,将避免延迟加载的陷阱。
关于如何创建模型这个问题你已经有足够多的答案了,我只会强调你的问题 class
你在用户模型中有 craete postid 作为 FK,因此它总是将它视为用户和 post 之间的关系,你必须删除它并设计类似于什么的东西史蒂夫说