I am learning full-stack web development, and writing test project "Knowledge testing system". Entities in this particular case are: AppUsers, Examinations (describes when, who and what test took), Answers (describes tests answers) and UserAnswers (describes which answers user chose). I need to make quite complicated query with EF. The goal is, for each user, find number of tests user took, minimum, maximum and average amount of points he got. This is targeted SQL that I want to get from EF:
with
agg as (
select
s.UserId,
count(*) cnt,
min(s.sump) minPoints,
max(s.sump) maxPoints,
avg(s.sump) avgPoints
from (
select
e.UserId,
sum(a.Points) sump
from Examinations e
join UserAnswers ua on ua.ExamId = e.id
join Answers a on a.Id = ua.AnswerId
group by e.UserId, e.Id
) s
group by s.UserId
)
select
u.UserId,
u.Name,
u.Surname,
u.Patronimic,
agg.cnt,
agg.minPoints,
agg.maxPoints,
agg.avgPoints
from AppUsers u
join agg on u.UserId = agg.UserId
This is LINQ I was able to write:
_db.AppUsers
.GroupJoin(
_db.Examinations
.Include(x => x.UserAnswers)
.Where(x => x.FinishDateTime != null && x.ExamPassed != null),
user => user.UserId,
exam => exam.UserId,
(user, exam) => new
{
user.UserId,
user.Name,
user.Surname,
user.Patronimic,
aggregates = exam
.Select(g => new
{
g.UserId,
UserPoints = g.UserAnswers
.Join(
_db.Answers,
uAnswer => uAnswer.AnswerId,
answer => answer.Id,
(uAnswer, answer) => new { answer.Points }
).Sum(x => x.Points)
})
.GroupBy(g => g.UserId)
.Select(x => new
{
min = x.Min(a => a.UserPoints),
max = x.Max(a => a.UserPoints),
avg = x.Average(a => a.UserPoints),
cnt = x.Count()
})
.FirstOrDefault()
}
)
.Select(a => new UserExamsTotalModel
{
UserId = a.UserId,
UserName = a.Name,
UserSurname = a.Surname,
UserPatronimic = a.Patronimic,
UserTookeTestsTotal = a.aggregates != null ? a.aggregates.cnt : 0,
MinPointsInTest = a.aggregates != null ? a.aggregates.min : 0,
MaxPointsInTest = a.aggregates != null ? a.aggregates.max : 0,
AvgPointsInTest = a.aggregates != null ? a.aggregates.avg : 0
})
.ToListAsync();
It's too complicated and I get "could not be translated" at runtime. I don't even know it's correct. Can somebody help refactor/split it?
Edit: Added models for clarification.
public class ExaminationModel
{
public int Id { get; set; }
public int IdentityUserId { get; set; }
public int TestId { get; set; }
public DateTime StartDateTime { get; set; }
public DateTime? FinishDateTime { get; set; }
public bool? ExamPassed { get; set; }
}
public class UserAnswer
{
public int Id { get; set; }
public int ExamId { get; set; }
public int QuestionId { get; set; }
public int AnswerId { get; set; }
}
public class AnswerModel
{
public int Id { get; set; }
public string Text { get; set; } = string.Empty;
public int QuestionId { get; set; }
public int Points { get; set; }
}
public class UserModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Surname { get; set; } = string.Empty;
public string? Patronimic { get; set; }
public DateTime BirthDate { get; set; }
public string Email { get; set; } = string.Empty;
}
Related
I can use the dynamic model I want to add where condition in the "Tbl_ProductImg" table in this table I want only those record which has "isActive" value 1.
model.Product = db.Tbl_Product
.Include(t => t.Tbl_Product_Category)
.Include(y=>y.Tbl_Product_Dimensions)
.Include(I => I.Tbl_ProductImg)
.Where(x => x.Id == id && x.IsActive == 1 && x.Tbl_ProductImg.Any(y=>y.isActive==1))
.FirstOrDefault();
var result = Global.jsonConvert(model);
I assume that you have a Product class and ProductImg class which represents your database tables and if you want to get your all ProductImg data.
Product.cs:
public class Product
{
public int Id { get; set; }
public int IsActive { get; set; }
public IEnumerable<ProductCategory> ProductCategories { get; set; }
public IEnumerable<ProductDimension> ProductDimensions { get; set; }
public IEnumerable<ProductImg> ProductImages { get; set; }
}
ProductImg.cs:
public class ProductImg
{
public int Id { get; set; }
public int ProductId { get; set; }
public int IsActive { get; set; }
public Product Product { get; set; }
}
And you can get your Active Product Images with:
model.Product = db.Tbl_Product
.Where(p => p.Id == id && p.IsActive == 1)
.Select(p => new Product
{
Id = p.Id,
IsActive = p.IsActive,
//and you can get whatever you want..
ProductImages = p.ProductImages.Where(pi => pi.ProductId == p.Id && pi.IsActive == 1).Select(pi => new ProductImg
{
Id = pi.Id,
IsActive = pi.IsActive
//and you can get whatever you want..
})
}).FirstOrDefault();
how come this works:
var query = _context.Routing_Tool
.Where(rt => rt.Id == id)
.FirstOrDefault();
return query;
but this does not
var query = _context.Routing_Tool
.Join(_context.Routing_Tool_Prioritization_Matrix,
rt => rt.Id,
rp => rp.RoutingToolId,
(rt, rp) => new
{
Id = rt.Id,
Title = rt.Title,
LoeName = rp.LoeName,
}
)
.Where(rt => rt.Id == id)
.FirstOrDefault();
return query;
instead, I get and implicitly convert type error After I try to join a table. Help is appreciated
The whole new method - Including the old one I was working on for reference.
public Routing_Tool GetItemsInitById(int id)
{
//var query = (from rt in _context.Set<Routing_Tool>()
// join rp in _context.Set<Routing_Tool_Prioritization_Matrix>()
// on rt.Id equals rp.RoutingToolId into grouping
// from rp in grouping.DefaultIfEmpty()
// select new Routing_Tool {
// Id = rt.Id,
// Title = rt.Title,
// Classification = rt.Classification,
// MainPOC = rt.MainPOC,
// RequestingDirectorate = rt.RequestingDirectorate,
// IsEnduring = rt.IsEnduring,
// IsApproved = rt.IsApproved,
// IsAssociated = rt.IsAssociated,
// DirectingRequirement = rt.DirectingRequirement,
// RequirementDescription = rt.RequirementDescription,
// RequestType = rt.RequestType,
// // LoeName = rp.LoeName,
// // LoePriority = rp.LoePriority,
// }
// ).FirstOrDefault();
// return query;
var query = _context.Routing_Tool
.Join(_context.Routing_Tool_Prioritization_Matrix,
rt => rt.Id,
rp => rp.RoutingToolId,
(rt, rp) => new
{
Id = rt.Id,
Title = rt.Title,
}
)
.Where(rt => rt.Id == id)
.FirstOrDefault();
return query;
//return _context.Routing_Tool.FirstOrDefault(p => p.Id == id);
}
In the first case you the query variable is of IQueryable<Routing_Tool> type, which is right, you return it and everything is fine.
In the second case you return anonymous type and you have two options here:
Create a model for that type (with Id, Title and LoeName properties) and return "IQueryable< MyType >" instead
Return "Routing_Tool" from the expression ((rt, rp) => rt), which has no sense
I had to modify the Models for both tables by adding a collection, a new public method, a new select query:
public Routing_Tool GetItemsInitById(int id)
{
var initPage = _context.Routing_Tool
.Include(pub => pub.Routing_Tool_Prioritization_Matrices)
.Where(rt => rt.Id == id)
.FirstOrDefault();
return initPage;
}
routing tool model:
public class Routing_Tool
{
public Routing_Tool()
{
Routing_Tool_Prioritization_Matrices = new HashSet<Routing_Tool_Prioritization_Matrix>();
}
[Key]
[Required]
public int Id { get; set; }
[MaxLength(255)]
[Required]
public string Title { get; set; }
[MaxLength(255)]
[Required]
public string Classification { get; set; }
[MaxLength(255)]
[Required]
public string MainPOC { get; set; }
[MaxLength(10)]
[Required]
public string RequestingDirectorate { get; set; }
[MaxLength(1)]
[Required]
public int IsEnduring { get; set; }
[MaxLength(1)]
[Required]
public int IsApproved { get; set; }
[MaxLength(1)]
[Required]
public int IsAssociated { get; set; }
[Required]
public string DirectingRequirement { get; set; }
[Required]
public string RequirementDescription { get; set; }
[Required]
public string RequestType { get; set; }
public virtual ICollection<Routing_Tool_Prioritization_Matrix> Routing_Tool_Prioritization_Matrices { get; set; }
}
Dimensional table with FK:
public class Routing_Tool_Prioritization_Matrix
{
[Key]
[Required]
public int Id { get; set; }
public string LoeName { get; set; }
public string LoePriority { get; set; }
public int RoutingToolId { get; set; }
[ForeignKey("RoutingToolId")]
public virtual Routing_Tool Routing_Tool { get; set; }
}
I set up an asynchronous method in webapi(.NET Core 3.1), use linq to search the database and get the number of each category, and return it in the controller. I use Swagger to test but there are always errors. I don't know where the error is. Can i ask for some help?
The service:
public async Task<ClassficationSimpleInfo[]> SearchZulib(string token, string keyWord)
{
var data = zudb.ZuFileinfo.Include(x => x.Classify).Where(x => x.IsHiden != 1)
.Where(x => keyWord == "" || x.FamilyName.Contains(keyWord) || x.Description.Contains(keyWord))
.GroupBy(x => x.Classify)
.Select(x => new { classify = x.Key, count = x.Count() })
.ToList();
var result = data.Select(x => new ClassficationSimpleInfo(x.classify.Name, x.classify.ClassificationCode)
{
Count = x.count,
Folder = x.classify.Folder,
}).ToArray();
return result;
}
The controller:
[HttpGet]
[Route("Controller/SearchZulib")]
public async Task<ClassficationSimpleInfo[]> SearchZulib(string token, string keyWord)
{
return await service.SearchZulib(token, keyWord);
}
The definition of related Class:
namespace ZulibWebServer.Entities
{
public class ClassficationSimpleInfo
{
public int Id { get; set; }
public string ClassifyCode { get; set; }
public string Name { get; set; }
public int Count { get; set; }
public string Folder { get; set; }
public bool Existed { get; set; }
public ClassficationSimpleInfo(string name, string classifyCode)
{
Name = name;
ClassifyCode = classifyCode;
}
}
}
namespace ZulibWebServer.Models
{
public partial class ZuFileinfo
{
public int FileId { get; set; }
public string FamilyName { get; set; }
public string FileUrl { get; set; }
public int ClassifyId { get; set; }
public string Description { get; set; }
public byte[] ThumbImage { get; set; }
public int? MinVer { get; set; }
public string LargeImage { get; set; }
public int IsHiden { get; set; }
public string UploaderName { get; set; }
public int? UploaderId { get; set; }
public virtual ZuClassfication Classify { get; set; }
}
}
public partial class ZuClassfication
{
public ZuClassfication()
{
ZuFileinfo = new HashSet<ZuFileinfo>();
ZuMapingrule = new HashSet<ZuMapingrule>();
}
public int ClassificationIdid { get; set; }
public string ClassifyName { get; set; }
public string ClassificationCode { get; set; }
public string RelQc { get; set; }
public string RelCbimcode { get; set; }
public string RelOminClass { get; set; }
public string Reluniformat { get; set; }
public string OtherCode { get; set; }
public string Name { get; set; }
public int? ParentCodeId { get; set; }
public string Folder { get; set; }
public virtual ICollection<ZuFileinfo> ZuFileinfo { get; set; }
public virtual ICollection<ZuMapingrule> ZuMapingrule { get; set; }
}
}
But the error response is
System.InvalidOperationException: The LINQ expression 'DbSet
.Where(z => z.IsHiden != 1)
.Where(z => False || z.FamilyName.Contains(__keyWord_0) || z.Description.Contains(__keyWord_0))
.Join( outer: DbSet, inner: z => EF.Property>(z, "ClassifyId"), outerKeySelector: z0 => EF.Property>(z0, "ClassificationIdid"), innerKeySelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i ))
.GroupBy( source: z => z.Inner, keySelector: z => z.Outer)' could not be translated.
Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I tested it again and found the error in GroupBy(x => x.Classify)
Yeah, it is invalid to GroupBy a Navigation property.
Also, you can simplify your query by linq like below:
var data = zudb.ZuFileinfo.Include(x => x.Classify).Where(x => x.IsHiden != 1)
.Where(x => keyWord == "" || x.FamilyName.Contains(keyWord) || x.Description.Contains(keyWord))
.GroupBy(x => x.ClassifyId)
.Select(x => new { classifyId = x.Key, count = x.Count() })
.ToList();
var result = (from d in data
join c in zudb.ZuClassfication on d.classifyId equals c.ClassificationIdid
select new ClassficationSimpleInfo(c.Name, c.ClassificationCode)
{
Count = d.count,
Folder = c.Folder
}).ToArray();
I tested it again and found the error in GroupBy(x => x.Classify), so i modified the code to query the database twice.
var data =await zudb.ZuFileinfo
.Where(x => x.IsHiden != 1)
.Where(x => keyWord == "" || x.FamilyName.Contains(keyWord) || x.Description.Contains(keyWord))
.GroupBy(x => x.ClassifyId).Select(x => new { classifyId = x.Key, count = x.Count() })
.ToListAsync();
var classifies =await zudb.ZuClassfication.ToDictionaryAsync(x => x.ClassificationIdid);
var result = data.Select(x =>
{
if (!classifies.TryGetValue(x.classifyId, out var classify)) return null;
return new ClassficationSimpleInfo(classify.Name, classify.ClassificationCode)
{
Count = x.count,
Folder = classify.Folder,
};
}).ToArray();
Finally, I succeeded.
I have the following model where I'd like to get the sum of all OrderTotalItems for all Orders of a Customer where the OrderTotalType (Enumeration) is "total" or 99:
public class Customer
{
...
public ICollection<Order> Orders { get; set; } = new Collection<Order>();
}
public class Order
{
...
public ICollection<OrderTotalItem> OrderTotalItems { get; set; } = new Collection<OrderTotalItem>();
}
public class OrderTotalItem
{
[Required]
public int Id { get; set; }
[Required]
[Column(TypeName = "decimal(10, 4)")]
public decimal Value { get; set; }
[Required]
public OrderTotalType Type { get; set; }
}
I am building a CustomerAdminDTO to include all relevant data of a customer for the admin client:
public class CustomerAdminDto
{
public int Id { get; set; }
public string UserId { get; set; }
public Gender Gender { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string VATId { get; set; } = "";
public bool VATIdValid { get; set; } = false;
public DateTime Added { get; set; }
public DateTime LastModified { get; set; }
public decimal OrdersTotal { get; set; }
public CustomerStatusShortDto CustomerStatus { get; set; }
public CustomerAddressDto CustomerAddress { get; set; }
public CustomerAddressDto BillingAddress { get; set; }
public ICollection<OrderListShortDto> Orders { get; set; }
}
In my data service I fill the DTO like that
var customerAdmin = await _context.Customers
.Include(x => x.Addresses)
.Include(x => x.CustomerStatus)
.Include(x => x.Orders)
.ThenInclude(x => x.OrderTotalItems)
.Where(x => x.UserId == userid)
.Select(customer => new CustomerAdminDto
{
Id = customer.Id,
UserId = customer.UserId,
Gender = customer.Gender,
FirstName = customer.FirstName,
LastName = customer.LastName,
VATId = customer.VATId,
VATIdValid = customer.VATIdValid,
Added = customer.Added,
LastModified = customer.LastModified,
OrdersTotal = customer.Orders.Sum(x => x.OrderTotalItems
.Where(x => x.Type == Enums.OrderTotalType.Total)
.Sum(x => x.Value)),
CustomerStatus = new CustomerStatusShortDto
{
Id = customer.CustomerStatus.Id,
Name = customer.CustomerStatus.Name,
},
...
}
.FirstOrDefaultAsync();
where everything works, except the OrdersTotal.
API compiles fine but throws the following error at runtime:
Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Thanks for your hints!
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
This error in SQL server means that you tried to call aggregation function (customer.Orders.Sum() in your case) on other expression that contains aggregation function (.Sum(x => x.Value) in your case). In order to avoid this you can simplify your LINQ expression for OrdersTotal:
OrdersTotal = customer.Orders.SelectMany(o => o.OrderTotalItems).Where(x => x.Type == Enums.OrderTotalType.Total).Sum(x => x.Value)
There is only one aggregation here so it should work fine
Hey Im trying to populate my model that its a List<T> and inside have List<T> but I cant linq doesn't allow me to make a .ToList inside a Linq.
Those are my models
CTIPOCHECKLIST :
public class CTIPOCHECKLIST
{
public int ID { get; set; }
public int ID_DEPTO{ get; set; }
public string DESCRIPCION { get; set; }
}
CTITLE :
public class CTITLE
{
public int ID { get; set; }
public int ID_TIPOCHECKLIST { get; set; }
public string Descripcion { get; set; }
public virtual CTIPOCHECKLIST Tipo_CheckList { get; set; }
public List<CSUBTITLE> Subtitulos { get; set; }
TROP.Models.TROPEntities db = new Models.TROPEntities();
public List<CTITLE> getAll(string codigo, int tipoCheckList)
{
List<CTITLE> model = new List<CTITLE>();
try
{
var result = (from a in db.TITLE
from t in db.Tipo_CheckList
where a.ID_TipoChecklist == t.ID
where a.ID_TipoChecklist == tipoCheckList
select new CTITLE
{
Descripcion = a.Descripcion,
ID = a.ID,
ID_TIPOCHECKLIST = a.ID_TipoChecklist,
Tipo_CheckList = new CTIPOCHECKLIST
{
DESCRIPCION = t.Descripcion,
ID = t.ID,
ID_DEPTO = t.ID_Depto
},
Subtitulos = (from s in db.SUBTITLE
where s.ID_TITLE == a.ID
select new CSUBTITLE
{
AMOUNTCK = s.AMOUNT_CK,
DESCRIPCION = s.DESCRIPCION,
ID = s.ID,
ID_IDTITLE = s.ID_TITLE,
NUMERACION = s.NUMERACION,
CHECKLIST = (from ck in db.CHECKLIST
where ck.ID_SUBTITLE == s.ID
&& ck.CODIGO == codigo
select new CCHECKLIST
{
CK = ck.CK,
CODIGO = ck.CODIGO,
ID = ck.ID,
ID_IDSUBTITLE = ck.ID_SUBTITLE,
USERCRE = ck.USERCRE,
USERMOD = ck.USERMOD
}).FirstOrDefault()
})//here I put ToList
}).ToList();
return result;
}
catch
{
}
return model;
}
CSUBTITLE :
public class CSUBTITLE
{
public int ID { get; set; }
public int ID_IDTITLE { get; set; }
public string NUMERACION { get; set; }
public string DESCRIPCION { get; set; }
public bool AMOUNTCK { get; set; }
public CTITLE TITLE { get; set; }
public CCHECKLIST CHECKLIST { get; set; }
}
CCHECKLIST :
public class CCHECKLIST
{
public int ID { get; set; }
public int ID_IDSUBTITLE { get; set; }
public string CODIGO { get; set; }
public bool CK { get; set; }
public string USERCRE { get; set; }
public string USERMOD { get; set; }
public CSUBTITLE SUBTITLE { get; set; }
}
And my main class is CTITLE that have the method GetAll off type List
and I just want to populate with this query but the Subtitulos property of CTITLE that its a list When I populate it using a type IEnumerable it work, But I dont want it returning IEnumerable I need List But when I turn the property Subtitle to List and add .ToList() inside the query LinQ doesnt allow me, I have spend like 3 days trying to solve this problem. I cant figure out.
The problem here is, that you want to execute a Linq-to-objects query operation within a Linq-to-entities operation.
A Linq-to-entities expressions-tree gets translated to SQL and fetches the data from the DB. After that the object get materialzed as .Net objects. Only after this step it is possible to call .ToList()
So, this means, you could restructure your query a little bit and delay the .ToList() calls:
var result =
(from a in db.TITLE
from t in db.Tipo_CheckList
where a.ID_TipoChecklist == t.ID
where a.ID_TipoChecklist == tipoCheckList
let subtitulos = from s in db.SUBTITLE
where s.ID_TITLE == a.ID
select new CSUBTITLE {
AMOUNTCK = s.AMOUNT_CK,
DESCRIPCION = s.DESCRIPCION,
ID = s.ID,
ID_IDTITLE = s.ID_TITLE,
NUMERACION = s.NUMERACION,
CHECKLIST = (from ck in db.CHECKLIST
where ck.ID_SUBTITLE == s.ID
&& ck.CODIGO == codigo
select new CCHECKLIST
{
CK = ck.CK,
CODIGO = ck.CODIGO,
ID = ck.ID,
ID_IDSUBTITLE = ck.ID_SUBTITLE,
USERCRE = ck.USERCRE,
USERMOD = ck.USERMOD
}).FirstOrDefault()
}
let ctitle = new CTITLE {
Descripcion = a.Descripcion,
ID = a.ID,
ID_TIPOCHECKLIST = a.ID_TipoChecklist,
Tipo_CheckList = new CTIPOCHECKLIST
{
DESCRIPCION = t.Descripcion,
ID = t.ID,
ID_DEPTO = t.ID_Depto
}
}
select new { CTITLE = ctitle, SUBTITULOS = subtitulos }
)
.Select(t => t.CTITLE.Subtitulos = t.SUBTITULOS.ToList())
.Select(t => t.CTITLE)
.ToList()
This could look (modulo some typos and brackets) like the statement above.