Entity Framework 6 has multiplicity 1 or 0..1 - c#

I am receiving the following error when trying to insert an object into a child collection after the model binder has created the model, children and grandchildren and then using context.SaveChanges();
Multiplicity constraint violated. The role 'OrderDetail_OrderDetailPricelistProductOptions_Source' of the relationship 'PPLib.Models.OrderDetail_OrderDetailPricelistProductOptions' has multiplicity 1 or 0..1.
My models are as follows (removed properties for brevity);
public class Order
{
public int OrderId { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; } //FK NAV
public int? PricelistProductId { get; set; } // if a subscriber order ...has the ProductId from a PriceList.
private decimal _Price = 0;
public decimal Price { get { return _Price; } set { _Price = value; } }
private int _Quantity = 1;
public int Quantity { get { return _Quantity; } set { _Quantity = value; } }
public virtual List<OrderDetailPricelistProductOption> OrderDetailPricelistProductOptions { get; set; }
}
public class OrderDetailPricelistProductOption
{
public int OrderDetailPricelistProductOptionId { get; set; }
public int OrderDetailId { get; set; }
public virtual List<OrderDetailPricelistProductOptionsDetail> OrderDetailPricelistProductOptionsDetails { get; set; }
}
public class OrderDetailPricelistProductOptionsDetail
{
public int OrderDetailPricelistProductOptionsDetailId { get; set; }
public int OrderDetailPricelistProductOptionId { get; set; }
public string Name { get; set; }
}
To be clearer:
If I submit a complete new Order, with a list of OrderDetails, its list of OrderDetailPricelistProductOptions and its list of OrderDetailPricelistProductOptionsDetails, the model binder does its job and I receive no error doing:
db.Orders.Add(order);
db.SaveChanges();
If I submit an Edit with and Existing Order and a NEW a list of OrderDetails, its list of OrderDetailPricelistProductOptions and its list of OrderDetailPricelistProductOptionsDetails, I get the Order from the DB context and then merge the OrderDetails from the view model, using:
order.OrderDetails.AddRange(pricelistProductVM.Order.OrderDetails);
and I receive no error doing:
db.Entry(order).State = EntityState.Modified;
db.SaveChanges();
I have a particular situation, where I have to instantiate a new OrderDetail called autoFillOd, and inject its values from one of the existing OrderDetails assembled by the Model Binder. I change its Quantity value and then add it to the collection of OrderDetails in the ViewModel, like so:
pricelistProductVM.Order.OrderDetails.Add(autoFillOd);
When I do db.SaveChanges(), I receive the error.
You'll notice that the error is on the child of the OrderDetails: OrderDetail_OrderDetailPricelistProductOptions_Source
Why can I not add an OrderDetail dynamically into the collection of OrderDetails? All the OrderDetails are new (to be inserted) so the values are the same between the copies, except for the Quantity property which should not be an issue.
The controller action is as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Add(pricelistProductVM pricelistProductVM)
{
OrderLogic ol = new OrderLogic();
//Auth is running on execute
int userId = WebSecurity.CurrentUserId;
int websiteId = (int)Session["websiteId"];
int? id = null; // mediaId
int productId = pricelistProductVM.Product.ProductId;
int? eventId = pricelistProductVM.eventId;
string err = "";
if (productId > 0)
{
//Set Pricelist
Pricelist pricelist = ol.setPricelist(websiteId, id, eventId);
if (pricelist.PricelistId != 0)
{
//get the pricelistproduct from the pricelist
PricelistProduct pp = await (from ppx in db.PricelistProducts
where ppx.ProductId == productId
&& ppx.PricelistId == pricelist.PricelistId
&& ppx.isAvailable == true
&& ppx.DiscontinuedDate == null
&& ppx.Product.isAvailable == true
&& ppx.Product.DiscontinuedDate == null
select ppx).SingleOrDefaultAsync();
if (pp != null)
{
Order order = new Order();
//set some default values for the Order entity
if (pricelistProductVM.Order.OrderId == 0)
{
pricelistProductVM.Order.WebsiteId = websiteId;
pricelistProductVM.Order.UserId = userId;
pricelistProductVM.Order.EventId = eventId;
pricelistProductVM.Order.StartedDate = DateTime.UtcNow;
order = pricelistProductVM.Order;
}
else
{
order = await db.Orders.FindAsync(pricelistProductVM.Order.OrderId);
}
//set some default values for the OrderDetails entity
pricelistProductVM.Order.OrderDetails.First().InjectFrom(pp);
pricelistProductVM.Order.OrderDetails.First().IsPackage = false;
//determine if this product should be automatically added to any packages in the order
OrderDetail autoFillOd = ol.packageCheck(ref pp, ref pricelistProductVM, ref order, websiteId, db);
if (autoFillOd != null)
{
if (autoFillOd.Quantity > 0)
{
//This is where the OrderDetail that causes a problem is added
pricelistProductVM.Order.OrderDetails.Add(autoFillOd);
}
}
if (pricelistProductVM.Order.OrderId == 0)
{
db.Orders.Add(order);
}
else
{
order.OrderDetails.AddRange(pricelistProductVM.Order.OrderDetails);
db.Entry(order).State = EntityState.Modified;
}
db.SaveChanges();
}
else
{
//return error
err = "The product was not found in the available pricelist. Please reload your browser and make sure you are signed-in.";
}
}
}
else
{
//return error
err = "A productId was not passed so no product could not be found. Please reload your browser and make sure you are signed-in.";
}
if (err == "")
{
ViewBag.data = JsonConvert.SerializeObject(new { Success = 1, Msg = "The product was successfully added to your cart." });
}
else
{
ViewBag.data = JsonConvert.SerializeObject(new { Success = 0, Msg = err });
}
return View();
}
I appreciate the help!

