I am using the WebAPI in MVC4. I have a simple form which submits data to the API via a PUT request. The data arrives and is serialized fine and everything looks wonderful, except that anywhere there is a foreign key is not getting updated. And yes, the foreign key exists.
I have the following classes:
public class TriageRecord
{
[Key]
public Int64 PKEY { get; set; }
public DateTime DateAdded { get; set; }
public DateTime DateUpdated { get; set; }
public Int64? RecordSource_Id { get; set; }
public Int64? AssignedVolunteer_Id { get; set; }
public string FacebookID { get; set; }
public Int64? VANID { get; set; }
public Int64? NationBuilderID { get; set; }
public DateTime? DateVanSubmitted { get; set; }
public Int64? FollowUpLevel_Id { get; set; }
public bool? Complete { get; set; }
public string First { get; set; }
public string Mid { get; set; }
public string Last { get; set; }
public string Email { get; set; }
public string Address1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip5 { get; set; }
public string HomePhone { get; set; }
public string CellPhone { get; set; }
public Int32? EmployeeStatus { get; set; }
public string StoreNumber { get; set; }
public virtual WorkArea WorkArea { get; set; } //put your focus here
public virtual Department Department { get; set; }
public virtual Shift Shift { get; set; }
}
Here is the WorkArea class which I am updating via my form.
public class WorkArea
{
[Key]
public Int64 Id { get; set; }
[StringLength(200)]
public string WorkAreaName { get; set; }
}
Here is the JSON I am posting which arrives very nicely to my API Controller:
var saveRecord = function() {
var record = {
"PKEY": $("#PKEY").val(),
"DateAdded": $("#DateAdded").val(),
"DateUpdated": "#DateTime.Now.ToString()",
"First": $("#First").val(),
"Mid": $("#Mid").val(),
"Last": $("#Last").val(),
"RecordSource_Id": $("#RecordSource_Id").val(),
"AssignedVolunteer_Id": $("#AssignedVolunteer_Id").val(),
"FacebookID": $("#FacebookID").val(),
"VANID": $("#VANID").val(),
"NationBuilderID": $("#NationBuilderID").val(),
"DateVanSubmitted": Date.now(),
"FollowUpLevel_Id": $("#FollowUpLevel_Id").val(),
"Complete": $("#Complete").val(),
"Email": $("#Email").val(),
"Address1": $("#Address1").val(),
"City": $("#City").val(),
"State": $("#State").val(),
"Zip5": $("#Zip5").val(),
"HomePhone": $("#HomePhone").val(),
"CellPhone": $("#CellPhone").val(),
"EmployeeStatus": $("#EmployeeStatus").val(),
"StoreNumber": $("#StoreNumber").val(),
"WorkArea": {
"Id": 1,
"WorkAreaName": "GM"
},
"Department": $("#Department").val(),
"Shift": $("#Shift").val()
};
It is serialized and it arrives to my controller where I can see when I set my breakpoint that the WorkArea (Models.WorkArea) is populated with Id = 1 & WorkAreaName = "GM"
So this is my controller:
public HttpResponseMessage PutTriageRecord(long id, TriageRecord triagerecord)
{
if (id == triagerecord.PKEY) //breakpoint here shows triagerecord contains workarea values
{
db.Entry(triagerecord).State = EntityState.Modified;
try
{
db.SaveChanges(); //changes get saved for everything (ie, first name) but not workarea...
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Everything is updated in my TriageRecord except for WorkArea... Why?
Update: NOT WORKING. This is what I added to my controller... it just keeps creating a ton of entires in the WorkAreas table.
public HttpResponseMessage PutTriageRecord(long id, TriageRecord triagerecord)
{
if (id == triagerecord.PKEY) //breakpoint here shows triagerecord contains workarea values
{
db.Entry(triagerecord).State = EntityState.Modified;
try
{
db.TriageRecords.Attach(triagerecord); //attach
db.Entry(triagerecord.WorkArea).State = EntityState.Added; //add
db.SaveChanges(); //WORKS!
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
You need to call Attach and set the state of the child objects to Added. It doesn't have anything to do with WebApi, this is how EF works when you are working with detached objects (e.g. when you send them out to client side and back). The db context track's the changes so it won't know what has been added modified automatically when you work with detached objects.
"If you don't call Attach the children stay detached until SaveChanges
is called where EF will assume that they are new entities (because they
are not attached to the context) and set their state to Added. Then they
will be inserted into the database."
EF 4.3 CF does not update relationships when saving changes
Related
I am actually trying a simple web app with relational database using web api. But i got some unexpected error.
product.cs and Warehouse.cs
//Product.cs
public class Products
{
public int Id { get; set; }
public string Name { get; set; }
//relationship
public int WarehouseId { get; set; }
public Warehouse WarehouseList { get; set; }
}
//Warehouse.cs
public class Warehouse
{
public int Id { get; set; }
public string WarehouseList { get; set; }
}
Now shaping the data to return with DTO's
ProductToReturnDto.cs
public class ProductToReturnDto
{
public int Id { get; set; }
public string Name { get; set; }
public string WarehouseList { get; set; }
}
ProductRepository.cs
...
public async Task<Products> GetProductByIdAsync(int id)
{
return await _context.Products
.Include(p=>p.WarehouseList)
.FirstOrDefaultAsync(p=>p.Id==id);
}
ProductController.cs
...
[HttpGet("{id}")]
public async Task<ActionResult<ProductToReturnDto>> GetProduct(int id)
{
var product = await _repo.GetProductByIdAsync(id);
return new ProductToReturnDto
{
Id = product.Id,
Name = product.Name,
WarehouseList = product.Warehouse.WarehouseList, //here i found main error
};
}
Here i found this error:-
enter image description here
My goal is actully show "Warehouselist" via cascade in Angular. I am beginner. i don't understand how i resolve this error problem.
I have a problem with EF and MVC on net core.
I have an "item" model. This model has the required "createdate" and "updateDate" fields (I can't have a record without having the record date).
I use both fields with inheritance over BaseEntity.
The point is that to assign these dates, I do it directly in the context, overriding the "SaveChanges" function.
Because of this, by not giving it a value in either the view or the controller, the model evaluation fails because the dates are null. I actually give it value but after passing validation.
What do you think would be the most correct solution?
This is my model:
public class ItemType : BaseEntity
{
[Key]
public int Id { get; set; }
[Required]
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntity
{
[Required]
public bool Active { get; set; }
public DateTime? DeleteDate { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
}
this is my savechanges override:
public override int SaveChanges()
{
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is BaseEntity && (
e.State == EntityState.Added
|| e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
((BaseEntity)entityEntry.Entity).UpdatedDate = DateTime.Now;
if (entityEntry.State == EntityState.Added)
{
((BaseEntity)entityEntry.Entity).CreatedDate = DateTime.Now;
}
}
return base.SaveChanges();
}
this is my controller:
public async Task<IActionResult> Create([Bind("Id,Description,Active,DeleteDate,CreatedDate,UpdatedDate,Comments")] SizeType sizeType)
{
if (ModelState.IsValid)
{
_context.Add(sizeType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(sizeType);
}
Thanks you very much!!
One easy option would be to just make the properties nullable i.e. DateTime? CreatedDate since the entity isn't already created or deleted it makes sense for the CreatedDate or UpdatedDate properties to be null. However, a better option is to just create a DTO or in other words, a ViewModel that wraps the data of your entity and exposes it to the View. For example:
public class ItemTypeRequest : BaseEntityRequest
{
[Required]
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntityRequest
{
[Required]
public bool Active { get; set; }
public string Comments { get; set; }
}
Normally in a well-defined ViewModel, you are exposing to the View only the minimal set of data that is needed to perform the operation. In this case the Create View does not need an Id, CreatedDate, or UpdatedDate, because the entity isn't already created i.e. it does not exist in the database and therefore has no Id or creation date. After creating the ViewModels you can leverage AutoMapper to map the ViewModels data to the entity data
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<ItemTypeRequest , ItemType>();
cfg.CreateMap<ItemType, ItemTypeResponse>();
});
var mapper = config.CreateMapper();
public async Task<IActionResult> Create(ItemTypeRequest itemTypeRequest)
{
if (ModelState.IsValid)
{
ItemType itemType = mapper.Map<ItemType>(itemTypeRequest);
_context.Add(itemType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View();
}
Following the same login, you should create a response DTO/ViewMode this time with more data if you need it i.e.
public class ItemTypeResponse : BaseEntityResponse
{
public int Id { get; set; }
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntityResponse
{
public bool Active { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
}
This time the properties CreatedDate and UpdatedDate are included since they have values after you have created the entity.
I have the post method which creates a new cream
public ActionResult CreateCream(CreamModel cream, string creamTypeId)
{
if (ModelState.IsValid)
{
if (creamTypeId != string.Empty)
{
try
{
cream.CreamTypeModel_id = int.Parse(creamTypeId);
creamManager.CreateCream(cream);
TempData["message"] = string.Format("Игрок {0} сохранен", cream.Name);
return RedirectToAction("Index", "Home");
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
}
}
ViewBag.ChoosingCreamType = GetCreamSelectList();
return View(cream);
}
when I call
public void CreateCream(CreamModel newCream)
{
if (newCream.Id == 0)
{
context.CreamModels.Add(newCream);
context.SaveChanges();
}
}
when I call context.SaveChanges() the code fails and I go to View, instead of redirect! I don't understand why it doesn't work? If i delete SaveChanges() it executes, but doesn't save in database.
my model
public class CreamModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string ImageName { get; set; }
public int? CreamTypeModel_id { get; set; }
public CreamTypeModel CreamTypeModel { get; set; }
}
error message
SqlException: The column name 'CreamTypeModel_id' is specified more
than once in the SET clause or column list of an INSERT. A column
cannot be assigned more than one value in the same clause. Modify the
clause to make sure that a column is updated only once. If this
statement updates or inserts columns into a view, column aliasing can
conceal the duplication in your code.
The issue that comes to mind is that you have a relationship without associating the FK:
public class CreamModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string ImageName { get; set; }
[ForeignKey("CreamTypeModel")]
public int? CreamTypeModel_id { get; set; }
public virtual CreamTypeModel CreamTypeModel { get; set; }
}
This links up the FK to the associated reference property.
So I created two model classes for my web API. The buyers and Apartments Classes. A buyer can have multiple apartments. So My Model Classes are as follows:
public class Apartment
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public int NbofRooms { get; set; }
public int Price { get; set; }
public string Address { get; set; }
}
and
public class Buyer
{
[Key]
public int ID { get; set; }
public string FullName { get; set; }
public int Credit { get; set; }
public ICollection<Apartment> apartments { get; set; }
}
So the project created two tables in the database with the Apartments table containing BuyerID. So I have no idea if I want to create a purchase function given an Apartment ID and Buyer ID how to change the BuyerID in the database.
Here is my attempted method in the Buyers Controller:
[HttpPut("{id}/{ApartmentID}")]
public async Task<IActionResult> PutBuyerApartment([FromRoute] int id, [FromRoute] int apartID, [FromBody] Buyer buyer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != buyer.ID)
{
return BadRequest();
}
var Apartments = await _context.Apartments.SingleOrDefaultAsync(m => m.ID == apartID);
_context.Entry(Apartments).State = EntityState.Modified;
buyer.apartments.Add(Apartments);
_context.Entry(buyer).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BuyerExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
How should I change this one so that it works or is there any other way?
Thanks
The better approach it's to tell EF their direct relationship on your model Apartments
public class Apartment
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public int NbofRooms { get; set; }
public int Price { get; set; }
public string Address { get; set; }
public int BuyerId { get; set; } //Make it nullable if an aparment may not have a buyer at some point
public Buyer Buyer { get; set; }
}
This convention as the same impact in the database but by changing the Apartment buyers Id it will reflect on your buyers apartment list, plus adds the benefit to be able to access directly through the Apartment the Buyer's info
I have a Model like this
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<string> SolvedBy { get; set; }
}
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
}
and then Controller like this. But I cannot update the List "SolvedBy", the next time I step through with the debugger, the list is still empty.
[HttpPost]
public string Index(string flag = "", int id=0)
{
Challenge challenge = db.Challenges.Find(id);
if (flag == challenge.Flag)
{
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<string>();
}
chall.SolvedBy.Add(User.Identity.Name);
db.Entry(chall).State = EntityState.Modified;
db.SaveChanges();
//congrats, you solved the puzzle
return "got it";
}
else
{
return "fail";
}
}
is there any way around it to make a list of strings kept in the database?
EF don't know how to store an array in database table so it just ignore it. You can create another table/entity or use XML/JSON to store the list. You can serialize the list before saving and deserialize it after loading from database
A List<T> in a model would normally map to a second table, but in your DbContext you only have a single table. Try adding a second table.
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
public DbSet<Solution> Solutions {get; set;}
}
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<Solution> SolvedBy { get; set; }
}
public class Solution
{
public int ID { get; set; }
public string Name { get; set; }
}
Then your controller can use code along the lines of...
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<Solution>();
}
chall.SolvedBy.Add(new Solution {Name=User.Identity.Name});
None of the above has been tested and I may have made some mistakes there, but the general principle I want to illustrate is the fact that you need another table. The List<T> represents a JOIN in SQL.