EF Core 6 updated data retrieve problem with multiple repositories - entity-framework-core

I am working on a Blazor Server Application that has a Radzen master-detail data grid. This data grid is populated with IsActive = 1 data OnInitializedAsync method.
Here is the Order repository and related query which retrieves active data:
namespace IMS.Plugins.EFCore
{
public class OrderRepository : IOrderRepository
{
private readonly IMSContext _db;
public OrderRepository(IMSContext db)
{
_db = db;
}
public async Task<IEnumerable<Order?>> GetAllOrders(ClaimsPrincipal user)
{
if (user.IsInRole("Administrators"))
{
return await _db.Orders.Include(d => d.OrderDetails.Where(od => od.IsActive == 1)).ThenInclude(v => v.Vendor).ToListAsync();
}
return await _db.Orders.Include(d => d.OrderDetails.Where(od => od.IsActive == 1)).ThenInclude(v => v.Vendor).ToListAsync();
}
}
}
Here is the Detail repository which sets the related order detail to IsActive = 0
namespace IMS.Plugins.EFCore
{
public class OrderDetailRepository : IOrderDetailRepository
{
private readonly IMSContext _db;
public OrderDetailRepository(IMSContext db)
{
_db = db;
}
public async Task PassiveOrderDetailAsync(OrderDetail orderDetail)
{
var detail = await this._db.OrdersDetail.FindAsync(orderDetail.Id);
if (detail != null)
{
detail.IsActive = 0; // 0-Passive
await _db.SaveChangesAsync();
}
}
}
}
Here is the master-detail data grid populated OnInitializedAsync method. By the way, this part is working. (gets IsActive = 1)
protected override async Task OnInitializedAsync()
{
user = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User;
//userName = user.Identity.Name;
if (!user.Identity.IsAuthenticated)
{
NavigationManager.NavigateTo("/Identity/Account/Login", false);
}
_orders = await ViewAllOrdersUseCase.ExecuteAsync(user);
SelectedOrders = new List<Order?> { _orders.FirstOrDefault() };
_vendors = await ViewAllVendorsUseCase.ExecuteAsync();
_customers = await ViewAllCustomersUseCase.ExecuteAsync();
}
The problem starts when I try to update a detail to IsActive = 0 as shown on the screenshot.
Related Blazor:
<RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Class="m-1" Click="#(args => PassiveDetail(detail))">
</RadzenButton>
Here is what I do in the related portion:
RadzenDataGrid<OrderDetail> _gridDetail;
IEnumerable<Order?> _orders = new List<Order?>();
...
async Task PassiveDetail(OrderDetail orderDetail)
{
if (orderDetail == _detailToInsert)
{
_detailToInsert = null;
}
await _gridDetail.UpdateRow(orderDetail);
await PassiveOrderDetailUseCase.ExecuteAsync(orderDetail);
_orders = await ViewAllOrdersUseCase.ExecuteAsync(user);
StateHasChanged();
}
Selected row updated successfully but when this line calls _orders = await ViewAllOrdersUseCase.ExecuteAsync(user); it still gets the old data IsActive = 0. I couldn't find out why? However, OnInitializedAsync method works as excepted. Frankly, I couldn't solve.
Edit 1
Is it because there are 2 separate repositories for both order and order details? They are having their own insert and updates?
Edit 2
I should have added earlier Order and OrderDetail entities:
public class Order
{
public int Id { get; set; }
[Required]
public DateTime OrderDateTime { get; set; }
[Required]
[MaxLength(250)]
public int CustomerId { get; set; }
public string Status { get; set; }
[MaxLength(50)]
public string DoneBy { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
public Customer Customer { get; set; }
}
public class OrderDetail
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string ProductCode { get; set; }
[Required]
[MaxLength(250)]
public string ProductName { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
public double BuyUnitPrice { get; set; }
public double CostRatio { get; set; }
public double UnitCost { get; set; }
public double TotalBuyPrice { get; set; }
public double? SellUnitPrice { get; set; }
public double? TotalSellPrice { get; set; }
[MaxLength(150)]
public string? ShippingNumber { get; set; }
public string? Status { get; set; }
[MaxLength(150)]
public string? TrackingNumber { get; set; }
[MaxLength(400)]
public string? Description { get; set; }
public string? Currency { get; set; }
public string? CustomerStockCode { get; set; }
public string? CustomerOrderNumber { get; set; }
public int IsActive { get; set; }
public double? TotalUnitCost { get; set; }
public int OrderId { get; set; }
public int VendorId { get; set; }
public Order Order { get; set; }
public Vendor Vendor { get; set; }
}
Edit 3
I think I found the suspect. The query below shouldn't get the IsActive=0 but it somehow gets! Any ideas for this situation?
Here is the query:
public async Task<IEnumerable<Order?>> GetAllOrders(ClaimsPrincipal user)
{
if (user.IsInRole("Administrators"))
{
return await _db.Orders.Include(d => d.OrderDetails.Where(od => od.IsActive == 1)).ThenInclude(v => v.Vendor).ToListAsync();
}
return await _db.Orders.Include(d => d.OrderDetails.Where(od => od.IsActive == 1)).ThenInclude(v => v.Vendor).ToListAsync();
}
The record is updated, why is the query doesn't work the way it is expected? First I am updating then I am querying IsActive = 1
await PassiveOrderDetailUseCase.ExecuteAsync(orderDetail); //sets to IsActive = 0
_orders = await ViewAllOrdersUseCase.ExecuteAsync(user); // calls GetAllOrders method above,should get only the actives IsActive = 1
But IsActive = 0 also comes inside of _orders as seen in the screenshot below.
Edit 4
When I add AsNoTracking() the query in my previous post gets only IsActive = 1 that is what I want but somehow doesn't get the Customer which is why I want to include it. Order have 2 children, Customer and OrderDetail. OrderDetail has one, Vendor. Couldn't manage to include Customer tough to the query.
This works so far.
Orders
.Include(d => d.OrderDetails.Where(od => od.IsActive == 1))
.ThenInclude(v => v.Vendor)
.Include(c => c.Customer)
.AsNoTracking()
.ToListAsync();

Related

Error adding an object to a field list Entity Framework

Sorry if I don't respect the conventions, I'm new here.
I have a problem in my learning of ASP.NET Core and Entity Framework.
I have bets and users that can have multiple bets. The bets can have no users (they are just displayed on the site and users add them to their account).
Here are my two models :
Bets :
public class Bets
{
public int Id { get; set; }
public string name { get; set; }
public string description { get; set; }
public double team1 { get; set; }
public double team2 { get; set; }
public string sport { get; set; }
public bool enabled { get; set; } // 1 = yes 2 = no
public virtual User user {get;set;}
public int? UserId {get;set;}
}
User :
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public double moneyBalance { get; set; }
public int admin { get; set; }
public virtual ICollection<Bets> bets {get; set;}
}
Then I tried to add a bet that already existed to a user:
public async Task<bool> addUserBet(int id, Bets bet)
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (!(user.bets == bet))
{
user.bets.Add(bet);
await _context.SaveChangesAsync();
return true;
}
return false;
}
The controller :
[AllowAnonymous]
[HttpPut("addBetUser/{id}")]
public async Task<IActionResult> AddBetUser(int id, BetForAddToUserDto betForAddTo)
{
Bets bets = new Bets();
_mapper.Map(betForAddTo, bets);
await _repo.addUserBet(id, bets);
await _repo.SaveAll();
return StatusCode(200);
}
But I get this error in Postman when I try the endpoint above:
What am I doing wrong that I don't understand? Please help me, thank you :)
I tried to add a bet that already existed to a user
If you have existed a bet and a user.And you want to add this bet to the user.
Here is a working demo like below:
1.Model:
public class Bets
{
public int Id { get; set; }
public string name { get; set; }
public string description { get; set; }
public double team1 { get; set; }
public double team2 { get; set; }
public string sport { get; set; }
public bool enabled { get; set; } // 1 = yes 2 = no
public virtual User user { get; set; }
public int? UserId { get; set; }
}
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public double moneyBalance { get; set; }
public int admin { get; set; }
public virtual IList<Bets> bets { get; set; }
}
public class BetForAddToUserDto
{
public int Id { get; set; }
public string name { get; set; }
public string description { get; set; }
public double team1 { get; set; }
public double team2 { get; set; }
public string sport { get; set; }
public bool enabled { get; set; } // 1 = yes 2 = no
public virtual User user { get; set; }
public int? UserId { get; set; }
}
2.Controller:
public async Task<bool> addUserBet(int id, Bets bet)
{
var user = await _context.User.FirstOrDefaultAsync(u => u.UserId == id);
if (!(user.bets == bet))
{
bet.UserId = id;
_context.Bets.Update(bet);
await _context.SaveChangesAsync();
return true;
}
return false;
}
[AllowAnonymous]
[HttpPut("addBetUser/{id}")]
public async Task<IActionResult> PutUser(int id, BetForAddToUserDto betForAddTo)
{
Bets bets = new Bets();
_mapper.Map(betForAddTo, bets);
var bet = _context.Bets.Find(bets.Id);
await addUserBet(id, bet);
return StatusCode(200);
}
3.Profile:
public class AutoMapping: Profile
{
public AutoMapping()
{
CreateMap<Bets, BetForAddToUserDto>().ReverseMap();
}
}
4.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AutoMapping));
}
This:
if (!(user.bets == bet))
User.bets is a collection, you cannot compare it to a single bet
The right way to check it would be:
if (!(user.bets.Any(b => b.ID == bet.ID)))
Also you are not passing in anything for this parameter:
BetForAddToUserDto betForAddTo

