probably someone can help me with this (at least for me) complicated problem.
Lets say i have the following data (in DB)
Tab1 (id_t1): Item
(1)
(2)
(3)
Tab2 (id_t2, id_t1): Group
(4, 1)
(5, 1)
(6, 2)
(7, 3)
Tab3 (id_t3, id_t2, v): GroupField
(10, 4, 100)
(11, 4, 300)
(12, 5, 200)
(13, 6, 100)
(14, 6, 200)
(15, 7, 100)
(16, 7, 300)
Now i'd like to select all Items that include all of some specific GroupFields.
Eg. i have v = list(100,200)
and i like to get back 1,2 but not 3
1 because Group4 holds the Field10 with v=100 and Group5 holds Field12 with v=200
and 2 because Group6 holds Field13 with v=100 and Field14 with v=200
Is something like this possible in Linq? (i allready tried different ways (any/all) but without success so far.
I don't get the point how to overcome that "field can be in any Group and not all in one Group"...
I don't even know how to do this in SQL in one command without using temp-tables/cursors.
_rene
Try this:
var result =
groups.Join(fields, o => o.Id, i => i.GroupId,
(o, i) => new { Group = o, Field = i } )
.GroupBy(x => x.Group.ItemId)
.Where(x => values.All(y => x.Any(z => z.Field.Value == y)))
.Select(x => x.Key)
.Distinct();
The following classes are used:
class Group
{
public Group(int id, int itemId)
{
Id = id;
ItemId = itemId;
}
public int Id { get; set; }
public int ItemId { get; set; }
}
class GroupField
{
public GroupField(int id, int groupId, int value)
{
Id = id;
GroupId = groupId;
Value = value;
}
public int Id { get; set; }
public int GroupId { get; set; }
public int Value { get; set; }
}
and the following initialization:
var groups = new [] { new Group(4, 1), new Group(5, 1),
new Group(6, 2), new Group(7, 3) };
var fields = new [] { new GroupField(10, 4, 100),
new GroupField(11, 4, 300),
new GroupField(12, 5, 200),
new GroupField(13, 6, 100),
new GroupField(14, 6, 200),
new GroupField(15, 7, 100),
new GroupField(16, 7, 300)
};
var values = new [] { 100, 200 };
Related
If we have an EF entity with basic structure of
public class Entity
{
public int Id {get; set;}
public string name {get; set;}
public DateTime lastUpdated {get; set;}
}
And a collection of them with 4 entities:
entities = new List<Entity>{
new { Id = 0, Name = "Thing One", LastUpdated = new DateTime(2020, 10, 1, 0, 0, 0,)},
new { Id = 1, Name = "Thing One", LastUpdated = new DateTime(2020, 10, 4, 0, 0, 0,)},
new { Id = 2, Name = "Thing One", LastUpdated = new DateTime(2020, 10, 3, 0, 0, 0,)},
new { Id = 3, Name = "Thing One", LastUpdated = new DateTime(2020, 10, 2, 0, 0, 0,)}
};
If we use Linq to select the FirstOrDefault by the Name property.
var selectedEntity = entities.FirstOrDefault(e => e.Name == "Thing One");
What is this going to return?
This is causing issues in a piece of code that I'm reviewing; which will be fixed by only allowing unique entities and using SingleOrDefault, but I'm curious at to what the FirstOrDefault is going to return by default when there is no OrderBy clause.
The first item matching this condition is returned, if there are multiple items matching the result is unpredictable/undefined. If you want to ensure a specifc item you need to apply an OrderBy:
var selectedEntity = entities
.Where(e => e.Name == "Thing One")
.OrderByDescending(e => e.LastUpdated)
.FirstOrDefault();
What is returned by the database depends on the vendor. This related qustion might help: When no 'Order by' is specified, what order does a query choose for your record set?
I have a StudentData class
public class StudentData
{
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public int? Bonus { get; set; }
public int? Subject1Mark { get; set; }
public int? Subject2Mark { get; set; }
public int? Subject3Mark{ get; set; }
}
Each student has a unique Id that identifies him
I have a List<StudentData> CurrentData that has data of
1, John, Smith, 10, 50 ,50 ,50
2, Peter, Parker, 10, 60 ,60 ,60
3, Sally, Smart, 10, 70 ,70 ,70
4, Danny, Darko, 20, 80, 80, 80
I then have a List<StudentData> DataToUpdate which only contains the Id and Marks fields. Not the other fields.
1, null, null, null, 50 ,50 ,50
2, null, null, null, 65, 60 ,60
3, null, null, null, 70 ,70 ,70
The Ids of the list are not necessary in the same order
If you compare the two lists only Peter Parker's marks have changed in one subject.
I want to get the output to return
2, Peter, Parker, 10, 65 ,60 ,60
I want to takeList<StudentData> CurrentData inner join this with List<StudentData> DataToUpdate but only where marks are different
So in SQL it want the following
SELECT
CurrentData.Id,
CurrentData.FirstName ,
CurrentData.Surname,
CurrentData.Bonus,
DataToUpdate.Subject1Mark,
DataToUpdate.Subject2Mark,
DataToUpdate.Subject3Mark
FROM CurrentData
INNER JOIN DataToUpdate
ON CurrentData.Id= DataToUpdate.Id
AND (
CurrentData.Subject1Mark<> DataToUpdate.Subject1Mark
OR
CurrentData.Subject2Mark<> DataToUpdate.Subject2Mark
OR
CurrentData.Subject3Mark<> DataToUpdate.Subject3Mark
)
How do I do the above in LINQ?
In the Linq select how do I take all properties from CurrentData but include the 3 Subject properties from DataToUpdate in it to give me List<ChangedData>?
I could map each and every property but my StudentData has 100 fields and I would prefer to have something like
select new StudentData {
this=CurrentData,
this.Subject1Mark=DataToUpdate.Subject1Mark,
this.Subject2Mark=DataToUpdate.Subject2Mark,
this.Subject3Mark=DataToUpdate.Subject3Mark,
}
but I'm not sure how to write this
There is an answer in another stackoverflow question which should work but it doesn't. If I implement that solution (I simplify the example for simplicity)
var changedData = currentData
.Join(dataToUpdate, cd => cd.Id, ld => ld.Id, (cd, ld) => new { cd, ld })
.Select(x => { x.cd.Subject1Mark= x.ld.Subject1Mark; return x.cd; })
;
but the above x.cd.Subject1Mark isn't updated by x.ld.Subject1Mark although I use the answer in the linked stackoverflow question
The structure of LINQ query looks very similar to SQL:
var res =
from cur in CurrentData
join upd in DataToUpdate on upd.Id equals cur.Id
where (cur.Subject1Mark != upd.Subject1Mark || cur.Subject2Mark != upd.Subject2Mark || cur.Subject3Mark != upd.Subject3Mark)
select new {
Current = cur
, UpdatedSubject1Mark = upd.Subject1Mark
, UpdatedSubject2Mark = upd.Subject2Mark
, UpdatedSubject3Mark = upd.Subject3Mark
};
The main difference is that filtering out by inequality has moved from the on clause in SQL to a where clause of LINQ.
I need to order my list by points, then by positions. How can I order my list by the Positions List property?
public class Sailor
{
public string Name { get; set; }
public int Points { get; set; }
public List<int> Positions { get; set; }
public Sailor(string name, int points, List<int> positions)
{
Name = name;
Points = points;
Positions = positions;
}
}
var sailors = new List<Sailor>
{
new Sailor("Carl", 20, new List<int> { 2, 2, 4, 1, 1 }),
new Sailor("Paul", 10, new List<int> { 4, 5, 3, 2, 5 }),
new Sailor("Anna", 20, new List<int> { 1, 1, 1, 3, 4 }),
new Sailor("Lisa", 11, new List<int> { 3, 4, 5, 5, 2 }),
new Sailor("Otto", 11, new List<int> { 5, 3, 2, 4, 3 })
};
foreach (var sailor in sailors)
{
sailor.Positions.Sort();
}
var orderedListOfSailors = sailors.OrderByDescending(x => x.Points);
This gives me:
Carl, Anna, Lisa, Otto, Paul
What I want it to be:
Anna, Carl, Otto, Lisa, Paul
Why? Because Anna have 3 first places, Carl have 2. Otto have 2, 3, 3, Lisa have 2, 3, 4.
The problem can be solved by using lexicographical odering on instances of Sailor; either you could implement a custom comparator for Sailor or use the ThenBy extension method.
After ordering them by Points, order them again by the number of places.
var orderedListOfSailors = sailors
.OrderByDescending(x => x.Points)
.ThenByDescending(x => x.Positions.Count(y => y == 1))
.ThenByDescending(x => x.Positions.Count(y => y == 2))
.ThenByDescending(x => x.Positions.Count(y => y == 3))
.ThenByDescending(x => x.Positions.Count(y => y == 4));
This question already has answers here:
Closed 10 years ago.
I am fairly new to linq and I need to join two tables with the following requirements:
Should left join t1 and t2.
If t2 is empty then the query should not fail - should use default values.
My query:
var final = from t1 in saDist.AsEnumerable()
from t2 in sapGrouped.AsEnumerable()
where
t1.Supplier.Id == t2.Supplier.Id && t1.VatRate == t2.VatRate
select
new
{
t1.Supplier,
Amount = t1.Amount - t2.Amount,
Advance = t1.Advance - t2.Advance,
Balance = t1.Balance - t2.Balance,
t1.VatRate
};
Can someone correct this?
This works in Linqpad as a C# program.
Basically your join syntax needed tweaking (see this), and you needed to take into account when there was nothing to join to for "t2" (so we do a null check and use 0 when null, otherwise t2.Amount, etc)
I created some dummy data so you can play around.
See http://codingsense.wordpress.com/2009/03/08/left-join-right-join-using-linq/ for another example.
I hope it does what you want it to do.
Thanks,
Dominique
public class A
{
void Main()
{
Distributor dist1 = new Distributor() { SupplierID = 1, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "A", DeptSupplierID = 1 };
Distributor dist2 = new Distributor() { SupplierID = 2, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "B", DeptSupplierID = 1 };
Distributor dist3 = new Distributor() { SupplierID = 3, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "C", DeptSupplierID = 1 };
Distributor dist4 = new Distributor() { SupplierID = 4, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "D", DeptSupplierID = 2 };
Distributor dist5 = new Distributor() { SupplierID = 5, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "E", DeptSupplierID = 2 };
Distributor dist6 = new Distributor() { SupplierID = 6, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "F", DeptSupplierID = 2 };
Distributor dist7 = new Distributor() { SupplierID = 7, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "G", DeptSupplierID = 6 };
Distributor dist8 = new Distributor() { SupplierID = 8, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "H", DeptSupplierID = 3 };
Distributor dist9 = new Distributor() { SupplierID = 9, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "I", DeptSupplierID = 3 };
Distributor dist10 = new Distributor() { SupplierID = 10, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "J", DeptSupplierID = 7 };
Distributor dist11 = new Distributor() { SupplierID = 11, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "K", DeptSupplierID = 7 };
Distributor dist12 = new Distributor() { SupplierID = 12, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "L", DeptSupplierID = 5 };
SAPGroup Dept1 = new SAPGroup() { SupplierID = 1, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Development" };
SAPGroup Dept2 = new SAPGroup() { SupplierID = 2, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Testing" };
SAPGroup Dept3 = new SAPGroup() { SupplierID = 3, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Marketing" };
SAPGroup Dept4 = new SAPGroup() { SupplierID = 4, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Support" };
List ListOfDistributors = new List();
ListOfDistributors.AddRange((new Distributor[] { dist1, dist2, dist3, dist4, dist5, dist6, dist7,
dist8, dist9, dist10, dist11, dist12 }));
List ListOfSAPGroup = new List();
ListOfSAPGroup.AddRange(new SAPGroup[] { Dept1, Dept2, Dept3, Dept4 });
var final = from t1 in ListOfDistributors
join t2 in ListOfSAPGroup
on new { t1.SupplierID, t1.VatRateID } equals new { t2.SupplierID, t2.VatRateID }
into JoinedDistAndGrouped
from t2 in JoinedDistAndGrouped.DefaultIfEmpty()
select new
{
Name1 = t1.Name,
Name2 = (t2 == null) ? "no name" : t2.Name,
SupplierID = t1.SupplierID,
Amount = t1.Amount - (t2 == null ? 0 : t2.Amount),
Advance = t1.Advance - (t2 == null ? 0 : t2.Advance),
Balance = t1.Advance - (t2 == null ? 0 : t2.Balance),
VatRateID = t1.VatRateID
};
final.Dump();
}
}
class Distributor
{
public string Name { get; set; }
public int SupplierID { get; set; }
public int VatRateID { get; set; }
public int DeptSupplierID { get; set; }
public int Amount { get; set; }
public int Advance { get; set; }
public int Balance { get; set; }
}
class SAPGroup
{
public int SupplierID { get; set; }
public int VatRateID { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public int Advance { get; set; }
public int Balance { get; set; }
}
public class Result
{
public string Name1 { get; set; }
public string Name2 { get; set; }
public int SupplierID { get; set; }
public int Amount { get; set; }
public int Advance { get; set; }
public int Balance { get; set; }
public int VatRateID { get; set; }
}
Thanks for your input. None of the answers did quite what I wanted, but I managed to get my original code working:
var final = from t2 in saDist.AsEnumerable()
from t1 in sapGrouped.AsEnumerable().DefaultIfEmpty()
where
t1 == null || (t2.Supplier.Id == t1.Supplier.Id && t2.VatRate == t1.VatRate)
select
new
{
t2.Supplier,
Amount = t2.Amount - (t1 == null ? 0 : t1.Amount),
Advance = t2.Advance - (t1 == null ? 0 : t1.Advance),
Balance = t2.Balance - (t1 == null ? 0 : t1.Balance),
t2.VatRate
};
If you have any comments or improvements on this let me know, thanks.
According to this, you are looking for something like (this is untested, but hopefully leads you on the right track):
var final = from t1 in saDist.AsEnumerable()
join t2 in sapGrouped.AsEnumerable()
on t1.Supplier.Id equals t2.Supplier.Id
and t1.VatRate equals t2.VatRate into t1_t2 //not sure about this line
from t2 in t1_t2.DefaultIfEmpty()
{
t1.Supplier,
Amount = t1.Amount - t2.Amount,
Advance = t1.Advance - t2.Advance,
Balance = t1.Balance - t2.Balance,
t1.VatRate
};
Notice the .DefaultIfEmpty(), this satisfies: "If t2 is empty then the query should not fail - should use default values."
I can't get this bit of logic converted into a Linq statement and it is driving me nuts. I have a list of items that have a category and a createdondate field. I want to group by the category and only return items that have the max date for their category.
So for example, the list contains items with categories 1 and 2. The first day (1/1) I post two items to both categories 1 and 2. The second day (1/2) I post three items to category 1. The list should return the second day postings to category 1 and the first day postings to category 2.
Right now I have it grouping by the category then running through a foreach loop to compare each item in the group with the max date of the group, if the date is less than the max date it removes the item.
There's got to be a way to take the loop out, but I haven't figured it out!
You can do something like that :
from item in list
group item by item.Category into g
select g.OrderByDescending(it => it.CreationDate).First();
However, it's not very efficient, because it needs to sort the items of each group, which is more complex than necessary (you don't actually need to sort, you just need to scan the list once). So I created this extension method to find the item with the max value of a property (or function) :
public static T WithMax<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector)
{
var max = default(TValue);
var withMax = default(T);
var comparer = Comparer<TValue>.Default;
bool first = true;
foreach (var item in source)
{
var value = selector(item);
int compare = comparer.Compare(value, max);
if (compare > 0 || first)
{
max = value;
withMax = item;
}
first = false;
}
return withMax;
}
You can use it as follows :
from item in list
group item by item.Category into g
select g.WithMax(it => it.CreationDate);
UPDATE : As Anthony noted in his comment, this code doesn't exactly answer the question... if you want all items which date is the maximum of their category, you can do something like that :
from item in list
group item by item.Category into g
let maxDate = g.Max(it => it.CreationDate)
select new
{
Category = g.Key,
Items = g.Where(it => it.CreationDate == maxDate)
};
How about this:
private class Test
{
public string Category { get; set; }
public DateTime PostDate { get; set; }
public string Post { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<Test> test = new List<Test>();
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 5, 12, 0, 0), Post = "A1" });
test.Add(new Test() { Category = "B", PostDate = new DateTime(2010, 5, 5, 13, 0, 0), Post = "B1" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 12, 0, 0), Post = "A2" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 13, 0, 0), Post = "A3" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 14, 0, 0), Post = "A4" });
var q = test.GroupBy(t => t.Category).Select(g => new { grp = g, max = g.Max(t2 => t2.PostDate).Date }).SelectMany(x => x.grp.Where(t => t.PostDate >= x.max));
}
Reformatting luc's excellent answer to query comprehension form. I like this better for this kind of query because the scoping rules let me write more concisely.
from item in source
group item by item.Category into g
let max = g.Max(item2 => item2.PostDate).Date
from item3 in g
where item3.PostDate.Date == max
select item3;