【问题标题】:Entity Framework add new item with related child item实体框架添加具有相关子项的新项
【发布时间】:2020-10-06 16:15:33
【问题描述】:

当我尝试添加带有子项的项时出现错误。

当 IDENTITY_INSERT 设置为 OFF 时,无法为表“类别”中的标识列插入显式值。

我的课:

public class Product
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Category Category { get; set; }
}

public class Category
{
    public long Id { get; set; }
    public string CatName { get; set; }
}

当我像这样添加新的Product 时:

var p = new Product();
p.Name = "test";
p.Description = "test 1";

var c = new Category();
c.Id = 2;
c.CatName = "Demo";
p.Category = c;

_context.Product.Add(p);

类别 (CatName = Demo, Id = 2) 已存在于数据库中。

我得到错误:

当 IDENTITY_INSERT 设置为 OFF 时,无法为表“类别”中的标识列插入显式值。

我不想添加新的Category。当我从数据库中读取Category 时,它工作正常...

var p = new Product();
p.Name = "test";
p.Description = "test 1";
p.Category = _context.Category.Where(c => c.Id.Equals(2)).FirstOrDefault();

_context.Product.Add(p);

但我不想阅读所有(还有更多)子属性。

我做错了什么?

【问题讨论】:

  • 如果您提供自己的值(身份)或不提供id,您需要打开IDENTITY_INSERT,因为它会为您完成。另一种选择是运行查询为您执行此操作:SET IDENTITY_INSERT Category ON;您可能还需要更新您的身份模型。
  • 如果您想将新产品实体连接到现有类别 - 只需将 CategoryId 外键值(您应该在您的 Product 类上)设置为 Id现有类别 - 这将是实现这一目标的最简单方法
  • @marc_s:我尝试使用 'p.Category.Id = 2',但它抛出了同样的错误 :(
  • 您正在插入一个已经存在的 Id=2。将其设为 0(零)以便 EF 理解为新类别
  • 使用 _context.Product.Update(p);相反...它将添加产品而不是类别...

标签: c# entity-framework


【解决方案1】:

当您将一个未跟踪的对象引用作为添加操作的一部分传递给上下文时,它会将其未跟踪的每个对象引用视为已添加的实体。引用就是 EF 中的一切。

上下文没有跟踪您的类别,那么它如何知道 ID 为 2 的类别对象是指向现有类别还是新类别?

如果您可以保证您想要的类别和所有其他引用存在于数据库中,那么您可以检查上下文的本地缓存中的实例(如果找到则使用),否则附加类别。例如:

var c = _context.Categories.Local.SingleOrDefault(c => c.Id == 2); // Checks cache, doesn't go to DB.
if(c == null)
{
    c = new Category { Id = 2 }; // Only need PK to associate an entity.
    _context.Categories.Attach(c);
}

var p = new Product()
{ 
    Name = "test",
    Description = "test 1",
    Category = c
};

_context.Product.Add(p);

在可能跟踪一个或多个引用实体的情况下,无论哪种方式,您都需要检查上下文。试图通过创建和附加引用来简化事情可能会导致间歇性错误,尤其是在处理更大的操作或实体集时。例如,您可能会测试或通常处理使用不同类别并且工作正常的多个实体,但是如果使用共享相同类别的 2 个产品调用,则第二个产品添加将失败,因为 DbContext 已经在跟踪具有相同 ID 的类别.

转到上下文加载相关实体并不是一个昂贵的负担,它有助于确保您提出的更新是有效的。通过 ID 获取引用非常快,并且 DbContext 无论如何都会在访问数据库之前查看它的本地缓存。即使在您可能将许多产品更新到不同类别的情况下,也可以进行优化。例如,如果我有一个产品视图模型列表,其中包含要关联的类别:(有些可能具有相同的类别,或任意数量的不同类别)

class ProductViewModel
{
     string Name { get; set; }
     string Description { get; set; }
     int CategoryId { get; set; } 
     // ...
}

var categoryIds = productViewModels.Select(p => p.CategoryId).Distinct();
var categories = _context.Categories.Where(c => categoryIds.Contains(c.Id)).ToList();

foreach(var productVM in productViewModels)
{
    var c = categories.Single(c => c.Id == productVM.CategoryId);
    var p = new Product()
    { 
        Name = productVM.Name,
        Description = productVM.Description,
        Category = c
    };
    
    _context.Product.Add(p);
}

我们不必在循环等中一个一个地获取数据。它可以通过检查我们可能需要的数据并获取我们将感兴趣的 ID 在一次调用数据库中加载。但建议在处理您希望找到的引用时利用像 Single 这样的方法而不是 @ 987654324@ 或 FirstOrDefault 以便明确说明您希望在哪里找到实体。 Single 说“我希望找到 1 条符合此条件的记录”,所以如果它没有找到一条记录,或者碰巧找到了不止一条记录,那么此时你会得到一个有意义的异常。 FirstOrDefault 如果发现多于一行,它不会抛出异常,它只会选择它遇到的第一个,如果没有找到记录,它不会抛出异常。稍后可能会发生异常,代码假定找到了引用并尝试访问#null 上的属性。 SingleOrDefault 在检查本地缓存时适用于第一个示例,因为我想处理在缓存中跟踪记录或明确尚未跟踪记录的可能性。

【讨论】:

    猜你喜欢
    • 2021-12-04
    • 2021-05-03
    • 2018-06-29
    • 2020-11-23
    • 1970-01-01
    • 2011-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多