how to add array into list<> with linq - c#

I have the following problem: the query with linq works ok, but I need to add the coordinates within an array for each record that is repeated
Model
public class AppPoisModel
{
public int idPoiType { get; set; }
public string sector { get; set; }
public double latPoint { get; set; }
public double lngPoint { get; set; }
}
Query
var result = (from c in db.coord
select new AppPoisModel
{
idPoiType = c.id,
sector = c.sector,
latPoint = c.latitude ?? 0,
lngPoint = c.longitude ?? 0
}).ToList();
Result
[
{
"$id": "1",
"sector" : "A",
"latPoint": 0,
"lngPoint": 0
},
{
"$id": "2",
"sector" : "A",
"latPoint": 0,
"lngPoint": 0
}
]
Desired result
[
{
"$id": "1",
"sector" : "A",
"coords": [{latPoint, lngPoint}, {latPoint, lngPoint}]
}
]
thank you very much for your contributions

looks like you need a group by...
Also you might create a class for Coords, and make AppPoisModel or a new result class, with a coords field typed as a collection of coords
check it out: Group by in LINQ
similar solution https://stackoverflow.com/a/47580961

The initial query you suggested has no clue that there's a common relationship with items in the same sector.
To accomplish this, you'll want to use the Enumerable.GroupBy() method to group those items together with the basis of having the same id and sector together.
If both of those will always be correlated, you can just GroupBy() one of them to make the comparison simpler, but the results will also reflect that.
var result = (from c in db.coord
select new AppPoisModel
{
idPoiType = c.id,
sector = c.sector,
latPoint = c.latitude ?? 0,
lngPoint = c.longitude ?? 0
}).GroupBy(x => new { id = x.idPoiType, sector = x.sector });
In your case, possibly with both id and sector. This will be your key when you want to loop over the results. So that you can morph the results into the data type you want.

Related

order the list based on a condition

