代码之家  ›  专栏  ›  技术社区  ›  JMon

实体框架:通过单个插入添加多个相关实体

  •  7
  • JMon  · 技术社区  · 9 年前

    我有一个GenericService添加方法,如下所示:-

        public bool Add(T entity, Expression<Func<T, bool>> filter = null)
        {
            try
            {
                _genericRepository.Add(entity, filter);
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }
    

    和一个通用的位置添加方法,如下所示:-

        public void Add(T entity, Expression<Func<T, bool>> filter = null)
        {
            var existing = Get<T>(filter);
            if (existing.Result != null) return;
            Context.Add(entity);
            Save();
        }
    

    这是我在ProductsController中执行的调用:-

        [HttpPost]
        public IActionResult Create([FromBody] Product product)
        {
            if (product == null)
                return BadRequest();
    
            var result = _productsService.Add(product, m => m.Name == product.Name);
            if (result)
            {
                return CreatedAtRoute("GetProducts", new { id = product.Id }, product);
    
            }
            return BadRequest("Item not added");
        }
    

    我通过以下集成测试创建此项:-

            testBrand = new Brand { Name = "testBrand" };
            testImage = new Image { Name = "testImage", Url = "/Brands/adidas_logo_test.png" };
            testBrand.Image = testImage;
    
            testCategory = new Category {Name = "testCategory"};
    
            testProduct = new Product
            {
                Category = testCategory,
                Name = "testProduct",
                Brand = testBrand,
                BrandId = testBrand.Id,
                CategoryId = testCategory.Id,
                Deal = false,
                Description = "testDescription",
                Discount = "50% Discount",
                Image = testImage,
                ImageId = testImage.Id,
                Price = new decimal(50.00),
                Stock = 5
            };
    
        [Test]
        public async Task Create_CreateAProduct_NewBrandNewCategoryNewImageProductsController()
        {
            //Arrange 
    
            //Act
            //create new image
            var requestImage = "api/Images/";
            var postResponseImage = await _client.PostAsJsonAsync(requestImage, testImage);
            var created = await postResponseImage.Content.ReadAsStringAsync();
            var createdImage = JsonConvert.DeserializeObject<Image>(created);
    
            //Act
            testBrand.Image = createdImage;
            testBrand.ImageId = createdImage.Id;
            testImage.Id = createdImage.Id;
    
            var postResponseProduct = await _client.PostAsJsonAsync(requestProduct, testProduct);
            var createdProduct = await postResponseProduct.Content.ReadAsStringAsync();
            var createdProductObj = JsonConvert.DeserializeObject<Product>(createdProduct);
    
            var getResponse = await _client.GetAsync(requestProduct + "Get/" + createdProductObj.Id);
            var fetched = await getResponse.Content.ReadAsStringAsync();
            var fetchedProduct = JsonConvert.DeserializeObject<Product>(fetched);
    
            // Assert
            Assert.IsTrue(postResponseProduct.IsSuccessStatusCode);
            Assert.IsTrue(getResponse.IsSuccessStatusCode);
    
            Assert.AreEqual(testProduct.Name, createdProductObj.Name);
            Assert.AreEqual(testProduct.Name, fetchedProduct.Name);
    
            Assert.AreNotEqual(Guid.Empty, createdProductObj.Id);
            Assert.AreEqual(createdProductObj.Id, fetchedProduct.Id);
        }
    

    一切正常,直到我尝试插入一个具有多个相关实体的实体。让我举个例子。

    假设我有一个产品,它有一个FK ImageId,一个FK代表BrandId,一一个Fn代表CategoryId。品牌实体已经为图像实体提供了FK ImageId。

    现在,当我尝试插入一个新产品时,它会插入两个图像,一个是品牌附带的,另一个是产品本身的图像。因此,在图像表中,我得到了2个条目,而我只需要为产品图像创建1个新条目。此外,当我想将现有图像用于新产品时,这也会造成问题。

    因此,我考虑为产品创建一个新的服务/存储库,以继承通用服务/存储,并为其添加更多的逻辑。然而,有更好的方法吗?

    谢谢你的帮助和时间

    2 回复  |  直到 9 年前
        1
  •  7
  •   Alisson Reinaldo Silva    9 年前

    现在我明白了。

    当使用客户机进行测试时,mvc会接收带有json数据的请求,并正确创建模型。

    然而,mvc不知道你想要的是同样的 Image 对于产品和品牌,它将为每个产品和品牌创建一个实例,如下所示(我简化了示例):

    var product = new Product();
    var brand = new Brand();
    product.Image = new Image();
    product.Brand = brand;
    brand.Image = new Image(); // new image with same info...
    

    同样,实体框架将假设它们是具有相同数据的两个不同图像。只要让它知道它是一样的,通过在你的操作中执行类似的操作(当然,你会创建更好的代码,这只是一个简单的示例):

    [HttpPost]
    public IActionResult Create([FromBody] Product product)
    {
        if (product == null)
            return BadRequest();
    
        // If the image already exists...nullify image so EF won't try to insert a new one...
        if (product.ImageId > 0)
            product.Image = null;
        // If the image already exists...and the brand doesn't have an existing image, use the same image and nullify the brand's image as well...
        if (product.ImageId > 0 && product.Brand != null && !(product.Brand.ImageId > 0))
        {
            product.Brand.ImageId = product.ImageId;
            product.Brand = null;
        }
        // If product is reveiving a new image...and the brand doesn't have an existing image, use the same new image...
        if (product.Image != null && product.Brand != null && !(product.Brand.ImageId > 0))
            product.Brand.Image = product.Image;
    
        var result = _productsService.Add(product, m => m.Name == product.Name);
        if (result)
        {
            return CreatedAtRoute("GetProducts", new { id = product.Id }, product);
    
        }
        return BadRequest("Item not added");
    }
    

    为了在控制台应用程序中进行测试,我复制了它,如下所示。某些类别:

    public class Brand
    {
        public int Id { get; set; }
        public virtual Image Image { get; set; }
        public int ImageId { get; set; }
    
    }
    
    public class Image
    {
        public int Id { get; set; }
    }
    
    public class Product
    {
        public int Id { get; set; }
        public virtual Image Image { get; set; }
        public virtual Brand Brand { get; set; }
        public int ImageId { get; set; }
        public int BrandId { get; set; }
    }
    

    这个 DbContext 配置:

    public class MyDbContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
    
        public DbSet<Brand> Brands { get; set; }
    
        public DbSet<Image> Images { get; set; }
    
        public MyDbContext()
            : base("name=MyDbContext")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
    
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    
            modelBuilder.Properties<int>().Where(p => p.Name == "Id").Configure(p => p.IsKey());
            modelBuilder.Entity<Product>().HasRequired(p => p.Brand).WithMany().HasForeignKey(p => p.BrandId);
            modelBuilder.Entity<Product>().HasRequired(p => p.Image).WithMany().HasForeignKey(p => p.ImageId);
            modelBuilder.Entity<Brand>().HasRequired(p => p.Image).WithMany().HasForeignKey(p => p.ImageId);
    
        }
    }
    

    最后是代码本身。

    在第一种情况下,我使用相同的实例:

    class Program
    {
        static void Main(string[] args)
        {
    
            using (var db = new MyDbContext())
            {
    
                var image = new Image();
                var product = new Product();
                var brand = new Brand();
                product.Image = image;
                product.Brand = brand;
                brand.Image = image; // same instance
    
                db.Products.Add(product);
    
                db.SaveChanges();
    
            }
    
        }
    }
    

    我的结果是:

    first case

    然后我再次运行,现在使用一个新实例:

    class Program
    {
        static void Main(string[] args)
        {
    
            using (var db = new MyDbContext())
            {
    
                var image = new Image();
                var product = new Product();
                var brand = new Brand();
                product.Image = image;
                product.Brand = brand;
                brand.Image = new Image();
    
                db.Products.Add(product);
    
                db.SaveChanges();
    
            }
    
        }
    }
    

    现在我们有两个新图像:

    second case

        2
  •  0
  •   Chris Pratt    9 年前

    您必须保存图像 第一 ,然后保存引用它的实体。否则,实体框架将每个实例视为应单独保存的新实例。