List<dynamic> a = new List<dynamic>();
a.Add(new { Foo = 1, Baz = "Inga", Name = "Alice"});
a.Add(new { Foo = 2, Baz = "Baz", Name = "Bob"});
a.Add(new { Foo = 3, Baz = "Hi", Name = "Charlie"});
List<dynamic> b = new List<dynamic>();
b.Add(new { Foo = 1, Value = "Bar", Code = "A"});
b.Add(new { Foo = 1, Value = "Quux", Code = "B"});
b.Add(new { Foo = 2, Value = "Bar", Code = "C"});
b.Add(new { Foo = 3, Value = "Mint", Code = "A"});
b.Add(new { Foo = 3, Value = "Seven", Code = "Q"});
b.Add(new { Foo = 3, Value = "Threeve", Code = "T"});
Ok....so I have a problem(naturally)
This is contrived and simplified to focus on the problem at hand.
I need to modify a Linq query to project the two Lists to the following response:
[
{ Foo = 1
, Baz = "Inga"
, Code = "A"
, Bars = [{ Value = "Bar", Code = "A"}
,{ Value = "Quux", Code = "B"}
]
}
,{ Foo = 2
, Baz = "Baz"
, Code = "C"
, Bars = [{ Value = "Fizz", Code = "C"}]
}
,{ Foo = 3
, Baz = "Hi"
, Code = "A"
, Bars = [{ Value = "Mint", Code = "A"}
,{ Value = "Seven", Code = "Q"}
,{ Value = "Threeve", Code = "T"}
]
}
]
First, the TL;DR
Is there any way to query collection b to select
(b.First Where Distinct By b.Foo) AsEnumerable() ?
...The long version
I need to select a projection of a but as it is being materialized, identify the first Code in list b where b.Foo == a.Foo and put b.Code directly on a. Then the items from b where b.Foo == a.Foo need to be put into a.Bars.
The problem I have on my hands is that I am not identifying a singular a so I can't preselect the a and b values to simplify this mess and there's no opportunity to remodel.
So, if I want to search Where Value = Bar; Alice and Bob need to be returned with the proper mapping and projection.
The naive attempt would be...
var results = a.Join( b
, master => master.Foo
, detail => detail.Foo
, (master, detail) => new { master, detail})
.Select(item => new
{
item.master.Foo
, item.master.Baz
, item.master.Name
, item.detail.Code
, Bars = b.Select(x => x.Foo.Equals(item.master.Foo))
};
but this causes my results to contain duplicate "Alice" records and duplicate "Charlie" records because it inner joined a and b. What I really want to do (pseudo) is
a.Join(
b.Where(b.Foo.Equals(a.Foo)).First()
, master => master.Foo
, detail => detail.Foo
, (master, detail) => new { master, detail}
)
.Select(item => new
{
item.master.Foo
, item.master.Baz
, item.master.Name
, item.detail.Code
, Bars = b.Select(x => x.Foo.Equals(item.master.Foo))
};
but no matter what I try, it comes out a mess.
...Note, I can't take the naive approach and then run a DistinctBy because the projection is anonymous.
Can anyone resolve this purely with Linq to Object queries? (Note: I'm not needing a single pass resolution)
I would just stick with grouping the second group (denoted b here) and then using that paired with a find to compose the projection.
var results = b.GroupBy( d => d.Foo ).Select( g => new {
Foo = g.Key,
Baz = a.First( i => i.Foo == g.Key ).Baz,
Code = g.First().Code,
Bars = g.Select( e => new { Value = e.Value, Code = e.Code }).ToArray()
});
If I understand correctly, the Code in a single result entry is just the Code of the first joined b element.
So try this:
var result = a.GroupJoin(b,
a0 => a0.Foo,
b0 => b0.Foo,
(a0, bs) =>
new
{
Foo = a0.Foo,
Baz = a0.Baz,
Code = bs.Select(b1 => b1.Code).FirstOrDefault(),
Bars = bs.Select(b1 => new {b1.Value, b1.Code}).ToArray()
}).ToArray();
GroupJoin is what you need here. You can think of Join like SelectMany while GroupJoin - like Select. The difference is the type of the second argument of the projection - TInner for Join and IEnumerable<TInner> for GroupJoin. In LINQ syntax the GroupJoin is achieved by into clause.
With all that being said, here is how it looks for your example in both syntaxes:
var resultsA = a.GroupJoin(b, master => master.Foo, detail => detail.Foo, (master, details) => new
{
master.Foo,
master.Baz,
master.Name,
Code = details.Select(detail => detail.Code).First(),
Bars = details.Select(detail => new { detail.Value, detail.Code })
});
var resultsB =
from master in a
join detail in b on master.Foo equals detail.Foo into details
select new
{
master.Foo,
master.Baz,
master.Name,
Code = details.Select(detail => detail.Code).First(),
Bars = details.Select(detail => new { detail.Value, detail.Code })
};
var query = from ai in a
let bs = b.Where(bi => bi.Foo == ai.Foo)
select new
{
ai.Foo,
ai.Baz,
Code = bs.Select(bi => bi.Code).FirstOrDefault(),
Bars = bs.Select(bi => new { bi.Value, bi.Code }),
};
Related
I have two var variable data type which I want to have a union of both. how can I do that?
var results = from table1 in dtTimeListTable.AsEnumerable()
join table2 in readyDataTable.AsEnumerable() on (decimal)table1["Avnr"] equals (int)table2["Avnr"]
orderby table2["Substation"], table2["S6_NAME"]
select new
{
Substation = (string)table2["Substation"],
S6_NAME = (string)table2["S6_NAME"],
ptime = ((DateTime)table1["DBTM"]).ToString("dd/MM/yyyy HH:mm").Substring(11, 5),
Wert = (decimal)table1["Wert"]
};
var resultsMaxMin = from table1 in dtMaxMin.AsEnumerable()
join table2 in readyDataTable.AsEnumerable() on (decimal)table1["Avnr"] equals (int)table2["Avnr"]
orderby table2["Substation"], table2["S6_NAME"]
select new
{
Substation = (string)table2["Substation"],
S6_NAME = (string)table2["S6_NAME"],
ptime =table1["ptime"],
Wert = (decimal)table1["Wert"]
};
This snippet may help:
var list = new List<int>();
var sublist1 = list.Select(x => new { foo = 1, bar = 2, baz = "baz" });
var sublist2 = list.Select(x => new { bar = 3, foo = 1, baz = "baz" });
var sublist3 = list.Select(x => new { foo = 3, bar = "a", baz = "baz" });
var union1 = sublist1.Union(sublist2); // compilation error
var union2 = sublist1.Union(sublist3); // compilation error
The Union() calls only work if the anonymous types match up exactly - names, ordering and types of fields. The errors in the snippet go away if you swap the order of foo and bar in the second select, and turn bar into an int value in the third.
In your example, ptime differs in type between the two lists - string and object.
Given the follow class structure:
int Id;
string[] Codes;
And the following data:
Foo { Id = 1, Codes = new[] { "01", "02" } }
Foo { Id = 2, Codes = new[] { "02", "03" } }
Foo { Id = 3, Codes = new[] { "04", "05" } }
I would like to end up with the following structure.
Code = "01", Id = 1
Code = "02", Id = 1
Code = "02", Id = 2
Code = "03", Id = 2
Code = "04", Id = 3
Code = "05", Id = 3
I've got the following query, but it's giving me a collection as the Id rather than the flat structure I am after.
collection.GroupBy(f => f.Codes.SelectMany(c => c), f => f.Id,
(code, id) => new { Code = code, Id = id })
.ToArray()
What am I missing?
SelectMany can return multiple elements for each item as a single list
items
.SelectMany(foo => foo.Codes.Select(code => new { Id = foo.Id, Code = code }));
The answer of Diego Torres is correct; I would only add to it that this query is particularly concise and readable in the comprehension form:
var q = from foo in foos
from code in foo.Codes
select new { Code = code, foo.Id };
I am having two lists:
ListA:
[
{
Id = 1,
Name = "A",
Summary = ""
},
{
Id = 2,
Name = "B",
Summary = ""
}
]
ListB:
[
{
Id = 1,
Value = "SomeThing"
},
{
Id = 2,
Value = "EveryThing"
}
]
I want to join that two list using LINQ and want to return ListA which value is update as Below
[
{
Id = 1,
Name = "A",
Summary = "SomeThing"
},
{
Id = 2,
Name = "B",
Summary = "EveryThing"
}
]
I am joining ListA and ListB based on Id and assigning value to summary.
I tried below approach:
var query = from obj1 in ListA
join obj2 in ListB on obj1.Id equals obj2.Id
select obj1.Summary = obj2.Value, return obj1;
**=>so here i want assign data from obj2 to obj1 then want to return obj1 **
is that possible or how we can do this?
You could also update the existing ListA with a simple loop
foreach (var itemA in ListA)
{
itemA.Summary = ListB.FirstOrDefault(x => x.Id == itemA.Id)?.Value;
}
Join approach
var query = ListA.Join(ListB,
ia => ia.Id,
ib => ib.Id,
(ia, ib) => new aItem() //type of ListA here
{
Id = ia.Id,
Name = ia.Name,
Summary = ib.Value
});
You could try to join the two lists like this:
var listA = new List<ClassA>();
var listB = new List<ClassB>();
var list = listA.Join(listB, a => a.Id, b => b.Id, (a, b) =>
new ClassA
{
Id = a.Id,
Name = a.Name,
Summary = b.Value
});
Using method syntax Enumerable.Join is the easier one to use here:
var result = listA.Join(listB, // Join ListA and ListB
a => a.Id, // from every a in ListA take a.Id
b => b.Id, // from every b in ListB take b.Id
(a, b) => new // when they match, take the a and the b
{ // to create a new object with properties
Id = a.Id,
Name = a.Name,
Summary = b.Value,
});
Note that the result is of an anonymous type, If you want a result with the same type as the items in ListA (let's say they are of class A), change the last part of the join:
(a, b) => new A() // when they match, take the a and the b
{ // to create a new object of class A
Id = a.Id,
Name = a.Name,
Summary = b.Value,
});
I have the following relation (for example)
A contains one or more B's
Each B contains one or more C's and D's
I want to flatten everything using SelectMany along with some search conditions and get A,B,C and D's . This is what i have.
context.A.Where(a => (string.IsNullOrEmpty(name) || a.Name.Contains(name)))
.SelectMany(ab =>ab.b.Where(n=>n.bname.Contains(name) || string.IsNullOrEmpty(name)),
(aa, bb) => new { aa, bb }) //gets all a's and b's
.SelectMany(bc => bb.c.Where(w => w.KEYWORD.Contains(Keyword) || string.IsNullOrEmpty(Keyword)),
(bc,words) => new {bc,kwords}) //gets all b's and c's
Is what i am doing right? If so , then how to get B along with all D's adding to the above expression?
Data Selection using Lambda Syntax:
var flatData = context.A.SelectMany(a => a.B.SelectMany(b => b.Select(new {a,b,c = b.C,d = b.D})
Going further, following checks shall be done before applying the Where Clause, as they check the constant input supplied, name and keyword
string.IsNullOrEmpty(name)
string.IsNullOrEmpty(keyword)
Remaining checks would be simple:
if(!string.IsNullOrEmpty(name))
flatData = flatData.Where(data => data.a.Name.Contains(name))
.Where(data => data.b.Name.Contains(name));
if(!string.IsNullOrEmpty(keyword))
flatData = flatData.Where(data => data.c.Keyword.Contains(keyword));
Important points:
flatData above has a cascading filter, first on a.Name, b.Name and c.Keyword
Agreeing with what Ivan suggested you can flatten this 3 levels deep structure like this:
var query = (from a in A
from b in (List<dynamic>)a.b
from c in (List<dynamic>)b.c
from d in (List<dynamic>)b.d
select new { a, b, c, d });
if (!string.IsNullOrEmpty(name))
{
query = query.Where(record => record.b.bname.Contains(name));
}
if (!string.IsNullOrEmpty(keyword))
{
query = query.Where(record => record.c.keyword.Contains(keyword));
}
var result = query.ToList();
You can also add the where clauses in the query at the top but seeing that you are checking if you got any valid input at all I'd put it after
Tested it with this sample data:
List<dynamic> A = new List<dynamic>
{
new { b = new List<dynamic> { new { bname = "a", c = new List<dynamic> { new { keyword = "b" } }, d = new List<dynamic> { 1, 2, 3 } } } },
new { b = new List<dynamic> { new { bname = "a", c = new List<dynamic> { new { keyword = "d" } }, d = new List<dynamic> { 1, 2, 3 } } } }
};
string name = "a";
string keyword = "b";
Given the following input, how do I write a LINQ query or expression to return an aggregated result set for the quantity?
Input:
var foo = new[] { new { PO = "1", Line = 2, QTY = 0.5000 },
new { PO = "1", Line = 2, QTY = 0.2500 },
new { PO = "1", Line = 2, QTY = 0.1000 },
new { PO = "1", Line = 2, QTY = -0.1000 }
}.ToList();
Desired result:
Something along the lines of
new { PO = "1", Line = 2, QTY = 0.7500 } // .5 + .25 + .1 + -.1
How would I write it for multiple lines as well (see the object model in foo)?
How about this:
var result = foo.GroupBy(x => x.Line)
.Select(g => new { PO = g.First().PO,
Line = g.Key,
QTY = g.Sum(x => x.QTY) });
In the case you just have one Line, just add a .Single() - result is an IEnumerable of the anonymous type defined when you set up foo.
Edit:
If both PO and Line should designate different groups (PO can have different values), they both have to be part of the group key:
var result = foo.GroupBy(x => new { x.PO, x.Line})
.Select(g => new {
PO = g.Key.PO,
Line = g.Key.Line,
QTY = g.Sum(x => x.QTY)
});
var query = (from t in foo
group t by new {t.PO, t.Line}
into grp
select new
{
grp.Key.PO,
grp.Key.Line,
QTY = grp.Sum(t => t.QTY)
}).ToList()