Querying DTOs based on EF using Odata - asp.net-core

I have an ASP.NET Core Web API setup with a SQL Server database and an EF data model.
Versions:
EF: Microsoft.EntityFrameworkCore 5.0.0-preview.7.20365.15
OData: Microsoft.AspNetCore.OData 7.4.1
.Net Core 3.1
The question is: I can't use filter, select in expand (nested expand). Example URLs which OData does not add filters to the where condition of SQL query which is seen on SQL Server Profiler:
https://localhost:44327/odata/clientcontract?$expand=ContactsInfo($filter=value eq '100003265')
https://localhost:44327/odata/clientcontract?$expand=Documents($filter=documentnnumber eq '100003265')
These are my database-first entity models:
public partial class ClientRef
{
public ClientRef()
{
Addresses = new HashSet<Address>();
Assets = new HashSet<Asset>();
ClientContactInfoComps = new HashSet<ClientContactInfoComp>();
ClientRelationCompClient1Navigations = new HashSet<ClientRelationComp>();
ClientRelationCompClient2Navigations = new HashSet<ClientRelationComp>();
Clients = new HashSet<Client>();
CommentComps = new HashSet<CommentComp>();
Companies = new HashSet<Company>();
Documents = new HashSet<Document>();
PhysicalPeople = new HashSet<PhysicalPerson>();
}
[Column("Inn")]
public int Id { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Asset> Assets { get; set; }
public virtual ICollection<ClientContactInfoComp> ClientContactInfoComps { get; set; }
public virtual ICollection<ClientRelationComp> ClientRelationCompClient1Navigations { get; set; }
public virtual ICollection<ClientRelationComp> ClientRelationCompClient2Navigations { get; set; }
public virtual ICollection<Client> Clients { get; set; }
public virtual ICollection<CommentComp> CommentComps { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public virtual ICollection<Document> Documents { get; set; }
public virtual ICollection<PhysicalPerson> PhysicalPeople { get; set; }
}
public partial class Document
{
public int Id { get; set; }
public string DocumentNumber { get; set; }
public int DocumentType { get; set; }
public int Inn { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public DateTime? DocumentExpireDate { get; set; }
public virtual ClientRef InnNavigation { get; set; }
}
public partial class ClientContactInfoComp
{
public int Id { get; set; }
public int Inn { get; set; }
public int ContactInfoId { get; set; }
public int? Point { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
[ForeignKey("ContactInfoId")]
public ContactInfo ContactInfo { get; set; }
public virtual ClientRef InnNavigation { get; set; }
}
public partial class ContactInfo
{
public ContactInfo()
{
CallHistories = new HashSet<CallHistory>();
ClientContactInfoComps = new HashSet<ClientContactInfoComp>();
CommentComps = new HashSet<CommentComp>();
}
public int Id { get; set; }
public int? Type { get; set; }
public string Value { get; set; }
public virtual ICollection<CallHistory> CallHistories { get; set; }
public virtual ICollection<ClientContactInfoComp> ClientContactInfoComps { get; set; }
public virtual ICollection<CommentComp> CommentComps { get; set; }
}
public partial class CommentComp
{
public int Id { get; set; }
public int Inn { get; set; }
public int? CommentId { get; set; }
public int? ContactId { get; set; }
public virtual Comment Comment { get; set; }
public virtual ContactInfo Contact { get; set; }
public virtual ClientRef InnNavigation { get; set; }
}
public partial class Comment
{
public Comment()
{
CommentComps = new HashSet<CommentComp>();
}
public int Id { get; set; }
public string Text { get; set; }
public DateTime? CreateTimestamp { get; set; }
public string Creator { get; set; }
public virtual ICollection<CommentComp> CommentComps { get; set; }
}
These are my DTOs:
[DataContract]
public class ClientContract
{
public ClientContract()
{
ContactsInfo = new List<ContactInfoContract>();
Documents = new List<DocumentContract>();
Relations = new List<RelationContract>();
ClientComment = new CommentContract();
}
[DataMember(Name = "INN")]
[Key]
public int INN { get; set; }
[DataMember(Name = "validfrom")]
public DateTime ValidFrom { get; set; }
[DataMember(Name = "validto")]
public DateTime ValidTo { get; set; }
[DataMember(Name = "clienttype")]
public ClientType ClientType { get; set; }
[DataMember(Name = "companyname")]
public string CompanyName { get; set; }
[DataMember(Name = "firstname")]
public string FirstName { get; set; }
[DataMember(Name = "lastname")]
public string LastName { get; set; }
[DataMember(Name = "fathername")]
public string FatherName { get; set; }
[DataMember(Name = "pinnumber")]
public string PinNumber { get; set; }
[DataMember(Name = "birthdate")]
public DateTime? BirthDate { get; set; }
[DataMember(Name = "positioncustom")]
public string PositionCustom { get; set; }
[DataMember(Name = "position")]
public int Position { get; set; }
[DataMember(Name = "monthlyincome")]
public decimal MonthlyIncome { get; set; }
[DataMember(Name = "clientcomment")]
public CommentContract ClientComment { get; set; }
[DataMember(Name = "contactsinfo")]
public List<ContactInfoContract> ContactsInfo { get; set; }
[DataMember(Name = "documents")]
[ForeignKey("Documents")]
public List<DocumentContract> Documents { get; set; }
[DataMember(Name = "relations")]
public List<RelationContract> Relations { get; set; }
}
public class DocumentContract
{
[Key]
public int Id { get; set; }
[DataMember(Name = "documentNumber")]
public string documentNumber { get; set; }
[DataMember(Name = "documentType")]
public int documentType { get; set; }
[DataMember(Name = "documentexpiredate")]
public DateTime? documentExpireDate { get; set; }
}
[DataContract]
public class ContactInfoContract
{
public ContactInfoContract()
{
ContactComment = new CommentContract();
}
[Key]
public int Id { get; set; }
[DataMember(Name = "type")]
public int Type { get; set; }
[DataMember(Name = "value")]
public string Value { get; set; }
[DataMember(Name = "contactComment")]
public CommentContract ContactComment { get; set; }
}
public class CommentContract
{
[DataMember(Name = "Text")]
public string Text { get; set; }
[DataMember(Name = "creator")]
public string Creator { get; set; }
}
In DTOs there is not relation model. For instance: in EF, there is a ClientContactInfoComp model, which connects ClientRefs and ContactInfo models, but in DTO ClientContract is directly referenced with ContactInfoContract.
Model Builder in Startup.cs
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<ClientContract>("ClientContract").EntityType.HasKey(x => x.INN).Name = "ClientRef";
builder.EntitySet<DocumentContract>("DocumentContract");
builder.EntitySet<ContactInfoContract>("ContactInfoContracts").EntityType.HasKey(x => x.Id);
return builder.GetEdmModel();
}
public class ClientContractController : ControllerBase
{
[EnableQuery(MaxExpansionDepth = 10)]
public IQueryable<ClientContract> Get()
{
var clientRefs = _context.ClientRefs
.Include(x => x.Clients.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.PhysicalPeople.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.Companies.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.Documents.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.ClientContactInfoComps.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.ThenInclude(x => x.ContactInfo)
.Include(x => x.Assets.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now));
List<ClientContract> contracts = new List<ClientContract>();
foreach (var clientRef in clientRefs)
{
ClientContract clientContract = new ClientContract() { INN = clientRef.Id };
foreach(var c in clientRef.Clients)
{
clientContract.ClientType = (ClientType) c.ClientType;
}
foreach (var pp in clientRef.PhysicalPeople)
{
clientContract.FirstName = pp.FirstName;
clientContract.LastName = pp.LastName;
}
foreach (var comp in clientRef.Companies)
{
clientContract.CompanyName = comp.CompanyName;
}
foreach (var doc in clientRef.Documents)
{
clientContract.Documents.Add(new DocumentContract()
{
documentNumber = doc.DocumentNumber,
documentExpireDate = doc.DocumentExpireDate,
documentType = doc.DocumentType
});
}
foreach (var comp in clientRef.ClientContactInfoComps)
{
clientContract.ContactsInfo.Add(new ContactInfoContract
{
Type = comp.ContactInfo.Type.Value,
Value = comp.ContactInfo.Value
});
}
contracts.Add(clientContract);
}
return contracts.AsQueryable();
}
}

Yes I getting result from following links:
https://localhost:44327/odata/clientcontract
https://localhost:44327/odata/clientcontract?$filter=Id eq 4
https://localhost:44327/odata/clientcontract?$expand=documents,contactsinfo
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();//.AddNewtonsoftJson(); ;
services.AddDbContext<DbContext>(options =>
options.UseSqlServer("connectionstring"));
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
//endpoints.EnableDependencyInjection();
endpoints.Expand().Select().Filter().OrderBy().Count().MaxTop(10);
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
}
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<ClientContract>("ClientContract").EntityType.HasKey(x => x.INN).Name = "ClientRef";
builder.EntitySet<DocumentContract>("DocumentContract");
builder.EntitySet<ContactInfoContract>("ContactInfoContracts").EntityType.HasKey(x => x.Id);
return builder.GetEdmModel();
}
}

