【问题标题】:EntityFramework adds duplicate related objects on SaveChangesEntityFramework 在 SaveChanges 上添加重复的相关对象
【发布时间】:2015-03-22 00:38:15
【问题描述】:

对象

public class Noun
{
    public int Id { get; set; }

    [MaxLength(128)]
    public string Name { get; set; }
    public bool Active { get; set; }

    public virtual Category Category { get; set; }
    public int CategoryId { get; set; }
}

public class Category
{
    public int Id { get; set; }

    [MaxLength(128)]
    public string Name { get; set; }
}

存储库

public Noun CreateNoun(string name, string categoryName)
{
    using (MyContext context = new MyContext())
    {
        Noun noun;
        Category category;

        lock (NounLock)
        {
            // don't create it if it already exists
            noun = context.Nouns
                .Include(t => t.Category)
                .FirstOrDefault(t => t.Name == name && t.Category.Name == categoryName);

            if (noun == null)
            {
                // make the category if it doesn't already exist
                lock (CategoryLock)
                {
                    category = context.Categories.FirstOrDefault(c => c.Name == categoryName);
                    if (category == null)
                    {
                        category = new Category() { Name = categoryName };
                        context.Categories.Add(category);
                        context.SaveChanges();
                    }
                }

                noun = new Noun()
                {
                    Name = name,
                    Category = category,
                    CategoryId = category.Id
                };

                context.Nouns.Add(noun);
            }
            else
            {
                category = noun.Category;
            }

            // make sure the noun is set as active
            noun.Active = true;

            context.Entry(category).State = EntityState.Unchanged;
            context.SaveChanges();

            return noun;
        }
    }
}

上下文

internal class MyContext : DbContext
{ 
    public DbSet<Category> Categories { get; set; }
    public DbSet<Noun> Nouns { get; set; }

    public MyContext()
        : base("DefaultConnection")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Nouns
        modelBuilder.Entity<Noun>()
            .HasRequired(t => t.Category);
    }
}

问题

当调用 CreateNoun() 时,当该类别的名词已经存在时,它应该(基于我认为我正在做的事情),只需从数据库中加载现有名词并将其标记为活动。但相反,它插入了一个新名词和一个新类别。我究竟做错了什么?我知道这可能是一件小事。

PS:锁是静态的并且就位,因为这可能被多线程租户使用

示例

Noun newNoun = repo.CreateNoun("name", "cat");
// should load existing noun from db, and set it as active, but instead duplicates it
Noun oldNoun = repo.CreateNoun("name", "cat"); 

【问题讨论】:

  • 检查以确保您的重复检查实际上得到了重复。
  • 尝试删除 context.Entry(category).State = EntityState.Unchanged 看看是否有影响。
  • 我发现了问题,完全是在另一个线程中被 Noun 欺骗了 :( 多线程很棒。感谢所有 cmets/回复,为大家点赞!

标签: c# entity-framework ef-code-first entity-framework-6 duplication


【解决方案1】:

您在CreateNoun 方法中存在一些问题,我做了一些更改,稍后将解释:

public Noun CreateNoun(string name, string categoryName)
{
   using (MyContext context = new MyContext())
   {
       Noun noun;
       Category category;

       lock (NounLock)
       {
          // don't create it if it already exists
          noun = context.Nouns
                    .FirstOrDefault(t => t.Name == name && t.Category.Name == categoryName);

          if (noun == null)
          {
             // make the category if it doesn't already exist
             lock (CategoryLock)
             {
               category = context.Categories.FirstOrDefault(c => c.Name == categoryName);
               if (category == null)
               {
                  category = new Category() { Name = categoryName };
                  context.Categories.Add(category);
                }
             }
             noun = new Noun()
             {
               Name = name,
               Category = category,
             };
             context.Nouns.Add(noun);
          }

          // make sure the noun is set as active
          noun.Active = true;

          context.SaveChanges();

          return noun;
       }
   }
}
  • 你不需要调用Include,EF会加载Category 财产给你。这个导航属性是虚拟的,所以它将是 lazy loaded.
  • 当你没有找到Category时,你不需要打电话给 SaveChanges 方法。在返回名词之前,您正在调用它 方法,它将保存您之前所做的所有更改。
  • 您不需要设置外键CategoryId。你不知道 如果category 是从数据库加载或最近创建的。只需设置 导航属性Category
  • 您无需更改Category 状态

另一个建议:

OnModelCreating 方法中,您正在根据需要配置Category。当您要配置关系时,使用方法HasRequired。在你的情况下,它会是这样的:

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
 {
        modelBuilder.Entity<Noun>()
            .HasRequired(t => t.Category).WithMany().HasForeignKey(n=>n.CategoryId);
 }

更新

正如@Shoe 在他关于第一点的评论中所说,如果您想使用此方法返回的Noun 并查阅其类别,请在搜索Noum 时调用Include 方法。以前做过。

【讨论】:

  • 您的第一个要点不正确。假设在他的上下文被处理后他需要类别。如果没有Include,这将引发异常。
  • 你好@Shoe,你是对的,我没有意识到他可以在外面使用返回的名词,我以为他只是想插入或更新那些实体。我会更新我的答案。
  • 找到了问题,但它与我展示的任何内容都无关,对此感到抱歉。投赞成票,感谢您的提示,我将它们合并到我的代码中。
【解决方案2】:

您尚未在模型中标记您的密钥。两个Id 属性都需要用[Key] 标记,Noun 类中的Category 属性需要[ForeignKey("CategoryId")] 注释。就目前而言,重复检查始终失败,因为t.Category.Name 始终为空。

【讨论】:

  • 这就是模型构建器所做的。我实际上查看了我的数据库,它创建了没有装饰的主键/外键。
猜你喜欢
  • 2016-06-28
  • 1970-01-01
  • 2016-05-23
  • 2022-01-07
  • 2022-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多