EF Core错误的连接语句

时间:2019-10-26 21:06:15

标签: sql-server entity-framework asp.net-core ef-core-2.2

我在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加入模型时使用哪个属性?

3 个答案:

答案 0 :(得分:1)

用户和帖子之间的关系是错误的。这应该是您的模型:

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; }
}

那是一对多的关系:一个用户可以有多个帖子,而一个帖子只能有一个用户。

答案 1 :(得分:1)

从对另一个响应的讨论中,您似乎希望用户包含帖子,但又希望用户跟踪对最新帖子的引用。 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正确连接用户和帖子之间的关系:

// 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”。

现在可以说我去添加一个新帖子,并将其与该用户相关联,并将其分配为该用户的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();
}

您可以通过加载用户并将“最新帖子”引用设置为所需的帖子来更新“最新”帖子引用。

但是您应该考虑这种结构的风险。问题在于,没有办法(在数据级别)可靠地强制用户中的LatestPost引用实际引用与该用户关联的帖子。例如,如果我有指向特定帖子的最新帖子,那么我将从用户的帖子集合中删除该帖子引用,这可能导致FK约束错误,或者干脆将帖子与用户解除关联,但用户最新帖子仍然指向该记录。我还可以将其他用户的帖子分配给该用户的最新帖子参考。即

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”将被设置为此新帖子,即使该帖子的UserId仅引用User1。

处理类似“最新”帖子之类的更好的解决方案是对模式进行非规范化以适应它。取而代之的是,在实体中使用未映射的属性来获取最新帖子,或者更好的是,在需要时依靠投影来检索此数据。在这两种情况下,您都将从用户表中删除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(); }
    }
}

未映射的属性方法的警告是,如果您要访问此属性,则需要记住要在用户上热切加载帖子,否则将导致延迟加载。尽管它们可以与EFCore一起使用,但您也不能在发送到EF(EF6)的Linq表达式中使用此属性,但是如果将该表达式尽早转换为内存中的内容,则会存在性能问题。由于架构中没有密钥,因此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>();
}

然后,如果您要检索用户及其最新帖子,

var userAndPost = context.Users.Where(x => x.UserId == userId)
    .Select(x => new { User = x, LatestPost = x.Posts.OrderByDescending(PostedAt).FirstOrDefault()} ).Single();

使用Select进行投影可以检索感兴趣的实体,或者更好的是,只需将这些实体中的字段返回到展平的视图模型或DTO中以发送到UI等。这样可以对数据库进行更有效的查询。使用Select来检索详细信息,您不必担心通过Include进行快速加载,并且正确完成后,将避免延迟加载带来的陷阱。

答案 2 :(得分:0)

关于这个问题,您对如何创建模型有足够的答案,我只会在您的课程中强调出什么问题

您在用户模型中拥有craete postid为FK,因此它将始终将其视为用户与帖子之间的关系,您必须删除它并对其进行类似史蒂夫所说的设计