You should use the property name and not the attribute Name when use OData.
OData client library relies on it's own attribute OriginalNameAttribute to gain knowledge about class/member names as server emits them. The details you can see from here.
It works when I remove [DataContract] attribute.
public class ClientContract
{
public ClientContract()
{
ContactsInfo = new List<ContactInfoContract>();
}
[Key]
public int INN { get; set; }
public string CompanyName { get; set; }
public List<ContactInfoContract> ContactsInfo { get; set; }
}
public class ContactInfoContract
{
[Key]
public int Id { get; set; }
[DataMember(Name = "type")]
public int Type { get; set; }
[DataMember(Name = "value")]
public string Value { get; set; }
}

Related

Eager loading use Include got error in .Net

I'd like to display the 'ApplicationRole' Name with the 'ApplicationUser' data together using Eager loading.
here is my ApplicationUser
public class ApplicationUser : IdentityUser
{
public string? Name { get; set; }
public string? Department { get; set; }
public bool IsDeleted { get; set; }
public string RoleId { get; set; }
public virtual ApplicationRole Role { get; set; } = new ApplicationRole();
}
public class ApplicationUserDTO
{
public string Id { get; set; }
public int UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
// Foreign Key
public string RoleId { get; set; }
public virtual ApplicationRoleDTO Role { get; set; } = new ApplicationRoleDTO();
}
here is ApplicationRole
[NotMapped]
public class ApplicationRole : IdentityRole
{
public string Id { get; set; }
public string Name { get; set; }
//[InverseProperty(nameof(ApplicationUser.Role))]
public virtual ICollection<ApplicationUser> Users { get; set; } = new List<ApplicationUser>();
}
public class ApplicationRoleDTO
{
public string Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationUserDTO> Users { get; set; } = new List<ApplicationUserDTO>();
}
and here is the UserRepository
public async Task<IEnumerable<ApplicationUser>> GetAllUsersAsync()
{
//return await _dbContext.User.Where(u => u.IsDeleted == false).ToListAsync();
return await _dbContext.User.Include(u => u.Role).Where(u => u.IsDeleted == false).ToListAsync();
}
public async Task<ApplicationUser> GetUserAsync(string id)
{
return await _dbContext.User.Where(u => u.Id == id).Where(u => u.IsDeleted == false).FirstOrDefaultAsync();
}
But when I use the 'Include' keyword it show this error
When I try to use the string "Role", it also show error
public async Task<IEnumerable<ApplicationUser>> GetAllUsersAsync()
{
return await _dbContext.User.Include("Role").Where(u => u.IsDeleted == false).ToListAsync();
}
Here is the error show