How to bring name from another context by using foreign key?

I'm tying to write an EntityFramework query to bring hospital name by hospital ID from Hospitals Context to Departments context.I tried couple of things like join tables etc. but I couldn't complete to write that correct query.Here my models and context below
Models
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
}
Context
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Hospital> Hospitals { get; set; }
public DbSet<Department> Departments { get; set; }
}
Above you can see that model Department has HospitalId to connect Hospital table.After join I want to get that Hospital Name where department belongs to.Result should be department ID,department Name and its Hospital Name .
My Final Try
public async Task<IEnumerable<Department>> GetDepartment(string input)
{
var departmentWithHospital = _context.Departments
.Where(d => d.Hospital.Id == d.HospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
});
return await departmentWithHospital;
// Compiler Error:doesnt contain a definition for GetAwaiter and no
//accesible extension for GetAwaiter....
}
Three points to note:
1.The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. like below:
var hospital =await _context.Hospitals.ToListAsync();
return hospital;
2.The relationships between Hospital and Department is one-to-many , you could refer to Relationships to design your model as follows:
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
3.You want to return a new object list which contains department ID,department Name and its Hospital Name, but your return type of the method is IEnumerable<Department> .So you could directly return a Department collection or define a ViewModel with the properties you want
Return type :IEnumerable<Department>
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId).ToListAsync();
return departmentWithHospital;
DepartmentWithHospital ViewModel
public class DepartmentWithHospital
{
public int departmentId { get; set; }
public string departmentName { get; set; }
public string hospitalName { get; set; }
}
public async Task<IEnumerable<DepartmentWithHospital>> GetDepartment(int hospitalId)
{
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId)
.Select(d => new DepartmentWithHospital
{
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
}).ToListAsync();
return departmentWithHospital;
}
You need a Hospital in your Departments class, and a collection of Departments in your Hospital class.
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public virtual ICollection<Department> Departments { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
For the query, try this (Been awhile since I messed with EF, and this is for EF6). I can't remember if you need the include or not, but this should get you an anonymous object with the properties you requested.
This code is not tested.
var departmentWithHospital = context.Departments
.Where(d => d.Hospital.Id == hospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.DepartmentName,
hospitalName = d.Hospital.HospitalName
})
.ToList();
If I understood your question correctly, you are looking for this:
var departmentId = "123";
var result = from department in _context.Departments
join hospital in _context.Hospitals
on hospital.Id equals department.HospitalId
where department.Id == departmentId
select new
{
DepartmentID = departmentId,
DepartmentName = department.Name,
HospitalName = hospital.Name
};

