Entity Framework won't add children to parent object - c#

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.

Related

Deleting missing descendant on Update

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);

How to track identity changes in EF Core?

I have a service in my application which creates a User, and saves it to the database. Here's a method of my service:
public async Task<UserDTO> CreateUserAsync(User newUser)
{
var result = (UserDTO)await _userRepository.CreateUserAsync(newUser);
if (result != null)
{
await _userRepository.SaveChangesAsync();
}
return result;
}
And a method from a UserRepository:
public async Task<User> CreateUserAsync(User newUser) => (await _dbContext.AddAsync(newUser)).Entity;
Here's a User class:
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The problem is when the user is being added via service, UserId is not known yet. It has default value 0, then ef core saves it to a database, finding a proper UserId. But value returned by my methods has no UserId updated - it is still 0, and i would like to return updated value. How to achieve that in a proper way?
newUser WILL have an Id read from the database.
Your code is casting from User to UserDTO, which is unlikely to work.

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).

Unable to determine the principal end of the relationship ASP.NET MVC 5

I'm currently building an ASP.NET MVC 5 application with EF 6.1.3 linked to an SQL Server database.
My issue: I receive the following error when I attempt to save 2 or more instantiations of "PurchaseInvoiceSplitsViewModel", no issue when only 1 saved.
Unable to determine the principal end of the 'WebAS.Models.PurchaseInvoiceSplits_VehicleStock' relationship.
Multiple added entities may have the same primary key.
The issue arises at line _context.SaveChanges();
My ViewModel:
public class PurchaseInvoiceHeaderFormViewModel
{
public PurchaseInvoiceHeaderFormViewModel()
{
Splits = new List<PurchaseInvoiceSplitsViewModel>();
}
public List<PurchaseInvoiceSplitsViewModel> Splits { get; set; }
}
public class PurchaseInvoiceSplitsViewModel
{
public PurchaseInvoiceSplits PurchaseInvoiceSplits { get; set; }
public VehicleInformation VehicleInformation { get; set; }
public VehicleStock VehicleStock { get; set; }
}
My Models:
public class VehicleStock
{
[Key]
public int VehicleStockId { get; set; }
public int VehicleInformationId { get; set; }
public VehicleInformation VehicleInformation { get; set; }
}
public class PurchaseInvoiceSplits
{
[Key]
public int PurchaseInvoiceSplitsId { get; set; }
public int? VehicleStockId { get; set; }
public VehicleStock VehicleStock { get; set; }
}
public class VehicleInformation
{
[Key]
public int VehicleInformationId { get; set; }
}
Controller:
[HttpPost]
public ActionResult Save(PurchaseInvoiceHeaderFormViewModel viewModel)
{
_context.PurchaseInvoiceHeader.Add(viewModel.PurchaseInvoiceHeader);
foreach (var Split in viewModel.Splits)
{
Split.PurchaseInvoiceSplits.VehicleStockId =
Split.VehicleStock.Id;
Split.VehicleStock.VehicleInformationId =
Split.VehicleInformation.Id;
_context.VehicleInformation.Add(Split.VehicleInformation);
_context.VehicleStock.Add(Split.VehicleStock);
_context.PurchaseInvoiceSplits.Add(Split.PurchaseInvoiceSplits);
}
_context.SaveChanges();
return RedirectToAction("xxx", "xxx");
}
I have spent ages looking into this and I believe it is related to EF assigning temporary Id's of 0 to the models. This is fine when there is one model but appears to cause navigation/reference issues with multiple model instantiations. Answers to other forum posts suggest using temporary Id's which I have attempted but have not managed to get to work. Any help will be greatly appreciated. Please feel free to ask for further clarification/code snippets.
Issue resolved using Entity Framework Transactions (note, only appear to be available for version 6+). Very useful information found here:
Entity Framework Tutorials
MSDN turotials
Modifications only required within Controller:
[HttpPost]
public ActionResult Save(PurchaseInvoiceHeaderFormViewModel viewModel)
{
using (CustData Tcontext = new CustData())
{
using (DbContextTransaction transaction =
Tcontext.Database.BeginTransaction())
{
try
{
Tcontext.PurchaseInvoiceHeader.Add(viewModel.PurchaseInvoiceHeader);
Tcontext.SaveChanges();
foreach (var Split in viewModel.Splits)
{
Tcontext.VehicleInformation.Add(Split.VehicleInformation);
Split.VehicleStock.VehicleInformationId = Split.VehicleInformation.Id;
Tcontext.VehicleStock.Add(Split.VehicleStock);
Split.PurchaseInvoiceSplits.VehicleStockId = Split.VehicleStock.Id;
Split.PurchaseInvoiceSplits.PurchaseInvoiceNumberId = viewModel.PurchaseInvoiceHeader.Id;
Tcontext.PurchaseInvoiceSplits.Add(Split.PurchaseInvoiceSplits);
Tcontext.SaveChanges();
}
transaction.Commit();
}
catch (Exception ex)
{
Console.WriteLine("Error occurred." + ex);
}
}
}
return RedirectToAction("xxx", "xxx");
}
I believe that the order in which the .Add occurs is also important, hence the rearrange.
I had the same error and an unsimilar code setup, but...
I took a look at the above answer and noticed the comment at the bottom: I believe that the order in which the .Add occurs is also important, hence the rearrange.
What I did was arrange the saves in my code and was able to eliminate the error. I just thought I would post this for future error searches.

