【发布时间】:2025-12-27 18:50:16
【问题描述】:
我正在创建一个 API 端点,该端点创建一个新的 Talk,其中包含应该与谈话相关联的标签。我在我的域中的标签和对话之间建立了多对多关系,请参阅下面的关系。
标签.cs
using System;
using System.Collections.Generic;
namespace Conferency.Domain
{
public class Tag : IAuditable
{
public int Id { get; set; }
public string Name { get; set; }
public List<TalkTag> TalkTags { get; set; }
public DateTime ModifiedAt { get; set; }
public DateTime CreatedAt { get; set; }
}
}
Talk.cs
using System;
using System.Collections.Generic;
namespace Conferency.Domain
{
public class Talk : IAuditable
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<TalkTag> TalkTags { get; set; }
public DateTime Presented { get; set; }
public DateTime ModifiedAt { get; set; }
public DateTime CreatedAt { get; set; }
}
}
TalkTag.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Conferency.Domain
{
public class TalkTag
{
public int TalkId { get; set; }
public Talk Talk { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
}
ConferencyContext.cs(删除无关代码)
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using Conferency.Domain;
namespace Conferency.Data
{
public class ConferencyContext: DbContext
{
public DbSet<Talk> Talks { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<TalkTag> TagTalks { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TalkTag>()
.HasKey(s => new { s.TalkId, s.TagId });
modelBuilder.Entity<TalkTag>()
.HasOne(pt => pt.Talk)
.WithMany(p => p.TalkTags)
.HasForeignKey(pt => pt.TalkId);
modelBuilder.Entity<TalkTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.TalkTags)
.HasForeignKey(pt => pt.TagId);
base.OnModelCreating(modelBuilder);
}
}
}
TalkViewModel.cs
using System;
using System.Collections.Generic;
namespace Conferency.Application.Models
{
public class TalkViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public List<String> Tags { get; set; }
}
}
问题是我不知道如何创建演讲及其标签(如果存在则附加,如果不存在则创建)。我不确定以什么顺序完成此操作。我是否必须查询每个标签以检查它们是否存在,或者是否有我可以使用的 findOrCreate 方法?如果尚未创建 Talk,如何创建 TalkTag 记录?有没有一种我不理解的优雅方法来完成这个?
TalkRepository.cs
using System.Collections.Generic;
using Conferency.Domain;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Conferency.Data
{
public class TalkRepository : ITalkRepository
{
private ConferencyContext _context;
public TalkRepository(ConferencyContext context)
{
_context = context;
}
public void Add(Talk entity)
{
_context.Add(entity);
}
public void AddWithTags(Talk entity, List<String> tags)
{
// Create Talk
// Query for each tag
// Create if they don't exist
// Attach to talk
// ??
}
public IEnumerable<Talk> GetAllTalks()
{
return _context.Talks
.Include(c => c.TalkTags)
.OrderBy(c => c.Presented)
.ToList();
}
public Talk GetTalk(int id)
{
return _context.Talks
.Include(c => c.TalkTags)
.Where(c => c.Id == id)
.FirstOrDefault();
}
public async Task<bool> SaveAllAsync()
{
return (await _context.SaveChangesAsync()) > 0;
}
}
}
我是 c# 新手,我正在尝试学习最佳实践并熟悉 EF 和 ASP.NET Core,因此希望有人可以帮助指导我走上正确的道路。完整的解决方案在这里,如果你想看看https://github.com/bliitzkrieg/Conferency
我尝试自己解决它,但我得到一个 NullPointerException,这是我尝试的解决方案:
TalksController.cs
[HttpPost()]
public async Task<IActionResult> Post([FromBody]TalkViewModel model)
{
try
{
_logger.LogInformation("Creating a new Talk");
List<Tag> tags = _tagRepo.FindOrCreateTags(model.Tags);
Talk talk = new Talk { Name = model.Name, Url = model.Url };
List<TalkTag> talkTags = new List<TalkTag>();
tags.ForEach(tag =>
{
var talkTag = new TalkTag { TagId = tag.Id, Talk = talk };
talkTags.Add(talkTag);
});
talk.TalkTags.AddRange(talkTags); // Exception being thrown here
_repo.Add(talk);
if (await _repo.SaveAllAsync())
{
string newUri = Url.Link("TalkGet", new { id = talk.Id });
return Created(newUri, talk);
}
else
{
_logger.LogWarning("Could not save Talk");
}
}
catch (Exception ex)
{
_logger.LogError($"Threw exception while saving Talk: {ex}");
}
return BadRequest();
}
}
TagRepository.cs
using System;
using System.Collections.Generic;
using Conferency.Domain;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace Conferency.Data
{
public class TagRepository: ITagRepository
{
private ConferencyContext _context;
public TagRepository(ConferencyContext context)
{
_context = context;
}
public void Add(Tag entity)
{
_context.Add(entity);
}
public List<Tag> FindOrCreateTags(List<string> tags)
{
List<Tag> _tags = new List<Tag>();
tags.ForEach(t =>
{
try
{
var tag = _context.Tags
.Where(c => c.Name == t)
.FirstOrDefault();
if (tag != null)
{
_tags.Add(tag);
}
else
{
Tag created = new Tag { Name = t };
this.Add(created);
_tags.Add(created);
}
}
catch (Exception ex)
{
}
});
return _tags;
}
public async Task<bool> SaveAllAsync()
{
return (await _context.SaveChangesAsync()) > 0;
}
}
}
【问题讨论】:
-
由于您的实体的所有属性都不是
virtual,因此您似乎没有正确设置导航属性。 -
我对虚拟实体了解不多,但我遵循了 EF 核心文档 (docs.microsoft.com/en-us/ef/core/modeling/relationships) 上的多对多指南。
-
您也应该提供您的 TalkViewModel 代码。根据我对您的代码的了解,我猜您的 TalkViewModel 上需要一个 List
属性,该属性具有标签 ID、标签文本和布尔“已选择”属性。然后,当您将 TalkViewModel 传递给您的 repo 时,过滤掉选定的 TagViewModels 并为每个标签添加一个具有正确 TagId 的 TalkTag 到您的 TalkTags 属性中。 EF 应该注意在 SaveChanges() 上添加正确的 TalkId。 -
如果您的用户可以在您的 Talk 表单上动态添加标签,请将其 ID 设置为 0,然后过滤这些 TagViewModel 并将它们作为新标签添加到您的 TalkTag 上的 Tag 属性中。让我知道这是否有效,我将提供正式答案。
-
我添加了 TalkViewModel。今晚我会尝试你的解决方案并给你更新
标签: asp.net entity-framework asp.net-core entity-framework-core