这就是我们如何定义彼此具有多对多关系的 Post 和 Tag:
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
}
当我运行数据库更新时,它已经能够生成三个表:
Post
、Tag
和PostTag
,它们具有主键[PostId, TagId]
。总的来说,数据库架构正是我所想的。
但是,没有提到的是如何插入与
Post
有关系的Tags
,反之亦然。
db.Posts.Add(new Post { Title="Title1", Content="Content1"});
db.Posts.Add(new Post { Title="Title2", Content="Content2"});
db.Tags.Add(new Tag { TagId="Tag1"});
db.Tags.Add(new Tag { TagId="Tag2"});
db.SaveChanges();
现在数据库每个表中有2条记录。我的问题是我们如何添加具有 tag1 和 tag2 的新帖子?我的下面的代码不起作用。有人可以指出哪里出了问题吗?或者我是否需要通过添加第三个实体来执行旧方法
PostTag
?
这是我的代码:
ICollection<Tag> tags = new List<Tag> {db.Tags.FirstOrDefault(i => i.TagId="Tag1"), db.Tags.FirstOrDefault(i => i.TagId="Tag2")};
db.Posts.Add(new Post { Title="Title3", Content="Content3", Tags=tags});
db.SaveChanges();
我得到的是异常,说它无法将 post3 插入数据库。
更新:感谢大家的帮助。发现我的真实代码犯了一个错误。这是我的实际代码:
vICollection<Tag> tags = new List<Tag> {db.Tags.FirstOrDefault(i => i.TagId="Tag1"), db.Tags.FirstOrDefault(i => i.TagId="Tag2")};
var post3 = new Post { Title="Title3", Content="Content3"};
db.Posts.Add(post3);
post3.Tags = tags; // wrong position, should be called before db.Posts.Add(post3)
db.SaveChanges();
错误是
post3.Tags = tags;
是在db.Posts.Add(post3);
之后调用的。应该是这样的:
vICollection<Tag> tags = new List<Tag> {db.Tags.FirstOrDefault(i => i.TagId="Tag1"), db.Tags.FirstOrDefault(i => i.TagId="Tag2")};
var post3 = new Post { Title="Title3", Content="Content3"};
post3.Tags = tags; // correct position
db.Posts.Add(post3);
db.SaveChanges();
我还从我的代码中删除了 PostId。
当您添加
Post
并指定PostId
时,会出现以下错误:
无法在表“Posts”中插入显式标识列值 IDENTITY_INSERT 设置为 OFF。
只需删除
PostId
,代码就可以正常工作:
ICollection<Tag> tags = new List<Tag> {
db.Tags.FirstOrDefault(i => i.TagId="Tag1"),
db.Tags.FirstOrDefault(i => i.TagId="Tag2")};
db.Posts.Add(new Post { Title="Title3", Content="Content3", Tags=tags});
db.SaveChanges();
正如 @Rena 已经提到的,您的代码无法工作,因为您在创建
PostId=3
实体时设置主键值 (Post
)。我只是添加一个改进点。
在下面的代码中,您将多次访问数据库以一次获取一个
Tag
-
ICollection<Tag> tags = new List<Tag> {db.Tags.FirstOrDefault(i => i.TagId="Tag1"), db.Tags.FirstOrDefault(i => i.TagId="Tag2")};
您可以一次性获取标签列表,例如-
var tags = db.Tags.ToList();
你的最终代码可能看起来像 -
var tags = db.Tags.ToList();
var post = new Post
{
Title = "Title3",
Content = "Content3",
Tags = tags.Where.(i => i.TagId == "Tag1" || i.TagId == "Tag2")).ToList()
}
db.Posts.Add(post);
db.SaveChanges()
对其他人之前的解决方案的改进是,您可以避免从数据库中获取相关标签,只需将它们
Attach
添加到DbContext
。
var tags = [
new Tag { Id = "Id1" },
new Tag { Id = "Id2" }
];
db.Posts.AttachRange(tags);
db.Posts.Add(new Post { Title="Title1", Content="Content1", Tags = tags});
db.SaveChanges();
请注意,只有当您已经拥有相关标签的 Id 并且不需要查询 Id 时,才可以进行此优化。如果您有有效的标签 ID,则提供的代码只会将新帖子和一些记录插入到连接表
这样,
Attach
开始跟踪处于Unchanged
状态的标签,因此在保存更改时不会插入标签:
默认情况下使用“未更改”状态开始跟踪给定实体和可从给定实体访问的条目