How can I create a data set with filler rows in linq? - linq

If I have a list like this:
var teams = new List() { "Team A", "Team B", "Team C" };
And I have a data set with scores like this:
var scores = new List<scoredata> {
new scoredata() { Team = 'Team A', Date = '1/1/2012', Value = 1 },
new scoredata() { Team = 'Team B', Date = '1/1/2012', Value = 1 },
new scoredata() { Team = 'Team C', Date = '1/1/2012', Value = 1 },
new scoredata() { Team = 'Team A', Date = '1/2/2012', Value = 2 },
new scoredata() { Team = 'Team B', Date = '1/3/2012', Value = 3 },
new scoredata() { Team = 'Team C', Date = '1/4/2012', Value = 4 }
}
Is it possible to construct a data set that looks like this?
Team A, '1/1/2012', 1
Team B, '1/1/2012', 1
Team C, '1/1/2012', 1
Team A, '1/2/2012', 2
Team B, '1/2/2012', null
Team C, '1/2/2012', null
Team A, '1/3/2012', null
Team B, '1/3/2012', 3
Team C, '1/3/2012', null
Team A, '1/4/2012', null
Team B, '1/4/2012', null
Team C, '1/4/2012', 4
I'm not sure what this is called, but I want to fill out blank dates and scores in my final dataset so that it always returns all Teams for each date, but if score data is not available returns null.

var dates = scores.Select(s => s.Date).Distinct();
var result =
from date in dates
from team in teams
let teamScores = scores.Where(s => s.Team == team && s.Date == date)
orderby date
select new { team, date, Score = teamScores.FirstOrDefault() };
Didn't check with compiler though, give it a try.

Using pure LINQ to Objects.
public class ScoreData
{
public string Team { get; set; }
public string Date { get; set; }
public int? Value { get; set; }
}
var teams = new[] { "Team A", "Team B", "Team C" };
var scores = new[]
{
new ScoreData { Team = "Team A", Date = "1/1/2012", Value = 1 },
new ScoreData { Team = "Team B", Date = "1/1/2012", Value = 1 },
new ScoreData { Team = "Team C", Date = "1/1/2012", Value = 1 },
new ScoreData { Team = "Team A", Date = "1/2/2012", Value = 2 },
new ScoreData { Team = "Team B", Date = "1/3/2012", Value = 3 },
new ScoreData { Team = "Team C", Date = "1/4/2012", Value = 4 },
};
var dates = scores.Select(score => score.Date).Distinct();
var query =
from date in dates
from team in teams
join score in scores
on new { Team = team, Date = date }
equals new { score.Team, score.Date }
into filteredScores
let defaultScore = new ScoreData
{
Team = team,
Date = date,
Value = null,
}
from score in filteredScores.DefaultIfEmpty(defaultScore)
select score;
Note, this most likely won't work as-is in LINQ to SQL or LINQ to Entities, it will need some tweaks.

Related

LINQ GroupBy while converting from string to decimal and then back to string