I want to order a lambda result . I did this:
var dicWords= _context.dicWords
.Select(p=>new WordsDicDto
{
Id=p.Id,
OrderingLetter=p.OrderingLetter,
Word=p.Word,
})
.ToList();
I want to order the list by this condition. if the OrderingLetter is numeric it should be ordered by int.parse(p.OrderingLetter) and if it is not numeric so it should ordered by p,OrderingLetter itself. how should accomplish that?
If I understand your question right, you are looking for something like shown below. The most important place is the CompareTo() method which defines which order two elements have to each other. A negative value means that the current instance (accessed by this.) is preceding obj in sort order. Positive means the opposite. (See also here for the official documentation for CompareTo(): https://learn.microsoft.com/en-us/dotnet/api/system.icomparable.compareto?view=net-6.0#returns)
var dicWords = new List<WordsDicDto>(){
new WordsDicDto() {Id = "1", OrderingLetter = "M", Word = "Mouse" },
new WordsDicDto() {Id = "3", OrderingLetter = "2", Word = "Bycicle"},
null,
new WordsDicDto() {Id = "4", OrderingLetter = "1", Word = "Dog"},
new WordsDicDto() {Id = "2", OrderingLetter = "C", Word = "Car"},
};
Console.WriteLine("The words before sorting:");
dicWords.ForEach(p => Console.WriteLine($"{p?.Id} | {p?.OrderingLetter} | {p?.Word}"));
// This is the filtering
var result = dicWords.OrderBy<WordsDicDto, object>(p => p).ToList();
Console.WriteLine("--");
Console.WriteLine("The words after sorting:");
result.ForEach(p => Console.WriteLine($"{p?.Id} | {p?.OrderingLetter} | {p?.Word}"));
This is the used implementation of WordsDicDto with the implementation of CompareTo():
internal class WordsDicDto : IComparable
{
public string Id { get; set; }
public string OrderingLetter { get; set;}
public string Word { get; set; }
public int CompareTo(object obj)
{
if (obj is WordsDicDto p)
{
if (int.TryParse(this.OrderingLetter, out int instanceValue) &&
int.TryParse(p.OrderingLetter, out int numericValue))
{
return instanceValue - numericValue;
}
else
{
return String.Compare(this.OrderingLetter, p.OrderingLetter, StringComparison.Ordinal);
}
}
else
{
return -1;
}
}
}

How to Right Join two Json files in C#?

I want to join two json files using a common key and get all the records from the right file and matching data from the left.
If it was SQL.
SELECT json1.CategoryDescription, json2.CategoryID, json2.TechName, json2.SpawnID
FROM json1
RIGHT JOIN json2
ON json1.CategoryID = json2.CategoryID
WHERE GameVersion = "A" OR GameVersoion = "2" AND CategoryID = "metals"
I need to get all the json2 records and the json1.CategoryDescription for each of them. But at the moment it just lists all the records from json1 then all the records from json2.
Here is my current attempt:
using System;
using System.IO;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace ConsoleApp1
{
public class Program
{
public static void Main()
{
// Filter variables
var gameVer = "2";
var cat = "metals";
// Load the categories.json
JObject catObj = JObject.Load(new JsonTextReader(File.OpenText("D:/Code/Tests/categories.json")));
// Load the techtype.json
JObject ttObj = JObject.Load(new JsonTextReader(File.OpenText("D:/Code/Tests/techtypes.json")));
// Read techtype.json into an array
var mergeSettings = new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
};
catObj.Merge(ttObj, mergeSettings);
// Does not work,
/*
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at ConsoleApp1.Program.Main() in D:\Code\Tests\ReadTechTypes\ReadTechTypes\Program.cs:line 30
*/
// (catObj.SelectToken("Categoris") as JArray).Merge(ttObj.SelectToken("TechType"), mergeSettings);
// Does not work, same error
//var mergedArray = catObj.SelectToken("Categoris") as JArray;
//string json = mergedArray.ToString();
Console.WriteLine(catObj);
}
}
}
The left json
{
"Categories":[
{
"CategoryID":"baseupgrades",
"CategoryDescription":"Base Upgrades",
"IncludeCategory":true,
"GameVersion":"A"
},
{
"CategoryID":"batteries",
"CategoryDescription":"Batteries",
"IncludeCategory":true,
"GameVersion":"A"
},
{
"CategoryID":"blueprint",
"CategoryDescription":"Blueprint",
"IncludeCategory":false,
"GameVersion":"A"
}
// Other category values omitted
]
}
The right json
{
"Items":[
{
"CategoryID":"crystalline",
"TechName":"Quartz",
"SpawnID":"quartz",
"TechID":1,
"GameVersion":"A"
},
{
"CategoryID":"metals",
"TechName":"Metal Salvage",
"SpawnID":"scrapmetal",
"TechID":2,
"GameVersion":"A"
},
{
"CategoryID":"outcrop",
"TechName":"Limestone Outcrop",
"SpawnID":"limestonechunk",
"TechID":4,
"GameVersion":"A"
}
// Other items omitted
]
}
Any ideas?
You can try this
categoriesRoot = JsonConvert.DeserializeObject<CategoriesRoot>(categoriesJson);
itemsRoot = JsonConvert.DeserializeObject<ItemsRoot>(itemsJson);
var items = from cr in categoriesRoot.Categories
join ir in itemsRoot.Items on cr.CategoryID equals ir.CategoryID into irj
from ir in irj.DefaultIfEmpty()
where ( (cr.GameVersion == "A") || (cr.GameVersion == "2" && cr.CategoryID == "metals"))
select new {
cr.CategoryDescription,
ir.CategoryID,
ir.TechName,
ir.SpawnID
};
var newItemsJson=JsonConvert.SerializeObject(items);
after creating these classes
public class Item
{
public string CategoryID { get; set; }
public string TechName { get; set; }
public string SpawnID { get; set; }
public int TechID { get; set; }
public string GameVersion { get; set; }
}
public class ItemsRoot
{
public List<Item> Items { get; set; }
}
public class Category
{
public string CategoryID { get; set; }
public string CategoryDescription { get; set; }
public bool IncludeCategory { get; set; }
public string GameVersion { get; set; }
}
public class CategoriesRoot
{
public List<Category> Categories { get; set; }
}
output will be like this
[
{"CategoryDescription":"Base Upgrades","CategoryID":"crystalline","TechName":"Quartz","SpawnID":"quartz"},
{"CategoryDescription":"Batteries","CategoryID":"metals","TechName":"Metal Salvage","SpawnID":"scrapmetal"}
]
And by the way you have a bug in your SQL query
WHERE GameVersion = "A" OR GameVersoion = "2" AND CategoryID = "metals"
this is an ambiguous code, since there are GameVersion and CategoryID in both queries.
The following should work:
// Filter variables
var gameVersions = new HashSet<string> { "A", "2" };
var categoryIDs = new HashSet<string> { "metals" };
// Left outer join on ttObj. Select all Items[*] array items
var query = from i in ttObj.SelectTokens("Items[*]").OfType<JObject>()
// Filter on the game version and category ID
let categoryId = (string)i["CategoryID"]
let gameVersion = (string)i["GameVersion"]
where categoryIDs.Contains(categoryId) && gameVersions.Contains(gameVersion)
// Join with "Categories[*]" on category ID
join c in catObj.SelectTokens("Categories[*]") on categoryId equals (string)c["CategoryID"] into joined
// DefaultIfEmpty makes this a left join
from cat in joined.DefaultIfEmpty()
// Select all records of i and add the CategoryDescription from cat.
select new JObject(i.Properties()) { new JProperty("CategoryDescription", cat?["CategoryDescription"]) };
var results = query.ToList(); // Materialize the query into a list of results.
Which results in:
[
{
"CategoryID": "metals",
"TechName": "Metal Salvage",
"SpawnID": "scrapmetal",
"TechID": 2,
"GameVersion": "A",
"CategoryDescription": null
}
]
Notes:
I changed the query from a right join to a left join because it made the filtering look a little more natural. See LINQ Left Join And Right Join if you would prefer the right join syntax.
The final select statements creates a new JObject with all the records from the JObject i item object then adds the CategoryDescription from the cat category object. It does not modify the existing object i.
JContainer.Merge() isn't going to help you here because it doesn't have any ability to merge based on some primary key.
ttObj.SelectTokens("Items[*]") uses the JSONPath wildcard operator [*] to select all items in the "Items" array.
As there is no category with "CategoryID":"metals", cat is null in the ultimate select statement.
Demo fiddle here.
The problem is that you are merging the "Category" list with the "Items" list, and "Items" is not present on catObj.
[I suggest to you to convert the items in class (with visual studio you can do a "Special paste" as JSON class).]
You have to iterate over the items of the first list and merge with the corresponding element in the second list, member with member, not list with list.

