C# LINQ and Lambda expressions equivalent to SQL GROUP BY and MAX - c#

I have a list of Logs for the history of each Task. I need a query to retrieve the last Status for every Task, based on the log date.
class Log
{
public int Id { get; set; }
public int TaskId { get; set; }
public string LogDate { get; set; }
public string Status { get; set; }
static void Main()
{
Log[] logs = {
new Log() { Id = 1, TaskId = 1, LogDate = "2022-12-21", Status = "ToDo" },
new Log() { Id = 2, TaskId = 2, LogDate = "2022-12-22", Status = "ToDo" },
new Log() { Id = 3, TaskId = 1, LogDate = "2022-12-23", Status = "InProgress" },
new Log() { Id = 4, TaskId = 1, LogDate = "2022-12-24", Status = "Done" },
new Log() { Id = 5, TaskId = 3, LogDate = "2022-12-25", Status = "ToDo" },
new Log() { Id = 6, TaskId = 2, LogDate = "2022-12-26", Status = "InProgress" }
};
var lastLogs = logs.GroupBy(/* ... need a query to get below results */);
/*
Id = 4, TaskId = 1, LogDate = 2022-12-24, Status = Done
Id = 5, TaskId = 3, LogDate = 2022-12-25, Status = ToDo
Id = 6, TaskId = 2, LogDate = 2022-12-26, Status = InProgress
*/
foreach (var item in lastLogs)
{
Console.WriteLine($"Id = {item.Id}, TaskId = {item.TaskId}, LogDate = {item.LogDate}, Status = {item.Status}");
}
}
}

Related

Extract a sorted list of a list by c#

I have a userList and want to extract a sorted list of them which contains the total amount of each user by c#. The users may be more than one in the list. To do so I used another list usernameAndAmountList and used two cascading foreach loops. This works, but I want to know if there is another way to modify the following code to avoid using theses foreach loops or at least decrease the number of them to just one? I have written it to a console application project.
This is my code:
static void Main(string[] args)
{
List<User> userList = new List<User>();
userList.Add(new User { BuyId = 1, UserID = 2, UserName = "UserTwo", Amount = 20 });
userList.Add(new User { BuyId = 2, UserID = 2, UserName = "UserTwo", Amount = 10 });
userList.Add(new User { BuyId = 3, UserID = 3, UserName = "UserThree", Amount = 30 });
userList.Add(new User { BuyId = 4, UserID = 5, UserName = "UserFive", Amount = 50 });
userList.Add(new User { BuyId = 5, UserID = 5, UserName = "UserFive", Amount = 12 });
userList.Add(new User { BuyId = 6, UserID = 4, UserName = "UserFour", Amount = 14 });
var groupedCustomerList = userList
.GroupBy(u => u.UserID)
.Select(grp => grp.ToList())
.ToList();
double MaximumAmount = 0;
string name = "";
List<UsernameAndAmount> usernameAndAmountList = new List<UsernameAndAmount>();
foreach (var i in groupedCustomerList)
{
double TotalAmount = 0;
foreach (var j in i)
{
TotalAmount += j.Amount;
name = j.UserName;
//Console.WriteLine($"{j.UserName} , {j.Amount.ToString("#,0")}");
}
if (TotalAmount > MaximumAmount)
{
MaximumAmount = TotalAmount;
}
usernameAndAmountList.Add(new UsernameAndAmount { Amount = TotalAmount, UserName = name });
}
var sortedList = usernameAndAmountList.OrderByDescending(c => c.Amount);
//for monitoring:
foreach (var item in sortedList)
Console.WriteLine($"{item.UserName}: {item.Amount.ToString("#,0")}");
}
public class User
{
public int BuyId { get; set; }
public int UserID { get; set; }
public string UserName { get; set; }
public double Amount { get; set; }
}
public class UsernameAndAmount
{
public string UserName { get; set; }
public double Amount { get; set; }
}
The result is:
UserFive: 62
UserTwo: 30
UserThree: 30
UserFour: 14
Use LINQ to avoid multiple for loops
and you can run at .net Fiddle
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<User> userList = new List<User>();
userList.Add(new User { BuyId = 1, UserID = 2, UserName = "UserTwo", Amount = 20 });
userList.Add(new User { BuyId = 2, UserID = 2, UserName = "UserTwo", Amount = 10 });
userList.Add(new User { BuyId = 3, UserID = 3, UserName = "UserThree", Amount = 30 });
userList.Add(new User { BuyId = 4, UserID = 5, UserName = "UserFive", Amount = 50 });
userList.Add(new User { BuyId = 5, UserID = 5, UserName = "UserFive", Amount = 12 });
userList.Add(new User { BuyId = 6, UserID = 4, UserName = "UserFour", Amount = 14 });
var groupedCustomerList = userList
.GroupBy(u => u.UserID)
.Select(c => new UsernameAndAmount(){
UserName = c.First().UserName,
Amount = c.Sum(i=> i.Amount)
} )
.OrderByDescending(c => c.Amount);
foreach (var item in groupedCustomerList){
Console.WriteLine($"{item.UserName} : {item.Amount.ToString("#,0")}");
}
}
}
public class User
{
public int BuyId { get; set; }
public int UserID { get; set; }
public string UserName { get; set; }
public double Amount { get; set; }
}
public class UsernameAndAmount
{
public string UserName { get; set; }
public double Amount { get; set; }
}