Retrieving parent/child records (object containing list) from Azure Mobile Services using EF

I have .net 4.5.2 test app playing about with Azure Mobile Services and I'm attempting to store data using the TableController. I have my data types as follows:
public class Run:EntityData
{
public int RunId { get; set; }
public DateTime? ActivityStarted { get; set; }
public DateTime? ActivityCompleted { get; set; }
public List<Lap> LapInformation { get; set; }
public Run()
{
LapInformation = new List<Lap>();
}
}
public class Lap
{
[Key]
public int LapNumber { get; set; }
public int CaloriesBurnt { get; set; }
public double Distance {get; set;}
//Some other basic fields in here
public DateTime? LapActivityStarted { get; set; }
public DateTime? LapActivityCompleted { get; set; }
public Lap()
{
}
In my Startup class I call:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
And in my MobileServiceContext class:
public class MobileServiceContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString2";
public MobileServiceContext() : base(connectionStringName)
{
}
public DbSet<Run> Runs { get; set; }
public DbSet<Lap> Laps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
In my controller then, I have:
[MobileAppController]
public class RunController: TableController<Run>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Run>(context, Request);
}
public IList<Run> GetAllRuns()
{
var runs = context.Runs.Include("LapInformation").ToList();
return runs;
}
public SingleResult<Run> GetRun(string id)
{
return Lookup(id);
}
public async Task<IHttpActionResult> PostRun(Run run)
{
Run current = await InsertAsync(run);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
public Task DeleteRun(string id)
{
return DeleteAsync(id);
}
}
I can then POST a record in fiddler which responds with a 201 and the Location of the newly created Item. An Example of the data I'm posting is:
{RunId: 1234, LapInformation:[{LapNumber:1,Distance:0.8, LapActivityStarted: "2017-06-19T00:00:00", LapActivityCompleted: "2017-06-19T00:00:00", CaloriesBurnt: 12}]}
However, when I GET that object, I'm only getting the fields from Run, without the list of Detail records (Lap). Is there anything I have to configure in Entity Framework so that when I GET a Run record from the DB, it also gets and deserializes all associated detail records?
Hopefully that makes sense.
EDIT
Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
You can use custom EF query with Include() method instead of Lookup call preferably overload that takes function from System.Data.Entity namespace.
var runs = context.Runs.Include(r => r.LapInformation)
Take a look at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
AFAIK, you could also use the $expand parameter to expand your collections as follows:
GET /tables/Run$expand=LapInformation
Here is my sample, you could refer to it:
You could mark your action with a custom ActionFilterAttribute for automatically adding the $expand property to your query request as follows:
// GET tables/TodoItem
[ExpandProperty("Tags")]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
For more details, you could refer to adrian hall's book chapter3 relationships.
EDIT Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
I defined the following models in my mobile client:
public class TodoItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public List<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string TagName { get; set; }
}
After execute the following pull operation, I could retrieve the tags as follows:
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
Note: The Tags data is read-only, you could only update the information in the ToDoItem table.
Additionally, as adrian hall mentioned in Data Access and Offline Sync - The Domain Manager:
I prefer handling tables individually and handling relationship management on the mobile client manually. This causes more code on the mobile client but makes the server much simpler by avoiding most of the complexity of relationships.

Categories

Resources