I think OrderDetailPricelistProductOption.OrderDetailId can't be single -> it should be a list because it can appear in many OrderDetails...

Related

How to automap the property with same class?

I want to map all properties from product to result.
I use the route to pass the model, then follow the tutorial to map all properties.
But I want to make my code more flexible.
So I try both to direct pass product to result and use AutoMapper.
But either doesn’t work.
I have print changeTracker. When I use like result.Name = product.Name; ETC, it can track the change. But The method I try to use doesn’t work;
Original
public IActionResult Edit(TempProducts product)
{
if (this.ModelState.IsValid)
{
var result = (from s in _db.Product where s.ID == product.ID select s).FirstOrDefault();
result.Name = product.Name;
result.Description = product.Description;
result.PublishDate = product.PublishDate;
result.CategoryId = product.CategoryId;
result.Price = product.Price;
result.DefaultImageId = product.DefaultImageId;
result.Quantity = product.Quantity;
result.Status = product.Status;
_db.ChangeTracker.DetectChanges();
Console.WriteLine(_db.ChangeTracker.DebugView.LongView);
_db.SaveChanges();
return RedirectToAction(nameof(Pass2.index));
}
else
{
return View(product);
}
}
Direct pass
public IActionResult Edit(TempProducts product)
{
if (this.ModelState.IsValid)
{
var result = (from s in _db.Product where s.ID == product.ID select s).FirstOrDefault();
result = product;
_db.ChangeTracker.DetectChanges();
Console.WriteLine(_db.ChangeTracker.DebugView.LongView);
_db.SaveChanges();
return RedirectToAction(nameof(Pass2.index));
}
else
{
return View(product);
}
}
Use AutoMapper
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(TempProducts product)
{
if (this.ModelState.IsValid)
{
var config = new MapperConfiguration(c =>
{
c.CreateMap<TempProducts, TempProducts>();
});
IMapper mapper = config.CreateMapper();
#nullable disable
var result = (from s in _db.Product where s.ID == product.ID select s).FirstOrDefault();
result = mapper.Map<TempProducts,TempProducts>(product);
_db.ChangeTracker.DetectChanges();
Console.WriteLine(_db.ChangeTracker.DebugView.LongView);
_db.SaveChanges();
return RedirectToAction(nameof(Pass2.index));
}
else
{
return View(product);
}
}
Edit: TempProducts
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace forLearn.Models.RouteTest
{
public partial class TempProducts
{
public uint ID { get; set; }
public string Name { get; set; }=null!;
public string Description { get; set; }=null!;
[Range(0, 999)]
public int CategoryId { get; set; }
[Range(0, 999.99)]
public int Price { get; set; }
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; }
public bool Status { get; set; }
public int DefaultImageId { get; set; }
[Range(0, 999)]
public int Quantity { get; set; }
}
}
I have found the answer.
Just add this below the result.
_db.Entry(result).CurrentValues.SetValues(product);