Is it possible to convert a string value to a decimal value within a LINQ expression that performs an aggregate function like SUM or AVERAGE?
Assume the example below where I have a collection of Bank Accounts where my goal is to obtain an average of each customers bank account if they have a balance. The data comes from an XML API where all the data is read in a strings.
public class BankAccount
{
string Id{ get; set; }
string CustomerId { get; set; }
string Balance { get; set; }
}
Sample data ...
{ Id = "1", CustomerId = "Bob", Balance = "1" }
{ Id = "2", CustomerId = "Bob", Balance = "2" }
{ Id = "3", CustomerId = "Sam", Balance = "4" }
{ Id = "4", CustomerId = "Sam", Balance = "" }
{ Id = "5", CustomerId = "Alice", Balance = "" }
LINQ grouping expression. Is there a way to convert the value of Balance to a decimal so an average can be taken within the LINQ statement? I tried x => Decimal.Parse(x.Balance) but got an Input string was not in a correct format error. I only need to convert the Balance property to decimal for the Average calculation as the results would be rendered as a string in the XML.
At the same time, if an account does not have a balance listed (i.e. it's blank like Sams's first account and Alice's only account above) then I don't want the Average to take that entry included in the average, though I still want the account grouped in for display.
var groupedResults = allAccounts
.GroupBy(x => new {x.CustomerId, x.Balance})
.Select(g => new BankAccount {
CustomerId = g.Customer.Key.CustomerId,
Balance = g.Average(x => x.Balance)
}).ToList();
These are the results I am looking for:
{ CustomerId = "Bob", Balance = "1.5" }
{ CustomerId = "Sam", Balance = "4" }
{ CustomerId = "Alice", Balance = "" }
I think to achieve the result you are looking for you should try this:
var groupedResults = allAccounts
.GroupBy(x =>x.CustomerId)
.Select(g => new BankAccount {
CustomerId = g.Key,
Balance = g.Where(x =>!string.IsNullOrEmpty(x.Balance))
.Select(x =>(decimal?)decimal.Parse(x.Balance))
.DefaultIfEmpty(null)
.Average().ToString()
}).ToList();
First just group by CustomerId, is not necessary to include the Balance there. Then, to get the average and avoid the error parsing include the condition to make sure the Balance is not empty.
Another way to do it using query syntax:
from e in allAccounts
group e by e.CustomerId into g
let temp=g.Where(x =>!string.IsNullOrEmpty(x.Balance))
select new BankAccount(){CustomerId = g.Key,
Balance =temp.Any()?
temp.Average(x =>Decimal.Parse(x.Balance)).ToString():""
};
decimal d;
var groupedResults = allAccounts.GroupBy(a => a.CustomerId)
.Select(g => new BankAccount { CustomerId = g.Key, Balance = g.Average(b =>
decimal.TryParse(b.Balance, out d) ? (decimal?)d : null).ToString() }).ToList();
The .TryParse part results in (decimal?)null for strings that can't be parsed, which are then ignored by .Average. Also, the last average for Alice results in (decimal?)null and then in "".

Linq to Sql Group By without count

I have a linq to sql and have been researching how to use linq to sql to group your results. I only see samples with count and sum in them. My model is that each customer order has a variety of notes and could have multiple notes. Right now it's listing all the customer orders and multiple times if it has multiple notes.
How do I use group by in Linq to Sql without the sums/counts aggregate
I have tried:
public IQueryable<object> getAllamcase()
{
try
{
var q = (from c in _context.Customer
join am in _context.table2 on c.id equals am.id
join ampn in _context.table3 on am.id equals ampn.id
join ay in _context.tabl4 on am.id equals ay.id
join oim in _context.table5 on am.id equals oim.id
group c.FileNum by new
{
FileNum = c.order,
assignmentdt = am.Assignment_DT,
oimname = oim.FullName,
notes = ampn.ProgressNotes,
years = ay.AMYear
}).AsQueryable();
return q;
}
catch (Exception ex)
{
_logger.LogError("Could not get......", ex);
return null;
}
}
My results are look as multiple jsons
Customer notes
1 notes 1
1 notes 2
1 notes 3
2 notes 1
2 notes 2
I just want it to return in one json like
Customer notes
1 notes 1
notes 2
notes 3
2 notes 1
notes 2
Your question is unclear, but as I #GertArnold stated, if you want to load notes of customers you should use Navigation properties. Also, please look at naming conventions. Your code will be much cleaner if you name variables, etc. correctly. But according to your question header, I can suggest you following. Imagine that you have Note class:
public class Note
{
public int CustomerId { get; set; }
public string NoteName { get; set; }
}
And you have list of Notes as following:
List<Note> notes = new List<Note>
{
new Note { CustomerId = 1, NoteName = "note 1" },
new Note { CustomerId = 1, NoteName = "note 2" },
new Note { CustomerId = 1, NoteName = "note 3" },
new Note { CustomerId = 1, NoteName = "note 4" },
new Note { CustomerId = 2, NoteName = "note 1" },
new Note { CustomerId = 2, NoteName = "note 2" },
new Note { CustomerId = 3, NoteName = "note 1" },
};
If you want to get CustomerId-s and related notes from this list you can easyli achieve it by grouping them:
var result = notes
.GroupBy(m => m.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
Notes = g.Select(m => m.NoteName).ToList()
});
The result will be:
CustomerId || NoteName
1 note 1
note 2
note 3
note 4
2 note 1
note 2
3 note 1
I hope, it will help you.

LINQ EF QUERY (view difference from condition & dynamic)