How to make nested list from one base list

I would like to make nested list from this one list
public class Product
{
string Id;
string ProductName;
decimal Price;
string Supplier;
int Quantity;
string VersionId;
string TypeId;
}
public class ProductTypeDto
{
public string TypeId { get; set; }
public string ProductName { get; set; }
public string Price { get; set; }
public List<ProductVersionDto> ProductVersions { get; set; }
}
public class ProductVersionDto
{
public string VersionId { get; set; }
public string ProductName { get; set; }
public string Supplier { get; set; }
public int Quantity { get; set; }
}
I would like to know how I can create a list of ProductTypeDto using linq c# .
I need to get all products with the same TypeId , and ProductVersions field should contain products with same version Id (and of course same TypeId).
I don't want to use foreach loops, and I dont want to loop over products twice to make this nested list.. I think there would be a better way using Linq, would be great if you can help..
Edit: I add here what I have done so far, but it is not the way I want this to be fixed.
List<ProductTypeDto> products = this._dbContext
.Products
.Where(product => product.TypeId == query.TypeId)
.Select(product => new ProductTypeDto()
{
TypeId = product.TypeId,
ProductName = product.ProductName,
Price = product.Price,
ProductVersions = product.Products.Where(p => p.TypeId == product.TypeId)
.Select(p => new ProductVersionDto()
{
VersionId = p.VersionId,
ProductName = p.ProductName,
Supplier = p.Supplier,
Quantity = p.Quantity
}).ToList()
})
.ProjectTo<ProductTypeDto>(this._config)
.ToListAsync(cancellationToken);
This is the result I want to get:
var product1 = new Product() { Id = 1, ProductName = "foo", Price = 20, Supplier = "test1", Quantity = 3, VersionId = "1", TypeId = "1" };
var product2 = new Product() { Id = 2, ProductName = "foo1", Price = 60, Supplier = "test2", Quantity = 9, VersionId = "1", TypeId = "1" };
var product3 = new Product() { Id = 3, ProductName = "foo2", Price = 30, Supplier = "test3", Quantity = 5, VersionId = "2", TypeId = "1" };
var product4 = new Product() { Id = 4, ProductName = "foo3", Price = 10, Supplier = "test4", Quantity = 4, VersionId = "1", TypeId = "2" };
var product5 = new Product() { Id = 5, ProductName = "foo4", Price = 50, Supplier = "test5", Quantity = 8, VersionId = "1", TypeId = "3" };
List<ProductVersionDto> p1 = {
new ProductVersionDto { ProductName = "foo", Quantity= 3, Supplier ="test1"}
new ProductVersionDto { ProductName = "foo1", Quantity= 9, Supplier ="test2"}
};
List<ProductVersionDto> p2 = {
new ProductVersionDto { ProductName = "foo3", Quantity= 4, Supplier ="test4"}
};
List<ProductVersionDto> p3 = {
new ProductVersionDto { ProductName = "foo4", Quantity= 8, Supplier ="test5"}
};
List<ProductTypeDto> products = {
new ProductTypeDto{ ProductName = "foo", Price =20, ProductVersions = p1}
new ProductTypeDto{ ProductName = "foo1", Price =60, ProductVersions = p1}
new ProductTypeDto{ ProductName = "foo3", Price =10, ProductVersions = p2}
new ProductTypeDto{ ProductName = "foo4", Price =50, ProductVersions = p3}
}
Try this:
var dtos =
(
from p in products
group new ProductVersionDto()
{
VersionId = p.VersionId,
ProductName = p.ProductName,
Supplier = p.Supplier,
Quantity = p.Quantity
} by new { p.TypeId, p.ProductName, p.Price } into g
select new ProductTypeDto()
{
TypeId = g.Key.TypeId,
ProductName = g.Key.TypeId,
Price = g.Key.Price,
ProductVersions = g.ToList(),
}
).ToList();
It appears that you meant Price to be a decimal in ProductTypeDto, BTW.

