GroupBy and then Sum - c#

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; }
}

Related

System.InvalidOperationException: The LINQ expression

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.

Create LINQ request

I have some collections:
Dictionary<InvoceHeader, List<InvoiceHierarchy>> where
public class InvoceHeader
{
public int InvoiceId { get; set; }
public string DocumentNumber { get; set; }
public string DocumentDate { get; set; }
public string DocumentReference { get; set; }
}
public class InvoiceHierarchi
{
public string SerialNumber { get; set; }
public string ProductCode { get; set; }
public string Certificate { get; set; }
public string Description { get; set; }
}
I need to write a LINQ request to get such a collection from the original collection --> Dictionary<string, List<string>>, where key --> InvoceHeader.DocumentNumber, value --> List two elements from InvoiceHierarchi such as the SerialNumber and Certificate.
Dictionary<string, List<string>> converted = original
.ToDictionary(
kvp => kvp.Key.DocumentNumber,
kvp => kvp.Value.Select(ih => ih.SerialNumber + ih.Certificate).ToList());
Try following :
class Program
{
static void Main(string[] args)
{
List<InvoceHeader> headers = new List<InvoceHeader>();
List<InvoiceHierarchi> hierarchi = new List<InvoiceHierarchi>();
var query = (from header in headers
join hierarch in hierarchi on header.DocumentNumber equals hierarch.Certificate
select new { header = header, hierarch = hierarch }
).ToList();
Dictionary<string, object> dict = query
.GroupBy(x => x.header.DocumentNumber, y => new { header = y.header, hierachi = y.hierarch })
.ToDictionary(x => x.Key, y => (object)y.ToList());
}
}
public class InvoceHeader
{
public int InvoiceId { get; set; }
public string DocumentNumber { get; set; }
public string DocumentDate { get; set; }
public string DocumentReference { get; set; }
}
public class InvoiceHierarchi
{
public string SerialNumber { get; set; }
public string ProductCode { get; set; }
public string Certificate { get; set; }
public string Description { get; set; }
}
That should be the query that you are looking for, only that you can replace the anonymous types that I am using in the query with a custom type that you created.
var newCollection = originalCollection.Select(x => new
{
Key = x.Key.DocumentNumber,
Value = x.Value.Select(y => new
{
y.SerialNumber,
y.Certificate
}).ToList()
});
If you want again to convert it to Dictionary, you can also add that line:
var newDictionary = newCollection.ToDictionary(x => x.Key, y => y.Value);

C# Entity Framework, Simplify Select Child Property That Using Same Condition

I have 2 ef model, let's say Task() and Progress()
public class Task()
{
public int Id { get; set; }
public string Title { get; set; }
}
and then
public class Progress()
{
public int Id { get; set; }
public int Id_Task { get; set; }
public string Status { get; set; }
public DateTime Update { get; set; }
}
I want to get data for each task and it's last progress, So i make a ViewModel
public class ViewTask()
{
public int Id { get; set; }
public string Title { get; set; }
public string Last_Status { get; set; }
public DateTime Last_Update { get; set; }
}
Right now, this is what i did:
var data = _context.Task
.Select(x => new ViewTask
{
Id = x.Id,
Title = x.Title,
Last_Status = x.Progress.OrderByDescending(y => y.Update)
.Select(y => y.Status).FirstOrDefault(),
Last_Update = x.Progress.OrderByDescending(y => y.Update)
.Select(y => y.Update).FirstOrDefault()
}).ToList();
Is there any better ways so I don't need to repeat x.Progress... part? The only code i can think of is something like this:
var data = _context.Task
.Select(x => new
{
Id = x.Id,
Title = x.Title,
Progress = x.Progress.OrderByDescending(y => y.Update).FirstOrDefault()
})
.Select(x => new ViewTask
{
Id = x.Id,
Title = x.Title,
Last_Status = x.Progress.Status
Last_Update = x.Progress.Update
}).ToList();
I appriciete any kind of suggestion. Thanks :)
You can use MaxBy and MinBy methods from moreLINQ library.
var data = _context.Task
.Select(x => new ViewTask
{
Id = x.Id,
Title = x.Title,
Last_Status = x.Progress.MaxBy(y => y.Update).Status,
Last_Update = x.Progress.MaxBy(y => y.Update).Update
}).ToList();
Hope it helps!!

Populate multiple collections on same type

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

Pick the first item in a Linq to Entities query

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

Categories

Resources