【问题标题】:Primary Key Violation in Multi-threading EF6多线程 EF6 中的主键冲突
【发布时间】:2017-07-26 09:49:57
【问题描述】:

我正在开发一个 C# 控制台应用程序,该应用程序从激战 2 API 下载数据并使用 Entity Framework 6 将其输入到我的数据库中。我正在尝试使用多线程,以便我可以加快输入过程大量数据进入我的数据库。

问题是当代码在我的AddRecipes 方法中运行到我的DBContext.SaveChanges() 调用时,返回以下错误:

违反主键约束“PK_dbo.Items”。无法在对象“dbo.Items”中插入重复键。重复键值为 (0)。

这是与我的问题相关的代码部分:

class Program
{
    private static ManualResetEvent resetEvent;
    private static int nIncompleteThreads = 0;

    //Call this function to add to the dbo.Items table
    private static void AddItems(object response)
    {
        string strResponse = (string)response;

        using (GWDBContext ctx = new GWDBContext())
        {
            IEnumerable<Items> itemResponse = JsonConvert.DeserializeObject<IEnumerable<Items>>(strResponse);

            ctx.Items.AddRange(itemResponse);
            ctx.SaveChanges();
        }

        if (Interlocked.Decrement(ref nIncompleteThreads) == 0)
        {
            resetEvent.Set();
        }
    }

    //Call this function to add to the dbo.Recipes table
    private static void AddRecipes(object response)
    {
        string strResponse = (string)response;

        using (GWDBContext ctx = new GWDBContext())
        {
            IEnumerable<Recipes> recipeResponse = JsonConvert.DeserializeObject<IEnumerable<Recipes>>(strResponse);

            ctx.Recipes.AddRange(recipeResponse);

            foreach(Recipes recipe in recipeResponse)
            {
                ctx.Ingredients.AddRange(recipe.ingredients);
            }
            ctx.SaveChanges(); //This is where the error is thrown
        }

        if (Interlocked.Decrement(ref nIncompleteThreads) == 0)
        {
            resetEvent.Set();
        }
    }

    static void GetResponse(string strLink, string type)
    {
        //This method calls the GW2 API through HTTPWebRequest
        //and store the responses in a List<string> responseList variable.
        GWHelper.GetAllResponses(strLink);

        resetEvent = new ManualResetEvent(false);
        nIncompleteThreads = GWHelper.responseList.Count();

        //ThreadPool.QueueUserWorkItem creates threads for multi-threading
        switch (type)
        {
            case "I":
                {
                    foreach (string strResponse in GWHelper.responseList)
                    {
                        ThreadPool.QueueUserWorkItem(new WaitCallback(AddItems), strResponse);
                    }
                    break;
                }
            case "R":
                {
                    foreach (string strResponse in GWHelper.responseList)
                    {
                        ThreadPool.QueueUserWorkItem(new WaitCallback(AddRecipes), strResponse);
                    }
                    break;
                }
        }

        //Waiting then resetting event and clearing the responseList
        resetEvent.WaitOne();           
        GWHelper.responseList.Clear();
        resetEvent.Dispose();
    }

    static void Main(string[] args)
    {
        string strItemsLink = "items";
        string strRecipesLink = "recipes";

        GetResponse(strItemsLink, "I");
        GetResponse(strRecipesLink, "R");

        Console.WriteLine("Press any key to continue...");
        Console.ReadLine();
    }

这是我的 DBContext 类:

public class GWDBContext : DbContext
{
    public GWDBContext() : base("name=XenoGWDBConnectionString") { }

    public DbSet<Items> Items { get; set; }
    public DbSet<Recipes> Recipes { get; set; }
    public DbSet<Ingredient> Ingredients { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
    }
}

这也是我的表类(我知道名称令人困惑,我正在重写它们):

public class Items
{
    public Items()
    {
        Recipes = new HashSet<Recipes>();
        Ingredients = new HashSet<Ingredient>();
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)] //This attribute makes sure that the id column is not an identity column since the api is sending that).
    public int id { get; set; }

    .../...
    public virtual ICollection<Recipes> Recipes { get; set; }
    public virtual ICollection<Ingredient> Ingredients { get; set; }
}

public class Recipes
{
    public Recipes()
    {
        disciplines = new List<string>();
        ingredients = new HashSet<Ingredient>();
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)] //This attribute makes sure that the id column is not an identity column since the api is sending that).
    public int id { get; set; }
    public string type { get; set; }

    [ForeignKey("Items")] //This attribute points the output_item_id column to the Items table.

    .../...
    private List<string> _disciplines { get; set; }
    public List<string> disciplines
    {
        get { return _disciplines; }
        set { _disciplines = value; }
    }

    [Required]
    public string DisciplineAsString
    {
        //get; set;
        get { return string.Join(",", _disciplines); }
        set { _disciplines = value.Split(',').ToList(); }
    }

    public string chat_link { get; set; }

    public virtual ICollection<Ingredient> ingredients { get; set; }
    public virtual Items Items { get; set; }
}

