I am creating a simple API and I have a method that handles the update of a game:
[HttpPut("{Id}")]
public bool UpdateGame(Guid Id, [FromBody] Game game)
{
Game? oldGame = _unitOfWork.Games.GetById(Id);
if (oldGame != null)
{
oldGame.SeasonId = game.SeasonId;
oldGame.VenueId = game.VenueId;
oldGame.GameTypeId = game.GameTypeId;
oldGame.GameTitle = game.GameTitle;
oldGame.GameDateTime = game.GameDateTime;
oldGame.PublishResults = game.PublishResults;
oldGame.GameDetails = game.GameDetails;
oldGame.Buyin = game.Buyin;
oldGame.Fee = game.Fee;
_unitOfWork.Save();
return true;
}
else
{
return false;
}
}
I have tests against the repo and the controller and the update works fine.
I'm now implementing some CRUD pages using Blazor Server:
async Task OnSubmit()
{
if (Game != null)
{
try
{
var response = await _apiClient.httpClient.PutAsJsonAsync<Game>($"/api/Games/{Id}", Game);
bool updated = await response.Content.ReadFromJsonAsync<bool>();
if (updated)
{
_navManager.NavigateTo("/settings/games");
}
}
catch (Exception ex)
{
AlertIsVisible = true;
Message = ex.Message;
MessageType = AlertMessageType.Danger;
}
}
}
This is failing because of a bad request 400, but this is all the output is telling me. The put method in the controller doesn't hit as my breakpoint doesn't trigger.
Simpler models in my app post just fine using similar code.
The difference here I believe is this Game entity:
public class Game
{
public Guid Id { get; set; }
public Guid? SeasonId { get; set; }
public Guid? GameTypeId { get; set; }
public Guid? VenueId { get; set; }
public string? GameTitle { get; set; }
public DateTime GameDateTime { get; set; }
public bool PublishResults { get; set; }
public string? GameDetails { get; set; }
public double Buyin { get; set; }
public double Fee { get; set; }
public virtual Season Season { get; set; } = default!;
public virtual GameType GameType { get; set; } = default!;
public virtual Venue Venue { get; set; } = default!;
public virtual ICollection<Result> Results { get; set; } = new HashSet<Result>();
}
I think there is a serialisation issue with the navigation properties (Season, GameType and Venue - not the Results collection). If I remove the navigation properties the update succeeds. I'm at a loss at this point on how to handle the put request so that it works with the navigation properties on my model.
You declare Season, GameType and Venue as not Nullable [no ?], and then forcibly set to default!, which for any object is null.
You normally use default! when you know an object will be set to a value before any attempt is made to use it, which is not the case here.
Related
I use Entity Framework as an ORM for SQL Server.
This is the code inside my POST API:
var group = await _context.Groups.FindAsync(id);
foreach (CalendarRuleGroup calendarRuleGroup in group.CalendarRuleGroups)
{
CalendarRule calendarRule = _context.CalendarRules
.Where(cr => cr.CalendarRuleGroupId == calendarRuleGroup.Id)
.First();
CalendarRule calendarRuleTemp = (CalendarRule)calendarRule.Clone();
_context.CalendarRules.Add(calendarRuleTemp);
}
await _context.SaveChangesAsync();
This is the Clone() function:
public object Clone()
{
CalendarRule clonedRule = (CalendarRule)MemberwiseClone();
clonedRule.Calendar = null;
clonedRule.CalendarId = default;
clonedRule.CalendarRuleGroup = null;
clonedRule.CalendarRuleGroupId = default;
clonedRule.CalendarContexts = null;
clonedRule.Id = default;
List<CalendarRuleCondition> conditions = new List<CalendarRuleCondition>();
foreach (CalendarRuleCondition condition in clonedRule.CalendarRuleConditions)
{
conditions.Add((CalendarRuleCondition)condition.Clone());
}
clonedRule.CalendarRuleConditions = conditions;
return clonedRule;
}
This is the model of CalendarRule:
public class CalendarRule: ICloneable
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartingFrom { get; set; }
public DateTime? Until { get; set; }
public int? Count { get; set; }
public double Duration { get; set; }
public Frequency Frequency { get; set; }
public int Interval { get; set; }
public int Priority { get; set; }
public bool IsWorking { get; set; }
public int Efficiency { get; set; }
public int Cost { get; set; }
public bool AllowedSetup { get; set; }
public int Color { get; set; }
public string Description { get; set; }
public bool? IsActive { get; set; }
public int CalendarId { get; set; }
public int? CalendarRuleGroupId { get; set; }
public virtual Calendar Calendar { get; set; }
public virtual CalendarRuleGroup CalendarRuleGroup { get; set; }
public virtual ICollection<CalendarContext> CalendarContexts { get; set; }
public virtual ICollection<CalendarRuleCondition> CalendarRuleConditions { get; set; }
}
And this is the error that appears when "clonedRule.CalendarRuleConditions" inside the foreach executes:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HMG79T5K5ECK", Request id "0HMG79T5K5ECK:00000002": An unhandled exception was thrown by the application.
System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning': An attempt was made to lazy-load navigation 'CalendarRuleConditions' on a detached entity of type 'CalendarRuleProxy'. Lazy-loading is not supported for detached entities or entities that are loaded with 'AsNoTracking'. This exception can be suppressed or logged by passing event ID 'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
It appears as if the data contained in "CalendarRule.CalendarRuleConditions" has not been loaded. I don't use "AsNoTracking" and there isn't any call to SaveChangesAsync() before the code of the API that i have posted. How can I solve the problem?
This code here doesn't make any sense:
foreach (CalendarRuleCondition condition in clonedRule.CalendarRuleConditions)
{
conditions.Add((CalendarRuleCondition)condition.Clone());
}
clonedRule.CalendarRuleConditions = conditions;
If you are calling this Clone() method from within the entity instance you want to clone, this should be:
foreach (CalendarRuleCondition condition in CalendarRuleConditions)
{
conditions.Add((CalendarRuleCondition)condition.Clone());
}
clonedRule.CalendarRuleConditions = conditions;
Where we go through each condition in this object's lazy loaded CalendarRuleConditions to build a cloned list for the clone. Even then it would be better to eager load the CalendarRuleConditions and only rely on lazy loading as a fall-back.
This question is similar to this other.
I think the problem is in (CalendarRule)MemberwiseClone(), you should create entity with Create (ef) or CreateProxy (efcore) intead new.
I'm facing this problem. I have this nested model inside my file js
var request={
idCliente:form.idCliente,
appUserId:user.data.id,
idFiliale:user.data.idFiliale,
idSuggeritore:form.idSuggeritore,
contrattoLuceRequest:{
numeroContratto:form.contratti[0].numeroContratto,
idTipoContratto:form.contratti[0].idTipoContratto,
idTipoOperazione:form.contratti[0].idTipoOperazione,
idStatoContratto:form.contratti[0].idStatoContratto,
note:form.contratti[0].note,
numeroPod:form.contratti[0].contrattoLuce.numeroPod,
allegati:form.contratti[0].allegatiContratto
},
formFiles:form.contratti[0].allegatiContratto
}
properties "allegati" of the object "contrattoLuceRequest" and "formFiles" of the object "request", are a collection of files uploaded with the component Material Ui Dropzone.
I have to send this model to my .net core 2.2 web api backend.
This is the service that I use to build my formData object. For this test, I'm ignoring other properties...I focus my test only on "formFiles" and "allegati" properties.
let f = new FormData();
for (var key2 in interazione.contrattoLuceRequest.allegati) {
f.append("formFiles", interazione.contrattoLuceRequest.allegati[key2]);
}
for (var key3 in interazione.contrattoLuceRequest.allegati) {
f.append("interazione.contrattoLuceRequest.allegati",
interazione.contrattoLuceRequest.allegati[key3]);
}
I send all to the server with axios
axios.post('/Interazioni',f,{
headers:{
'Content-Type':'multipart/form-data'
}
}).then(response => {
resolve(response);
}).catch(err=>{
return reject(err);
})
And this is the .net core 2.2 web api
[HttpPost]
public async Task<IActionResult> AddInterazione([FromForm] InterazioneRequest interazioneRequest)
{
try
{
if (interazioneRequest == null)
return BadRequest();
await this.interazioniService.AddInterazione(interazioneRequest);
return Ok();
}
catch (ArgumentException ex)
{
return StatusCode(409, ex.Message);
}
catch (Exception ex)
{
return StatusCode(500, ex.Message);
}
}
This is the server model "InterazioneRequest"
public class InterazioneRequest
{
public int? Id { get; set; }
public int IdCliente { get; set; }
public int AppUserId { get; set; }
public int IdFiliale { get; set; }
public int? IdSuggeritore { get; set; }
public ContrattoLuceRequest ContrattoLuceRequest { get; set; }
public ContrattoGasRequest ContrattoGasRequest { get; set; }
public ContrattoDualRequest ContrattoDualRequest { get; set; }
public ContrattoMultiEleRequest ContrattoMultiEleRequest { get; set; }
public ContrattoMultiGasRequest ContrattoMultiGasRequest { get; set; }
public IFormFileCollection formFiles { get; set; }
}
And this is the "ContrattoLuceRequest" model, that is used for our test case
public class ContrattoLuceRequest
{
public int? Id { get; set; }
public string NumeroContratto { get; set; }
public string NumeroPod { get; set; }
public int IdStatoContratto { get; set; }
public int IdTipoContratto { get; set; }
public int IdTipoOperazione { get; set; }
public string Note { get; set; }
public IFormFileCollection Allegati { get; set; }
}
And now, the problem. From the frontend I post all my data to the server, but only "formFiles" are bounded correctly... the nested property "Allegati" of the object "ContrattoLuceRequest" is always null. Is it a bug, or I'm ignoring something important? I also attach here the screenshot of the request sent to the server taken on the Chrome Dev Tools. Thank you so much
Sorry guys. The problem is that this part of code is not correct
for (var key3 in interazione.contrattoLuceRequest.allegati) {
f.append("interazione.contrattoLuceRequest.allegati",
interazione.contrattoLuceRequest.allegati[key3]);
}
The correct way is to remove "interazione" level, because my model on the main level is "contrattoLuceRequest", so the correct code is
for (var key3 in interazione.contrattoLuceRequest.allegati) {
f.append("contrattoLuceRequest.allegati",
interazione.contrattoLuceRequest.allegati[key3]);
}
Now is working. Sorry
I see this error when updating an Application. The object has two external connections: ApplicationVisitors and ApplicationPlatforms. The properties in Application have been updated, but external connections was not be updated.
What am I doing wrong? How to update Application correctly?
Route
[Route("update")]
[HttpPut]
public async Task<IActionResult> UpdateApplication(ApplicationDTO applicationDTO)
{
if (!ModelState.IsValid)
return BadRequest("Модель не валидна!");
await applicationService.UpdateApplication(applicationDTO);
return Ok();
}
Service
public async Task UpdateApplication(ApplicationDTO applicationDTO)
{
var visitors = Mapper.ToVisitors(applicationDTO.ApplicationVisitors);
var visitorsToCreate = visitors.Where(w => w.Id == 0).ToList();
var createdVisitors = visitors.Where(w => w.Id > 0).ToList();
var resultCreateVisitors = await _wrapper.Visitor.CreateVisitorsAsync(visitorsToCreate);
createdVisitors.AddRange(resultCreateVisitors);
applicationDTO.ApplicationVisitors = Mapper.ToVisitors(createdVisitors);
await _wrapper.Application.UpdateAsync(Mapper.ToApplication(applicationDTO));
}
Repository method
public async Task UpdateAsync(Application application)
{
Update(application);
await SaveAsync();
}
BaseRepository
public void Update(T entity)
{
_repositoryContext.Set<T>().Attach(entity);
_repositoryContext.Entry(entity).State = EntityState.Modified;
}
public async Task SaveAsync()
{
await _repositoryContext.SaveChangesAsync();
}
I have not any exeption in debug. Application was filled by ID, but Platform and Visitor in collections ApplicationPlatforms and ApplicationVisitors does not filling by foreign key.
References is existing in classes.
view result Attach
Application
public class Application
{
public int Id { get; set; }
[Column(TypeName="date")]
public DateTime DateStart { get; set; }
[Column(TypeName = "date")]
public DateTime DateEnd { get; set; }
public int ApproverId { get; set; }
public User Approver { get; set; }
public int StatusId { get; set; }
public ApplicationStatus Status { get; set; }
public string VisitPurpose { get; set; }
public DateTime CreatedAt { get; set; }
public int AuthorId { get;set; }
public User Author { get; set; }
public IList<Draft> Drafts { get; set; }
public IList<ApplicationVisitor> ApplicationVisitors { get; set; }
public IList<ApplicationPlatform> ApplicationPlatforms { get; set; }
public IList<Pass> Passes { get; set; }
}
ApplicationVisitor
public class ApplicationVisitor
{
public int ApplicationId { get; set; }
[ForeignKey("ApplicationId")]
public Application Application { get; set; }
public int VisitorId { get; set; }
[ForeignKey("VisitorId")]
public Visitor Visitor { get; set; }
}
ApplicationPlatform
public class ApplicationPlatform
{
public int ApplicationId { get; set; }
[ForeignKey("ApplicationId")]
public Application Application { get; set; }
public int PlatformId { get; set; }
[ForeignKey("PlatformId")]
public Platform Platform { get; set; }
}
UPD: 22.10.2019. This is my solution what working for me on base selected answer!
I rewrited update method in ApplicationRepository
public async Task UpdateAsync(Application application)
{
var app = await GetAsync(application.Id);
app.DateStart = application.DateStart;
app.DateEnd = application.DateEnd;
app.ApproverId = application.ApproverId;
app.StatusId = application.StatusId;
app.VisitPurpose = application.VisitPurpose;
app.CreatedAt = application.CreatedAt;
app.AuthorId = application.AuthorId;
app.ApplicationVisitors = application.ApplicationVisitors;
app.ApplicationPlatforms = application.ApplicationPlatforms;
Update(app);
await SaveAsync();
}
And rewrided method in my BaseRepository
public void Update(T entity)
{
_repositoryContext.Set<T>().Update(entity);
}
Although you attached the Application object and set its state to Modified, all other objects references by it will be in Unchanged state, as stated here:
For entity types with generated keys if an entity has its primary key
value set then it will be tracked in the Unchanged state. If the
primary key value is not set then it will be tracked in the Added
state. This helps ensure only new entities will be inserted. An entity
is considered to have its primary key value set if the primary key
property is set to anything other than the CLR default for the
property type.
You can either manually set the state of all referenced objects or, A better approach in my option, is to load the objects from the database (and have EF track them for changes) and modify these objects. These way EF will know the exact state for each object when it comes to save it to the database.
I am facing the same issue as described in this question. Problem: my method GetAllConferences() returns correctly all the conferences from the DB, but when I return the result to the View from the controller return Ok(tripListVm) inly the first collection item is returned to the client. On the otehr side, by setting to null all the FK references (as pointed out in the SO question above) I can return correctly all the entities to the client, however this does not seem to me the proper way of proceeding.
EDIT: the solution was much simpler than I though. In the code below (I leave it in its original form for others to see it) I was not mapping the FK entities inside the ViewModel to Dto objects, but returning the model entity itself. That was the reason why I needed to null those inner references to make it work. By returning all Dtos objects, it works properly.
I have three entities involved with 1-many relationships:
public class Conference
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Venue> Venues { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
}
public class Venue
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public int? ConferenceId { get; set; }
public Trip Conference { get; set; }
public int? LocationId { get; set; }
public City City { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Conference> Conferences { get; set; }
public ICollection<Venue> Venues { get; set; }
}
In the repository, I have a method that returns the conferences and the related entities (City and Venues):
public IEnumerable<Conference> GetAllConferences()
{
return _context.Conferences
.Include(t => t.Venues)
.Include(t => t.City)
.ToList();
}
In the controller I need to use the following code to return all the results:
var conferences = _repository.GetAllConferences();
if (conferences.Any())
{
var conferenceListVm = trips.ToConferenceVmList();
//Without setting the FK references to null, I can return only the first result of the collection
foreach (var vm in conferenceListVm)
{
foreach (var pm in vm.PoinOfInterests)
{
pm.Trip = null;
}
vm.Location.Conferences = null;
vm.Location.Venues = null;
}
return Ok(conferenceListVm);
}
public static ConferenceViewModel ToConferenceVm(this Conference conference)
{
var confVm = new ConferenceViewModel();
confVm.Name = conference.Name;
confVm.City = conference.City;
confVm.Venues = conference.Venues;
return tripVm;
}
public static IEnumerable<ConferenceViewModel> ToConferenceVmList(this IEnumerable<Conference> conferences)
{
return conferences.Select(c => c.ToConferenceVm()).ToList();
}
I am trying to create within MVC 5 and am getting a validation error even though the ModelState is coming back valid.
Error message
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
and when I look at the message, it shows....
The name 'e' does not exist in the current context
When I look at the POST data, the model that was created has all required fields filled in. I did notice that the model ID was assigned 0. I'm not sure if that is the error or if it is supposed to pass a zero for the ID.
What might the problem be?
WosController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "id,woNumber,woDescription,dueDate,qty,item_id,releaseDate,parentWO_id,wip_id")] Wo wo)
{
if (ModelState.IsValid)
{
db.Wos.Add(wo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wo);
}
Wo.cs
public partial class Wo
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Wo()
{
this.WoParts = new HashSet<WoPart>();
this.WoStatuses = new HashSet<WoStatus>();
}
public int id { get; set; }
public string woNumber { get; set; }
public string woDescription { get; set; }
public Nullable<System.DateTime> dueDate { get; set; }
public string qty { get; set; }
public Nullable<int> item_id { get; set; }
public Nullable<System.DateTime> releaseDate { get; set; }
public string status { get; set; }
public Nullable<int> parentWO_id { get; set; }
public int wip_id { get; set; }
public Nullable<int> part_id { get; set; }
public virtual Item Item { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoPart> WoParts { get; set; }
public virtual Wo woParentWO { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoStatus> WoStatuses { get; set; }
public virtual Part Part { get; set; }
public virtual Wip Wip { get; set; }
}
Wrap your call to SaveChangesAsync in a try...catch like so:
try
{
await db.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
var errorMessages = e.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(e.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, e.EntityValidationErrors);
}
That will show you the actual properties causing the validation issues. Then, update your question with the results, if you still need assistance.
Likely, your database is out of sync with your entities. The status property is not required on your entity, and by default properties of type string are nullable. That would explain why you're passing validation on post, but failing on actually saving the entity.
Generally, it's best not to rely on the database setting a default value in the first place. Instead, have the property itself have a default value, and then it will always be fine, regardless of what's going on at the database level:
private string _status;
public string status
{
get { return _status ?? "Default Value"; }
set { _status = value;
}
Short of that, if status is truly not required, then you should ensure that the status column on your table is nullable.