How to implement method of add related data in EF core?

c# entity-framework-core linq-to-entities

Question

How to implement a method of adding related data in EF core? What objects need to be submitted to the add method and what should it return? I need of related data add in DB, example:

 {
        "productId": 0,
        "number": "xxx",
        "amount": 5.65,
        "primeCost": 20.33,
        "productTypeId": 0,
        "parameters": [
            {
                "id": 0,
                "name": "Type",
                "value": null
            },
            {
                "id": 3,
                "name": "steel grade",
                "value": "CK45"
            },
            {
                "id": 4,
                "name": "diameter",
                "value": "40"
            }
        ]
    }

These are my model classes:

public class Product //: BaseObject
{
    public int Id { get; set; }

    public string Name { get; set; }
    public string Number { get; set; }
    public double Amount { get; set; }
    public double PrimeCost { get; set; }

    [ForeignKey("ProductTypeId")]
    public int  ProductTypeId {  get; set; }
    public virtual ProductType ProductType { get; set; }

    public ICollection<ProductParameter> ProductParameters { get; set; } = new List<ProductParameter>();
}
public class ProductType //: BaseObject
{   
    public int Id { get; set; }

    public string NameType { get; set; }

    public ICollection<Parameter> Parameters { get; set; } = new List<Parameter>();
    public ICollection<Product> Products { get; set; } = new List<Product>();
}
 public class Parameter //: BaseObject
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("ProductTypeId")]
    public int ProductTypeId { get; set; }
    public ProductType ProductType { get; set; }

    public ICollection<ProductParameter> ProductParameters { get; set; } = new List<ProductParameter>();
}
 public class ProductParameter //: BaseObject
{
    public int Id { get; set; }

    public int ProductId { get; set; }
    public virtual Product Product { get; set; }

    public int ParameterId { get; set; }
    public virtual Parameter Parameter { get; set; }


    public string Value { get; set; }

}

These are my DTO classes:

public class ProductDTO
{   

    public int ProductId { get; set; }
    public string Number { get; set; }
    public double Amount { get; set; }
    public double PrimeCostEUR { get; set; }

    public int ProductTypeId { get; set; }
    public string NameType { get; set; }

    public ICollection<ParameterDTO> Parameters { get; set; } = new List<ParameterDTO>();
}
public class ParameterDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }

}

My implementation of method of add related data in DB:

public async Task<IEnumerable<ProductDTO>> AddProducts(ProductDTO ProductDTO, 
        List<ParameterDTO> ParameterDTO)
    {
        var EntryProduct = await _context.Products.FindAsync(ProductDTO.ProductId);

        if (EntryProduct == null)
        {

            _context.Products.Add(new Product
            {
                Id = ProductDTO.ProductId,
                Number = ProductDTO.Number,
                Amount = ProductDTO.Amount,
                PrimeCostEUR = ProductDTO.PrimeCostEUR,
            });
            _context.SaveChanges();

            foreach (var i in ParameterDTO)
            {
                var EntryParameter = await _context.Parameters.FindAsync(i.Id);
                if (EntryParameter != null)
                {
                    _context.ProductParameters.Add(
                      new ProductParameter
                      {
                          ProductId = ProductDTO.ProductId,
                          ParameterId = i.Id,
                          Value = i.Value
                      });
                    _context.SaveChanges();
                }
            }

        }

        return ProductDTO;
    }

I am getting the following exception a compiler error:

Severity Code Description Project File Line Suppression State Error CS0266 Cannot implicitly convert type 'GdmStore.DTO.ProductDTO' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)

1
0
4/1/2019 8:21:10 AM

Accepted Answer

You method is expecting to return an IEnumerable but you are returning just the single Product DTO that was passed in.

The signature should be:

public async Task<ProductDTO> AddProducts(ProductDTO ProductDTO, List<ParameterDTO> ParameterDTO)

Given ProductDTO has a collection of ParameterDTO, is the second argument still needed? (Looks like it would send the parameters twice)

With your entity definitions I see a few problems:

[ForeignKey("ProductTypeId")]
public int  ProductTypeId {  get; set; }
public virtual ProductType ProductType { get; set; }

should be

[ForeignKey("ProductType")] // FK to the reference property.
public int  ProductTypeId {  get; set; }
public virtual ProductType ProductType { get; set; }

All navigation properties such as the collections, and producttype should be declared as virtual otherwise you'll get inconsistent behaviour. Ones declared virtual will have access to lazy-loading if needed, the others will be left as #null.

Both Product and Parameter should not have references to ProductType, from what I can see it probably should just be on Product to avoid denormalization issues. (Product with Parameters with different ProductTypes set.)

When dealing with navigation properties I recommend removing the FK property from the entity and using mapping (EF6) / shadow properties. (EF Core)

For example:

 public class ProductParameter 
{
    public int Id { get; set; }

    public virtual Product Product { get; set; } // No ProductId/ParameterId
    public virtual Parameter Parameter { get; set; }

    public string Value { get; set; }
}

modelBuilder.Entity<Product>()
    .HasMany(x => x.ProductParameters)
    .WithOne(x => x.Product)
    .HasForeignKey("ProductId"); // Sets up a shadow property.

The issue with mapping FKs is that then there are 2 sources of truth for the reference. ProductParameter.ProductId vs. ProductParameter.Product.Id. Normally these will point to the same value, but code may be dependent on one path vs. the other and lead to consistency bugs if one is changed without the other.

Use async operations sparingly. If you're pulling back a single record by ID, or any other relatively fast operation, don't use async as there is a performance cost with registering the continuation. (Faster to do just a synchronous call) Async is geared towards operations that are expected to take a while. (I.e. more than a second)

Lastly, the code may work, but it's not leveraging EF very well, setting all of these entities individually, and you generally do not want to call SaveChanges multiple times to ensure that the data is committed all together or none at all if there is an issue.

   var EntryProduct = _context.Products.Find(ProductDTO.ProductId);

    if (EntryProduct != null)
        return ProductDTO;

    var product = new Product
    {
        Id = ProductDTO.ProductId,
        Number = ProductDTO.Number,
        Amount = ProductDTO.Amount,
        PrimeCostEUR = ProductDTO.PrimeCostEUR,
    };

    var parameterIds = ParameterDTO.Select(x => x.Id).ToList();
    var parametersToAdd = context.Parameters
        .Where(x => parameterIds.Contains(x.ParameterId))
        .Select(x => new ProductParameter
        {
            Product = product,
            Parameter = x
        }).ToList();

    product.ProductParameters.AddRange(parametersToAdd);
    await _context.SaveChangesAsync();

    return ProductDTO;

I don't recommend using a module level variable for the DbContext (_context) as the context should be short-lived to help avoid potential issues where one workflow intends to save, while other code may not. If it's injected by an IoC container and scoped to a lifetime matching the request then that shouldn't cause any issues. Just be cautious of contexts being left open longer than needed.

1
4/1/2019 5:21:59 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow