Using TryUpdateModelAsync with an IEnumerable Collection - c#

Model:
public class InvoiceCategory
{
[Key]
public int InvoiceCategoryID { get; set; }
[Required]
public string CategoryName { get; set; }
}
Controller Edit Method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(IEnumerable<InvoiceCategory> invoiceCategories)
{
if (ModelState.IsValid)
{
try
{
var categoryToUpdate = await _context.InvoiceCategory.ToListAsync();
if (await TryUpdateModelAsync<InvoiceCategory>(
categoryToUpdate,
"",
i => i.InvoiceCategories)) /// <-- This is incorrect
{
}
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
...
}
}
return View();
}
How can you use TryUpdateModelAsync to update an IEnumerable collection?
Or is there a better way to update it?

Related

Entity Framework Core deletes entities during update

I have a problem concerning entities in ASP.NET Core.
I use Entity Framework Core as data access library.
The issue I've come across happens when I'm trying to update an entity. After I modify the properties and call SaveChanges, the entity gets deleted and I don't understand why.
Here's the entity:
public class Contract
{
public int Id { get; set; }
[Required]
public DateTime ExpiryDate { get; set; }
[Required]
[Range(0, float.MaxValue)]
public float MonthlyFee { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public int CarId { get; set; }
public User User { get; set; }
public Car Car { get; set; }
}
Here's the related entities for reference:
public class User : IdentityUser
{
[Required]
[PersonalData]
public string Name { get; set; }
[Required]
[PersonalData]
public string Surname { get; set; }
[Required]
[PersonalData]
public string TaxCode { get; set; }
[Required]
[PersonalData]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
public string ProfilePictureUrl { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
}
public class Car
{
public int Id { get; set; }
[Required]
[RegularExpression("[A-Z][A-Z][0-9][0-9][0-9][A-Z][A-Z]")]
public string LicensePlate { get; set; }
public int CarModelId { get; set; }
public string FittingDescription { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
public CarModel CarModel { get; set; }
}
Here's my update method in repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.User = entity.User;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Has anyone got any idea how to solve this?
UPDATE:
This is the new Update method:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Here's the Fluent API configuration:
private void _configureUsers(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOne(u => u.Contract)
.WithOne(c => c.User)
.HasForeignKey<Contract>(c => c.UserId);
}
private void _configureCars(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => c.LicensePlate);
modelBuilder.Entity<Car>()
.HasOne(c => c.Contract)
.WithOne(c => c.Car)
.HasForeignKey<Contract>(c => c.CarId);
}
Both this methods get called in the OnModelCreating method of the context.
I've finally managed to solve my issue.
I was already tracking the entity in my api controller like that:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
var contract = await _contractService.GetContractDTO(id);
if (contract == null)
return NotFound();
var modifiedContract = await _contractService.UpdateContract(viewModel);
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
}
This type of approach works in one to many relationships, but evidently when you have one to one relationship and you have to objects that rapresent the same entity the ChangeTracker cannot track the changes correctly.
I post my new controller and repository code if someone will burst into my same problem.
Controller:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
ContractDTO modifiedContract;
try
{
modifiedContract = await _contractService.UpdateContract(viewModel);
}
catch (EntityNotFoundException)
{
return NotFound();
}
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
Service:
public async Task<ContractDTO> UpdateContract(PutContractViewModel viewModel)
{
try
{
return await ParseContractToContractDTO(await _contractRepository.Update(ParsePutContractViewModelToContract(viewModel)));
}
catch(EntityNotFoundException)
{
throw;
}
}
Repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await _context.Contracts.Include(c => c.User).Include(c => c.Car).FirstOrDefaultAsync(c => c.Id == entity.Id);
if (dbContract == null)
{
throw new EntityNotFoundException();
}
var dbUser = await _context.Users.Include(u => u.Contract).FirstOrDefaultAsync(u => u.Id == entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _context.Cars.Include(c => c.Contract).FirstOrDefaultAsync(c => c.Id == entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.MonthlyFee = entity.MonthlyFee;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
I want to thank you all, you've been very helpful and patient with me.

WEB API post from uri/ Query string in post

i have a model
public partial class TalentVendorShots
{
public int Id { get; set; }
public string Email { get; set; }
public string One { get; set; }
public string Two { get; set; }
public string Three { get; set; }
public string Four { get; set; }
public string Five { get; set; }
public string Six { get; set; }
public string Seven { get; set; }
public string Eight { get; set; }
public string Nine { get; set; }
public string Ten { get; set; }
}
and basic controllers
[Route("api/[controller]")]
[ApiController]
public class TalentVendorShotsController : ControllerBase
{
private readonly champagneDatabase _context;
public TalentVendorShotsController(champagneDatabase context)
{
_context = context;
}
// GET: api/TalentVendorShots
[HttpGet]
public async Task<ActionResult<IEnumerable<TalentVendorShots>>> GetTalentVendorShots()
{
return await _context.TalentVendorShots.ToListAsync();
}
// GET: api/TalentVendorShots/5
[HttpGet("{id}")]
public async Task<ActionResult<TalentVendorShots>> GetTalentVendorShots(int id)
{
var talentVendorShots = await _context.TalentVendorShots.FindAsync(id);
if (talentVendorShots == null)
{
return NotFound();
}
return talentVendorShots;
}
// PUT: api/TalentVendorShots/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTalentVendorShots(int id, TalentVendorShots talentVendorShots)
{
if (id != talentVendorShots.Id)
{
return BadRequest();
}
_context.Entry(talentVendorShots).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TalentVendorShotsExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/TalentVendorShots
[HttpPost]
public async Task<ActionResult<TalentVendorShots>> PostTalentVendorShots(TalentVendorShots talentVendorShots)
{
_context.TalentVendorShots.Add(talentVendorShots);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTalentVendorShots", new { id = talentVendorShots.Id }, talentVendorShots);
}
// DELETE: api/TalentVendorShots/5
[HttpDelete("{id}")]
public async Task<ActionResult<TalentVendorShots>> DeleteTalentVendorShots(int id)
{
var talentVendorShots = await _context.TalentVendorShots.FindAsync(id);
if (talentVendorShots == null)
{
return NotFound();
}
_context.TalentVendorShots.Remove(talentVendorShots);
await _context.SaveChangesAsync();
return talentVendorShots;
}
private bool TalentVendorShotsExists(int id)
{
return _context.TalentVendorShots.Any(e => e.Id == id);
}
}
}
all of this works fine. i get information from the database fine. now i want to make a post to the table via uri. no body.for example
/api/TalentVendorShots/id=1,email=testemail should create a new record with id of 1 and email of testemail. how can i accomplish this?
The basic rule is, You should use POST if the action is not idempotent. Though you can pass the query parameters and no body to POST. But It would not make sense in this scenario. Basically query parameters are used to get/filter information.
Similar way many Web API testing tools like ARC, Swagger, and PostMan (chrome extension does not allow, but standalone application allows) does not allow to send body with the GET request. Though you can send the body in GET requests.

How to add/update many to many relations with one end of entity in EF core

We have visited quite a few links on EF Core many to many update, yet could not figure a concrete answer to our question and clear our understanding.
Scenario:
We wish to add/update an entity and its related many to many relations in one go like (dbset.Add() or dbset.Update())
We were trying the following and could only add/update the parent entity and not the many-to-many relation list. Can you help us know where we are wrong? and what can be done?
Current Model Structure:
public class Teacher
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity), Required]
public long Id { get; set; }
public string Name { get; set; }
public List<TeacherDuty> TeacherDuties { get; set; }
}
public class Duty
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity), Required]
public long Id { get; set; }
public string Description { get; set; }
public List<TeacherDuty> TeacherDuties { get; set; }
}
public class TeacherDuty
{
public long TeacherId { get; set; }
public Teacher Teacher { get; set; }
public long DutyId { get; set; }
public Duty Duty { get; set; }
}
And we are trying to add/update using following methods:
public async Task<Teacher> AddTeacher(Teacher pTeacher)
{
try
{
return await _teacher.AddAsync(pTeacher);
}
catch (Exception ex) { throw ex; }
}
public async Task<Teacher> UpdateTeacher(Teacher pTeacher)
{
try
{
return await _teacher.Update(pTeacher);
}
catch (Exception ex) { throw ex; }
}
Kindly point us to our misinterpretation of concept and solution if possible.
Thanks.
I create a demo to add and edit a teacher.(_context is database context)
Add a teacher:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Teacher teacher)
{
//get your desired dutyId with your own logic
var SelectedDutyIds = new int[] { 1 };
var teacherDuties = new List<TeacherDuty>();
if (ModelState.IsValid)
{
_context.Add(teacher);
await _context.SaveChangesAsync();
foreach (var id in SelectedDutyIds)
{
var item = new TeacherDuty()
{
TeacherId = teacher.Id,
DutyId = id,
};
teacherDuties.Add(item);
}
_context.AddRange(teacherDuties);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(teacher);
}
Edit the teacher: remove all the existing TeacherDuties of the teacher firstly and then add new ones.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, Teacher teacher)
{
if (id != teacher.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
//your new dutyIds
var newSelectedDutyIds = new int[] { 3 };
var teacherDuties = new List<TeacherDuty>();
var tdList = await _context.TeacherDuties.Where(td => td.TeacherId == teacher.Id).ToListAsync() ;
_context.RemoveRange(tdList);
foreach (var newid in newSelectedDutyIds)
{
var item = new TeacherDuty()
{
TeacherId = teacher.Id,
DutyId = newid,
};
teacherDuties.Add(item);
}
_context.AddRange(teacherDuties);
_context.Update(teacher);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TeacherExists(teacher.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(teacher);
}
Refer to Entity framework core update many to many

Attempt to implement repository pattern for an MVC app causes a concurrency exception

I am learning ASP .NET Core and I am trying to use repository pattern to clean up my controllers. The way I thought this out was:
create a repository interface containing the basic methods for a repository
implement the previously created the interface
create a model based repository interface
create a model-based repository extending the basic repository created at step 2 and implemented the model-based interface
create a wrapper class which contains the context and all the application repositories and inject it in every controller
Unfortunately the Complete of 'Edit' method causes a DbConcurrencyException which I have tried to solve using this. using the previous solution causes an InvalidOperationException as one of the properties is read-only.
For some code:
public class User : IdentityUser
{
[PersonalData]
[DisplayName("First Name")]
[Required(ErrorMessage = "The first name is required!")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The first name must be between 3 and 30 characters long!")]
public string firstName { get; set; }
[PersonalData]
[DisplayName("Last Name")]
[Required(ErrorMessage = "The last name is required!")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The last name must be between 3 and 30 characters long!")]
public string lastName { get; set; }
[PersonalData]
[DisplayName("CNP")]
[Required(ErrorMessage = "The PNC is required!")]
[StringLength(13, MinimumLength = 13, ErrorMessage = "The last name must 13 digits long!")]
[RegularExpression(#"^[0-9]{0,13}$", ErrorMessage = "Invalid PNC!")]
public string personalNumericalCode { get; set; }
[PersonalData]
[DisplayName("Gender")]
[StringRange(AllowableValues = new[] { "M", "F" }, ErrorMessage = "Gender must be either 'M' or 'F'.")]
public string gender { get; set; }
public Address address { get; set; }
}
public class Medic : User
{
[DisplayName("Departments")]
public ICollection<MedicDepartment> departments { get; set; }
[DisplayName("Adiagnostics")]
public ICollection<MedicDiagnostic> diagnostics { get; set; }
[PersonalData]
[DisplayName("Rank")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The rank name must be between 3 and 30 characters long!")]
public string rank { get; set; }
}
public class MedicController : Controller
{
private readonly IUnitOfWork unitOfWork;
public MedicController(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
// GET: Medic
public async Task<IActionResult> Index()
{
return View(await unitOfWork.Medics.GetAll());
}
// GET: Medic/Details/5
public async Task<IActionResult> Details(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.FirstOrDefault(m => m.Id == id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// GET: Medic/Create
public IActionResult Create()
{
return View();
}
// POST: Medic/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> Create([Bind("rank,firstName,lastName,personalNumericalCode,Id,gender,Email")] Medic medic)
{
if (ModelState.IsValid)
{
unitOfWork.Medics.Add(medic);
await unitOfWork.Complete();
return RedirectToAction(nameof(Index));
}
return View(medic);
}
// GET: Medic/Edit/5
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.Get(id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// POST: Medic/Edit/5
// 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> Edit(string id, [Bind("rank,firstName,lastName,Id,personalNumericalCode,gender,Email")] Medic medic)
{
if (id != medic.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
var saved = false;
while (!saved)
{
try
{
unitOfWork.Medics.Update(medic);
await unitOfWork.Complete();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
if (!MedicExists(medic.Id))
{
return NotFound();
}
else
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Medic)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
proposedValues[property] = proposedValue;
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
return RedirectToAction(nameof(Index));
}
return View(medic);
}
// GET: Medic/Delete/5
public async Task<IActionResult> Delete(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.FirstOrDefault(m => m.Id == id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// POST: Medic/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string id)
{
Medic medic = await unitOfWork.Medics.Get(id);
unitOfWork.Medics.Remove(medic);
await unitOfWork.Complete();
return RedirectToAction(nameof(Index));
}
private bool MedicExists(string id)
{
return unitOfWork.Medics.Any(e => e.Id == id);
}
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly ApplicationDbContext context;
public Repository(ApplicationDbContext context)
{
this.context = context;
}
public void Add(TEntity entity)
{
context.Set<TEntity>().AddAsync(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
context.Set<TEntity>().AddRangeAsync(entities);
}
public bool Any(Expression<Func<TEntity, bool>> predicate)
{
return context.Set<TEntity>().Any(predicate);
}
public async Task<IEnumerable<TEntity>> Find(Expression<Func<TEntity, bool>> predicate)
{
return await context.Set<TEntity>().Where(predicate).ToListAsync();
}
public async Task<TEntity> FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return await context.Set<TEntity>().FirstOrDefaultAsync(predicate);
}
public async Task<TEntity> Get(string id)
{
return await context.Set<TEntity>().FindAsync(id);
}
public async Task<IEnumerable<TEntity>> GetAll()
{
return await context.Set<TEntity>().ToListAsync();
}
public void Remove(TEntity entity)
{
context.Set<TEntity>().Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
context.Set<TEntity>().RemoveRange(entities);
}
public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return context.Set<TEntity>().SingleOrDefault(predicate);
}
public void Update(TEntity entity)
{
context.Set<TEntity>().Update(entity);
}
}
public class MedicRepository : Repository<Medic>, IMedicRepository
{
public MedicRepository(ApplicationDbContext _context) : base(_context) { }
//TODO: add medic repository specific methods
}
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _context;
public IMedicRepository Medics { get; private set; }
public IPatientRepository Patients { get; private set; }
public IReceptionistRepository Receptionists { get; private set; }
public IDiagnosticRepository Diagnostics { get; private set; }
public IMedicationRepository Medications { get; private set; }
public IMedicineRepository Medicine { get; private set; }
public ILabTestRepository LabTests { get; private set; }
public ILabResultRepository LabResults { get; private set; }
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
Medics = new MedicRepository(_context);
Patients = new PatientRepository(_context);
Receptionists = new ReceptionistRepository(_context);
Diagnostics = new DiagnosticRepository(_context);
Medications = new MedicationRepository(_context);
Medicine = new MedicineRepository(_context);
LabTests = new LabTestRepository(_context);
LabResults = new LabResultRepository(_context);
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
}
Thanks!
There are a lot of things to notice. But I will only point to the biggest one. DbContext or ApplicationDbContext classes are not meant to be long lived and cross spanned. I am guessing ApplicationDbContext is a singleton. Which is a long lived object and is shared among different classes and also may be threads. This is exactly the design pattern you should avoid. In terms of Microsoft -
Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance. Concurrent access can result in undefined behavior, application crashes and data corruption. Because of this it's important to always use separate DbContext instances for operations that execute in parallel.
This page here describes the problem - https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#avoiding-dbcontext-threading-issues
In short, use scoped dbcontext.
If you are learning, I would say implement it yourself and change the implementations of your classes. Create and dispose contexts when you need them. Do not keep long lived contexts.
If you just need a repository, you can use this package, I use it for myself - https://github.com/Activehigh/Atl.GenericRepository
I managed to fix this out. The concurrency exception was thrown because I was creating users (which were inheriting IDentityUser) without using a UserManager<User>. After inspecting the database fields, I have discovered that the Identityuser related fields (like email, username, etc..) were empty. This was due to me only adding information for the class which inherited IDentityUser.

Web API loading related data

I am using ASP CORE 2.1 and EF CORE to create Web API with 2 data table with 1 ForeignKey
Model:
public partial class AuditInfo
{
public int Id { get; set; }
public string Level { get; set; }
public string Period { get; set; }
public string Auditor { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
public partial class Item
{
public int Id { get; set; }
public string Ponumber { get; set; }
public bool IsComplete { get; set; }
public AuditInfo AuditInfo { get; set; }
}
public partial class VEDHOMEContext : DbContext
{
public VEDHOMEContext()
{
}
public VEDHOMEContext(DbContextOptions<VEDHOMEContext> options)
: base(options)
{
}
public virtual DbSet<AuditInfo> AuditInfo { get; set; }
public virtual DbSet<Item> Item { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("auditAPI.Models.AuditInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Auditor");
b.Property<string>("Level");
b.Property<string>("Period");
b.HasKey("Id");
b.ToTable("AuditInfo");
});
modelBuilder.Entity("auditAPI.Models.Item", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("AuditInfoId");
b.Property<bool>("IsComplete");
b.Property<string>("Ponumber");
b.HasKey("Id");
b.HasIndex("AuditInfoId");
b.ToTable("Item");
});
modelBuilder.Entity("auditAPI.Models.Item", b =>
{
b.HasOne("auditAPI.Models.AuditInfo", "AuditInfo")
.WithMany("Items")
.HasForeignKey("AuditInfoId");
});
}
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class AuditInfoesController : ControllerBase
{
private readonly VEDHOMEContext _context;
public AuditInfoesController(VEDHOMEContext context)
{
_context = context;
}
// GET: api/AuditInfoes
[HttpGet]
public IEnumerable<AuditInfo> GetAuditInfo()
{
return _context.AuditInfo;
}
// GET: api/AuditInfoes/5
[HttpGet("{id}")]
public async Task<IActionResult> GetAuditInfo([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var auditInfo = await _context.AuditInfo.FindAsync(id);
if (auditInfo == null)
{
return NotFound();
}
return Ok(auditInfo);
}
// PUT: api/AuditInfoes/5
[HttpPut("{id}")]
public async Task<IActionResult> PutAuditInfo([FromRoute] int id, [FromBody] AuditInfo auditInfo)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != auditInfo.Id)
{
return BadRequest();
}
_context.Entry(auditInfo).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AuditInfoExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/AuditInfoes
[HttpPost]
public async Task<IActionResult> PostAuditInfo([FromBody] AuditInfo auditInfo)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.AuditInfo.Add(auditInfo);
await _context.SaveChangesAsync();
return CreatedAtAction("GetAuditInfo", new { id = auditInfo.Id }, auditInfo);
}
// DELETE: api/AuditInfoes/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteAuditInfo([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var auditInfo = await _context.AuditInfo.FindAsync(id);
if (auditInfo == null)
{
return NotFound();
}
_context.AuditInfo.Remove(auditInfo);
await _context.SaveChangesAsync();
return Ok(auditInfo);
}
private bool AuditInfoExists(int id)
{
return _context.AuditInfo.Any(e => e.Id == id);
}
}
However i got return null for the item related data, how can i fix it?
i am new to this framework, any help would appreciate, thanks.
[{"id":1,"level":"level1","period":"jan","auditor":"A","items":null},{"id":2,"level":"level2","period":"feb","auditor":"B","items":null}]
expected output:
[{"id":1,"level":"level1","period":"jan","auditor":"A","items":{"Id":1,"Ponumber":"0001","IsComplete":"True","AuditInfoId":1},{"id":2,"Ponumber":"0002","IsComplete":"True","AuditInfoId":1}}]
To who have similar problem, I solve it by adding
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
and edit controller
public async Task<List<AuditInfo>> GetAuditInfo()
{
//return _context.AuditInfo;
var infoes = await _context.AuditInfo.Include(a => a.Items).ToListAsync();
return infoes;
}
I'm not sure if you've seen the accepted answer to this question, but the problem is to do with how the JSON Serializer deals with circular references. Full details and links to more references can be found at the above link, and I'd suggest digging into those, but in short, adding the following to startup.cs will configure the serializer to ignore circular references:
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});

Categories

Resources