How can I get two different aggregates in a single LINQ? - c#

I have a list of this object:
public class Customer
{
public int Id { get; set; }
public string EmailAddress { get; set; }
public DateTime ServiceStartDate { get; set; }
public DateTime? BillingStartDate { get; set; }
public string Status { get; set; }
}
In preparation for making a chart displayed on a dashboard I am trying to condense this list into another list of this object:
public class DashboardCustomerConversions
{
public string Month { get; set; }
public int Trials { get; set; }
public int Purchased { get; set; }
}
Where the end result looks something like:
Month Trials Purchases
--------- ------ ---------
Dec 2010 390 250
Jan 2011 345 190
Feb 2011 576 340
I am having a hard time coming up with a LINQ statement that can achieve the desired end result. This statement is very close:
var list = from b in results
group b by new { b.ServiceStartDate.Year, b.ServiceStartDate.Month } into g
select new Test
{
Month = string.Format("{0} {1}", CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(g.Key.Month), g.Key.Year),
Trials = g.Count(),
Purchased = g.Count()
};
The obvious problem in is the "Purchased = g.Count()" line in that it just repeats the Trials result. I would like to count objects where the BillingStartDate.HasValue is true.
Is there a way to restructure the LINQ to make this work?
Edit: I would prefer a fluent style of syntax but I was unable to get the above to work. Answer in any variation would be great.

You need to pass a condition to the Count method.
Purchased = g.Count(q => q.BillingStartDate.HasValue)

So SLaks had the right solution. Here it is written in fluent syntax:
listOfCustomer.GroupBy(c => new { c.ServiceStartDate.Year, c.ServiceStartDate.Month })
.Select(group => new DashboardCustomerConversions()
{
Month = string.Format("{0} {1}", CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(group.Key.Month), group.Key.Year),
Trials = group.Count(),
Purchased = group.Count(c => c.BillingStartDate.HasValue)
});

Related

Linq two table with sum column

