Hello and thank you in advance for taking time to read this post. I am new to EF Core and I am attempting to setup the following database connection where I have scaffold-ed controllers from the models.
namespace VotingWebApp.Models
{
public class QuestionObject
{
public int QuestionObjectID { get; set; }
public string Question { get; set; }
public string AnswerA { get; set; }
public string AnswerB { get; set; }
public int AnswerAVote { get; set; }
public int AnswerBVote { get; set; }
public int GroupItemID { get; set; }
public ICollection<ResponseItem> ResponseItems { get; set; }
public GroupItem GroupItem { get; set; }
}
}
namespace VotingWebApp.Models
{
public class GroupItem
{
public int GroupItemID { get; set; }
public string GroupName { get; set; }
public int MemberCount { get; set; }
public int Host { get; set; }
public ICollection<GroupUserLink> GroupUserLinks { get; set; }
public ICollection<QuestionObject> QuestionItems { get; set; }
}
}
I receive the following error when I attempt to create a new QuestionObject in the database (Even when I supply an existing GroupItem key).
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_QuestionObject_GroupItem_GroupItemID". The conflict occurred in database "aspnet-VotingWebApp-02945df4-961a-4b8f-8999-19aa61dfd02e", table "dbo.GroupItem", column 'GroupItemID'.
I am not sure how to go about solving this conflict. I have read other posts and they mainly highlight how the person is not supplying an existing key. I am new to EF Core so any help would be appreciated.
// GET: QuestionObjects/Create
public async Task<IActionResult> CreateQuestion(int? id)
{
if (id == null)
{
return NotFound();
}
var groupItem = await _context.GroupItem.SingleOrDefaultAsync(m => m.GroupItemID == id);
if (groupItem == null)
{
return NotFound();
}
QuestionObject questionObject = new QuestionObject();
questionObject.GroupItemID = groupItem.GroupItemID;
return View(questionObject);
}
// POST: QuestionObjects/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateQuestion([Bind("AnswerA,AnswerAVote,AnswerB,AnswerBVote,GroupID,Question")] QuestionObject questionObject)
{
if (ModelState.IsValid)
{
_context.Add(questionObject);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(questionObject);
}
I have added the code for the insertion. This is within the GroupItem controller.
This looks more like a binding problem. First, your white list of included fields does not have GroupItemID so add it (unless GroupId was a typo):
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateQuestion([Bind(Include="AnswerA,AnswerAVote,AnswerB,AnswerBVote,GroupID,GroupItemID,Question")] QuestionObject questionObject)
{
if (ModelState.IsValid)
{
_context.QuestionObjects.Add(questionObject);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(questionObject);
}
Next, you need to either have an input field or hidden field on your view for GroupItemID or it will come back null
#Html.HiddenFor(m => m.GroupItemID)
Try out populating GroupItem object on QuestionObject to help EF understand FK relationship.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateQuestion([Bind("AnswerA,AnswerAVote,AnswerB,AnswerBVote,GroupID,Question")] QuestionObject questionObject)
{
if (ModelState.IsValid)
{
questionObject.GroupItem = _context.GroupItem.Single(m => m.GroupItemID == questionObject.GroupItemID)
_context.Add(questionObject);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(questionObject);
}
Related
I want an item to have several sales linked to it:
Sale.cs
public class Sale
{
public int Id { get; set; }
public int Amount { get; set; }
public decimal Price { get; set; }
}
Item.cs
public class Item
{
[Key]
public string Name { get; set; }
public string Market { get; set; }
public string Market_api { get; set; }
public List<Sale> Sales { get; set; }
}
I save Sales like this:
[HttpPost]
public async Task<IActionResult> Sale(SaleViewModel vm, string name)
{
Item item = _repo.GetItem(name);
item.Sales = item.Sales ?? new List<Sale>();
item.Sales.Add(new Sale
{
Amount = vm.Amount,
Price = vm.Price
});
_repo.UpdateItem(item);
await _repo.SaveChangesAsync();
return RedirectToAction("Index");
}
_repo:
public void UpdateItem(Item item)
{
_ctx.Items.Update(item);
}
public async Task<bool> SaveChangesAsync()
{
if(await _ctx.SaveChangesAsync() > 0)
{
return true;
}
return false;
}
and when I debug this it all looks good (List is in item)
Debug information
Peek into db
but when I try to access it like:
item.Sales it always returns null. I honestly don't know what's going on, I can see that the correct foreign key is saved in the Sales table but as to why I can't access it I have no clue.
Include Sales in your repo get items method
_repo.GetItem(name);
_ctx.Items.Include(i => i.Sales).FirstOrDefault(i => i.Name == name);
I have the following models in my API:
namespace API.Models
{
public class StudentDetailsViewModel
{
[Key]
public int StudentId { get; set; }
public AddressViewModel Address { get; set; }
public List<CoursesViewModel> Courses { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public int StudentId { get; set; }
public string Address { set; set; }
}
public CoursesViewModel
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Grade { get; set; }
}
}
I am writing a PUT method for StudentDetailsViewModel. The list in this model could have a number of records removed or added or a number of fields in one of the records updated. For example, grade for one of the courses updated or a course added or dropped.
What is the best approach in updating a model containing an object list like the above? Is it best to delete the entire list and re-add them?
I have the following thus far:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutStudenDetailsViewModel(StudentDetailsViewModel studentDetailsViewModel)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var address = new DataAccess.Address
{
AddressID = studentDetailsViewModel.Address.AddessId,
StudentID = studentDetailsViewModel.Address.StudentId,
Address = studentDetailsViewModel.Address.Address
};
_context.Entry(address).State = EntityState.Modified;
// TODO: This is where the list Course entity needs to be updated
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if(!AddressViewModelExists(address.AddressID))
return NotFound();
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
Just an example from MS documentation for EF Core
public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = context.Blogs
.Include(b => b.Posts)
.FirstOrDefault(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog); //or 404 response, or custom exception, etc...
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
}
context.SaveChanges();
}
Im new to asp.net and i started with basic crud operations.
I made the css class with db name values (code,name,price,currency) and DBcontext for this, when i made the controller it doesnt not add the values to the database but it's stored in somewhere.
When i use as a normal query from c# code, it's stored into the database.
My question is why does the CRUD operations from the (csfile and context) are not in the database?
--cs class--
public class Productos
{
[Key]
public int Codigo { get; set; }
public string Nombre { get; set; }
public decimal Precio { get; set; }
public string Moneda { get; set; }
}
-product context
public class ProductoContext : DbContext
{
public ProductoContext() : base("database name")
{
}
public DbSet<Productos> Producto { get; set; }
}
---this is the code to create---
// GET: Productos/Create
public ActionResult Create()
{
return View();
}
// POST: Productos/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Codigo,Nombre,Precio,Moneda")] Productos productos)
{
if (ModelState.IsValid)
{
db.Producto.Add(productos);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(productos);
}
---- connection string------
I am trying to add some entries to a database in a controllers HTTP post event. However I am getting the following error:
The entity type 'List' was not found. Ensure that the
entity type has been added to the model.
Code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Name,Weight)] List<WBLoading> wBLoading)
{
if (ModelState.IsValid)
{
_context.Entry(wBLoading).State = EntityState.Modified;
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wBLoading);
}
I have added the table to the DbContext:
public DbSet<Models.WBLoading> WBLoading { get; set; }
Model:
public class WBLoading
{
public int ID { get; set; }
public string Name { get; set; }
public int Weight { get; set; }
}
You need to write the following code.
_context.WBLoading.add(wBLoading);
await _context.SaveChangesAsync();
I'm trying to use ViewModels for the first time using AutoMapper. I have two models:
public class Item
{
public int ItemId { get; set; }
public bool Active { get; set; }
public string ItemCode { get; set; }
public string Name { get; set; }
public List<ItemOption> ItemOptions { get; set; }
//...
}
public class ItemOption
{
public int ItemOptionId { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
//...
}
Which I have turned into two ViewModels:
public class ItemDetailViewModel
{
public int ItemDetailViewModelId { get; set; }
public int ItemId { get; set; }
public bool Active { get; set; }
public string ItemCode { get; set; }
public string Name { get; set; }
public List<ItemDetailItemOptionViewModel> ItemOptions { get; set; }
}
public class ItemDetailItemOptionViewModel
{
public int ItemDetailItemOptionViewModelId { get; set; }
public int ItemOptionId { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
}
I then set the following in my application start-up:
Mapper.CreateMap<Item, ItemDetailViewModel>();
Mapper.CreateMap<ItemOption, ItemDetailItemOptionViewModel>();
Finally I scaffolded my ItemDetailViewModel:
I then built my project and added a new Item through /Item/Create
I had a look in the database expecting to see that I would have an entry in the Item table, but instead I have ItemDetailViewModel and ItemDetailItemOptionViewModel tables, which I wasn't expecting and the data is is ItemDetailViewModel.
I assume I have done something wrong with my scaffolding? How do I scaffold off the ViewModel without making it part of the main business models?
Further Details
If it isn't possible to scaffold the controller with a ViewModel, then how do I reference the ViewModel in the controller and save changes back to the database?
For example what would the following change to once I remove ItemDetailViewModel from the db context?
//
// POST: /Item/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel itemdetailviewmodel)
{
if (ModelState.IsValid)
{
db.ItemDetailViewModels.Add(itemdetailviewmodel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(itemdetailviewmodel);
}
Further Details [2]
So am I correct that my Index/Details should work as so or is there a better way of doing it?
//
// GET: /Item/
public ActionResult Index()
{
var items = db.Items.ToList();
var itemdetailviewmodel = AutoMapper.Mapper.Map<ItemDetailViewModel>(items);
return View(itemdetailviewmodel);
}
//
// GET: /Item/Details/5
public ActionResult Details(int id = 0)
{
ItemDetailViewModel itemdetailviewmodel = AutoMapper.Mapper.Map<ItemDetailViewModel>(db.Items.Find(id));
if (itemdetailviewmodel == null)
{
return HttpNotFound();
}
return View(itemdetailviewmodel);
}
Scaffolding is not that intelligent. The standard controller scaffolding template is creating a DbContext with the controller model and presumes you are working with the DB models, not view models and it does not use Automapper. So you'll need to either not use scaffolding, or check what it has done before using it.
And nothing is wrong with the way you use scaffolding, it is just not supposed to do what you expect.
Update this is how you do this without scaffolding
// Without Automapper
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel model)
{
if (ModelState.IsValid)
{
var item = new Item()
{
Active = model.Active,
ItemCode = model.ItemCode,
Name = model.Name,
ItemOptions = // code to convert from List<ItemDetailItemOptionViewModel> to List<ItemOption>
}
db.Items.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
// with Automapper - not recommended by author of Automapper
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel model)
{
if (ModelState.IsValid)
{
var item = Automapper.Mapper.Map<Item>(model);
db.Items.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You'll need to modify your DbContext to have IDbSet<Item> Items instead of IDbSet<ItemDetailViewModels> ItemDetailViewModels.
Automapper was created to map from Domain Models to View Models and not the other way. I have done that for a while, but this is troublesome and causes subtle bugs and other maintenance problems. Even Jimmy Bogard himself says you should not map from view models to domain models.