I need to make a query to filter records, when get distinct records, get these records information by difference conditions. Also I need these to be dynamic(quantity filter in first select)
Let me show you an example:
I have 2 tables:
tblCustomers:
id customerName
1 John
2 Philip
3 Steve
tblOrders
id customerId ordId payment
1 1 100 True
2 1 101 True
3 1 102 False
4 2 101 True
5 2 102 True
6 2 103 False
7 3 101 True
My condition is:
where (orderId = 101 and orderId = 102)
but get all records of this customer that payment = true I mean my condition is different from what I need to see.
I want to receive all records with payment=True without care of orderId
I must get:
john 100
john 101
Philip 101
Philip 102
Clearing: I need two step - first filter customer who has orderId=101&102, in second step i want to show these selected customers' orderId which payment is true. so for example in first step i get john(who has order id =101&102) then show john 100 - john 101 (which payment istrue). consider tblorder.id=1 isn't in first query but I must show in final result.
#Raphael direct me to better expression:I want to see all payment true order for the customers that have orders (101 & 102). but orderids may be more than 2 (thanks #Raphael).
2nd problem is: it must be dynamic. Sometimes I have more than 10 orderId that must be checked - sometimes less. I mean my query must be flexible.
In SQL Server select command, I can prepare a string variable and use but in linq I can't do it.
From what I understood from your post and the comments, you need all customers, where the orderId is 101 or 102 and the payment is true.
You need the where clause with the orderIds to be dynamic so you can change the Ids to be checked against outside of the query.
List<int> IDList = new List<int>();
IDList.Add(101);
IDList.Add(102);
IDList.Add(110);
//...
var result = from cust in tblCustomers
join order in tblOrders on cust.id equals order.customerId
where IDList.Contains(order.ordId) && order.payment == true
select new {
Name = cust.customerName
OrderId = order.ordId
payment = order.payment
//...
}
With this you can store all orderIds which need to be checked against in a list, which in turn you can edit from your code.
EDIT
I really haven't found a clean solution to your problem, so I took a detour, which isn't very clean but should work. In my example I created 2 classes, Customer & Order and filled it with your data from above. Then I took my first query and attached a groupBy to it and a where-clause comparing the length of the grouping with the length of the list
var result = (from cust in Customers
join order in Orders on cust.Id equals order.customerId
where IDList.Contains(order.orderId) &&
order.payment == true
select new {
Name = cust.Name,
OrderId = order.orderId,
Payment = order.payment
//...
}).GroupBy (r => r.Name)
.Where (r => r.Count() == IDList.Count());
Output:
Name OrderId Payment
Philip 101 True
Philip 102 True
If you want/need it, I can provide you with the whole Linqpad query, so you can see my whole code and what I have done. Speaking of Linqpad: ignore the result.Dump() line. It won't work on visual Studio.
void Main()
{
List<Customer> customers = new List<Customer>
{
new Customer { Id = 1, Name = "John" },
new Customer { Id = 2, Name = "Philip" },
new Customer { Id = 3, Name = "Steve" }
};
List<Order> orders = new List<Order>
{
new Order { Id = 1, CustomerId = 1, OrderId = 100, Payment = true },
new Order { Id = 2, CustomerId = 1, OrderId = 101, Payment = true },
new Order { Id = 3, CustomerId = 1, OrderId = 102, Payment = false },
new Order { Id = 4, CustomerId = 2, OrderId = 101, Payment = true },
new Order { Id = 5, CustomerId = 2, OrderId = 102, Payment = true },
new Order { Id = 6, CustomerId = 2, OrderId = 103, Payment = false },
new Order { Id = 7, CustomerId = 3, OrderId = 101, Payment = true }
};
List<int> orderIds = new List<int> { 101, 102 };
var customersWithRelevantOrders =
from ord in orders
group ord by ord.CustomerId into customerOrders
where orderIds.All (
i => customerOrders.Select (co => co.OrderId).Contains(i))
select customerOrders.Key;
var paymentTrueOrdersForTheseCustomers =
from ord in orders
join cust in customers on ord.CustomerId equals cust.Id
where ord.Payment
where customersWithRelevantOrders.Contains(cust.Id)
select new
{
Name = cust.Name,
OrderId = ord.OrderId
};
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
public bool Payment { get; set; }
}

PIVOT with LINQ from Datatable [duplicate]

