实体框架使用单个插入

时间:2017-01-25 14:40:05

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

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

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

和GenericRepository Add方法如下: -

    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,一个用于BrandId的FK,一个用于CategoryId的FK。 Brands实体已经为Image实体提供了FK ImageId。

现在,当我尝试插入新产品时,它会插入2张图片,其中一张是品牌附带的图片,另一张是产品本身的图片。所以在图像表中,当我只想要一个新的产品图像条目时,我得到2个条目。此外,当我想将现有图像用于新产品时,这会导致问题。

所以我正在考虑为从通用服务/存储库继承的产品创建一个新的服务/存储库,并为其添加更多逻辑。但是有更好的方法吗?

感谢您的帮助和时间

2 个答案:

答案 0 :(得分:2)

现在我明白了。

当使用客户端进行测试时,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

答案 1 :(得分:0)

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