【问题标题】:Cloning an object and saving the clone to db causes the original object to lose relational data克隆对象并将克隆保存到 db 会导致原始对象丢失关系数据
【发布时间】:2018-05-25 06:54:18
【问题描述】:

当我尝试克隆 Product-object 并将克隆保存到数据库时,原始对象会丢失其所有关系数据,例如 ProductPropertyOptionForProductsIdentifierForProductsInCategories

这是产品型号:

public class Product
{
    public int Id { get; set; }
    public int ProductGroupId { get; set; }
    public int ProductGroupSortOrder { get; set; }

    [Required, MaxLength(30), MinLength(4)]     public string Title { get; set; }
    [MaxLength(200)]                            public string Info { get; set; }
    [MaxLength(4000)]                           public string LongInfo { get; set; }
    [Required, DataType(DataType.Currency)]     public decimal Price { get; set; }
                                                public int Weight { get; set; }
                                                public int ProductTypeId { get; set; }
    public ICollection<ProductImage> Images { get; set; }

    // Selected property options for this product
    public ICollection<PropertyOptionForProduct> ProductPropertyOptionForProducts { get; set; }

    // A product can have multiple identifiers (EAN, ISBN, product number, etc.)
    public ICollection<IdentifierForProduct> IdentifierForProducts { get; set; }

    public ProductType Type { get; set; }
    public ICollection<FrontPageProduct> InFrontPages { get; set; }
    public ICollection<ProductInCategory> InCategories { get; set; }
}

一些相关模型:

public class ProductInCategory
// A linking table for which products belongs to which categories
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int ProductCategoryId { get; set; }
    public int SortOrder { get; set; }

    // Nav.props.:
    public Product Product { get; set; }
    public ProductCategory ProductCategory { get; set; }
}

public class PropertyOptionForProduct
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int ProductPropertyId { get; set; }
    public int ProductPropertyOptionId { get; set; }

    // Nav.props.
    public Product Product { get; set; }
    public ProductPropertyOption ProductPropertyOption { get; set; }
}

public class IdentifierForProduct
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int ProductIdentifierId { get; set; }
    [StringLength(30), MaxLength(30)]
    public string Value { get; set; }

    public ProductIdentifier ProductIdentifier { get; set; }
    public Product Product { get; set; }
}

原件是这样加载的:

public async Task<Product> GetProduct(int Id)
{
    Product DbM = await _context.Products
        .Include(ic => ic.InCategories)
            .ThenInclude(pc => pc.ProductCategory)
        .Include(t => t.Type)
            .ThenInclude(iit => iit.Identifiers) //ProductIdentifiersInTypes
                .ThenInclude(i => i.Identifier) // ProductIdentifiers
                    .ThenInclude(ifp => ifp.ProductIdentifiers) // IdentifiersForProducts
        .Include(t => t.Type)
            .ThenInclude(pit => pit.Properties) // ProductPropertiesInTypes
                .ThenInclude(p => p.Property) // ProductProperties
                    .ThenInclude(po => po.Options) // ProductPropertyOptions
        .Include(p => p.ProductPropertyOptionForProducts)
        .Where(p => p.Id == Id)
        .SingleOrDefaultAsync();
    return DbM;
}

这是克隆方法:

private async Task<Product> MakeClone(Product Original)
{
    Product Clone = new Product
    {
        ProductGroupId = Original.ProductGroupId,
        ProductGroupSortOrder = Original.ProductGroupSortOrder + 1,
        IdentifierForProducts = Original.IdentifierForProducts,
        Images = Original.Images,
        InCategories = Original.InCategories,
        Info = Original.Info,
        InFrontPages = Original.InFrontPages,
        LongInfo = Original.LongInfo,
        Price = Original.Price,
        ProductPropertyOptionForProducts = Original.ProductPropertyOptionForProducts,
        ProductTypeId = Original.ProductTypeId,
        Title = Original.Title,
        Type = Original.Type,
        Weight = Original.Weight
    };
    _context.Add(Clone);
    await _context.SaveChangesAsync();
    return Clone; // and go to the Edit-view.
}

现在,克隆具有原始产品的所有属性,但原始产品的所有关系数据已被剥离。在数据库中,看起来克隆的关系数据已经替换了原来的关系数据。

更新

根据 Georg 的回答,我将 MakeClone()-method 更改为:

private Product MakeClone(Product Original)
{
    List<IdentifierForProduct> identifiers = Original
            .IdentifierForProducts
            .Select(i => CloneIdentifierForProduct(i))
            .ToList();
    List<PropertyOptionForProduct> propertyOptions = Original
            .ProductPropertyOptionForProducts
            .Select(o => ClonePropertyOptionForProduct(o))
            .ToList();
    List<ProductInCategory> inCategories = Original
            .InCategories
            .Select(c => CloneProductInCategory(c))
            .ToList();
    List<FrontPageProduct> inFrontPages = Original
            .InFrontPages
            .Select(f => CloneFrontPageProduct(f))
            .ToList();
    List<ProductImage> images = Original.Images.Select(i => CloneProductImage(i)).ToList();
    Product Clone = new Product
    {
        ProductGroupId = Original.ProductGroupId,
        ProductGroupSortOrder = Original.ProductGroupSortOrder + 1,
        Info = Original.Info,
        LongInfo = Original.LongInfo,
        Price = Original.Price,
        ProductTypeId = Original.ProductTypeId,
        Title = Original.Title,
        Type = Original.Type,
        Weight = Original.Weight,
        IdentifierForProducts = identifiers,
        ProductPropertyOptionForProducts = propertyOptions,
        InCategories = inCategories,
        InFrontPages = inFrontPages,
        Images = images
    };
    _context.Add(Clone);
    // fix FKs
    foreach (var ifp in Clone.IdentifierForProducts) ifp.ProductId = Clone.Id;
    foreach (var ofp in Clone.ProductPropertyOptionForProducts) ofp.ProductId = Clone.Id;
    foreach (var pic in Clone.InCategories) pic.ProductId = Clone.Id;
    foreach (var fpp in Clone.InFrontPages) fpp.ProductId = Clone.Id;
    foreach (var pi in Clone.Images) pi.ProductId = Clone.Id;
    // Lagre klonen i databasen:
    _context.SaveChangesAsync();
    return Clone;
}

...并添加了用于克隆每个链接数据的单独方法(我认为我不需要显示所有五种方法):

private IdentifierForProduct CloneIdentifierForProduct(IdentifierForProduct ifp)
{
    IdentifierForProduct IFP = new IdentifierForProduct
    {
        Product = ifp.Product,
        ProductId = ifp.ProductId,
        ProductIdentifier = ifp.ProductIdentifier,
        ProductIdentifierId = ifp.ProductIdentifierId,
        Value = ifp.Value
    };
    return IFP;
}

现在我在创建子列表时收到ArgumentNullException

这可能与例如IdentifierForProduct 也有一个子属性(我也想克隆)这一事实有关吗?

【问题讨论】:

  • InCategories(例如)如何链接到这些对象?该表中是否有外键列?
  • 克隆对象是一个复杂的过程。例如,这里您没有克隆所有 IEnumerables,而只是将它们的引用复制到新对象中。当您将所有内容传递给实体框架时,我不确定这是否可以
  • 能否查看Original是否有ProductPropertyOptionForProducts等属性?如果不是,那么您必须使用Include(x =&gt; x.ProductPropertyOptionForProducts) 等等,如果是,则使用AutoMapper 克隆复杂对象。在调用MakeClone 方法之前,您是如何创建Original 的?。
  • @LasseVågsætherKarlsen 查看更新后的问题。我添加了一些链接模型。其余的也遵循相同的模式。
  • IdentifierForProducts 在克隆时也应该是 new

标签: c# asp.net-core-mvc entity-framework-core


【解决方案1】:

正如史蒂夫所指出的,您只是设置对原始集合属性的引用,而不是克隆集合。如果集合的关系没有定义为 many:many,这会从原始中删除相关实体并将它们添加到克隆中。

例如,要克隆IdentifierForProducts 集合,您必须克隆每个元素,然后将它们添加到克隆的集合中。

Product clone = new Product {
    IdentifierForProducts = Original.IdentifierForProducts.Select(ifp => MakeClone(ifp)).ToList(),
    // other properties ....
};

// fix FKs after cloning
foreach (var ifp in clone.IdentifierForProducts) {
    ifp.ProductId = clone.Id;
}

MakeClone&lt;IdentifierForProduct&gt;(IdentifierForProduct original) 类似于 MakeClone&lt;Product&gt;(Product original)

【讨论】:

  • 我可能太快无法接受您的回答...您的建议给了我例外InvalidOperationException: Collection was modified; enumeration operation may not execute.
  • foreach中是否抛出了这个异常?
  • 抛出在这一行:Product Clone = new Product.
  • 我在C# Fiddle 中尝试了这种方法,它似乎在原则上有效。您是否在任何CloneXXX() 方法中使用foreach?如果您更改要循环的集合(例如 foreach (var elem in theCollection) { theCollection.Remove(elem); }foreach (var elem in theCollection) { var newElem = new X(elem); theCollection.Add(newElem); }),通常会发生此异常。
  • 这个ArgumentNullException 可能是由为空的源列表引起的。尝试添加空检查:IdentifierForProducts = Original.IdentifierForProducts?.Select(ifp =&gt; MakeClone(ifp)).ToList() ?? new List&lt;IdentifierForProducts&gt;()
猜你喜欢
  • 2015-01-23
  • 2011-10-15
  • 2012-04-26
  • 2017-07-20
  • 2011-11-07
  • 2011-01-20
  • 2016-11-14
  • 2016-02-21
  • 2019-03-21
相关资源
最近更新 更多