Convert SQL query to Entity Framework which used AggregateFunctions and Where clause

How can I convert this query to Entity Framework?
SQL query:
SELECT Fullname, SUM(CoinCount+DiamondCount) AS GeneralPoint
FROM Students, Groups
WHERE Students.GroupId = Groups.Id AND Groups.Name = 'FSDA_1813_az'
GROUP BY Fullname
ORDER BY GeneralPoint
Entity:
public class Student
{
public int Id { get; set; }
public int GroupId { get; set; }
public string Fullname { get; set; }
public Nullable<int> DiamondCount { get; set; }
public Nullable<int> CoinCount { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public string Education { get; set; }
public string Email { get; set; }
public System.DateTime Birthdate { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public Nullable<System.DateTime> LastVisited { get; set; }
public string Facebook { get; set; }
public string Linkedin { get; set; }
public string SocialMedia { get; set; }
public byte[] Photo { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Comment> Comments { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Exam> Exams { get; set; }
public virtual Group Group { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Point> Points { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<StudentHomework> StudentHomeworks { get; set; }
}
UserRepository:
public ICollection<Student> GetUsersForGroup(string group)
{
using (var db = new EFContext())
{
var temp = db.Students.Where(x => x.Group.Name == group).ToList();
// I have to sort students in group by their DiamondCount+CoinCount as new GeneralCount
temp = temp.OrderBy(x => );
}
}
I have to sort students for their general point (DiamondCount+CoinCount).
But I can't send LINQ query by using Entity Framework. How can I do this?
Not tested but it should work:
var result = db.Students.Where(x => x.Group.Name == group)
.GroupBy(g => g.Fullname)
.Select(i => new
{
FullName = i.Key,
GeneralPoint = i.Sum(s => s.DiamondCount + s.CoinCount)
})
.OrderBy(o => o.GeneralPoint)
.ToList();

Relations with DbQuery

I have this Participant model DbSet<Participants>:
public class Participant {
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int IsCaptain { get; set; }
public Guid TeamId { get; set; }
[ForeignKey("TeamId")]
public Team Team { get; set; }
}
And this ParticipantDataView DbQuery<ParticipantsDataView>:
public class ParticipantDataView {
public Guid Id { get; set; } // = Participant.Id
public double? FirstWeight { get; set; }
public double? LastWeight { get; set; }
public double? WeightLoss => FirstWeight - LastWeight;
public Participant Participant { get; set; }
}
DbContext:
public class DBContext : DbContext {
public DBContext(DbContextOptions<DBContext> options) : base(options) {}
public DbSet<Participant> Participants { get; set; }
public DbQuery<ParticipantDataView> ParticipantsDataView { get; set; }
}
My query:
Participants = await _context.ParticipantsDataView
.Include(p => p.Participant)
.ThenInclude(t => t.Team)
.Where(p => p.Participant.Status == 1 && p.Participant.Team.Status == 1).OrderBy(p => p.WeightLoss)
.AsNoTracking()
.ToListAsync();
The error:
SqlNullValueException: Data is Null.
This method or property cannot be called on Null values.
Every item in ParticipantDataView has a match in Participants so I don't know why I get this error?

Entity Framework Parent\Child Retrieval

I have an ID of a "Component" parent record that I need to retrieve all the "Attachment" child records. The grid I am using need fields returned an placed in "TheComponentAttachmentsJoinedTable" I am only able to retrieve one record. I have used the value of FirstorDefault. That is the only way it will accept the line of code without complaining about the IEnumerable to int. Could someone please explain what is incorrect?
public class Component
{
public int ID { get; set; }
public string ComponentCode { get; set; }
public string ComponentDescription { get; set; }
public double ComponentWeight { get; set; }
public double ComponentQuantity { get; set; }
public string LastModUser { get; set; }
public DateTime LastModDate { get; set; }
public virtual ICollection<Attachment> Attachments { get; set; }
public virtual ICollection<Product> Products { get; set; }
public virtual ICollection<Element> Elements { get; set; }
}
public class Attachment
{
public int ID { get; set; }
public string AttachmentDescription { get; set; }
public string OriginalName { get; set; }
public string MimeType { get; set; }
public byte[] bytes { get; set; }
public string LastModUser { get; set; }
public DateTime LastModDate { get; set; }
//Navigation
public virtual ICollection<Element> Elements { get; set; }
public virtual ICollection<Component> Components { get; set; } //lt M-m
}
public class TheComponentAttachmentsJoinedTable
{
public int ComponentID { get; set; }
public int AttachmentID { get; set; }
public string OriginalName { get; set; }
}
public static List<TheComponentAttachmentsJoinedTable> ComponentAttachments_GetAllByComponentID(int ComponentID)
{
using (TheContext TheDB = new TheContext())
{
var r = (from x in TheDB.Component
.Where(x => x.ID == ComponentID)
.Include(x => x.Attachments)
select new TheComponentAttachmentsJoinedTable
{
ComponentID = x.ID,
AttachmentID = x.Attachments.Select(y => (y.ID)).FirstOrDefault(),
OriginalName = x.Attachments.Select(y => y.OriginalName).FirstOrDefault().ToString(),
}
);
return r.ToList();
}

Losing data on .Where(expression) to list()

Func<View_Album_Search, bool> expressionAlbum = Al => Al.name.Contains(text) || Al.soundName.Contains(text) || Al.artist.Contains(text);
var query = view_Album_SearchRepository.Where(expressionAlbum);
var b = query.Count(); (*Count = 1440*)
(*Lose data *)
var a = query.ToList(); (*Count = 154*)
/***************************************************\
public IEnumerable<TEntity> Where(Func<TEntity, bool> predicate)
{
return _context.Set<TEntity>().Where(predicate);
}
MODEL
public partial class View_Album_Search
{
public string name { get; set; }
public decimal id_album { get; set; }
public string soundName { get; set; }
public string artist { get; set; }
public byte feature { get; set; }
public Nullable<System.DateTime> album_date { get; set; }
public Nullable<decimal> hits { get; set; }
public string artist_twitter { get; set; }
public Nullable<int> feature_order { get; set; }
public string video_url { get; set; }
}
I dont understand, if I do it on get all works fine.
Aditional information
On debug mode when he do .toList() he execute the exrpessionAlbum again
Func<View_Album_Search, bool> expressionAlbum = Al => Al.name.ToLower().Contains(text.ToLower()) || Al.artist.ToLower().Contains(text.ToLower());