使用实体框架代码保存分离的对象图首先会导致主键冲突



我正在尝试保存一个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; }
}

最新更新