Deleting missing descendant on Update - c#

I have the following method in my controller:
[HttpPost]
public async Task<ActionResult<ChartsCategoryDto>> PostChartsCategory(ChartsCategoryDto chartsCategory)
{
try
{
var category = _mapper.Map<ChartsCategoryDto, ChartsCategory>(chartsCategory);
if (category.Id == 0)
{
category = _context.ChartsCategories.Add(category).Entity;
await _context.SaveChangesAsync();
return CreatedAtAction("GetChartsCategory", new { id = category.Id }, _mapper.Map<ChartsCategory, ChartsCategoryDto>(category));
}
else
{
_context.ChartsCategories.Update(category);
await _context.SaveChangesAsync();
return NoContent();
}
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error updating data");
}
}
Which is supposed to handle both create and update of the entity named ChartsCategory, which has a collection of Charts:
public partial class ChartsCategory
{
public ChartsCategory()
{
Charts = new HashSet<Chart>();
InverseParent = new HashSet<ChartsCategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public string Icon { get; set; }
public int? ParentId { get; set; }
public virtual ChartsCategory Parent { get; set; }
public virtual ICollection<Chart> Charts { get; set; }
public virtual ICollection<ChartsCategory> InverseParent { get; set; }
}
When I create a new entity - everything works as expected.
When I update an existing entity - everything works as expected.
Updating or creating a singular Chart within a ChartCategory - as expected.
Deleting a Chart within the ChartsCategory doesn't work.
I would expect the Update() functionality to remove missing items.
I found this answer however it seems a little too explicit. I want to globally state that a descendant item must be sent, otherwise it should be removed.
Thanks

I think that in the first case the retrieved user is not tracked by the context.
_context.ChartsCategories.AsNoTrcking.Remove(category);

Related

Entity Framework won't add children to parent object

The api response returns as if the function ran without problems but when i look the database the row simple is not there.
I don't get any error messages.
Even if I try to add one exam directly through _context.Exames it won't add.
I'm getting really frustrated because I don't even know where or what I should look for as the api returns that the method run successfully
I'm using ASP.NET Core 5 and EF Core with MySQL.
Here is the code:
public class Account
{
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
public List<Exame> Exames { get; set; }
public List<Consulta> Consultas { get; set; }
}
public class Exame
{
[Key]
public int ExameID { get; set; }
public long Image { get; set; }
}
public class DataContext : DbContext
{
public DbSet<Account> Accounts { get; set; }
public DbSet<Exame> Exames { get; set; }
}
[HttpPost("exame")]
public IActionResult CreateExame(ExameRequest exame)
{
_accountService.CreateExame(exame);
return Ok(new { message = "Exame added successfully" });
}
public void CreateExame(ExameRequest model)
{
var account = _context.Accounts.SingleOrDefault(x => x.Id == model.AccountId);
var exame = new Exame();
exame.ExameID = model.Id;
exame.Image = model.Image;
if (account.Exames == null)
{
account.Exames = new List<Exame>();
}
account.Exames.Add(exame);
_context.Accounts.Update(account);
_context.SaveChangesAsync();
}
It looks like it's returning before the Save has finished, try altering it to something like:
[HttpPost("exame")]
public Task<IActionResult> CreateExame(ExameRequest exame)
{
var result = await _accountService.CreateExame(exame);
// check the value and return the appropriate message.
return Ok(new { message = "Exame added successfully" });
}
public async Task<int> CreateExame(ExameRequest model)
{
// removed other code
return await _context.SaveChangesAsync();
}
Since the call to SaveChangesAsync() is async, the code is making the call and moving straight back to the api call and returning OK. The result of the SaveChangesAsync is as follows.
A task that represents the asynchronous save operation. The task
result contains the number of state entries written to the underlying
database. This can include state entries for entities and/or
relationships. Relationship state entries are created for many-to-many
relationships and relationships where there is no foreign key property
included in the entity class (often referred to as independent
associations).
I have the same problem as I am following a course.
it is probably a versioning problem, cause my code is exactly the same as the instructor but I can't get the result I want.
I think it is because of Microsoft.EntityFrameworkCore.InMemory version.
the 5th version apparently don't have this problem but 7 does.

Nested one-to-many tables SQLite-Net Extensions [duplicate]

I am trying to use SQLite-Net Extensions to create a Relational Database. I'm running into an issue when trying to pull the Term object from the database. It successfully pulls over its associated courses, but not the courses associated assessments and notes. I'm not sure if the problem lies in how I insert the objects into the database, how I pull the objects from the database, or how I have the objects attributes listed.
I feel like the SQLite-Net Extensions documentation is extremely limited, so I'm not even sure what's going on. I've tried it many different ways, including adding CascadeOperations, but non of those seemed to help.
Here is the (simplified) code for my objects:
[Table("Terms")]
public class Term
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Course> Courses { get; set; }
public Term() { }
public Term(string name, List<Course> courses)
{
Name = name;
Courses = courses;
}
Courses
[Table("Courses")]
public class Course
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
[ForeignKey(typeof(Term))]
public int TermID { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Assessment> Assessments { get; set; }
[OneToMany]
public List<Note> Notes { get; set; }
public Course() { }
public Course(string name, List<Assessment> assessments, List<Note> notes)
{
Name = name;
Assessments = assessments;
Notes = notes;
}
}
Assessments
[Table("Assessments")]
public class Assessment
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
[ForeignKey(typeof(Course))]
public int CourseID { get; set; }
public string Name { get; set; }
public Assessment() { }
public Assessment(string name)
{
Name = name;
}
}
Notes
[Table("Notes")]
public class Note
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
[ForeignKey(typeof(Course))]
public int CourseID { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public Note() { }
public Note(string name, string note)
{
Name = name;
Text = note;
}
}
And here is the code for inserting and getting objects:
Inserting
public bool SaveTermAsync(Term term)
{
if (term.ID != 0)
{
_database.UpdateWithChildrenAsync(term);
return true;
}
else
{
foreach (var course in term.Courses)
{
foreach (var assessment in course.Assessments)
{
_database.InsertAsync(assessment);
}
foreach (var note in course.Notes)
{
_database.InsertAsync(note);
}
_database.InsertAsync(course);
}
_database.InsertAsync(term);
_database.UpdateWithChildrenAsync(term);
return false;
}
}
Getting
public Task<List<Term>> GetTermsAsync()
{
return _database.GetAllWithChildrenAsync<Term>();
}
I know it's a bit of a code dump, but I have no idea where or what could be going wrong. If anyone could give any information about what is potentially going wrong, that would be awesome. Perhaps I'm simply expecting something to happen that isn't actually how it works. I don't know.
Also, if anyone has any links to some better documentation than https://bitbucket.org/twincoders/sqlite-net-extensions/src/master/ that would be awesome
EDIT
I tried using Cascading Options as well, CascadeRead, CascadeInsert, and CascadeAll. Using CascadeInsert or CascadeAll with _database.InsertWithChildrenAsync(term, true) resulted in a crash. The crash does not provide any error messages, and even wrapping the InsertWithChildren with a try catch block didn't work. Removing the recursive bool caused the program not to crash, and actually get the closest to what I'm looking for. Assessments and Notes are no longer null, but are still empty. Here's my updated code:
Saving and Getting:
public async Task<List<Term>> GetTermsAsync()
{
return await _database.GetAllWithChildrenAsync<Term>(recursive: true);
}
public async void SaveTermAsync(Term term)
{
if (term.ID != 0)
{
await _database.UpdateWithChildrenAsync(term);
}
else
{
//Trying this with recursion results in crash
await _database.InsertWithChildrenAsync(term);
}
}
One-To-Many Relationships:
//In Term
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Course> Courses { get; set; }
//In Courses
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Assessment> Assessments { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Note> Notes { get; set; }
Also, I forgot to include last time how I'm populating the tables in the first place.
public bool CreateTables()
{
_database.CreateTableAsync<Term>().Wait();
_database.CreateTableAsync<Course>().Wait();
_database.CreateTableAsync<Assessment>().Wait();
_database.CreateTableAsync<Note>().Wait();
return true;
}
public Task<int> ClearTablesTest()
{
_database.DropTableAsync<Term>();
_database.DropTableAsync<Course>();
_database.DropTableAsync<Assessment>();
return _database.DropTableAsync<Note>();
}
async public Task<int> PopulateTestData()
{
await ClearTablesTest();
CreateTables();
Term term = new Term("Test Term", true, DateTime.Now, DateTime.Now.AddDays(10),
new List<Course>
{
new Course("Test Course", CourseStatus.Completed, "Guys Name", "(999)-999-9999", "email#gmail.com", 6, DateTime.Now, DateTime.Now.AddDays(10),
new List<Assessment>
{
new Assessment("Test Assessment", AssessmentType.Objective, false, DateTime.Now, DateTime.Now.AddDays(10))
},
new List<Note>
{
new Note("Test Note", "This is a test note.")
})
});
App.Database.SaveTermAsync(term);
return 0;
}
I finally figured out what was causing the crash as well as causing general confusion within SQLite-Net Extensions.
In my Assessment class, the property
public string BackgroundColor
{
get { return IsComplete ? "#558f45" : "Gray"; }
set { BackgroundColor = value; }
}
was causing the crash when recursion was used. I've been scouring the web for over two weeks looking for solutions to this issue, but haven't found anything similar to this. I submitted a bug report on the SQLite-Net Extensions bitbucket.
If anyone knows why this specific line would cause issues, I'd love to hear your input. Until then I'm going to mark this question as answered and continue work on my app.
Thanks #redent84 for your help thus far on this issue.

How to insert graph in Entity Framework with circular dependency

This is how database structure looks like:
Vehicle has lot of CanNetworks and each CanNetwork has lot of ECUs. And that would save perfectly if that was only I have.
But, each vehicle also has one special ECU called gatewayECU so problem happens with saving because entity framework core does not know how to handle that scenario. It needs to insert vehicle before inserting ecus, but how to insert vehicle when ecu is not inserted.
This is what I tried: ignore (delete, invalidate) gatewayecu field (column is nullable in database), then I insert whole graph and then update vehicle with gatewayEcuId field I stored in some variable before doing anything.
Solution is not pretty. How to handle this scenario.
public class Vehicle : BaseEntity
{
public Vehicle()
{
CANNetworks = new List<CANNetwork>();
}
public List<CANNetwork>? CANNetworks { get; set; }
public ECU? GatewayECU { get; set; } = default!;
public int? GatewayECUId { get; set; }
}
public class CANNetwork : BaseEntity
{
public CANNetwork()
{
ECUs = new List<ECU>();
}
public string Name { get; set; } = default!;
public ICollection<ECU>? ECUs { get; set; }
public int VehicleId { get; set; }
public Vehicle? Vehicle { get; set; } = default!;
}
public class ECU : BaseEntity
{
public int CANNetworkId { get; set; }
public CANNetwork? CANNetwork { get; set; } = default!;
}
This is ugly solution which I don't want:
public async Task<int> Insert(Vehicle vehicleDefinition, ECU vehicleGatewayECU)
{
var result = -1;
using (var transaction = _databaseContext.Database.BeginTransaction())
{
result = await Insert(vehicleDefinition);
if (vehicleGatewayECU != null)
{
var ecu = await _databaseContext.ECUs.FirstOrDefaultAsync(x => x.Name == vehicleGatewayECU.Name && vehicleDefinition.Name == x.CANNetwork.Vehicle.Name);
if (ecu != null)
{
vehicleDefinition.GatewayECUId = ecu.Id;
result = await Update(vehicleDefinition);
transaction.Commit();
return result;
}
}
else
{
transaction.Commit();
}
}
return result;
}
EDIT:
I am now thinking about changing table structure in a way to get rid of gatewayECU field on Vehicle, and put some flag IsGatewayEcu in ECU table

How should I update a table that is joined with another table in Entity Framework?

I have the following entities:
namespace SomeDataAccess
{
public partial class Patch
{
public int PatchID { get; set; }
public double Number { get; set; }
}
public partial class PatchFile
{
public int FileID { get; set; }
public int PatchID{ get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
}
And I have the following api model:
namespace Web_API.Models
{
[Table("SomeFiles")]
public class SomeFilesViewModel
{
[Key]
public int FileId { get; set; }
public int PatchNumber{ get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
}
The GET method is implemented successfully as following:
/ GET: api/SomeFiles/5
[ResponseType(typeof(SomeFileViewModel))]
public async Task<IHttpActionResult> GetSomeFileViewModel(int id)
{
var patchFile = await _context.PatchFile.FindAsync(id);
return someFile == null
? (IHttpActionResult)NotFound()
: Ok(new someFileViewModel
{
FileId = patchFile.FileID,
PatchNumber = patch.Number,
Name = patchFile.Name,
Type = patchFile.Type,
});
}
Thus far, I have implemented the PUT method as following:
// PUT: api/SomeFiles
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutSomeFileViewModel(SomeFilesViewModel someFileViewModel)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var file = new SomeDataAccess.PatchFile
{
FileID = someFileViewModel.FileId,
PatchID = _context.Patch.FirstOrDefault(i => i.Number == someFileViewModel.PatchNumber).PatchID
// How to get the relavent patch id by having the patch Number?
Name = someFileViewModel.Name,
Type = someFileViewModel.Type
};
_context.Entry(file).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!FileExists(file.FileID))
return NotFound();
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
And a sample payload:
Sample PayLoad:
{
"FileId" = 4
"PatchNumber" = 894
"Name" = "MyFile.exe"
Type = "Application"
}
How can I update or add a record to PatchFile entity if I only have the PatchNumber and not the PatchId to prevent conflicted with the FOREIGN KEY constraint?
_context.Patch.FirstOrDefault(i => i.Number == someFileViewModel.PatchNumber).PatchID
Is above the correct approach? If yes, Is this not making another trip to database? Is there a better approach?
You could add the PatchID to the SomeFilesViewModel along the PatchNumber. Otherwise there will be this extra query to the DB. On the other hand: this might create another possible problem, as the sent data don't have to be accurate and you'll need to check/validate it and that would be another trip to DB.
If you decide to stick with the extra query I would suggest rewriting it as following:
_context.Patch.Where(i => i.Number == someFileViewModel.PatchNumber).Select(i => i.PatchID).FirstOrDefault();
That way you get only the ID from the DB; assuming you don't need to work with other parts of your Patch object.

How to work only with the Entity Framework Scaffolded Models from Database

I have been working with .Net Core Entity Framework database first approach with the Scaffolding technique.
It generated me a couple Models/Classes from my Database Tables, but for now, I will just minimize the issue I am having to this two tables... a relation one to many on the both ChampionID column:
So, after scaffolding/mapping the models with EntityCore Tools it generated the following two classes (and several others that are not relevant):
Champion.cs:
public partial class Champion
{
public Champion()
{
ChampionScreenshot = new HashSet<ChampionScreenshot>();
ChampionUser = new HashSet<ChampionUser>();
ChampionUserRate = new HashSet<ChampionUserRate>();
}
public int ChampionId { get; set; }
public string Name { get; set; }
public string Nickname { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public byte AttackDamageScore { get; set; }
public byte AbilityPowerScore { get; set; }
public byte ResistanceScore { get; set; }
public byte PlayingDifficult { get; set; }
public int PrimaryClassId { get; set; }
public int SecondaryClassId { get; set; }
public ChampionClass PrimaryClass { get; set; }
public ChampionClass SecondaryClass { get; set; }
public ICollection<ChampionScreenshot> ChampionScreenshot { get; set; }
public ICollection<ChampionUser> ChampionUser { get; set; }
public ICollection<ChampionUserRate> ChampionUserRate { get; set; }
}
ChampionScreenshot.cs:
public partial class ChampionScreenshot
{
public int ChampionScreenshotId { get; set; }
public string ImagePath { get; set; }
public int ChampionId { get; set; }
public Champion Champion { get; set; }
}
My doubt is: what is the correct way to retrieve a Champion object with the ChampionScreenshot attribute filled?
For example, this is what I am doing in my Service layer:
public async Task<Champion> GetChampion(int id)
{
Champion champion = await _context.Champion.FirstAsync(m => m.ChampionId == id);
champion.ChampionScreenshot = _context.ChampionScreenshot.ToListAsync().Result.FindAll(m => m.ChampionId == champion.ChampionId);
return champion;
}
So I am basically getting a specific Champion and then filling the ChampionScreenshot attribute (which is also a Class) separately, but the thing is that inside my ChampionScreenshot there is also a Champion class attribute which fully loads once again:
Which is obviously generating an error once it is exposed in the endpoint of the Restful Service:
[Produces("application/json")]
[Route("api/Champions")]
public class ChampionsController : Controller
{
[HttpGet("{id}")]
public async Task<IActionResult> GetChampion([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var champion = await _service.GetChampion(id);
if (champion == null)
{
return NotFound();
}
return Ok(champion);
}
...
Error:
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'champion' with type 'ChampionsService.Models.Champion'. Path 'championScreenshot[0]'.
So, I was thinking in just creating my custom model and fill it with the data extracted from my DbContext instead of returning the models already created but I really think that there should be a way to fully use only the mapped Models, I was wondering that...
Champion references itself:
Champion > multiple ChampionScreenshot > Champion (back to the original object)
That's easy to solve:
return Json(champion, new JsonSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
Or you could do it for the entire application:
services.AddMvc().AddJsonOptions(opts =>
{
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
And then just:
return Json(champion);
The following troubles me, though:
Champion champion = await _context.Champion.FirstAsync(m => m.ChampionId == id);
champion.ChampionScreenshot = _context.ChampionScreenshot.ToListAsync().Result.FindAll(m => m.ChampionId == champion.ChampionId);
You are saying "go to the database, download every single championscreenshot and find the ones I want through an in-memory search". That's not only horrible slow, it also wastes a lot of resources in your application and in the database. For including data, you use Include:
Champion champion = await _context.Champion
.Include(x => x.ChampionScreenshot)
.FirstAsync(x => x.ChampionId == id);
(this says "go to the database and bring me the champion but also include all the ChampionScreenshot that correspond, through an inner join).

Categories

Resources