我正在尝试保存一个POCO的对象图,我已经使用代码优先的流畅符号映射到EF6。
然而,在保存对象图时,我偶然发现了违反主键的异常。
对象图非常简单:
一个Issue
可以包含多个WorkItems
,每个Author
(作为User
)。
对象在外部填充(使用Web API)
当我试图用引用同一作者的两个工作项保存问题时,我希望插入问题,插入工作项并插入一个作者,另一个被引用或更新。
然而,问题被插入,工作项被插入,对同一用户的两个引用都被插入,从而导致主键冲突。
简化问题对象:
public class Issue
{
public Issue()
{
WorkItems = new List<WorkItem>();
}
public string Id { get; set; }
private List<WorkItem> _workItems;
public List<WorkItem> WorkItems
{
get { return _workItems ?? new List<WorkItem>(); }
set { _workItems = value; }
}
}
简化的工作项:
public class WorkItem
{
public string Id { get; set; }
public string AuthorLogin
{
get; set;
}
private WorkItemAuthor _author;
public WorkItemAuthor Author
{
get { return _author; }
set { _author = value;
if (value != null)
{
AuthorLogin = value.Login;
}
else
{
AuthorLogin = string.Empty;
}
}
}
}
简化的用户对象:
public class User
{
public string Login { get; set; }
public string FullName { get; set; }
}
他们的代码优先配置:
internal IssueConfiguration()
{
HasKey(x => x.Id);
HasMany(x => x.WorkItems);
}
internal WorkItemConfiguration()
{
HasKey(x => x.Id);
HasRequired(p => p.Author)
.WithMany(b => b.WorkItems)
.HasForeignKey(x=>x.AuthorLogin);
}
internal UsersConfiguration()
{
HasKey(x => x.Login);
}
一切都很简单。在创建数据库时,反表看起来也很好,在人们期望的列上有FK
现在,在保存问题时,如果插入对象图,并且自动识别对现有对象的引用,并选择性地仅插入或引用,那就太好了。
我尝试添加相应的问题:
using (var db = new Cache.Context())
{
if (db.Issues.Any(e => e.Id == issue.Id))
{
db.Issues.Attach(issue);
db.Entry(issue).State = EntityState.Modified;
}
else
{
db.Issues.Add(issue);
}
db.SaveChanges();
}
这个问题的解决方案是我遍历对象图,手动添加或附加图中的其他对象吗?我希望通过定义适当的外键值,这些引用将被识别。
我终于做了类似的事情,相当费力,我仍然想找到更好的方法。找出一个实体是否已经连接或存在于数据库中,结果发现对模型的污染太大了(实现IEquatable<T>
很好,但我认为在我的POCO上实现IEntityWithKey
对POCO的污染太多了
internal static void Save(this List<Issue> issues)
{
using (var db = new Context())
{
foreach (var issue in issues.ToList())
{
foreach (var workItem in issue.WorkItems.ToList())
{
if (workItem.Author != null)
{
var existing = db.Users.SingleOrDefault(e => e.Login == workItem.Author.Login);
if (existing == null)
{
db.Users.Add(workItem.Author);
}
else
{
//Update existing entities' properties
existing.Url = workItem.Author.Url;
//Replace reference
workItem.Author = existing;
}
db.SaveChanges();
}
var existingWorkItem = db.WorkItems.SingleOrDefault(e => e.Id == workItem.Id);
if (existingWorkItem == null)
{
db.WorkItems.Add(workItem);
}
else
{
//Update existing entities' properties
existingWorkItem.Duration = workItem.Duration;
//Replace reference
issue.WorkItems.Remove(workItem);
issue.WorkItems.Add(existingWorkItem);
}
db.SaveChanges();
}
var existingIssue = db.Issues.SingleOrDefault(x => x.Id == issue.Id);
if (existingIssue == null)
{
db.Issues.Add(issue);
}
else
{
//Update existing entities' properties
existingIssue.SpentTime = issue.SpentTime;
}
db.SaveChanges();
}
}
}
Issue对象中有一个小错误。
"return _workItems??new List();"可以在每次获取时返回一个新的WorkItem,如果_workItemss变为null。这是固定版本。
public class Issue {
public Issue() {
WorkItems = new List<WorkItem>();
}
public String Id {
get; set;
}
public List<WorkItem> WorkItems { get; private set; }
}