2017-01-25 142 views
3

我有一個GenericService添加方法如下所示: -實體框架添加多個相關的實體與單個插入

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

和GenericRepository添加方法如下所示: -

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"); 
    } 

我正在通過集成測試的方式創建此文件作爲fo llows: -

 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,一個用於BrandId的FK和一個用於CategoryId的FK。 Brands實體已經爲Image實體提供了FK ImageId。

現在,當我嘗試插入新產品時,會插入2張圖片,一張來自品牌,另一張來自產品本身的圖片。所以在圖像表中,當我只需要一個新的產品圖像條目時,我會得到2個條目。此外,當我想要將現有圖像用於新產品時,這會造成問題。

所以我想創建一個新的服務/存儲庫的產品從通用服務/存儲庫繼承,並添加更多的邏輯。但是,有沒有更好的方法來做到這一點?

感謝您的幫助和時間

+0

你在哪裏DbCOntextFIle和實體。藉助EF,您可以執行您想要的插入操作。 –

+0

我的DBContext文件在Startup.cs(EF核心)中聲明,我的實體在Models文件夾中作爲類 – Johann

+0

您可以顯示您爲'Brand'和'Product'分配'Image'的代碼,到你的「添加」方法? – Alisson

回答

1

現在我明白了。

當使用客戶端進行測試時,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

+0

這仍然無法正常工作。在上下文跟蹤之前,它將被視爲單獨的圖像實例,而不管它們是否是相同的C#類實例。 –

+0

我在一個控制檯應用程序中測試過它(一個單一的圖像)....我可以在這裏粘貼我的完整代碼... – Alisson

+0

一個單一的圖像在一個單一的行*在數據庫表* *? –

0

您必須保存圖像第一,然後保存引用它的實體。否則,實體框架將每個實例看作是應單獨保存的新實例。

+0

嗨Chris,我已經嘗試先保存圖像,然後是品牌,然後是我的集成測試中的產品,但是,當我嘗試保存品牌時,獲取圖像的同一個實例,並告訴我它不能在圖像表中插入相同的圖像 – Johann

+0

首次創建圖像時出現以下錯誤,然後出現品牌System.Data.SqlClient.SqlException:無法插入顯式值對於IDENTITY_INSERT設置爲OFF時表'Images'中的標識列。 – Johann

相關問題