Linq Groupby return original object

I need some help filtering some data. I've got an object class with three properties. The collection of objects I've got can have many matches of the first property, Point3d. From that collection of matches I need to see if the second property has unique values, Tag. Finally, I need to be able to identify the objects whos Point3d match, and Tags are different, using the third property, it's Id (which is always unique).
class pmatch
{
public string Point3d { get; set; }
public string Tag { get; set; }
public string Id { get; set; }
}
An example of what i'm looking for would be:
List<pmatch> dataset = new List<pmatch>
{
new pmatch { Point3d = "1, 1, 1", Tag = "5", Id = "123" },
new pmatch { Point3d = "1, 1, 1", Tag = "6", Id = "124" },
new pmatch { Point3d = "1, 1, 2", Tag = "7", Id = "125" },
new pmatch { Point3d = "1, 1, 2", Tag = "7", Id = "126" }
};
I need to be able to identify Id's 123 and 124, as their Point3ds match, but their Tags do not. I've been able to identify these instances using LINQ:
var result = datalist.GroupBy(item => item.Point3d, item => item.Tag);
foreach (var group in result)
{
Console.WriteLine(group.Key);
var uniqueTags = group.Distinct().ToList();
if (uniqueTags.Count > 1)
{
Console.WriteLine("Found mismatched tags");
foreach (string Tag in group)
{
Console.WriteLine(" {0}", Tag);
}
}
}
However these results do not give me the Id, so I can not access the object I have identified. How do I get these results along with the Id, or the pmatch object itself?
You can accomplish the desired result like so:
var resultSet =
dataset.GroupBy(item => item.Point3d)
.Where(group => group.Select(item => item.Tag)
.Distinct()
.Count() > 1)
.ToDictionary(item => item.Key, item => item.ToList());
This will identify Id's 123 and 124, as their Point3ds match, but their Tags do not and also resultSet is of type Dictionary<string, List<pmatch>> so you have access to all the properties of the pmatch object.

How to flatten nested dictionaries within Class using LINQ