I have a collection of items that contain an Enum (TypeCode) and a User object, and I need to flatten it out to show in a grid. It's hard to explain, so let me show a quick example.
Collection has items like so:
TypeCode | User
---------------
1 | Don Smith
1 | Mike Jones
1 | James Ray
2 | Tom Rizzo
2 | Alex Homes
3 | Andy Bates
I need the output to be:
1 | 2 | 3
Don Smith | Tom Rizzo | Andy Bates
Mike Jones | Alex Homes |
James Ray | |
I've tried doing this using foreach, but I can't do it that way because I'd be inserting new items to the collection in the foreach, causing an error.
Can this be done in Linq in a cleaner fashion?
I'm not saying it is a great way to pivot - but it is a pivot...
// sample data
var data = new[] {
new { Foo = 1, Bar = "Don Smith"},
new { Foo = 1, Bar = "Mike Jones"},
new { Foo = 1, Bar = "James Ray"},
new { Foo = 2, Bar = "Tom Rizzo"},
new { Foo = 2, Bar = "Alex Homes"},
new { Foo = 3, Bar = "Andy Bates"},
};
// group into columns, and select the rows per column
var grps = from d in data
group d by d.Foo
into grp
select new {
Foo = grp.Key,
Bars = grp.Select(d2 => d2.Bar).ToArray()
};
// find the total number of (data) rows
int rows = grps.Max(grp => grp.Bars.Length);
// output columns
foreach (var grp in grps) {
Console.Write(grp.Foo + "\t");
}
Console.WriteLine();
// output data
for (int i = 0; i < rows; i++) {
foreach (var grp in grps) {
Console.Write((i < grp.Bars.Length ? grp.Bars[i] : null) + "\t");
}
Console.WriteLine();
}
Marc's answer gives sparse matrix that can't be pumped into Grid directly.
I tried to expand the code from the link provided by Vasu as below:
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
return source.GroupBy(key1Selector).Select(
x => new
{
X = x.Key,
Y = source.GroupBy(key2Selector).Select(
z => new
{
Z = z.Key,
V = aggregate(from item in source
where key1Selector(item).Equals(x.Key)
&& key2Selector(item).Equals(z.Key)
select item
)
}
).ToDictionary(e => e.Z, o => o.V)
}
).ToDictionary(e => e.X, o => o.Y);
}
internal class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public string Function { get; set; }
public decimal Salary { get; set; }
}
public void TestLinqExtenions()
{
var l = new List<Employee>() {
new Employee() { Name = "Fons", Department = "R&D", Function = "Trainer", Salary = 2000 },
new Employee() { Name = "Jim", Department = "R&D", Function = "Trainer", Salary = 3000 },
new Employee() { Name = "Ellen", Department = "Dev", Function = "Developer", Salary = 4000 },
new Employee() { Name = "Mike", Department = "Dev", Function = "Consultant", Salary = 5000 },
new Employee() { Name = "Jack", Department = "R&D", Function = "Developer", Salary = 6000 },
new Employee() { Name = "Demy", Department = "Dev", Function = "Consultant", Salary = 2000 }};
var result5 = l.Pivot3(emp => emp.Department, emp2 => emp2.Function, lst => lst.Sum(emp => emp.Salary));
var result6 = l.Pivot3(emp => emp.Function, emp2 => emp2.Department, lst => lst.Count());
}
* can't say anything about the performance though.
You can use Linq's .ToLookup to group in the manner you are looking for.
var lookup = data.ToLookup(d => d.TypeCode, d => d.User);
Then it's a matter of putting it into a form that your consumer can make sense of. For instance:
//Warning: untested code
var enumerators = lookup.Select(g => g.GetEnumerator()).ToList();
int columns = enumerators.Count;
while(columns > 0)
{
for(int i = 0; i < enumerators.Count; ++i)
{
var enumerator = enumerators[i];
if(enumator == null) continue;
if(!enumerator.MoveNext())
{
--columns;
enumerators[i] = null;
}
}
yield return enumerators.Select(e => (e != null) ? e.Current : null);
}
Put that in an IEnumerable<> method and it will (probably) return a collection (rows) of collections (column) of User where a null is put in a column that has no data.
I guess this is similar to Marc's answer, but I'll post it since I spent some time working on it. The results are separated by " | " as in your example. It also uses the IGrouping<int, string> type returned from the LINQ query when using a group by instead of constructing a new anonymous type. This is tested, working code.
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 1, UserName = "Mike Jones"},
new { TypeCode = 1, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
var Columns = from i in Items
group i.UserName by i.TypeCode;
Dictionary<int, List<string>> Rows = new Dictionary<int, List<string>>();
int RowCount = Columns.Max(g => g.Count());
for (int i = 0; i <= RowCount; i++) // Row 0 is the header row.
{
Rows.Add(i, new List<string>());
}
int RowIndex;
foreach (IGrouping<int, string> c in Columns)
{
Rows[0].Add(c.Key.ToString());
RowIndex = 1;
foreach (string user in c)
{
Rows[RowIndex].Add(user);
RowIndex++;
}
for (int r = RowIndex; r <= Columns.Count(); r++)
{
Rows[r].Add(string.Empty);
}
}
foreach (List<string> row in Rows.Values)
{
Console.WriteLine(row.Aggregate((current, next) => current + " | " + next));
}
Console.ReadLine();
I also tested it with this input:
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 3, UserName = "Mike Jones"},
new { TypeCode = 3, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
Which produced the following results showing that the first column doesn't need to contain the longest list. You could use OrderBy to get the columns ordered by TypeCode if needed.
1 | 3 | 2
Don Smith | Mike Jones | Tom Rizzo
| James Ray | Alex Homes
| Andy Bates |
#Sanjaya.Tio I was intrigued by your answer and created this adaptation which minimizes keySelector execution. (untested)
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
var lookup = source.ToLookup(x => new {Key1 = key1Selector(x), Key2 = key2Selector(x)});
List<TKey1> key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList();
List<TKey2> key2s = lookup.Select(g => g.Key.Key2).Distinct().ToList();
var resultQuery =
from key1 in key1s
from key2 in key2s
let lookupKey = new {Key1 = key1, Key2 = key2}
let g = lookup[lookupKey]
let resultValue = g.Any() ? aggregate(g) : default(TValue)
select new {Key1 = key1, Key2 = key2, ResultValue = resultValue};
Dictionary<TKey1, Dictionary<TKey2, TValue>> result = new Dictionary<TKey1, Dictionary<TKey2, TValue>>();
foreach(var resultItem in resultQuery)
{
TKey1 key1 = resultItem.Key1;
TKey2 key2 = resultItem.Key2;
TValue resultValue = resultItem.ResultValue;
if (!result.ContainsKey(key1))
{
result[key1] = new Dictionary<TKey2, TValue>();
}
var subDictionary = result[key1];
subDictionary[key2] = resultValue;
}
return result;
}

