AutoMapper with EF Core doesn't return OData #count correctly if using $top - entity-framework

I'm using the solution provided by this link: AutoMapper don't work with entity EF Core
My problem is when using $top, #odata.count always return the number informed in $top but should return the number of total record.
I know that ODataQueryOptions has a property “Count”, but I don't know if it's possible to use to solve the problem
I'm using the below the code provided by Дмитрий Краснов in his question including the solution by Ivan Stoev:
There is entities:
public class LessonCatalog {
public string Name { get; set; }
public int? ImageId { get; set; }
public virtual Image Image { get; set; }
public virtual ICollection<Lesson> Lessons { get; set; }
}
public class Lesson {
public string Name { get; set; }
public string Description { get; set; }
public int? ImageId { get; set; }
public virtual Image Image { get; set; }
public int LessonCatalogId { get; set; }
public virtual LessonCatalog LessonCatalog { get; set; }
}
Views:
public class LessonView {
public string Name { get; set; }
public string Description { get; set; }
public int? ImageId { get; set; }
public ImageView Image { get; set; }
public int LessonCatalogId { get; set; }
public LessonCatalogView LessonCatalog { get; set; }
}
public class LessonCatalogView {
public string Name { get; set; }
public int? ImageId { get; set; }
public ImageView Image { get; set; }
public IEnumerable<LessonView> Lessons { get; set; }
}
My maps:
CreateMap<LessonCatalog, LessonCatalogView>()
.ForMember(dest => dest.Image, map => map.ExplicitExpansion())
.ForMember(dest => dest.Lessons, map => map.ExplicitExpansion());
CreateMap<Lesson, LessonView>()
.ForMember(dest => dest.LessonCatalog, map => map.ExplicitExpansion());
In my repository:
protected readonly DbContext _context;
protected readonly DbSet<TEntity> _entities;
public Repository(DbContext context) {
_context = context;
_entities = context.Set<TEntity>();
}
public IEnumerable<TView> GetOData<TView>(ODataQueryOptions<TView> query,
Expression<Func<TEntity, bool>> predicate = null) {
IQueryable<TEntity> repQuery = _entities.AsQueryable();
IQueryable res;
if (predicate != null) repQuery = _entities.Where(predicate);
if (query != null) {
string[] expandProperties = GetExpands(query);
//!!!
res = repQuery.ProjectTo<TView>(Mapper.Configuration, null, expandProperties);
//!!!
var settings = new ODataQuerySettings();
var ofilter = query.Filter;
var orderBy = query.OrderBy;
var skip = query.Skip;
var top = query.Top;
if (ofilter != null) res = ofilter.ApplyTo(res, settings);
if (orderBy != null) res = orderBy.ApplyTo(res, settings);
if (skip != null) res = skip.ApplyTo(res, settings);
if (top != null) res = top.ApplyTo(res, settings);
} else {
res = repQuery.ProjectTo<TView>(Mapper.Configuration);
}
return (res as IQueryable<TView>).AsEnumerable();
}
If my query result has 1007 records, and I use
…$count=true&$top=5
the result for count should be
"#odata.count": 1007
But instead the result is always
"#odata.count": 5
Using SQL Server Profile I can see that the Select for count is including the “top”. So, how to avoid this to happen?

