c# linq filter plus concat - c#

I have the following code:
var data= from row in table.AsEnumerable()
group row by row.Field<string>("id") into g
select new { Id= g.Key };
I would like to add 1 more element to the data variable. How would I do it? (is there something like a data.Concat(1) etc

If you want to add an additional Id you can indeed concat it:
data = data.Concat( new [] { new { Id= "1" } });
This works because anonymous types that have the same fields in the same order are compiled down to the same type.

You can return the LINQ result as a List<string>.
Generic lists can Add more itens easily.
var data = (from row in table.AsEnumerable()
group row by row.Field<string>("id") into g
select new { Id = g.Key }).ToList();
data.Add(new { Id = "X" });
This way you will not need to declare another variable to hold the Enumerable with the new item (since an Enumerable is imutable and can't add new items to itself).
EDIT:
Like pointed, changing the Enumerable<T> to List<T> will put and hold all the elements on the memory, wich isn't a good performance approach.
To stay with the Enumerable<T>, you can do:
data = data.Concat(new [] { new { Id = "X" } });
Because a Enumerable<Anonymous> can be placed inside itself.

I don't know how with query syntax, but you can use Concat with a single value by creating a new array of values with a single item:
IEnumerable<int> data = GetData()
.Concat(new[] { "5" });
The problem with doing this simply is that your data is an IEnumerable<AnonymousType>, which you can't simply new up, and I don't think new anonymous types are compatible with each other. (Edit: According to BrokenGlass, they are compatible. You can try his solution instead).
If they aren't compatible, you could concat the item before your Select clause, but again, how do you create an item of that type.
The solution would probably be to select to an IEnumerable<string> first, concat, then re-select into your anonymous type:
var data = (from row in table.AsEnumerable()
group row by row.Field<string>("id") into g
select g.Key)
.Concat(new[] { "5" })
.Select(k => new { Id = key });
Or to create a new named structure for your result, and concatenate one of those:
var data = from row in table.AsEnumerable()
group row by row.Field<string>("id") into g
select new MyCustomResult() { Id = g.Key };
data = data.Concat(new MyCustomResult() { Id = "5" });

You would need to change from an anonymous type to a known type and then add that new element.
// ...
select new MyResultClass { Id = g.Key };
data.Add(new MyResultClass { Id = 4 });

Related

How do I sort a List<Type> by List<int>?

In my c# MVC project I have a list of items in that I want to sort in order of another list
var FruitTypes = new List<Fruit> {
new Fruit { Id = 1, Name = "Banana"},
new Fruit { Id = 2, Name = "Apple" },
new Fruit { Id = 3, Name = "Orange" },
new Fruit { Id = 4, Name = "Plum"},
new Fruit { Id = 5, Name = "Pear" },
};
SortValues = new List<int> {5,4,3,1,2};
Currently my list is showing as default of fruit type.
How can I sort the Fruit list by SortValues?
It's unclear if you are sorting by the indexes in SortValues or whether SortValues contains corresponding Id values that should be joined.
In the first case:
First you have to Zip your two lists together, then you can sort the composite type that Zip generates, then select the FruitType back out.
IEnumerable<FruitType> sortedFruitTypes = FruitTypes
.Zip(SortValues, (ft, idx) => new {ft, idx})
.OrderBy(x => x.idx)
.Select(x => x.ft);
However, this is simply sorting the first list by the ordering indicated in SortValues, not joining the ids.
In the second case, a simple join will suffice:
IEnumerable<FruitType> sortedFruitTypes = SortValues
.Join(FruitTypes, sv => sv, ft => ft.Id, (_, ft) => ft);
This works because Enumerable.Join maintains the order of the "left" hand side of the join.
While there is almost certainly a more LINQ-y way, if you tend towards verbosity, you could accomplish this with an iterator function. For example:
public IEnumerable<Fruit> SortFruits(IEnumerable<Fruit> unordered, IEnumerable<int> sortValues)
{
foreach (var value in sortValues)
yield return unordered.Single(f => f.Id == value);
}
I like that it's explicit about what it's doing. You may consider throwing an exception when the number of items in each list is different, or maybe you just don't return an item if there is no sort value for it. You'll have to decide what the behaviour should be for "missing" values in either collection is. I think that having to handle these scenarios is a good reason to put it all in a single method this way, instead of a longer LINQ query.
Time complexity:O(n) + TM of Linq.
Declare list of fruits to store result.
Iterate through each fruit type.
Use Linq FirstOrDefault to get element by sorted value.
List<int> SortValues = new List<int> { 5, 4, 3, 1, 2 };
List<Fruit> result = new List<Fruit>();
foreach (var element in SortValues)
{
Fruit f = FruitTypes.FirstOrDefault(fruitElement => fruitElement.Id == element);
result.Add(f);
}
Implementation: DotNetFiddler

Getting a JSON array from a linq on c#

I'm trying to output data like this:
[[...],[...],[...],[...]]
But my query gives me this result:
[{...},{...},{...},{...}]
Here is my query:
var result = (from c in displayedCompanies
group c by new { c.CodigoDeVenta } into s
select new{
ID = Convert.ToString(i++),
s.Key.CodigoDeVenta,
TotalInv = s.Sum(x => x.Inventario)
}).ToArray();
I've tried some options like the following but it's wrong:
var result = (from c in displayedCompanies
group c by new { c.CodigoDeVenta } into s
select new [] {
ID = Convert.ToString(i++),
s.Key.CodigoDeVenta,
TotalInv = s.Sum(x => x.Inventario)
}).ToArray();
Note that the diference is the [] in select new []. I used to have a query before I implemented the "group by" that worked correctly, but after I added the group by this does not work anymore.
Thanks!
Setting aside the grouping, focusing only on the syntax, your anonymous array (new [] {...}) should be fine.
However, it appears that you're trying to assign Id = and TotalInv = which looks like a leftover from the previous revision where you were selecting an anonymous object.
So, instead, you should be able to drop the member identifiers and select just the values you want in the child arrays:
select new [] {
Convert.ToString(i++),
s.Key.CodigoDeVenta,
s.Sum(x => x.Inventario)
}

Unioning two LINQ queries

I just need to make full outer join with Linq, But When i union two quires i get this error:
Instance argument: cannot convert from 'System.Linq.IQueryable' to 'System.Linq.ParallelQuery
And here is my full Code:
using (GoodDataBaseEntities con = new GoodDataBaseEntities())
{
var LeftOuterJoin = from MyCustomer in con.Customer
join MyAddress in con.Address
on MyCustomer.CustomerId equals MyAddress.CustomerID into gr
from g in gr.DefaultIfEmpty()
select new { MyCustomer.CustomerId, MyCustomer.Name, g.Address1 };
var RightOuterJoin = from MyAddress in con.Address
join MyCustomer in con.Customer
on MyAddress.CustomerID equals MyCustomer.CustomerId into gr
from g in gr.DefaultIfEmpty()
select new { MyAddress.Address1, g.Name };
var FullOuterJoin = LeftOuterJoin.Union(RightOuterJoin);
IEnumerable myList = FullOuterJoin.ToList();
GridView1.DataSource = myList;
GridView1.DataBind();
}
The types of your two sequences are not the same, so you can't do a Union.
new { MyCustomer.CustomerId, MyCustomer.Name, g.Address1 };
new { MyAddress.Address1, g.Name };
Try making sure that the fields have the same names and types in the same order.
Why not select it all as one thing? Depending on your setup (i.e., if you have foreign keys properly set up on your tables), you shouldn't need to do explicit joins:
var fullJoin = from MyCustomer in con.Customer
select new {
MyCustomer.CustomerId,
MyCustomer.Name,
MyCustomer.Address.Address1,
MyCustomer.Address.Name
};
Method syntax:
var fullJoin = con.Customers.Select(x => new
{
x.CustomerId,
x.Name,
x.Address.Address1,
x.Address.Name
});
union appends items from one collection to the end of another collection, so if each collection had 5 items, the new collection will have 10 items.
What you seem to want is to end up with 5 rows with more infomration is each. That's not a job for Union. You might be able to do it with Zip(), but you'll really be best with the single query as shown by DLeh.

C# Chart bind to var

I have var t from linq query with this structure:
[0] = { Type = "K", Count = 1 }
[1] = { Type = "Z", Count = 8 }
and now I want to bind it to my chart with elegance (no foreach). I try to do something like this:
series1.Points.DataBindXY(t. ????, t.????);
but I don't know how to get "first column" and "second column" of my var.
Please help
Edit:
My linq query:
var t= (from oAction in Actions
group oAction by oAction.Type.Name into g
select new { Type = g.Key, Count = g.Count() }).ToArray();
I have never worked with ASP.Net charts, so I'm not sure this will work, but I think it should:
series1.Points.DataBindXY(t.Select(x => x.Type), t.Select(x => x.Count));
This will assign a collection of Type values as the x values and collection of Count values as y values.
var t= (from oAction in Actions
group oAction by oAction.Type.Name into g
select new { Type = g.Key, Count = g.Count() }).ToArray();
All-right, so you are returning an "Anonymous" type. You can definitely access t.Type or t.Count but the catch is you can do this only inside the method where this LINQ query is defined. The scope of anonymous type is limited to the method in which it is defined. You can overcome this limitation by using "dynamic" keyword. But I haven't tried this, so can't be 100% sure.

c# More elegant way to assign array to a list?

I have this:
// Load changelog types
ChangeLogType[] Types = ChangeLogFunctions.GetAllChangelogTypes();
foreach(ChangeLogType Rec in Types){
ListItem N = new ListItem();
N.Text = Rec.Type;
N.Value = Rec.ID.ToString();
LstChangeLogType.Items.Add(N);
}
It calls a function that returns an array of ChangeLogTypes, and then adds each one into a list control. Is there a more elegant way of doing this? I feel I'm repeating code each time I do this or something similar.
Yup, LINQ to Objects is your friend:
var changeLogTypes = ChangeLogFunctions.GetAllChangelogTypes()
.Select(x => new ListItem {
Text = x.Type,
Value = x.ID.ToString() })
.ToList();
The Select part is projecting each ChangeLogType to a ListItem, and ToList() converts the resulting sequence into a List<ListItem>.
This is assuming you really wanted a new list with all these entries. If you need to add the results to an existing list, you'd do that without the ToList call, but calling AddRange on an existing list with the result of the Select call.
It's well worth learning more about LINQ in general and LINQ to Objects in particular - it can make all kinds of things like this much simpler.
var range = Types.Select(rec =>
new ListItem { Text = rec.Type, Value = rec.ID.ToString() });
LstChangeLogType.AddRange(range);
Linq?
LstChangeLogType.Items = Types.Select(x => new ListItem()
{ Text = x.Type, Value = x.ID.ToString() }).ToList();
using System.Linq;
var items = Types
.Select (rec => ListItem
{
Text = Rec.Type;
Value = Rec.ID.ToString();
}
LstChangeLogType.Items.AddRange(items);
Using some LINQ extension methods:
LstChangeLogType.AddItems.AddRange(
Types.Select(t =>
new ListItem() { Text = t.Type, Value = t.ID.ToString() }).ToArray());

Categories