The closest solution to what I was looking for is this thread How to flatten nested objects with linq expression
But I get an error trying that approach
The type arguments for method 'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable, System.Func>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
My code:
var aa = t.data.SelectMany(x =>
x.Value.innerData.SelectMany(y => new { /*Error at this SelectMany*/
url = x.Key,
disp = x.Value.disp,
date = y.Key,
count = y.Value.count,
rank = y.Value.rank,
}));
My classes:
public class TData {
public Dictionary<string, TDetail> data { get; set; }
}
public class TDetail {
public string disp { get; set; }
[Newtonsoft.Json.JsonProperty("data")]
public Dictionary<string, Metrics> innerData { get; set; }
}
public class Metrics {
public string count { get; set; }
public string rank { get; set; }
}
The JSON I get from a 3rd party API looks like below:
{
"data": {
"abc.com": {
"disp": "#712176",
"data": {
"2015-02-08": {
"count": 4,
"rank": 5.8
},
"2015-02-23": {
"count": 3,
"rank": 8.3
},
"2015-03-14": {
"count": 5,
"rank": 3.7
}
}
},
"nbc.com": {
"disp": "#822176",
"data": {
"2015-02-08": {
"count": 3,
"rank": 5.5
},
"2015-02-23": {
"count": 5,
"rank": 8.4
},
"2015-03-14": {
"count": 7,
"rank": 4.7
}
}
}
}
}
How do I specify the type arguments explicitly in this case? Thanks.
Too many SelectMany:
var t = new TData(); // your TData
var aa = t.data.SelectMany(x =>
x.Value.innerData.Select(y => new
{
url = x.Key,
disp = x.Value.disp,
date = y.Key,
count = y.Value.count,
rank = y.Value.rank,
}));
The inner one must be a Select.
SelectMany projects every individual item into a sequence of items (and then flattens it). Your outer SelectMany is projecting each item into a sequence, but your inner SelectMany is projecting each item into single items that aren't sequences. If you want to project each item in a sequence into a single item then you need to use Select, not SelectMany.

LINQ query output groups and subgroups in JSON

I have a table like this (Groups):
ID Name ParentID
1 Group 1 null
2 Group 2 null
3 SubGr 1-1 1
4 SubGr 1-2 1
5 SubGr 2-1 2
6 Group 3 null
7 SubGr 1-2-1 4
..... and so on
I want to serialize this to JSON looking like this:
[{"id":1,
"name":"Group 1",
"children": [
{
"id":3,
"name":"SubGr 1-1",
"children":null
},{
"id":4,
"name":"SubGr 1-2",
"children": [
{
"id":7,
"name":"SubGr 1-2-1",
"children": null
}
]
}
]
},
{"id":2,
"name":"Group 2",
"children": [
{
"id":5,
"name":"SubGr 2-1",
"children":null
}
]
},
{"id":6,
"name": "Group 3",
"children": null
}
]
As you can see, you can have indefinite subgroups.
How can I make such a query in LINQ and output it in JSON like the example above?
I have no problem outputting the JSON as seperated elements, with ParentID, but I need to have the structure as mentioned above.
This is the code that I am currently working with, after trying different things around, but with no luck still (this version only gives two levels):
public ActionResult GetGroups()
{
var groupobjs = db.GroupObjs.ToList();
var items = groupobjs.Where(p => p.ParentID == null).Select(p => new
{
id = p.ID,
name = p.Name,
children = groupobjs.Where(c => c.ParentID == p.ID).Select(c => new {
id = c.ID,
name = c.Name
})
});
return Json(items, JsonRequestBehavior.AllowGet);
}
I was working on some code similar to what #Hunter-974 recommended.
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public List<Group> Subgroups { get; set; }
public Group()
{
this.Subgroups = new List<Group>();
}
}
class Program
{
static void Main()
{
Group[] groups = new Group[]
{
new Group { Id = 1, Name = "Group 1", ParentId = null },
new Group { Id = 2, Name = "Group 2", ParentId = null },
new Group { Id = 3, Name = "SubGr 1-1", ParentId = 1 },
new Group { Id = 4, Name = "SubGr 1-2", ParentId = 1 },
new Group { Id = 5, Name = "SubGr 2-1", ParentId = 2 },
new Group { Id = 6, Name = "Group 3", ParentId = null },
new Group { Id = 7, Name = "SubGr 1-2-1", ParentId = 4 }
};
foreach (Group g in groups)
if (g.ParentId.HasValue)
groups.Single(group => group.Id == g.ParentId.Value).Subgroups.Add(g);
var rootgroups = groups.Where(g => g.ParentId == null);
JavaScriptSerializer js = new JavaScriptSerializer();
Console.WriteLine(js.Serialize(rootgroups));
}
}
I think that you should use a recursive methode instead of LINQ to do this.
1) Define a class which represent a group, with a property ID (int), a property Name (string), a property Children (List), a property Parent (int?, not serialized) and a method Serialize() (who calls Serialize for each Group in Children)
2) Define a List that will contain the "root" groups, and a List that will contain all the groups.
3) For each row of your datatable, create an object Group. Define all its properties. obviously, the list of children will be empty.
4) For each Group, if the parent's ID is not null, add it to his parent. This way, you will fill the Children list, for all the Groups.
5) For each Group, if the parent's ID is null, add the Group to the Roots list.
6) For each Group in the Roots list, call the method Serialize.
I hope this will help you. Ask me if you want more explanation or code instead of them.

Categories

Resources