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);
Related
I have a Web API for OData services. I have a lot of table with many relations. Here is some of the table:
MSADDRESSCOUNTRY
public partial class MSADDRESSCOUNTRY
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSCOUNTRY()
{
this.MSADDRESSPROVINCEs = new HashSet<MSADDRESSPROVINCE>();
}
public int ID { get; set; }
public string CODE { get; set; }
public string COUNTRYNAME { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSPROVINCE> MSADDRESSPROVINCEs { get; set; }
}
MSADDRESSPROVINCE
public partial class MSADDRESSPROVINCE
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSPROVINCE()
{
this.MSADDRESSDISTRICTs = new HashSet<MSADDRESSDISTRICT>();
}
public int ID { get; set; }
public Nullable<int> COUNTRYID { get; set; }
public string PROVINCENAME { get; set; }
public virtual MSADDRESSCOUNTRY MSADDRESSCOUNTRY { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSDISTRICT> MSADDRESSDISTRICTs { get; set; }
}
MSADDRESSDISTRICT
public partial class MSADDRESSDISTRICT
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSDISTRICT()
{
this.MSADDRESSSUBDISTRICTs = new HashSet<MSADDRESSSUBDISTRICT>();
}
public int ID { get; set; }
public Nullable<int> PROVINCEID { get; set; }
public string DISTRICTNAME { get; set; }
public virtual MSADDRESSPROVINCE MSADDRESSPROVINCE { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSSUBDISTRICT> MSADDRESSSUBDISTRICTs { get; set; }
}
I create DTO object model for every table with the property is the same with Database object model.
I want the client can use $expand keyword to get child data and/or parent data.
For MSADDRESSCOUNTRY I need to write the code like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressCountryObject> Get()
{
return db.MSADDRESSCOUNTRies.Select(c => new MsAddressCountryObject
{
ID = c.ID,
CODE = c.CODE,
COUNTRYNAME = c.COUNTRYNAME,
MSADDRESSPROVINCEs = c.MSADDRESSPROVINCEs.Select(data => new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = new MsAddressCountryObject()
{
ID = data.MSADDRESSCOUNTRY.ID,
CODE = data.MSADDRESSCOUNTRY.CODE,
COUNTRYNAME = data.MSADDRESSCOUNTRY.COUNTRYNAME,
},
MSADDRESSDISTRICTs = data.MSADDRESSDISTRICTs.Select(dist => new MsAddressDistrictObject()
{
ID = dist.ID,
PROVINCEID = dist.PROVINCEID,
DISTRICTNAME = dist.DISTRICTNAME,
})
})
});
}
For MSADDRESSPROVINCE I need to write the code like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressProvinceObject> Get()
{
return db.MSADDRESSPROVINCEs.Select(data => new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = new MsAddressCountryObject()
{
ID = data.MSADDRESSCOUNTRY.ID,
CODE = data.MSADDRESSCOUNTRY.CODE,
COUNTRYNAME = data.MSADDRESSCOUNTRY.COUNTRYNAME,
},
MSADDRESSDISTRICTs = data.MSADDRESSDISTRICTs.Select(dist => new MsAddressDistrictObject()
{
ID = dist.ID,
PROVINCEID = dist.PROVINCEID,
DISTRICTNAME = dist.DISTRICTNAME
})
});
}
That code works fast. But if I add/change/remove column, I have to modify the controller manually, one by one for all controller. For example, if I want to add geological coordinate in MSADDRESSDISTRICT, I have to change the code in Country Controller, Province Controller and District Controller.
So I decide to create extension method like this.
public static MsAddressCountryObject ToDTO(this MSADDRESSCOUNTRY data)
{
return new MsAddressCountryObject()
{
ID = data.ID,
CODE = data.CODE,
COUNTRYNAME = data.COUNTRYNAME,
};
}
public static IQueryable<MsAddressCountryObject ToDTO(this IEnumerable<MSADDRESSCOUNTRY datas)
{
return datas.Select(country =
{
var obj = country?.ToDTO();
obj.MSADDRESSPROVINCEs = country.MSADDRESSPROVINCEs?.ToDTO();
return obj;
}).AsQueryable();
}
public static MsAddressProvinceObject ToDTO(this MSADDRESSPROVINCE data)
{
return new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = data.MSADDRESSCOUNTRY?.ToDTO()
};
}
public static IQueryable<MsAddressProvinceObject ToDTO(this IEnumerable<MSADDRESSPROVINCE datas)
{
return datas.Select(province =
{
var obj = province?.ToDTO();
obj.MSADDRESSDISTRICTs = province.MSADDRESSDISTRICTs.ToDTO();
return obj;
}).AsQueryable();
}
public static MsAddressDistrictObject ToDTO(this MSADDRESSDISTRICT data)
{
return new MsAddressDistrictObject()
{
ID = data.ID,
PROVINCEID = data.PROVINCEID,
DISTRICTNAME = data.DISTRICTNAME,
MSADDRESSPROVINCE = data.MSADDRESSPROVINCE?.ToDTO()
};
}
public static IQueryable<MsAddressDistrictObject ToDTO(this IEnumerable<MSADDRESSDISTRICT datas)
{
return datas.Select(district =
{
var obj = district?.ToDTO();
obj.MSADDRESSSUBDISTRICTs = district.MSADDRESSSUBDISTRICTs?.ToDTO();
return obj;
}).AsQueryable();
}
And the controller just like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressCountryObject Get()
{
return db.MSADDRESSCOUNTRies.ToDTO()
}
And that makes the performance really bad. I think the extension is making a lot of memory allocation or some thing that make the result not being delivered directly to the client.
My goal is to create the code easy to maintain, and the performance not drop significantly.
I have many relation in other table. I want the $expand works without write all parent/child Select statement manually and one by one.
I have try to not calling ToDTO() from all the extension method. The result is the performance is fast. But I lost all the relation or I need to write the parent/child Select statement for all method.
Any suggestion will help.
Thanks.
I'm learning Asp.net Core and building a simple web with CRUD operations, SQL server and using Entity Framework.
When I try to build a sorting method I git this error in this line at parameter employees
return View(this.SortEmployees(employees, SortField, CurrentSortField, SortDirection));
and that's the error:
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.List<EmployeesApp.Models.Employee>' to 'System.Collections.Generic.List<EmployeesApp.Controllers.Employee>' EmployeesApp
that's my Model:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EmployeesApp.Models
{
[Table("Employee", Schema ="dbo")]
public class Employee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Display(Name ="Employee ID")]
public int EmployeeId { get; set; }
[Required]
[Column(TypeName ="varchar(5)")]
[MaxLength(5)]
[Display(Name ="Employee Number")]
public string EmployeeNumber { get; set; }
[Required]
[Column(TypeName = "varchar(150)")]
[MaxLength(100)]
[Display(Name = "Employee Name")]
public string EmployeeName { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name ="Date of Birth")]
[DisplayFormat(DataFormatString = "{0:dd-MMM-yyyy}")]
public DateTime DOB { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "Hiring Date")]
[DisplayFormat(DataFormatString = "{0:dd-MMM-yyyy}")]
public DateTime HiringDate { get; set; }
[Required]
[Column(TypeName ="decimal(12,2)")]
[Display(Name ="Gross Salary")]
public decimal GrossSalary { get; set; }
[Required]
[Column(TypeName = "decimal(12,2)")]
[Display(Name = "Net Salary")]
public decimal NetSalary { get; set; }
[ForeignKey("Department")]
[Required]
public int DepartmentId { get; set; }
[Display(Name = "Department")]
[NotMapped]
public string DepartmentName { get; set; }
public virtual Department Department { get; set; }
}
}
and that's my COntroller:
namespace EmployeesApp.Controllers
{
public enum SortDirection
{
Ascending,
Descending
}
public class Employee : Controller
{
HRDatabaseContext dbContext = new HRDatabaseContext();
public IActionResult Index(string SortField, string CurrentSortField, SortDirection SortDirection)
{
var employees = GetEmployees();
return View(this.SortEmployees(employees, SortField, CurrentSortField, SortDirection));
}
private List<Models.Employee> GetEmployees()
{
return (from Employee in dbContext.Employees
join Department in dbContext.Departments on Employee.DepartmentId equals Department.DepartmentId
select new Models.Employee
{
EmployeeId = Employee.EmployeeId,
EmployeeName = Employee.EmployeeName,
DOB = Employee.DOB,
HiringDate = Employee.HiringDate,
GrossSalary = Employee.GrossSalary,
NetSalary = Employee.NetSalary,
DepartmentId = Employee.DepartmentId,
DepartmentName = Department.DepartmentName
}).ToList();
}
public IActionResult Add()
{
ViewBag.Department = this.dbContext.Departments.ToList();
return View();
}
[HttpPost]
public IActionResult Add(Models.Employee model)
{
ModelState.Remove("EmployeeID");
ModelState.Remove("Department");
ModelState.Remove("DepartmentName");
if (ModelState.IsValid)
{
dbContext.Employees.Add(model);
dbContext.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Department = dbContext.Departments.ToList();
return View("Add", model);
}
public IActionResult Edit(int ID)
{
HRDatabaseContext dbContext1 = dbContext;
Models.Employee data = dbContext1.Employees.Where(e => e.EmployeeId == ID).FirstOrDefault();
ViewBag.Department = this.dbContext.Departments.ToList();
return View("Add", data);
}
[HttpPost]
public IActionResult Edit(Models.Employee model)
{
ModelState.Remove("EmployeeID");
ModelState.Remove("Department");
ModelState.Remove("DepartmentName");
if (ModelState.IsValid)
{
dbContext.Employees.Update(model);
dbContext.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Department = dbContext.Departments.ToList();
return View();
}
public IActionResult Delete(int ID)
{
Models.Employee data = this.dbContext.Employees.Where(e => e.EmployeeId == ID).FirstOrDefault();
if (data != null)
{
dbContext.Employees.Remove(data);
dbContext.SaveChanges();
}
return RedirectToAction("Index");
}
private List<Employee> SortEmployees(List<Employee> employees, String sortField, string currentSortField, SortDirection sortDirection)
{
if (string.IsNullOrEmpty(sortField))
{
ViewBag.SortField = "EmployeeNumber";
ViewBag.SortField = SortDirection.Ascending;
}
else
{
if (currentSortField == sortField)
{
ViewBag.SortDirection = sortDirection == SortDirection.Ascending ? SortDirection.Descending : SortDirection.Ascending;
}
else
ViewBag.SortDirection = sortDirection == SortDirection.Ascending;
ViewBag.SortField = sortField;
}
//* create the sorting proccess
var propertyInfo = typeof(Employee).GetProperty(ViewBag.SortField);
if (ViewBag.SortDirection == SortDirection.Ascending)
{
employees = employees.OrderBy(e => propertyInfo.GetValue(e, null)).ToList();
}
else
{
employees = employees.OrderByDescending(e => propertyInfo.GetValue(e, null)).ToList();
}
return employees;
}
}
}
Your SortEmployees method in your controller takes a List<Employee> which in this context is a Controllers.Employee and not a Models.Employee as you suspect.
The best solution in this case is to rename your Controller to EmployeeController to follow the ASP.NET convention. In ASP.NET controllers are always named [Name]Controller.
private List<Employee> SortEmployees(List<Employee> employees, String sortField, string currentSortField, SortDirection sortDirection)
{
The issue relates the above SortEmployees method, its parameter and return data using the EmployeesApp.Controllers.Employee model, istead of the EmployeesApp.Models.Employee. So, when calling this method, it will show this error.
To solve this issue, try to modify the SortEmployees method as below: using the Models.Employee
private List<Models.Employee> SortEmployees(List<Models.Employee> employees, String sortField, string currentSortField, SortDirection sortDirection)
{
It is due to the model class name and controller class name being the same due to which the correct modal is not being selected. In such a case you can explicitly add Models.Employee wherever you are using it or rename the controller name or model name itself.
I have a model like this,
[Table("ClientAccessories")]
public class ClientAccessory
{
public ClientAccessory()
{
LastModifiedDateTime = DateTime.UtcNow;
}
public string AccessoryId { get; set; }
public Guid ClientReference { get; set; }
public DateTime LastModifiedDateTime { get; set; }
public bool IsActive { get; set; }
public virtual Accessory Accessory { get; set; }
}
and I have this code in repository method,
public IEnumerable<ClientAccessory> GetClientAccessories(Guid ClientReference)
{
var _context = new DBContext();
var results = from a in _context.Accessories
join ca in _context.ClientAccessories
on new { AccessoryId = a.Id, ClientReference = new Guid(ClientReference) }
equals new { ca.AccessoryId, ca.ClientReference } into ca_join
from ca in ca_join.DefaultIfEmpty()
where
ca.IsActive == true ||
ca.IsActive == null
select new {};
}
Now problem is that, I am not sure how to return ClientAccessory including Accessory object together even though it's a virtual property.
Also Is it Okay to call 2 entities in one repository or should I return IQueryable and do it in domain service class. thank you.
I don't want to flat the values like this,
select new {
Id = a.Id,
ClientReference = ca.ClientReference
and so on...
};
if you query from ClientAccessory and include the Accessory, you should get what you what. Something like this:
public IEnumerable<ClientAccessory> GetClientAccessories(Guid ClientReference)
{
var _context = new DBContext();
var results = from ca in _context.ClientAccessory.Include("Accessory")
where ca.IsActive == true || ca.IsActive == null
select ca;
return results;
}
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.
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...