I received a help from the Github Guy (thanks to #Gennady Pundikov) and could now answer this question.
I changed the GetOData Method to get the Count before apply others settings:
public IEnumerable<TView> GetOData<TView>(ODataQueryOptions<TView> query,
Expression<Func<TEntity, bool>> predicate = null) {
IQueryable<TEntity> repQuery = _entities.AsQueryable();
IQueryable res;
if (predicate != null) repQuery = _entities.Where(predicate);
if (query != null) {
string[] expandProperties = GetExpands(query);
//!!!
res = repQuery.ProjectTo<TView>(Mapper.Configuration, null, expandProperties);
//!!!
var settings = new ODataQuerySettings();
var ofilter = query.Filter;
var orderBy = query.OrderBy;
var skip = query.Skip;
var top = query.Top;
if (ofilter != null) res = ofilter.ApplyTo(res, settings);
if (query.Count?.Value == true)
{
// We should calculate TotalCount only with filter
// http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html#_Toc371341773
// 4.8 Addressing the Count of a Collection
// "The returned count MUST NOT be affected by $top, $skip, $orderby, or $expand.
query.Request.ODataFeature().TotalCount = ((IQueryable<TView>)res).LongCount();
}
if (top != null) res = top.ApplyTo(res, settings);
if (orderBy != null) res = orderBy.ApplyTo(res, settings);
if (skip != null) res = skip.ApplyTo(res, settings);
} else {
res = repQuery.ProjectTo<TView>(Mapper.Configuration);
}
return (res as IQueryable<TView>).AsEnumerable();
}

Related

EF Core 6 updated data retrieve problem with multiple repositories

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

How to insert payload data in many-to-many relationships with EF Core 5

I have this relationship between Licitadores and Ofertas
public class Licitador
{
public int Id { get; set; }
public string Nombre { get; set; }
[StringLength(maximumLength: 15)]
public string CodigoSAP { get; set; }
public List<Oferta> Ofertas { get; set; } = new List<Oferta>();
}
public class Oferta
{
[StringLength(maximumLength:6)]
public string Id { get; set; }
[StringLength(maximumLength: 5)]
public string IdPresentada { get; set; }
....
public List<Licitador> Licitadores { get; set; } = new List<Licitador>();
}
And the join table in the context
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LicitacionesEnSolitario>().ToTable("LicitacionesSolitario");
modelBuilder.Entity<Licitador>()
.HasMany(o => o.Ofertas)
.WithMany(of => of.Licitadores)
.UsingEntity<LicitacionesEnSolitario>
(oo => oo.HasOne<Oferta>().WithMany(),
oo => oo.HasOne<Licitador>().WithMany())
.Property(oo => oo.Adjudicado)
.IsRequired();
}
I need this data in my entity/table LicitacionesEnSolitario in addition to PK y FK
public class LicitacionesEnSolitario
{
public int LicitadorId { get; set; }
public string OfertaId { get; set; }
public bool Adjudicado { get; set; }
public string Plazo { get; set; }
public decimal PresupuestoOfertado { get; set; }
public DateTime? FechaAdjudicacion { get; set; }
}
Here I insert the data importing them from another database
public int ImportarLicitacionesEnSolitario()
{
try
{
int registrosAñadidos = 0;
var registrosSAP = _contextSAP.LicitacionesEnSolitario
.FromSqlRaw("sql")
.ToList();
foreach (var registroSAP in registrosSAP)
{
var oferta = _contextBoletus.Ofertas.Find(registroSAP.OfertaId);
var licitador = _contextBoletus.Licitadores.Where(l => l.CodigoSAP == registroSAP.CodigoSAP).FirstOrDefault();
oferta.Licitadores.Add(licitador);
registrosAñadidos +=1;
}
_contextBoletus.SaveChanges();
return registrosAñadidos;
}
catch (Exception ex)
{
throw ex;
}
}
This works fine and insert data in "LicitacionesEnSolitario" but with this fields Adjudicado, Plazo, PresupuestoPfertado y FechaAdjudicacion with nulls.
I don't know how to insert them at the time I insert Licitadores and if I try to update after the Add method using the PKs I just added
foreach (var registroSAP in registrosSAP)
{
var oferta = _contextBoletus.Ofertas.Find(registroSAP.OfertaId);
var licitador = _contextBoletus.Licitadores.Where(l => l.CodigoSAP == registroSAP.CodigoSAP).FirstOrDefault();
oferta.Licitadores.Add(licitador);
var ls = _contextBoletus.Set<LicitacionesEnSolitario>()
.SingleOrDefault(ls => ls.OfertaId == oferta.Id & ls.LicitadorId == licitador.Id);
ls.Adjudicado = registroSAP.Adjudicado;
ls.PresupuestoOfertado = registroSAP.PresupuestoOfertado;
ls.FechaAdjudicacion = registroSAP.FechaAdjudicacion;
registrosAñadidos +=1;
}
_contextBoletus.SaveChanges();
return registrosAñadidos;
I get this error System.NullReferenceException: Object reference not set to an instance of an object.
Any idea, please?
Thanks
This is the best way I found
foreach (var registroSAP in registrosSAP)
{
var oferta = _contextBoletus.Ofertas.Find(registroSAP.OfertaId);
var licitador = _contextBoletus.Licitadores.Where(l => l.CodigoSAP == registroSAP.CodigoSAP).FirstOrDefault();
var ls = _contextBoletus.Set<LicitacionesEnSolitario>().Add(
new LicitacionesEnSolitario
{
LicitadorId = licitador.Id,
OfertaId = oferta.Id,
Adjudicado = registroSAP.Adjudicado,
Plazo = registroSAP.Plazo,
PresupuestoOfertado = registroSAP.PresupuestoOfertado,
FechaAdjudicacion = registroSAP.FechaAdjudicacion
});
registrosAñadidos += 1;
}
Thanks