public class Ingredient
{
    public Ingredient()
    {
        Recipe = new HashSet<Recipes>();
    }

    [Key]
    public int ingredientID { get; set; }

    [ForeignKey("Items")] //This attribute points the item_id column to the Items table.
    public int item_id { get; set; }
    public int count { get; set; }

    public virtual ICollection<Recipes> Recipe { get; set; }
    public virtual Items Items { get; set; }
}

以下链接解释了 Items/Recipes 类返回的内容:

Items

Recipes

我注意到在删除外键约束和public virtual Items Items { get; set; } 代码后,数据将被正确保存。我相信我的错误与在 Recipes 类中使用 public virtual Items Items 有关。但据我了解,我需要在类中包含该虚拟变量,以便实体框架可以知道类之间的关系。那么为什么在我的类中有这个虚拟变量会导致主键冲突被抛出呢?

【问题讨论】:

  • 这表示您的 ID 重复为 0。这表明您实际上并未设置 ID。你确定你确实得到了一个 id 字段并且它被正确地反序列化到你的对象中吗?
  • 我在错误行检查时有 0 个项目。食谱正在正确反序列化,并且在 AddRecipes 调用之前已正确插入项目。
  • 食谱上的项目是否也正确?只是想知道你是否有一些 ID 为 0 的项目或其他东西......
  • 类和链接的 json 对象之间存在相当大的不匹配。
  • 我认为这只是由于在插入成分时重新插入了现有的Items 对象,但我必须查看recipeResponse 的确切内容才能验证这一点。

标签: c# multithreading entity-framework ef-code-first primary-key


【解决方案1】:

您只需要食谱中的项目列表。如果您需要搜索哪些食谱具有特定项目,您可以通过搜索食谱的外键(项目的主键)来实现。

代码存在一个基本的命名缺陷。你有一个类Recipes,然后是一个名为Recipes 的Recipes 列表。物品也是如此。

然后你有一个外键 [ForeignKey("Items")] 用于食谱。这是哪些物品?列表或对象项。太容易出错了。

将您的课程重命名为 RecipeItem

public class Recipe
{ 
    public Recipe()


public class Item
{
    public Item()

另外 - 如 cmets 中所述,0Id 重复,听起来 Id 未设置。

查看食谱的链接:

{
    .../...
    "ingredients": [
            { "item_id": 19684, "count": 50 },
            { "item_id": 19721, "count": 1 },
            { "item_id": 46747, "count": 10 }
    ],
    "id": 7319,
    .../...
}

食谱不应包含项目列表,而应包含成分列表。类结构应该是:

Recipe has:
public virtual ICollection<Ingredient> Ingredients { get; set; }

Ingredient has:
public virtual ICollection<Item> Items { get; set; }

Item 类没有成分或食谱列表,这些列表是通过使用与项目的主键匹配的外键查询成分上的数据库项目或食谱上的数据库成分来检索的与成分的主键匹配的外键 - 然后您可以进行连接以查找这些成分的任何项目。

所以进行以下更改:

Item 类中无需提及配方或成分。

public Item() // remove pluralisation
{
    // Remove these from the constructor,
    // Recipes = new HashSet<Recipes>();
    // Ingredients = new HashSet<Ingredient>();
}

// remove these from the class.
// public virtual ICollection<Recipes> Recipes { get; set; }
// public virtual ICollection<Ingredient> Ingredients { get; set; }

一种成分有许多物品 - 因此是物品的集合

public Ingredient()
{
    // You don't need a collection of Recipes - 
    // you need a collection of Items.
    // Recipe = new HashSet<Recipes>();
}
.../...
[ForeignKey("Item")] // change this
public Item Item // include an Item object - the PK of the
    // Item is the FK of the Ingredient


.../...
// Remove Recipes
// public virtual ICollection<Recipes> Recipe { get; set; }
public virtual ICollection<Item> Items { get; set; }

我更喜欢using the object name for the variable Item Item

一个食谱有许多成分 - 因此是成分的集合

public Recipes()
{
    disciplines = new List<string>();
    ingredients = new HashSet<Ingredient>();
}
.../...
// [ForeignKey("Items")] remove this
[ForeignKey("Ingredient")] 
public Ingredient Ingredient // include as Ingredient object 
    // the PK of the Ingredient is the FK for the Recipe

.../...
public virtual ICollection<Ingredient> Ingredients { get; set; }
// The Recipe does not have an Item, the Ingredient has 
// has a collection of <Item> Items
// public virtual Items Items { get; set; }

我也不确定你为什么使用哈希集。如果您没有特别的理由使用它们,我会将它们放入列表中。

如果这不能修复您的代码,我将审查它的其余部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 1970-01-01
    • 2013-06-13
    相关资源
    最近更新 更多