One API call to retrieve all items in the model

I created a simple web api using Net Core 2.2. I have this api controller below, that gets one particular dungeon.
It is returning a dungeon as JSON, but it's not returning the MonsterList associated with the dungeon.
So this is my controller:
// GET: api/DungeonLists/5
[HttpGet("{id}")]
public async Task<ActionResult<DungeonList>> GetDungeonList(Guid id)
{
var dungeonList = await _context.DungeonList.FindAsync(id);
if (dungeonList == null)
{
return NotFound();
}
return dungeonList;
}
And here is my model for the Dungeon. As you can see, it has a MonsterList.
public partial class DungeonList
{
public DungeonList()
{
MonsterList = new HashSet<MonsterList>();
}
public Guid DungeonId { get; set; }
public string DungeonName { get; set; }
public string DungeonDesc { get; set; }
public string MapArea { get; set; }
public bool ShowProgress { get; set; }
public bool? DungeonResumable { get; set; }
public virtual ICollection<MonsterList> MonsterList { get; set; }
}
Here is my MonsterList model:
public partial class MonsterList
{
public string MonsterId { get; set; }
public Guid DungeonId { get; set; }
public string MonsterName { get; set; }
public byte? MonsterType { get; set; }
public bool IsBossMonster { get; set; }
public virtual DungeonList Dungeon { get; set; }
}
I want the JSON to also show the list of monsters associated with the dungeon.
Is there a way to do this? Or would I need to make a separate API call?
Thanks!
You need to change your code to the following:
[HttpGet("{id}")]
public async Task<ActionResult<DungeonList>> GetDungeonList(Guid id)
{
var dungeonList = await _context.DungeonList
.Include(i => i.MonsterList)
.FirstOrDefaultAsync(p => p.Id = id);
if (dungeonList == null)
{
return NotFound();
}
return dungeonList;
}
Additionally, since you arent using LazyLoading, you dont need the [virtual] on the MonsterList collection

