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.
Related
I'm newbie at entity framework. I can manage to create tables and working CRUD ops.
My models are below
public class Break
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("duration")]
public int Duration { get; set; }
[JsonProperty("countable")]
public bool Countable { get; set; }
}
public class BreakState
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("isSupervisor")]
public bool IsSupervisor { get; set; }
[JsonProperty("isActive")]
public bool IsActive { get; set; }
}
public class BreakRequest : BaseModel
{
[JsonProperty("dn")]
public string DN { get; set; }
[JsonProperty("agent")]
public string Agent { get; set; }
[JsonProperty("break")]
public Break Break { get; set; }
[JsonProperty("steps")]
public List<BreakStep> Steps { get; set; }
}
public class BreakStep : BaseModel
{
[JsonProperty("state")]
public BreakState BreakState { get; set; }
[JsonProperty("note")]
public string Note { get; set; }
[JsonProperty("request_id")]
public int BreakRequestId { get; set; }
[JsonIgnore]
public BreakRequest BreakRequest { get; set; }
}
Now I need to get complex data from context.
public int GetUsedBreak(string agent) {
int sum_breaks_used = 0;
var shift = GetShift(agent);
if (shift != null) {
using (var dbBreak = new BreakTimeDbContext())
{
var breaks_countable = dbBreak.BreakRequests
.Where(x => x.DN == agent && x.Break.Countable && x.Steps.Count == 4 && x.CreatedAt > shift.Start)
.Select(x => x.Id).ToArray();
if (breaks_countable.Length > 0) {
sum_breaks_used = dbBreak.BreakSteps
.Include(i => i.BreakState)
.Where(w => breaks_countable.Contains(w.BreakRequestId))
.GroupBy(x => x.BreakRequestId)
.Select(g => new {
BreakId = g.Key,
Level3Time = g.Where(w => w.BreakState.Level == 3).First().CreatedAt,
Level4Time = g.Where(w => w.BreakState.Level == 4).First().CreatedAt
})
.Sum(x => ( x.Level4Time - x.Level3Time ).Minutes);
}
}
}
return sum_breaks_used;
}
with this function, I try to get sum of date diffrences.
But I get the error below
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The LINQ expression 'GroupByShaperExpression:
KeySelector: b.BreakRequestId,
ElementSelector:EntityShaperExpression:
EntityType: BreakStep
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Where(w => w.BreakState.Level == 3)' 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
'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See
https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I need only level 4 and level 3 differences.
How can I achieve it with linq?
NOTE: title of the question may not be clear, can change it
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
I am trying to take the items from sortedItems and then create a GroupBy MaterialCode and MaterialThickness. Then I want to find the sum of NbrOfSheets where MaterialCode and MaterialThickness are the the same.
Here is my code.
var sortedItems =
db.Jobs
.Where(x => x.JobStatus == "O")
.OrderBy(x => x.Material.MaterialCode).ThenBy(x => x.MaterialThickness)
.ThenBy(x => x.IdealSheetSize).ThenByDescending(x => x.DueDate)
.Select(x => new
{
x.JobNum,
x.Material.MaterialCode,
x.MaterialThickness,
x.Part.CustPartNum,
x.Part.CustName,
x.IdealSheetSize,
x.NbrOfSheets,
x.DueDate,
x.ShipmentNotes,
JobRoutings = x.JobRoutings.Select(e => new
{
e.ProcessID,
e.CrewSize,
e.PiecesPerHour,
e.OnHand,
e.PC,
e.LastScannedDate,
e.OpSeq
})
}).AsNoTracking().ToList();
Group by an anonymous type with these properties, then use Sum on the groups:
var result = sortedItems
.GroupBy(x => new { x.MaterialCode, x.MaterialThickness })
.Select(g => new {
g.Key.MaterialCode, g.Key.MaterialThickness,
NbrOfSheetsSum = g.Sum(x => x.NbrOfSheets)
});
I modeled your database with classes to get syntax correct. See answer below :
class Program
{
static void Main(string[] args)
{
DataBase db = new DataBase();
var sortedItems =
db.Jobs
.Where(x => x.JobStatus == "O")
.OrderBy(x => x.Material.MaterialCode).ThenBy(x => x.MaterialThickness).ThenBy(x => x.IdealSheetSize).ThenByDescending(x => x.DueDate)
.Select(x => new
{
x.JobNum,
x.Material.MaterialCode,
x.MaterialThickness,
x.Part.CustPartNum,
x.Part.CustName,
x.IdealSheetSize,
x.NbrOfSheets,
x.DueDate,
x.ShipmentNotes,
JobRoutings = x.JobRoutings.Select(e => new
{
e.ProcessID,
e.CrewSize,
e.PiecesPerHour,
e.OnHand,
e.PC,
e.LastScannedDate,
e.OpSeq
})
}).ToList();
var groups = sortedItems.GroupBy(x => new { materialCode = x.MaterialCode, materialThickness = x.MaterialThickness })
.Select(x => new { job = x, count = x.Sum(y => y.NbrOfSheets) }).ToList();
}
}
public class DataBase
{
public List<Job> Jobs { get; set; }
}
public class Job
{
public string JobStatus { get; set; }
public Material Material { get; set; }
public string MaterialThickness { get; set; }
public string IdealSheetSize { get; set; }
public DateTime DueDate { get; set; }
public int JobNum { get; set; }
public Part Part { get; set; }
public int NbrOfSheets { get; set; }
public string ShipmentNotes { get; set; }
public List<JobRoutings> JobRoutings { get; set; }
}
public class Material
{
public string MaterialCode { get; set; }
}
public class Part
{
public string CustPartNum { get; set; }
public string CustName { get; set; }
}
public class JobRoutings
{
public string ProcessID {get;set;}
public string CrewSize {get;set;}
public string PiecesPerHour {get;set;}
public string OnHand {get;set;}
public string PC {get;set;}
public string LastScannedDate {get;set;}
public string OpSeq { get; set; }
}
I need to populate a Product object which contains two collections.
The current code works fine and populates the Product.GraphicItems collection, but I also need to populate the Product.InfoItems collection, but I can't figure out the syntax for multiple collections.
Current select:
var result = await this.Context.ShopCategorys
.Include(cat => cat.InfoItems)
.Include(cat => cat.Products)
.ThenInclude(prd => prd.GraphicItems)
.ThenInclude(itm => itm.Graphic)
.ThenInclude(gfx => gfx.Items)
.SingleAsync(cat => cat.Id.Equals(id));
Product.cs:
[Table("ShopProduct")]
public class Product : BaseShop
{
public bool Active { get; set; } = true;
public int CategoryId { get; set; }
public int CultureId { get; set; } = -1;
public List<ProductInfo> InfoItems { get; set; } = new List<ProductInfo>();
public List<ProductGraphic> GraphicItems { get; set; } = new List<ProductGraphic>();
}
ProductInfo.cs:
[Table("ShopProductInfo")]
public class ProductInfo : BaseShop, IInfoItem
{
public int? ProductId { get; set; }
public int CultureId { get; set; }
public Culture Culture { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Solution:
var result = await this.Context.ShopCategorys
.Include(cat => cat.InfoItems)
.Include(cat => cat.Products)
.ThenInclude(prd => prd.InfoItems)
.Include(cat => cat.Products)
.ThenInclude(prd => prd.GraphicItems)
.ThenInclude(itm => itm.Graphic)
.ThenInclude(gfx => gfx.Items)
.SingleAsync(cat => cat.Id.Equals(id));
I have the following code:
context.Posts
.SelectMany(x => x.Packs
.SelectMany(y => y.Files, (y, z) => new {
File = new { Key = z.Key }
})
.Select(y => new PostModel {
Id = x.Id,
File = y.File.Key,
Types = x.Types
})
).ToList();
This is working but one Post has Many PostLocalized.
I would like to, in my query, pick the PostLocalized which .Culture == culture.
And I need to use its data to create the PostModel. something like:
context.Posts
// PICK the first PostLocalized which .Culture property equals culture
.SelectMany(x => x.Packs
.SelectMany(y => y.Files, (y, z) => new {
File = new { Key = z.Key }
})
.Select(y => new PostModel {
Id = x.Id,
File = y.File.Key,
Types = x.Types,
//Title = PostLocalized.Title,
//Body = PostLocalized.Body
})
).ToList();
How can I do this?
NOTE:
The Post and PostLocalized entities are the following:
public class Post {
public Int32 Id { get; set; }
public Boolean Active { get; set; }
public PostTypes Types { get; set; }
public virtual ICollection<PostLocalized> PostsLocalized { get; set; }
} // Post
public class PostLocalized {
public Int32 Id { get; set; }
public String Culture { get; set; }
public String Body { get; set; }
public String Title { get; set; }
public virtual Post Post { get; set; }
public virtual ICollection<Pack> Packs { get; set; }
} // PostLocalized
public class Pack {
public Int32 Id { get; set; }
public Boolean Active { get; set; }
public DataType Type { get; set; }
public DateTime Updated { get; set; }
public virtual ICollection<File> Files { get; set; }
public virtual ICollection<PostLocalized> PostsLocalized { get; set; }
} // Pack
public class File {
public Int32 Id { get; set; }
public Byte[] Data { get; set; }
public Guid Key { get; set; }
public String Mime { get; set; }
public virtual Pack Pack { get; set; }
} // File
Thank You,
Miguel
This is not exactly beautiful or efficient on its own but it should at least work and the query optimizer will hopefully make it fast.
context.Posts
.SelectMany(post => post.Packs
.SelectMany(pack => pack.Files
.Select(file => new PostModel
{
Id = post.Id,
File = file.Key,
Types = post.Types,
Title = post.PostsLocalized.First(pl => pl.Culture == culture).Title,
Body = post.PostsLocalized.First(pl => pl.Culture == culture).Body
})))
.ToList();