问题
I have 2 objects with one to many relation. I'm trying to add multiple ProductImages with the same ProductId to the data base, while using a generic repository.
I have tried saving a list of ProductImage to a repository with dbSet.AddRange . after that i saved it with context.SaveChanges(); i have also tried looping trough the list of ProductImage and adding each item with dbSet.Add .and than context.SaveChanges() .
repository methods:
public virtual void Add(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void AddRange(List<TEntity> entityList)
{
dbSet.AddRange(entityList);
}
public void Save()
{
context.SaveChanges();
}
the result is the object saved to the data base is always the last one - but i want to save all of them.
Edit the method for adding the Objects:
private Task<int> InsertProductImagesToDB(List<ProductImageVM> productImages, int productId, List<IFormFile> imageFiles)
{
if (productImages?.Any()==true)
{
List<ProductImage> dbEntitys = new List<ProductImage>();
for(int i=0; i< productImages.Count; i++)
{
ProductImage p = productImages[i].ConvertToProductImageEntity(imageFiles[i], productId);
dbEntitys.Add(p);
}
ImagesRepository.AddRange(dbEntitys);
ImagesRepository.Save();
return Task.FromResult(dbEntitys.First().Id);//return first productImage as the main image
}
return Task.FromResult(-1);
}
as some of you mentioned in the comments - the object constructor is not outside the loop. while debugging i can see the dbEntitys list contains 2 items with defferent values.
image of the dbset after adding the objects before saving changes: db table after changes were saved:
回答1:
To expand on Steve's comment, the issue likely isn't with EF or adding the items to the DbSet, but rather with how you populate the entity list to save.
Take this for example:
List<ProductImage> productImages = new List<ProductImage>();
Product product = context.Products.Single(x => x.ProductId == productId);
ProductImage productImage = new ProductImage();
for (int count = 1; count < 5; count++)
{
productImage.ImageNumber = count;
productImage.Product = product;
productImages.Add(productImage);
}
// ...
context.ProductImages.AddRange(productImages);
The issue with the above code is that the loop is merely updating the same single reference of a ProductImage object. The list will contain 5 references to the same image and that image's Number will be "5".
Instead:
List<ProductImage> productImages = new List<ProductImage>();
Product product = context.Products.Single(x => x.ProductId == productId);
for (int count = 1; count < 5; count++)
{
ProductImage productImage = new ProductImage();
productImage.ImageNumber = count;
productImage.Product = product;
productImages.Add(productImage);
}
// ...
context.ProductImages.AddRange(productImages);
By moving the constructor for ProductImage into the loop, each image is a separate reference resulting in 5 unique items in the collection.
The important detail is that if all of the images are for the same product, that you do use a single product reference for all images. Whether that product is loaded from the database (above) or created as a new item. A common pitfall that developers new to EF make would be:
List<ProductImage> productImages = new List<ProductImage>();
for (int count = 1; count < 5; count++)
{
Product product = new Product { ProductId = productId };
ProductImage productImage = new ProductImage();
productImage.ImageNumber = count;
productImage.Product = product;
productImages.Add(productImage);
}
// ...
context.ProductImages.AddRange(productImages);
Where they may have an existing Product ID that the user has selected, and don't want to hit the database to load it, so they just create a Product entity and set the ID thinking all will be good. The issue here is that each Product instance will be a separate instance, and when you add the ProductImage to the DbSet, all untracked entities associated with that product image (Product in this case) will be treated as "Adds" as well. The result is that 5 new identical Products (with new IDs) would be added to the database provided the Product ID PK was configured as an Identity, or you'd get an error about inserting duplicate IDs. With EF it is important to work with unique instances where you want unique records created, and work with a single instance (loaded from the DB, created new, or created as a stub and attached to the context) where you want to reference a single existing or new row.
回答2:
after banging my head against the wall for a few days - i finally got the solution.
the problem was in my ProductImage Entity which looked like this:
public class ProductImage : ImageProp, DBEntity
{
[Column("Name", TypeName = "nvarchar(200)"), Required, Display(Name = "שם"), MaxLength(200)]
public string Name { get; set; }
public Product Product { get; set; }
[Required]
public int ProductId { get; set; }
}
so the problem was this line:
public Product Product { get; set; }
which apparently created a unique key for ProductId field in the data base table. when i tried to add multiple ProductImages with the same ProductId, i got an sql exception that said i cannot insert duplicate value for ProductId.
removing this line solved the problem.
来源:https://stackoverflow.com/questions/58122947/why-does-entity-framework-saves-only-the-last-item-of-a-list