我有以下实体 -
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()
清除主题,然后保存。然后分配新主题并再次保存更改,但也没有帮助
所以,经过反复试验,我成功了。问题在于在更改帖子之前附加主题
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;
}
}