Using Linq to determine the missing combination of records

Let me start to explain with an example.
var vProducts = new[] {
new { Product = "A", Location ="Y", Month = "January", Demand = 50 },
new { Product = "A", Location ="Y", Month = "February", Demand = 100 },
new { Product = "A", Location ="Y", Month = "March", Demand = 20 },
new { Product = "A", Location ="Y", Month = "June", Demand = 10 }
};
var vPeriods = new[] {
new { Priority = 1, Month = "January" },
new { Priority = 2, Month = "February" },
new { Priority = 3, Month = "March" },
new { Priority = 4, Month = "April" },
new { Priority = 5, Month = "May" },
new { Priority = 6, Month = "June" }
};
var vAll = from p in vProducts
from t in vPeriods
select new
{
Product = p.Product,
Location = p.Location,
Period = t.Priority,
PeriodName = t.Month,
Demand = p.Demand
};
This above query will create all combinations of Products & Period. But, I need to get a list of all products along with the ones that do not have matching Month as shown below.
example
Product Location Priority Month Demand
A Y 1 January 50
A Y 2 February 100
A Y 3 March 20
A Y 4 April null
A Y 5 May null
A Y 6 June 10
Thanks for any comments.
You want to do a left outer join, it will go something like:
var res = from period in vPeriods
join product in vProducts on period.Month equals product.Month into bla
from p in bla.DefaultIfEmpty()
select new { period.Month, period.Priority, Product = p == null ? null : p.Product, Demand = p == null ? -1 : p.Demand };
foreach (var a in res)
{
Console.WriteLine(string.Format("{0} {1} {2}", a.Product, a.Month, a.Demand));
}
Of course that products that do not have matching months don't have locations etc. (as you stated in your example)

Resources