Bind List instead list c# linq From multiple source

i have my dto
public class DocumentForListDto
{
public int Id { get; set; }
public string Title { get; set; }
public string SubmittedBy { get; set; }
public DateTime SubmittedAt { get; set; }
public List<AuditsUpdateForListDto> UpdatedDocuments { get; set; }
}
public class AuditsForListDto
{
public string FullName { get; set; }
public DateTime UpdatedAt { get; set; }
}
and this code in my controller :
var docs = await _repo.Doc.Get();
and i have this audit to save any action in database
var aud = await _repo.Audit.FindByPrimaryKey(Constants.Doc, documents.Select(x => x.Id).ToList());
and this mapper for map my doc to dto(content)
var contents = _mapper.Map<IEnumerable<DocumentForListDto>>(docs);
and this my foreach to bind from audit to contents (CreatedBy/CreatedAt && UpdateBy/UpdatedAt)
if (contents.Any() && contents.Count() > 0 && audits.Any() && audits.Count()
> 0)
{
foreach (var content in contents)
{
//Search here by create Action
foreach (var audit in audits.Where(x =>
Convert.ToInt32(Regex.Match(x.PrimaryKey, #"\d+").Value) ==
content.Id && x.Type.Equals(Constants.Create)))
{
content.SubmittedBy = string.Concat(audit.User.FirstName, " ",
audit.User.LastName);
content.SubmittedAt = audit.DateTime;
}
}
//Here I need to bind list of Updated By and Updated At But I try many times but I don't find the right solution
}
i need to bind list of Updated By and Updated At i try with many logics but without success ??

"Violation of PRIMARY KEY constraint '...'. Cannot insert duplicate key in object

I just started to make my first project with Codefirst-Approach with C#, Linq and MSSQLSERVER and run into an problem when trying to insert a new DB-entry that contains an reference to an already existing element from another table.
InnerException {"Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key in object 'dbo.Manufacturers'. The duplicate key value is (1d262e43-b9b6-4752-9c79-95d955d460ab).\r\nThe statement has been terminated."} System.Exception {System.Data.SqlClient.SqlException}
I broke the problem down to a simple project that I will upload to a share.
My data structure contains a class.Product that links to a Manufacturer object and a List of possible Suppliers.
public class Product
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
public Manufacturer Manuf { get; set; }
public List<Supplier> PossibleSupplier { get { return _possibleSupplier; } set { _possibleSupplier = value; } }
private List<Supplier> _possibleSupplier = new List<Supplier>();
}
public class Supplier
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
}
I now generate 2 products.
Both products are produced by the same manufacturer.
The List of PossibleSuppliers does also contain same suppliers
private void GenerateProducts()
{
Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
Supplier supplier1 = new Supplier() { Name = "first Supplier" };
Supplier supplier2 = new Supplier() { Name = "second Supplier" };
Product firstProduct = new Product() { Name = "Product 1", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
Product secondProduct = new Product() { Name = "Product 2", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1 } };
productList_ = new List<Product>() { firstProduct, secondProduct };
}
The following method is used for storing/updating the DB entries
public static class DbHandler
{
public static bool StoreProduct(Product product)
{
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
dbObject.Products.AddOrUpdate(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
return true;
}
}
public class ProductDbContext : DbContext
{
public ProductDbContext()
{
Database.SetInitializer<ProductDbContext>(new DropCreateDatabaseAlways<ProductDbContext>());
this.Database.Connection.ConnectionString = sqlConnection.ConnectionString;
}
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Manufacturer> Manufacturers { get; set; }
private static SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder()
{
DataSource = "localhost\\MSSQLSERVER2019", // update me
UserID = "", // update me
Password = "", // update me
InitialCatalog = "ProductDb",
IntegratedSecurity = true
};
}
The insertion of the first product can be done without problems.
Also inserting additional products that will have unique manufacturers and suppliers will work without problem.
**So I do not have the problem of uniqueness of my primary keys. **
I only receive this error, when I like to add a new entry that has a foreign key to an already existing entry.
Using dbObject.Products.AddOrUpdate(product); instead of dbObject.Products.Add(product); have not solved my problem.
I am also not able to remove the manufacturer entry before adding the second product, because this will violate the foreign key of my first product…
I found a possible solution for manufacturer by adding an additional property for ManufacturerId
public Guid? ManuId { get; set; }
[ForeignKey("ManuId")]
public Manufacturer Manuf { get; set; }
to my data object, but I would not have an idea how to do this with my List PossibleSupplier??
Can someone please push me into the right direction?
!!Many thanks for the fast replays!!
I have updated my DataStructure as following:
public class Product
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Supplier> PossibleSupplier { get; set; }
}
public class Supplier
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
[ForeignKey("Product")]
public Guid ProductId { get; set; }
public virtual Product Product { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
[ForeignKey("Product")]
public Guid ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
But I still get the "Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key..." error while trying to insert the second entry.
I have attached how the DB looks in SQL-Server
Okay so I believe I know what your issue is. It lies somewhat with this portion here:
private void GenerateProducts()
{
Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
Supplier supplier1 = new Supplier() { Name = "first Supplier" };
Supplier supplier2 = new Supplier() { Name = "second Supplier" };
Product firstProduct = new Product() { Name = "Product 1", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
Product secondProduct = new Product() { Name = "Product 2", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1 } };
productList_ = new List<Product>() { firstProduct, secondProduct };
}
When you assign Manuf = manufacturer1 in both portions below it will work for the first insert because the manufacturer does not exist yet. Now the reason why on the second insert it does not work is because of your code below:
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
dbObject.Products.AddOrUpdate(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
Right now when you go to insert the second product, it will throw the duplicate key exception because you are not referencing the existing entity within your context. You should change it to something like the following:
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
//Need to check if the manufacturer already exists in the db, if it does
//make sure your project references the EXISTING entity within your context
var check = dbObjec.Manufacturer.Where(x => x.Id == product.Manufacturer.Id).FirstOrDefault();
if (check != null)
product.Manufacturer = check;
dbObject.Products.Add(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
If you don't reference the existing manufacturer within the context and then assign it, EF will assume you are trying to add a new one not reference the existing one.
as mentioned in the commands I'd like to share my updated and working project for further use....
MainWindow that generates the test data and executes read/write to DB
public partial class MainWindow : Window
{
private List<Product> productList_;
public MainWindow()
{
GenerateProducts();
InitializeComponent();
}
private void InsertFirst_Click(object sender, RoutedEventArgs e)
{
DbHandler.StoreProduct(productList_[0]);
}
private void InsertSecond_Click(object sender, RoutedEventArgs e)
{
DbHandler.StoreProduct(productList_[1]);
}
private void Read_Click(object sender, RoutedEventArgs e)
{
var productList = DbHandler.GetAllProducts();
}
private void GenerateProducts()
{
Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
Supplier supplier1 = new Supplier() { Name = "first Supplier" };
Supplier supplier2 = new Supplier() { Name = "second Supplier" };
Supplier supplier3 = new Supplier() { Name = "third Supplier" };
Product firstProduct = new Product() { Name = "Product 1", Manufacturer = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
Product secondProduct = new Product() { Name = "Product 2", Manufacturer = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier2, supplier3 } };
productList_ = new List<Product>() { firstProduct, secondProduct };
}
}
DataStructure: Because of the many to many relation ship between Product and Supplier I have to add
[ForeignKey("Product")]
public ICollection<Guid> ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
to the Supplier class. I also decided to add a collection of Products to my manufacturer to make some query-calls more comfortable
public class Product
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Supplier> PossibleSupplier { get; set; }
}
public class Supplier
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
[ForeignKey("Product")]
public ICollection<Guid> ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get { return _id; } set { _id = value; } }
private Guid _id = Guid.NewGuid();
public string Name { get; set; }
//only nice for reverse object from Man --> Product
[ForeignKey("Product")]
public ICollection<Guid> ProductId { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
Before adding a new product to the DB it is important to load possible Manufacturer/Suppliers from the DB and assign them to the current product.
Adding new products is now working fine but as you can see the loading and assigning of the possible suppliers is not really handsome. Therefore I will try to make some modifications on this process in the upcoming days.... I will come back if I have found a "solution".
public static class DbHandler
{
public static List<Product> GetAllProducts()
{
using (ProductDbContext dbObject = new ProductDbContext())
{
//loading with childs and their reverse objects to products
var productList = dbObject.Products.Include("Manufacturer").Include("Manufacturer.Product").Include("PossibleSupplier").Include("PossibleSupplier.Product").Where(i => i.Id != null).ToList();
//loding with childs but without reverse objects
//var productList = dbObject.Products.Include("Manufacturer").Include("PossibleSupplier").Where(i => i.Id != null).ToList();
return productList;
}
}
public static bool StoreProduct(Product product)
{
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
//this does not solve the loading problem, even when property _id is changed to "private Guid _id = new Guid();
//dbObject.Entry(product).State = product.Id == new Guid() ? EntityState.Added : EntityState.Modified;
//dbObject.Entry(product.Manufacturer).State = product.Manufacturer.Id == new Guid() ? EntityState.Added : EntityState.Modified;
//foreach (var supplier in product.PossibleSupplier)
//{
// dbObject.Entry(supplier).State = supplier.Id == new Guid() ? EntityState.Added : EntityState.Modified;
//}
//Therefore loading must be done manually
Guid manufacturerId = product.Manufacturer.Id;
//Need to check if the manufacturer already exists in the db, if it does
//make sure your project references the EXISTING entity within your context
var checkManuf = dbObject.Manufacturers.Where(x => x.Id == manufacturerId).FirstOrDefault();
if (checkManuf != null)
product.Manufacturer = checkManuf;
List<Supplier> dbSuppliers = new List<Supplier>();
foreach (var posSupplier in product.PossibleSupplier)
{
var checkSupplier = dbObject.Suppliers.FirstOrDefault(x => x.Id == posSupplier.Id);
if (checkSupplier != null)
{
dbSuppliers.Add(checkSupplier);
}
}
foreach (var dbSup in dbSuppliers)
{
product.PossibleSupplier.Remove(product.PossibleSupplier.Single(i => i.Id == dbSup.Id));
product.PossibleSupplier.Add(dbSup);
}
dbObject.Products.Add(product);
dbObject.SaveChanges();
}
catch (Exception ex)
{
//
return false;
}
}
return true;
}
}
public class ProductDbContext : DbContext
{
public ProductDbContext()
{
Database.SetInitializer<ProductDbContext>(new DropCreateDatabaseIfModelChanges<ProductDbContext>());
this.Database.Connection.ConnectionString = sqlConnection.ConnectionString;
}
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Manufacturer> Manufacturers { get; set; }
private static SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder()
{
DataSource = "localhost\\MSSQLSERVER2019", // update me
UserID = "", // update me
Password = "", // update me
InitialCatalog = "ProductDb",
IntegratedSecurity = true
};
}
One way seams by using EntityStates but they do not work as expected --> I received duplicated entries in DB.
I have uploaded the current state of the project to the share - filename SqlTestporject_20200414_2027.zip
Br,
-------------------------- Update 2020-04-15 --------------------------
I ended up by writing methods that are handling the decision between update/insert for every single child on its own because I do not have found a way how to update possible offline-changes of already existing dbEntries by simultaneously adding not existing dbEntries.
The main problem was that I received duplicated entries in the DB during adding the second product. The strange thing was, that this duplicates event violates the PK-uniqueness without an error/exception....
So I do have to call down the AddOrUpdate() methods until I reach the last child for my complete data structure.
public static Product AddOrUpdateProduct(Product product)
{
using (ProductDbContext dbObject = new ProductDbContext())
{
try
{
product.Manufacturer = AddOrUpdateManufacturer(dbObject, product.Manufacturer);
List<Supplier> dbSupplierList = new List<Supplier>();
foreach (var supplier in product.PossibleSupplier)
{
dbSupplierList.Add(AddOrUpdateSupplier(dbObject, supplier));
}
product.PossibleSupplier.Clear();
product.PossibleSupplier = dbSupplierList;
if (product.Id == new Guid())
{
//add new product
dbObject.Products.Add(product);
dbObject.SaveChanges();
return product;
}
else
{
//update existing product
var dbProduct = dbObject.Products.Single(x => x.Id == product.Id);
dbProduct.Name = product.Name;
dbObject.SaveChanges();
return dbProduct;
}
}
catch (Exception)
{
throw;
}
}
}
private static Supplier AddOrUpdateSupplier(ProductDbContext dbObject, Supplier supplier)
{
supplier.Address = AddOrUpdateAdress(dbObject, supplier.Address);
if (supplier.Id == new Guid())
{
//add new product
dbObject.Suppliers.Add(supplier);
dbObject.SaveChanges();
return supplier;
}
else
{
//update existing product
var dbSupplier = dbObject.Suppliers.Single(x => x.Id == supplier.Id);
dbSupplier.Name = supplier.Name;
dbObject.SaveChanges();
return dbSupplier;
}
}
private static Manufacturer AddOrUpdateManufacturer(ProductDbContext dbObject, Manufacturer manufacturer)
{
manufacturer.Address = AddOrUpdateAdress(dbObject, manufacturer.Address);
if (manufacturer.Id == new Guid())
{
//add new product
dbObject.Manufacturers.Add(manufacturer);
dbObject.SaveChanges();
return manufacturer;
}
else
{
//update existing product
var dbManufacturer = dbObject.Manufacturers.Single(x => x.Id == manufacturer.Id);
dbManufacturer.Name = manufacturer.Name;
dbObject.SaveChanges();
return dbManufacturer;
}
}
private static Address AddOrUpdateAdress(ProductDbContext dbObject, Address address)
{
if (address.Id == new Guid())
{
//add new product
dbObject.Addresses.Add(address);
dbObject.SaveChanges();
return address;
}
else
{
//update existing product
var dbAddress = dbObject.Addresses.Single(x => x.Id == address.Id);
dbAddress.Street = address.Street;
dbAddress.HouseNumber = address.HouseNumber;
dbAddress.PLZ = address.PLZ;
dbAddress.City = address.City;
dbObject.SaveChanges();
return dbAddress;
}
}
This version can be found here - file SqlTestporject_20200415_1033.zip.
Additionally I'd like to share the following link. Maybe chapter Example 4.18: Creating a Generic Method That Can Apply State Through Any Graph can help others to implement a more comfortable solution.