My RowMultiplevaluw table is
public class RowMultipleValues
{
public int ID { get; set; }
public String Year{ get; set; }
public string country { get; set; }
public decial Admin { get; set; }
public decimal Finance { get; set; }
public virtual ICollection<UsedAmount> UsedAmount { get; set; }
}
My used amount table is
public class UsedAmount
{
public int ID { get; set; }
public string Year{ get; set; }
public string country { get; set; }
public decial UsedAmount { get; set; }
public int RowMultipleValues ID { get; set; }
Public virtual RowMultibleValue RowMultibleValue { get; set; }
}
My query is
var query = from mtv in context.multiplerowvaluetable
join usd in dbcontext.usedtsble on mtv.year equal usd.year group g by mtv.country into g
select new { country =g.key,sumadmincolumn =g.sum(Admin),sumfinancecolumn = g.sum(finance) }).tolist();
Result which I want is
ID Year Country Admin. UsedAdmin Finance UsedFinance
1. 2017 USA 100 50 200 300
2. 2017 China 300 300 500 400
Total. 400 350 700 700
Please help me my model design and query for result.Thank.
So you want to join every MultipleValue with the UsedAmount on equal year value. Then group the result into groups of joined items with same country. Finally from every group create one object with the country, the sum of all Admin values and the sum of all finance values.
// first join the two collections on same year.
// we only need properties Country, Admin, Finance:
var result = myDbContext.MultipleRowValueTable.Join(myDbContext.UsedAmountTable,
multipleRow => multipleRow.Year, // from every multipleRow take the year
usedAmount => usedAmount.Year, // from every usedAmount take the year
(multipleRow, usedAmount) => new // when they match make a new object
{
Country = multipleRow.Country,
Admin = multipleRow.Admin,
UsedAdmin = usedAmount.Admin,
Finance = multipleRow.Finance,
UsedFinance = usedAmount.Finance,
})
// group the elements from this join table into groups with same Country
.GroupBy(joinedItem => joinedItem.Country, // all items in the group have this Country
joinedItem => new // the elements of the group
{
Admin = joinedItem.Admin,
UsedAdmin = joinedItem.UsedAdmin,
Finance = joinedItem.Finance,
UsedFinance = joinedItem.UsedFinance,
})
// finally: from every group take the Key (which is the Country)
// and the sum of the Admins and Finances in the group
.Select(group => new
{
Country = group.Key,
SumAdminColumn = group
.Select(groupElement => groupElement.Admin)
.Sum(),
... // others are similar
});
// from every group take the elements and sum the properties
.Select(group => new
{
Id = multipleRowValue.Id,
Year = multipleRowValue.Year,
Country = multipleRowValue.Country,
}

List of Objects, update if found otherwise add

I have the code below which works:
List<XXTJobTableModel> xjobs = filexxts.GroupBy(x=> x.job)
.Select(fx => new JobTableModel
{
job_no = fx.First().job,
emps = fx.GroupBy(x=>x.emp_id).Select(x => new EmployeeTableModel
{
eid = x.First().emp_id,
heds = x.GroupBy(h => h.HED).Select(h => new HEDModel
{
hed = h.First().HED,
hours = h.Sum(c => c.HOURS),
amt = h.Sum(c => c.AMOUNTRATE)
}).ToList()
}).ToList()
}).ToList();
public class JobTableModel
{
public string job_no { get; set; }
public List<EmployeeTableModel> emps { get; set; }
}
public class EmployeeTableModel
{
public string emp_id { get; set; }
public List<HEDModel> heds { get; set; }
}
public class THEDModel
{
public string hed { get; set; }
public decimal hours { get; set; }
public decimal amt { get; set; }
}
I have another List<XXTJobTableModel> yjobs already loaded from another data source. What I would like to do, if the job_no is not found in yjobs, then add the job (with employee and he data) to yjobs. If the job_no is found in yjobs, and the emp_id is not found in the Employee table for that job, then add the employee (and hed data) to that jobs. If the employee is found in that job, then just add the HED data to the employees list.
I do not have a preference if I somehow merge xjobs and yjobs or if I load the xjobs directly into the yjobs list.
thanks
Are you looking for a very concise set of LINQ statements to do this merge operation or just any solution at all? What I would do is loop through one set and use LINQ to compare each item to the other set, and add items resulting from the comparison to a third set. Problems like this you have to decompose into manageable pieces, and then recompose it into a larger solution once the smaller pieces are working. Make an attempt and if you get stuck someone on here will surely be able to fill in the blanks.

LINQ GroupBy 3 properties?

I've got the following class:
public class ProductInventory : Entity
{
public virtual Product Product { get; set; }
public DateTime ExpirationDate { get; set; }
public Manager Manager { get; set; }
}
and a class used in my ViewModels that looks like so:
public class ProductInventoryCountModel
{
public Product Product { get; set; }
public DateTime ExpirationDate { get; set; }
public int Count { get; set; }
}
I want to get an output of Dictionary<Manager, List<ProductInventoryCountModel> that basically displays Products by manager and then by expiration date so that I can print out actual data looking something like this:
ManagerBoB
--ProductA, Expires 8/15/13, Count: 10
--ProductA, Expires 10/10/13, Count: 40
--ProductB, Expires 6/13/13, Count: 30
ManagerTim
--ProductA, Expires 10/10/13, Count: 5
--ProductB, Expires 5/25/13, Count: 10
--ProductB, Expires 6/13/13, Count 40
how would I write this query in LINQ when starting with a list of ProductInventory? I tried using multiple .GroupBy but that didn't work.
You need to group by an object with multiple properties:
list.GroupBy(p => new { p.Manager, p.Product.Name, p.ExpirationDate.Date })
This works because anonymous types implement Equals() and GetHashCode() to compare by value.
You stated that you want a Dictionary<Manager, List<ProductInventoryCountModel>>, so I think you'd have to do something like this:
var dictionary =
db.ProductInventory.GroupBy(x => new { x.Manager, x.Product, x.ExpirationDate })
.ToDictionary(g => g.Key.Manager,
g => g.Select(x => new ProductInventoryCountModel
{
Product = x.Key.Product,
ExpirationDate = x.Key.ExpirationDate,
Count = x.Count()
}).ToList());

RavenDB index to return most recent entry

I am new to RavenDB and I am trying to query the document model below with the index below. The index is almost working as desired, except now I need to only include the most recent status for a date in the total. For example, a client could have multiple import statuses for a date, but only the last status should be counted in the resulting totals.
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public IList<ImportStatusMessage> ImportStatuses { get; set; }
}
public class ImportStatusMessage
{
public DateTime TimeStamp { get; set; }
public ImportStatus Status { get; set; }
}
public enum ImportStatus
{
Complete,
Running,
Failed,
Waiting,
NoReport
}
I am using the following index:
public class Client_ImportSummaryByDate : AbstractIndexCreationTask<Client, ImportSummary>
{
public Client_ImportSummaryByDate()
{
Map = clients => from client in clients
from status in client.ImportStatuses
select new
{
status.Status,
Date = status.TimeStamp.Date,
Count = 1
};
Reduce = results => from result in results
group result by new { result.Status, result.Date }
into g
select new
{
g.Key.Status,
g.Key.Date,
Count = g.Sum(x => x.Count)
};
}
}
public class ImportSummary
{
public ImportStatus Status { get; set; }
public DateTime Date { get; set; }
public int Count { get; set; }
}
Can this be accomplished with an index? Do I need a different approach to solve this problem?
Instead of:
from status in client.ImportStatuses
Consider:
let status = client.ImportStatuses.Last()
If they might be out of order in the list, you could do:
let status = client.ImportStatuses.OrderBy(x => x.TimeStamp).Last()
You could also use First instead of Last if they were so ordered that way.
Any of these would index just a single status per client. If instead you mean that you want multiple status, but only the last on any given date, you could do:
Map = clients => clients.SelectMany(x => x.ImportStatuses, (x, y) => new {x.Id, y.Status, y.TimeStamp})
.GroupBy(x => new {x.Id, x.TimeStamp.Date})
.Select(g => g.OrderBy(x => x.TimeStamp).Last())
.Select(x => new
{
x.Status,
x.TimeStamp.Date,
Count = 1
});
All of this would be in the map part of the index, since the list is self-contained in each document.

20 most viewed "codes" with include "User" and "Language"

I have two tables "Codes" and "Views" and want to get a list of the 20 Codes with most views since x days ago (thats my variable dt).
Im able to get the list and sort it, but i have a problem i also would like to include 2 other tables "User" and "Language".
var query = from f in _db.Codes
select new
{
Code = f,
PostCount = f.ViewModels.Count(p => p.timestamp > dt)
};
var result = query.OrderByDescending(x => x.PostCount).Select(y => y.Code).Take(20)
But after doing like this i'm not able to Include my other tables. I tried to convert the result to a ObjectQuery but then it becomes null (there are 20 Codes in the result before trying to convert it).
The Code model looks like this
[Key]
public int CodeID { get; set; }
public int UserID { get; set; }
public UserModel User { get; set; }
[DisplayName("Language")]
public int LanguageID { get; set; }
public LanguageModel Language { get; set; }
public virtual ICollection<ViewModel> ViewModels { get; set; }
Easy solution:
IEnumerable<CodeModel> posts = _db.Codes
.Include("User")
.Include("Language")
.OrderByDescending(f => f.ViewModels.Count(p => p.timestamp > dt))
.Take(20);

Categories