Using Moq with Entity Framework 6 - Mocking Include and Where

I'm trying to create some In-Memory dbContext mocks using Moq and using EntityFramework.Testing.Moq extension methods:
https://github.com/scott-xu/EntityFramework.Testing
I'm hitting a brick wall when I'm trying to unit test my eagerly loaded queries using mocked context. The issue is, nothing is selected at all. I know something should be selected, because when I point the same query using LINQPad to my source database, I get the 2 results back that I expect.
I never know how much code etc I need to post, so hopefully the below will help.
Data Diagram
I have two main tables, dbo.Applicant that holds basically user details and dbo.Application, which holds job applications, there are lookup tables that dbo.Application hooks into:
Example Models
This is a code first approach based off the above diagram. I have removed properties to keep the code as clean and to the point for the question:
[Table("Application")]
public partial class Application
{
[Min(1)]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string ApplicationId { get; set; }
[Min(1)]
public int ApplicantId { get; set; }
public virtual Applicant Applicant { get; set; }
}
[Table("Applicant")]
public partial class Applicant
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Applicant()
{
Applications = new HashSet<Application>();
RowStatus = "L";
}
public int Id { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string AddressLine1 { get; set; }
[Required]
public string Email { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Application> Applications { get; set; }
}
Context
Again a cut down context, but just in case I'm ask for it in the comments:
public partial class WorkExperienceContext : DbContext
{
public WorkExperienceContext()
: base("name=WorkExperienceContext")
{
this.Configuration.LazyLoadingEnabled = false;
}
public WorkExperienceContext(string userTestConnectionString)
: base(userTestConnectionString)
{
Trace.Write("Using test context " + Add to dictionary);
}
public virtual DbSet<Applicant> Applicants { get; set; }
public virtual DbSet<Application> Applications { get; set; }
}
Unit Test method
The following is (again) a cut down version of the Unit test method where I expect two Application records to be pulled through based off the email.
As you can see from the below, there are:
2 Application records for Lucifer (ApplicantId = 666)
There is one Applicant record with an email of misunderstood#fireandbrimestone.co.uk
My issue is test is null, when querying the database itself with the same code, I get the correct results. I'm perplexed what I need to do. Remember I'm using the EntityFramework.Testing.Moq.SetupData() method which handles the mocking of the DbSet methods and transforming the List<> seed data to an IQueryable collection.
public void InMemory_Find_Application_By_Email_EFMoq()
{
var applications = new List<Application>
{
new Application { Id = 1, ApplicantId = 666, ApplicationId = "1-a", DivisionId = 2, PublishingAreaId = 4, SourceId = 2, SkillsLearnt = "How to pick up pen", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 2, ApplicantId = 666, ApplicationId = "1-a", DivisionId = 3, PublishingAreaId = 4, SourceId = 2, SkillsLearnt = "How to pick up pen", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 3, ApplicantId = 5, ApplicationId = "1-b", DivisionId = 3, PublishingAreaId = 1, SourceId = 2, SkillsLearnt = "Reading a book well" , RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 4, ApplicantId = 5, ApplicationId = "1-b", DivisionId = 2, PublishingAreaId = 1, SourceId = 2, SkillsLearnt = "Reading a book well ", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2 ,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 5, ApplicantId = 5, ApplicationId = "1-b", DivisionId = 7, PublishingAreaId = 1, SourceId = 2, SkillsLearnt = "Reading a book well" , RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2 ,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 6, ApplicantId = 24, ApplicationId = "1-c", DivisionId = 10, PublishingAreaId = 3, SourceId = 1, SkillsLearnt = "I can now re-iterate stuff", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay },
new Application { Id = 7, ApplicantId = 21, ApplicationId = "1-d", DivisionId = 2, PublishingAreaId = 2, SourceId = 1, SkillsLearnt = "I made some bread the other day", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2 ,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay }
};
var applicants = new List<Applicant>
{
new Applicant {Id = 5, FullName = "Pen Is", AddressLine1="Somewhere Over the Rainbow", County="West Sussex", Email="pen_is#hotmail.co.uk", RowStatus = "L",
GenderId = 2, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="PEN 1ST", Telephone="N/A"},
new Applicant {Id = 24, FullName = "Gareth Bradley", AddressLine1="an address", County="West Sussex", Email="gareth.bradley#hachette.co.uk", RowStatus = "L",
GenderId = 2, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="bn13 3qb",Telephone="N/A"} ,
new Applicant {Id = 21, FullName = "Lizzy Windsor", AddressLine1="A Palace (Take your pick)", County="Berkshire", Email="HerRoyalMaj#TheCrownJewels.co.uk", RowStatus = "L",
GenderId = 2, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="BE1 1HM",Telephone="N/A"} ,
new Applicant {Id = 666, FullName = "Lucifer MorningStar", AddressLine1="Hotsy Street", County="Down South", Email="misunderstood#fireandbrimes1one.co.uk" , RowStatus = "L",
GenderId = 3, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="HE11 6SS",Telephone="666-666"}
};
foreach (Applicant applicant in applicants)
{
applicant.Applications = applications.Where(a => a.ApplicantId == applicant.Id).ToArray();
}
foreach (Application application in applications)
{
application.Applicant = applicants.SingleOrDefault(a => a.Id == application.ApplicantId);
}
var mockSet = new Mock<DbSet<Application>>()
.SetupData(applications);
var mockSetUsers = new Mock<DbSet<Applicant>>()
.SetupData(applicants);
var mockContext = new Mock<WorkExperienceContext>();
mockContext.Setup(c => c.Applications).Returns(mockSet.Object);
mockContext.Setup(c => c.Applicants).Returns(mockSetUsers.Object);
var mockService = new WorkExperienceFormService(mockContext.Object);
var test = mockContext.Object
.Applications
.Include(a => a.Applicant)
.Where(e => e.Applicant.Email == "misunderstood#fireandbrimestone.co.uk")
.ToList();
Assert.AreEqual(2, test.Count());
}
The following is proof of my LINQPad is pulling the data without a mocked context:
Maybe it's a typo when you posted the code, but the e-mail for 'Lucifer MorningStar' is
misunderstood#fireandbrimes1one.co.uk instead of
misunderstood#fireandbrimestone.co.uk

How to translate t-sql sum() to linq sum()

I have something like this:
And I need to re-write this query into LINQ.
I tried:
var EachProduct = (from b in _context.ReleasePlans
join a in _context.Products
on b.ProductProductId equals a.ProductId
where b.DepartmentDepartmentId == departmentId
select new { ProductId = b, ProductName = a });
But how can I do SUM() and DATEPART() function in Linq?
UPD: Query using lambda expression. But the question remains the same
var EachProduct = _context.ReleasePlans
.Where(b => b.DepartmentDepartmentId == departmentId)
.Join(_context.Products, a => a.ProductProductId, b => b.ProductId,
(b, a) => new {ProductId = b, ProductName = a});
class Program
{
public class ReleasePlan
{
public int ProductProductId { get; set; }
public int Amount { get; set; }
public DateTime DateTime { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int DepartmentDepartmentId { get; set; }
}
static void Main()
{
var products = new List<Product>
{
new Product {ProductId = 1, ProductName = "1", DepartmentDepartmentId = 1},
new Product {ProductId = 2, ProductName = "2", DepartmentDepartmentId = 1},
new Product {ProductId = 3, ProductName = "3", DepartmentDepartmentId = 1},
};
var releasePlans = new List<ReleasePlan>
{
new ReleasePlan {ProductProductId = 1, Amount = 1, DateTime = DateTime.Now},
new ReleasePlan {ProductProductId = 1, Amount = 1, DateTime = DateTime.Now},
new ReleasePlan {ProductProductId = 1, Amount = 1, DateTime = DateTime.Now.AddMonths(-5)},
new ReleasePlan {ProductProductId = 2, Amount = 2, DateTime = DateTime.Now},
new ReleasePlan {ProductProductId = 2, Amount = 2, DateTime = DateTime.Now},
new ReleasePlan {ProductProductId = 2, Amount = 2, DateTime = DateTime.Now.AddMonths(-5)},
new ReleasePlan {ProductProductId = 3, Amount = 3, DateTime = DateTime.Now},
new ReleasePlan {ProductProductId = 3, Amount = 3, DateTime = DateTime.Now},
new ReleasePlan {ProductProductId = 3, Amount = 3, DateTime = DateTime.Now.AddMonths(-5)},
};
var amountByProducts = from rp in releasePlans
join p in products
on rp.ProductProductId equals p.ProductId
where p.DepartmentDepartmentId == 1 && (rp.DateTime.AddDays(2).Month/3) == 1
group new {rp, p} by new {rp.ProductProductId, p.ProductId, p.ProductName}
into grp
select new
{
grp.Key.ProductId,
grp.Key.ProductName,
PlannedAmount = grp.Sum(g => g.rp.Amount)
};
Console.WriteLine("ProductId ProductName PlannedAmount");
foreach (var producAmount in amountByProducts)
{
Console.WriteLine("{0} \t\t{1} \t\t{2}", producAmount.ProductId, producAmount.ProductName,
producAmount.PlannedAmount);
}
}
}

How would I pivot this object using linq?

If I have the following objects.
public class CFS
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public IList<Topic> Topics { get; set; }
public IList<Status> Status { get; set; }
}
public class Topic
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Status
{
public int ID { get; set; }
public string Name { get; set; }
}
How can I put it into the following object where Topic.ID == Status.ID && Status.Name = "pass"? The Topic and Status string values would be the Topic.Name and Status.Name values respectively. The list of string can be the FirstName, email, whatever, that part is trivial. I realize Topic and Status expose the same properties but that's just for this example.
public class SelectedTopic
{
public string Topic { get; set; }
public string Status { get; set; }
public IList<string> Person { get; set; }
}
I've tried several combinations of SelectMany, Any, Join and I can't seem to pivot the data the way I want.
I don't know why you would want to do this but here is how:
void Main()
{
List<Topic> topicA = new List<Topic>() { new Topic() { ID = 1, Name = "1" }, new Topic() {ID = 2 , Name = "2"}, new Topic() {ID = 3, Name = "3" } };
List<Topic> topicB = new List<Topic>() { new Topic() { ID = 2, Name = "2" }, new Topic() {ID = 3 , Name = "3"}, new Topic() {ID = 4, Name = "4" } };
List<Topic> topicC = new List<Topic>() { new Topic() { ID = 1, Name = "1" } };
List<Topic> topicD = new List<Topic>() { new Topic() {ID = 2 , Name = "2"}, new Topic() {ID = 3, Name = "3" } };
List<Status> statusA = new List<Status>() { new Status() { ID = 1, Name = "pass" }, new Status() {ID = 2 , Name = "2"}, new Status() {ID = 3, Name = "3" } };
List<Status> statusB = new List<Status>() { new Status() { ID = 2, Name = "2" }, new Status() {ID = 3 , Name = "pass"}, new Status() {ID = 4, Name = "pass" } };
List<Status> statusC = new List<Status>() { new Status() { ID = 1, Name = "pass" } };
List<Status> statusD = new List<Status>() { new Status() {ID = 2 , Name = "2"}, new Status() {ID = 3, Name = "pass" } };
List<CFS> test = new List<CFS>() {
new CFS() { FirstName = "A", LastName = "A", Email = "A#A.com", Topics = topicA, Status = statusA },
new CFS() { FirstName = "B", LastName = "B", Email = "B#B.com", Topics = topicB, Status = statusB },
new CFS() { FirstName = "C", LastName = "C", Email = "C#C.com", Topics = topicC, Status = statusC },
new CFS() { FirstName = "D", LastName = "D", Email = "D#D.com", Topics = topicD, Status = statusD },
};
var result = test.SelectMany(x => x.Topics.SelectMany((t) => x.Status, (topic,status) => new { CFS = x, T = topic, S = status }))
.Where(x => x.S.Name == "pass" && x.T.ID == x.S.ID)
.Select(x => new { first = x.CFS.FirstName, status = x.S.Name, topic = x.T.Name})
.GroupBy(x => x.topic)
.Select(x => new SelectedTopic { Topic = x.Key, Status = "pass", Person = x.Select(z => z.first).Distinct().ToList() })
.Dump();
}
Tested in LinqPad -- if you are not using this tool I suggest you do so.

Categories

Resources