issue when adding an item to a many to many relationship .net core

i have a course management app and i have an issue when i try to add a student to a course studentsList.
when save changes run the student list changes from a list which contains old elements+ the new element to a list with only the new element (the last student added) and then saves it to database.
using EntityFramework 5.0.0
relationship declaration and seeding in context OnModelCreating()
modelBuilder.Entity<Course>().HasMany<Student>(c => c.StudentsList).WithMany(s => s.Courses).UsingEntity(t => t.HasData(
new { CoursesCourseId = Convert.ToInt64(1), StudentsListStudentId="id1"},
new { CoursesCourseId = Convert.ToInt64(1), StudentsListStudentId="id2"},
new { CoursesCourseId = Convert.ToInt64(1), StudentsListStudentId="id3"}
));
models
public class Course
{
public long CourseId { get; set; }
public string CourseName { get; set; }
public IEnumerable<Lesson> LessonsList { get; set; }
public DateTime CourseStartDate { get; set; }
public DateTime CourseEndDate { get; set; }
public List<Student> StudentsList { get; set; }
public class Student
{
[Key]
public string StudentId { get; set; }
public List<Course> Courses { get; set; }
}
controller action
[HttpPatch("{id}")]
public async Task<IActionResult> RegisterStudentToCourse(long id, [FromBody] List<string> studentsIds)
{
Course course = await _courseRepo.GetCourse(id);
if (course == null || !studentsIds.Any())
return BadRequest();
IEnumerable<Lesson> list = await _lessonRepo.GetLessons(id);
List<Lesson> lessonsList = list.ToList();
List<Student> studentsList = course.StudentsList.ToList();
foreach (string studentId in studentsIds)
{
Student student = _studetRepo.GetStudent(studentId);
if (student == null)
return BadRequest();
if (studentsList.Contains(student))
continue;
//HERE IS THE CALL TO THE REPOSITORY ADDSTUDENT
await _courseRepo.AddStudent(id, studentId);
foreach (Lesson l in lessonsList)
{
Attendance attendance = await _attendanceRepo.CreateAttandance(studentId);
await _lessonRepo.AddAttendance(l.LessonId, attendance);
}
}
_context.SaveChanges();
return Ok();
}
repository function
public async Task<bool> AddStudent(long id, string studentId)
{
Course course = await _context.Courses.Include(c => c.StudentsList).FirstOrDefaultAsync(c => c.CourseId == id);
Student student = await _context.Students.FirstOrDefaultAsync(s => s.StudentId == studentId);
if (course == null || student==null)
return false;
course.StudentsList.Add(student);
return await _context.SaveChangesAsync() > 0;
}

DotNetCore Entity Framework | Generic Pagination

I was looking for a way to do generic pagination with Entity Framework in Dotnet Core 1.1.
I found this guide on MSDN: https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application
But this was not generic and did not allow me to reuse code.
Included is the answer I used if anyone is looking into this, I thought it would be nice to share.
It uses custom Attributes on models, and returns a pagination model.
EDIT:
The answer below is not correct due to the orderBy not translating into L2E correctly. All the records will be retrieved and sorted in memory what results into poor performance. Check comments for more information and posisble solution.
ORIGNAL:
My solution:
Model.cs:
public class User
{
// Sorting is not allowed on Id
public string Id { get; set; }
[Sortable(OrderBy = "FirstName")]
public string FirstName { get; set; }
}
SortableAttribute.cs:
public class SortableAttribute : Attribute
{
public string OrderBy { get; set; }
}
PaginationService.cs:
public static class PaginationService
{
public static async Task<Pagination<T>> GetPagination<T>(IQueryable<T> query, int page, string orderBy, bool orderByDesc, int pageSize) where T : class
{
Pagination<T> pagination = new Pagination<T>
{
TotalItems = query.Count(),
PageSize = pageSize,
CurrentPage = page,
OrderBy = orderBy,
OrderByDesc = orderByDesc
};
int skip = (page - 1) * pageSize;
var props = typeof(T).GetProperties();
var orderByProperty = props.FirstOrDefault(n => n.GetCustomAttribute<SortableAttribute>()?.OrderBy == orderBy);
if (orderByProperty == null)
{
throw new Exception($"Field: '{orderBy}' is not sortable");
}
if (orderByDesc)
{
pagination.Result = await query
.OrderByDescending(x => orderByProperty.GetValue(x))
.Skip(skip)
.Take(pageSize)
.ToListAsync();
return pagination;
}
pagination.Result = await query
.OrderBy(x => orderByProperty.GetValue(x))
.Skip(skip)
.Take(pageSize)
.ToListAsync();
return pagination;
}
}
Pagination.cs (model):
public class Pagination<T>
{
public int CurrentPage { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public int TotalItems { get; set; }
public string OrderBy { get; set; }
public bool OrderByDesc { get; set; }
public List<T> Result { get; set; }
}
UserController.cs (inside controller), context is EntityFramework context:
[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery] string orderBy, [FromQuery] bool orderByDesc, [FromQuery] int page, [FromQuery] int size)
{
var query = _context.User.AsQueryable();
try
{
var list = await PaginationService.GetPagination(query, page, orderBy, orderByDesc, size);
return new JsonResult(list);
}
catch (Exception e)
{
return new BadRequestObjectResult(e.Message);
}
}
I hope this helps someone in the future !

Update list object based on other LINQ / LAMBDA

Here are my two objects
public class ObjectHeaderBuffer
{
public int DataObjectId { get; set; }
public string FileName { get; set; }
public int RowCount { get; set; }
public string Checksum { get; set; }
public int ReconTarget { get; set; }
}
public class ObjectHeaderAttribute
{
public int DataObjectId { get; set; }
public int AttributeType { get; set; }
public int AttributeValue { get; set; }
}
var ohBuffer = new List<ObjectHeaderBuffer>();
var ohAttribute = new List<ObjectHeaderAttribute>();
I want to update ohBuffer.ReconTarget with ohAttribute.AttributeValue where ohBuffer.DataObjectId == ohAttribute.DataObjectId
what is linq or lambda of this?
You need to iterate each item in ohBuffer and look up the value in ohAttribute.
Assuming there is only one Attribute for each Buffer, this will work.
ohBuffer.ForEach(b => b.ReconTarget = ohAttribute
.SingleOrDefault(a => a.DataObjectId == b.DataObjectId).AttributeValue);
If the lookup returns null, you can either coalesce to a new object and take the default value
ohBuffer.ForEach(b => b.ReconTarget =
(ohAttribute.SingleOrDefault(a => a.DataObjectId == b.DataObjectId)
?? new ObjectHeaderAttribute())
.AttributeValue);
or you could just take null
ohBuffer.ForEach(b => b.ReconTarget =
{
var attribute = ohAttribute
.SingleOrDefault(a => a.DataObjectId == b.DataObjectId);
if (attribute == null)
return null;
return attribute.AttributeValue;
});
They way i did is:
foreach (var objectHeaderBuffer in ohBuffer)
{
var objectHeaderAttribute = (from c in ohAttribute where c.DataObjectId == objectHeaderBuffer.DataObjectId select c).First();
objectHeaderBuffer.ReconTarget = objectHeaderAttribute.AttributeValue;
}
If your relation is 1:1 Then
foreach (ObjectHeaderBuffer Itemx in ohBuffer)
{
ObjectHeaderAttribute Itemy= (from ObjectHeaderAttribute c in ohAttribute where c.DataObjectId == Itemx.DataObjectId select c).FirstOrDefault();
if(Itemy!=null)
{
Itemx .ReconTarget = Itemy.AttributeValue;
}
}
Or
foreach (ObjectHeaderBuffer Itemx in ohBuffer)
{
ObjectHeaderAttribute Itemy= ohAttribute.Where(c=>c.DataObjectId == Itemx .DataObjectId).FirstOrDefault();
if(Itemy!=null)
{
Itemx .ReconTarget = Itemy.AttributeValue;
}
}