How should my Get method look like - MVC 4?

Here is my API controller's Get method
[HttpGet]
public MyTable GetMyTable(byte id, string langId)
{
MyTable mytable;
if (id == 0)
{
mytable = db.MyTables.Find(langId);
}
else
{
mytable = db.MyTables.Find(id, langId);
}
if (mytable == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return mytable;
}
It has two keys, so a composite key. The DbSet.Find() method requires that both MyTable.ID and MyTable.LanguageId be specified.
My model for this table looks like:
[Table("MyTable", Schema = "MyScheme")]
public class MyTable
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Required]
[Column(Order = 0)]
public byte ID { get; set; }
[Required]
public string MyCode { get; set; }
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Required]
[Column(Order=1)]
public string LanguageId { get; set; }
[Required]
public byte LevelId { get; set; }
public string ConceptName { get; set; }
}
I want to be able to list all entries by given language id, but without specific id given.
I also want to be able to get single entry with id and langId.
How can I do that?
[HttpGet]
public JsonResult GetMyTable(string langId)
{
var mytable = db.MyTables.Find(langId);
return Json(mytable);
}
[HttpGet]
public JsonResult GetMyTable(byte id, string langId)
{
MyTable mytable = db.MyTables.Find(id, langId);
if (mytable == null)
{
return Json(new {Message = "NullreferenceExeption"})
}
return Json(mytable);
}
You have 2 queries which return different result sets. One returns a collection the other one returns a single result. I'm not going to get into describing how a proper RESTful response should look in api controller. That's a different topic. But if we look at what you have so far it should be like this:
[HttpGet]
public MyTable GetMyTable(byte id, string langId)
{
List<MyTable> results = new List<MyTable>();
if (id == 0)
{
results = db.MyTables.Where(x => x.LanguageId == langid).ToList();
}
else
{
var find = db.MyTables.Find(id, langId);
if (find != null) results.Add(find);
}
return results;
}
This way the results always come back as a list and if find is used for unique entry find then you get back a list with a single element.
My working code:
// GET api/MyTable?langid=mk-MK
[HttpGet]
public IQueryable<MyTable> GetMyTable(string langid)
{
IQueryable<MyTable> mytable = db.MyTables.Where(x => x.LanguageId == langid);
if (mytable == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return mytable;
}
// GET api/MyTable?id=5&langid=mk-MK
[HttpGet]
public MyTable GetMyTable(byte id, string langid)
{
MyTable mytable = db.MyTables.Find(id, langid);
if (mytable == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return mytable;
}

Store update, insert, or delete statement affected an unexpected number of rows (0)

I try to update my Products table but i can't because throw an error.
This is my hardcode controller:
[HttpPost]
public ActionResult EditProduct(ProductsViewModel productViewModel)
{
TechStoreEntities context = new TechStoreEntities();
Product newProduct = new Product
{
ProductId = productViewModel.ProductId,
Name = productViewModel.Name,
Price = productViewModel.Price,
Discount = productViewModel.Discount,
Quantity = productViewModel.Quantity,
Size = productViewModel.Size,
Description = productViewModel.Description,
ProducerName = productViewModel.ProducerName,
PaymentMethods = productViewModel.PaymentMethods,
CategoryID = productViewModel.CategoryID,
SubcategoryID = productViewModel.SubcategoryID,
IsNew = productViewModel.IsNew,
IsEnable = productViewModel.IsEnable
};
context.Entry(newProduct).State = EntityState.Modified;
context.SaveChanges();
ViewBag.CategoryID = new SelectList(context.Categories.Where(c => c.SubCategoryID == null), "CategoryID", "Name");
ViewBag.SubcategoryID = new SelectList(context.Categories.Where(c => c.SubCategoryID != null), "CategoryID", "Name");
return RedirectToAction("Products");
}
This is a model:
public class ProductsViewModel
{
public int ProductId { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public int Discount { get; set; }
public int Quantity { get; set; }
public string Size { get; set; }
public string Description { get; set; }
public string ProducerName { get; set; }
public string PaymentMethods { get; set; }
public bool IsNew { get; set; }
public bool IsEnable { get; set; }
public string Category { get; set; }
public int CategoryID { get; set; }
public string Subcategory { get; set; }
public int SubcategoryID { get; set; }
public DateTime? CreateDate { get; set; }
}
I use strongly typed view:
#model MyTechStore.Models.ProductsViewModel
I add in a view:
#Html.HiddenFor(model => model.ProductId)
When i start app and enter some data to update existing data and press save, throw me exception:
System.Data.Entity.Infrastructure.DbUpdateConcurrencyException
When i debugging i saw that only the ProductId was 0. Everything else is OK. I tested with scaffolding controller but there is OK. I want to use view model, not as scaffolding controller use the model from my database.
Can someone tell me where i'm wrong?
My GET method:
public ActionResult EditProduct(int? id)
{
TechStoreEntities context = new TechStoreEntities();
ProductsManipulate product = new ProductsManipulate();
ProductsViewModel editProduct = product.EditProduct(id);
ViewBag.CategoryID = new SelectList(context.Categories.Where(c => c.SubCategoryID == null), "CategoryID", "Name");
ViewBag.SubcategoryID = new SelectList(context.Categories.Where(c => c.SubCategoryID != null), "CategoryID", "Name");
return View(editProduct);
}
And my data access layer:
public ProductsViewModel EditProduct(int? id)
{
TechStoreEntities context = new TechStoreEntities();
Product dbProduct = context.Products.Find(id);
ProductsViewModel product new ProductsViewModel
{
Name = dbProduct.Name,
Price = dbProduct.Price,
Quantity = dbProduct.Quantity,
CategoryID = dbProduct.CategoryID,
SubcategoryID = dbProduct.SubcategoryID,
IsNew = dbProduct.IsNew
};
return product;
}
You need to populate ProductId in ProductsViewModel
public ProductsViewModel EditProduct(int? id)
{
TechStoreEntities context = new TechStoreEntities();
Product dbProduct = context.Products.Find(id);
ProductsViewModel product = new ProductsViewModel()
{
// You need this line to pass the value to View
ProductId = dbProduct.ProductId,
Name = dbProduct.Name,
Price = dbProduct.Price,
Quantity = dbProduct.Quantity,
CategoryID = dbProduct.CategoryID,
SubcategoryID = dbProduct.SubcategoryID,
IsNew = dbProduct.IsNew
};
return product;
}
What that error is saying, is that EF tried to update a Product with those fields, but the it returned 0 RowCount therefore it knows something went wrong.
As you have mentioned before, the ProductId is 0, meaning you probably don't have a Product with that ID, and therefore when EF tries to update it, the row count is 0, which causes EF to throw a DbUpdateConcurrencyException.
You need to make sure your Id is populated if you want to an update an existing product.
Otherwise if you want an upsert (Update or Insert) you first need to check if a record exists for your given ProductId and if it does, do update, otherwise do insert.

Categories

Resources