更新与 Entity Framework Core 具有多对多关系的帖子

问题描述 投票:0回答:1

我有以下实体 -

BaseEntity
按键:

/// <summary>
/// Base entity
/// </summary>
[Index("Uid", IsUnique = true)]
public abstract class BaseEntity
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int Id { get; set; }

    [Required]
    public virtual Guid Uid { get; set; } = Guid.NewGuid();
}

Post

public class Post : BaseEntity
{
    [MaxLength(100), MinLength(5)]
    public required string Title { get; set; }
    [MaxLength(3000), MinLength(50)]
    public required string Text { get; set; }
    public required DateTime Date { get; set; } = DateTime.Now;
    public required bool IsAnonymous { get; set; } = false;

    public Guid UserId { get; set; }
    public virtual User User { get; set; }

    public PostCount PostCount { get; set; } = null!;
    public virtual ICollection<Comment>? Comments { get; set; }
    public virtual ICollection<Topic> Topics { get; set; }
    public virtual ICollection<Like> Likes { get; set; }
}

主题:

public class Topic : BaseEntity
{
    public string Type { get; set; } = string.Empty;
    public virtual ICollection<User> Users { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

他们的配置:

public static void ConfigureTopics(this ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Topic>().ToTable("Topics");
    modelBuilder.Entity<Topic>().Property(x => x.Type).IsRequired();
    modelBuilder.Entity<Topic>().HasMany(x => x.Posts)
        .WithMany(x => x.Topics)
        .UsingEntity(t => t.ToTable("PostsTopics"));
}

以下是创建帖子的方法,效果很好:

public async Task<PostModel> Create(CreatePostModel model, Guid userId)
{
    try
    {
        await _createValidator.CheckAsync(model);

        await using var context = await _dbContextFactory.CreateDbContextAsync();
    
        // Create a new Post entity
        var post = _mapper.Map<Post>(model);

        if (post.Date.Kind == DateTimeKind.Local)
            post.Date = post.Date.ToUniversalTime();

        // Create a new PostCount entity
        var postCount = new PostCount
        {
            Post = post
        };

        // Associate the Post and PostCount entities
        post.PostCount = postCount;

        post.UserId = userId;

        // Fetch existing topics by Uid
        post.Topics = await context.Topics
            .Where(t => model.Topics.Contains(t.Uid))
            .ToListAsync();
    
        context.AttachRange(post.Topics);
    
        // Add the entities to the context
        await context.Posts.AddAsync(post);
        await context.PostCounts.AddAsync(postCount);

        // Save changes to the database
        await context.SaveChangesAsync();
        
        post.User = await context.Users.FirstAsync(u => u.Id == userId);
    
        var createdPost = _mapper.Map<PostModel>(post);
    
        return createdPost;
    }
    catch (Exception e)
    {
        _logger.Error($"Error creating post. Error message: {e.Message}");
        throw;
    }
}

PostsTopics
数据库中的表:

-- auto-generated definition
create table "PostsTopics"
(
    "PostsId"  integer not null
        constraint "FK_PostsTopics_Posts_PostsId"
            references "Posts"
            on delete cascade,
    "TopicsId" integer not null
        constraint "FK_PostsTopics_Topics_TopicsId"
            references "Topics"
            on delete cascade,
    constraint "PK_PostsTopics"
        primary key ("PostsId", "TopicsId")
);

alter table "PostsTopics"
    owner to postgres;

create index "IX_PostsTopics_TopicsId"
    on "PostsTopics" ("TopicsId");

这里是更新方法。这里的问题是 EF Core 由于某种原因没有从

PostTopics
表中删除主题,这就是为什么我收到错误

Microsoft.EntityFrameworkCore.DbUpdateException:保存实体更改时发生错误。有关详细信息,请参阅内部异常。

welisten_api-1 | ---> Npgsql.PostgresException (0x80004005): 23505: 重复的键值违反了唯一约束“PK_PostsTopics”

当我尝试传递空主题列表时,它会更新所有其他属性并保存它们,但主题仍与以前一样:

public async Task Update(Guid id, Guid userId, UpdatePostModel model)
{
    try
    {
        await _updateValidator.CheckAsync(model);

        await using var context = await _dbContextFactory.CreateDbContextAsync();

        var post = await context.Posts
            .Include(x => x.User)
            .Include(x => x.PostCount)
            .Include(x => x.Topics)
            .FirstOrDefaultAsync(x => x.Uid == id);

        if (post == null)
            throw new ProcessException($"Post with ID: {id} not found");

        if (post.UserId != userId)
            throw new AuthenticationException("Authentication failed");

        context.Entry(post).State = EntityState.Modified;
        
        // Clear existing topics
        post.Topics.Clear();

        // Update other properties of the post
        post.Title = model.Title;
        post.Text = model.Text;
        post.IsAnonymous = model.IsAnonymous;
        
        // Update topics
        post.Topics = await context.Topics
            .Where(t => model.Topics.Contains(t.Uid))
            .ToListAsync();
        
        context.AttachRange(post.Topics);
        
        // Save changes again to update the many-to-many relationship
        await context.SaveChangesAsync();
    }
    catch (Exception e)
    {
        _logger.Error($"Error updating post with ID: {id}. Error message: {e.Message}");
        throw;
    }
}

我尝试先用

Post.Topics.Clear()
清除主题,然后保存。然后分配新主题并再次保存更改,但也没有帮助

c# .net postgresql entity-framework-core many-to-many
1个回答
0
投票

所以,经过反复试验,我成功了。问题在于在更改帖子之前附加主题

context.AttachRange(post.Topics);
。然而,当我尝试用

更新 post.Topics 中的值时,EF 开始抱怨它无法同时跟踪两个实体
// Update topics
post.Topics = await context.Topics
  .Where(t => model.Topics.Contains(t.Uid))
  .ToListAsync()

我必须手动检查应删除哪些主题以及要添加哪些主题。

这是我的问题的最终工作解决方案:

public async Task Update(Guid id, Guid userId, UpdatePostModel model)
    {
        try
        {
            await _updateValidator.CheckAsync(model);

            await using var context = await _dbContextFactory.CreateDbContextAsync();

            var post = await context.Posts
                .Include(x => x.User)
                .Include(x => x.PostCount)
                .Include(x => x.Topics)
                .FirstOrDefaultAsync(x => x.Uid == id);

            if (post == null)
                throw new ProcessException($"Post with ID: {id} not found");

            if (post.UserId != userId)
                throw new AuthenticationException("Authentication failed");

            context.Entry(post).State = EntityState.Modified;
            context.AttachRange(post.Topics);

            // Update other properties of the post
            post.Title = model.Title;
            post.Text = model.Text;
            post.IsAnonymous = model.IsAnonymous;

            // Update topics
            var existingTopicIds = post.Topics.Select(t => t.Uid).ToList();
            var newTopicIds = model.Topics;

            // Remove topics that are not in the new list
            foreach (var topic in post.Topics.ToList())
            {
                if (!newTopicIds.Contains(topic.Uid))
                {
                    post.Topics.Remove(topic);
                }
            }
            
            // Add new topics
            foreach (var topicId in newTopicIds)
            {
                if (!existingTopicIds.Contains(topicId))
                {
                    var topic = await context.Topics.FirstOrDefaultAsync(t => t.Uid == topicId);
                    if (topic != null)
                    {
                        post.Topics.Add(topic);
                    }
                }
            }
            
            // Save changes to update the post entity
            await context.SaveChangesAsync();
        }
        catch (Exception e)
        {
            _logger.Error($"Error updating post with ID: {id}. Error message: {e.Message}");
            throw;
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.