One to Optional One Relation not updating the Principal Entity on EF Core

I Run into this scenario. I have related entities: Case and a Confinement
A Case has an optional navigation to confinement - ConfinementId, while the confinement has a required CaseId.
My problem is when I try to update the Case, by attaching the Confinement Record, the confinementID on the case entity is not updated.
Here are my mapping and Entity Classes:
public class Confinement : Entity
{
public int Id { get; set; }
public string ReferenceId { get; set; }
public int CaseId { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsActive { get; set; }
public int BranchId { get; set; }
public string Status { get; set; }
public virtual Case Case {get;set;}
}
public class Case : Entity
{
public int CaseId { get; set; }
public string CaseReferenceId { get; set; }
public int? ConsultationId { get; set; }
public int? ConfinementId { get; set; }
public virtual Confinement Confinement { get; set; }
****Omitted
}
Configuration
public void Configure(EntityTypeBuilder<Case> builder)
{
builder.HasKey(c => c.CaseId);
builder.HasMany(t => t.Details)
.WithOne(t => t.Case)
.HasForeignKey(t => t.CaseId);
builder.HasOne(t => t.Confinement)
.WithOne(a=>a.Case)
.HasForeignKey<Confinement>(t => t.CaseId);
}
public void Configure(EntityTypeBuilder<Confinement> builder)
{
builder.HasKey(c => c.Id);
builder.HasOne(a => a.Case)
.WithOne(a => a.Confinement)
.HasForeignKey<Confinement>(a => a.CaseId);
}
Controller Code
public async Task<IActionResult> AddConfinement(int caseId)
{
if (caseId == 0)
return BadRequest();
if (_service.ExistingCase(caseId))
return BadRequest("Case has already a confinement record!");
var #case = await _caseService.FindAsync(caseId);
if (#case == null)
return NotFound("Case not found!");
var confinement = _converter.ConvertFromCase(#case);
confinement.ObjectState = ObjectState.Added;
#case.Confinement = confinement;
#case.ObjectState = ObjectState.Modified;
#case.ConfinementId = confinement.Id;
_caseService.Update(#case);
await _unitOfWork.SaveChangesAsync();
return Ok();
}
When the AddConfinement Method is called, the confinement is added on the database, but the Case.ConfinementId is not updated. Is there anything wrong to this implementation.
As a workaround, I created a trigger on the database but i want to accomplish this on the application level.

Check that product with those values does not already excist

In a WebAPI project i have the follwing controller;
public async Task<IHttpActionResult> PostProduct(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Ok(product);
}
based on this model
public class Product
{
public int Id { get; set; }
public int ExternalId { get; set }
public string Title { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Created { get; set; }
public string Description { get; set; }
public int ProductTypeId { get; set; }
public virtual ProductType IncidentType { get; set; }
public virtual ICollection<Manufacture> Manufactures { get; set; }
}
In my controller, i want to make a check, before it saves the product, that checks that the ExternalId togethr with the ProductTypeId does not excist already.
Meaning, there should only be one product, that has the ExternalId of 123 togethr with the ProductTypeId of 23.
How do i add this check, to my controller above?
public async Task<IHttpActionResult> PostProduct(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if(db.Products.Any(x => x.ExternalId == product.ExternalId && x.ProductTypeId == product.ProductTypeId))
{
return Conflict();
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Ok(product);
}
Although I wouldn't recommend in having that kind of logic in your controller.