How can I convert this C# Linq query syntax to Linq method lambda syntax? - c#

public Dictionary<string, Dictionary<int, List<int>>> fuBenNPCDictionary;
。。。something;
IEnumerable<(string fuben, int pos)> query = from string fuben in fuBenNPCDictionary.Keys
let fubendict = fuBenNPCDictionary[fuben]
from int pos in fubendict.Keys
where fubendict[pos].Contains(npc.ID)
select (fuben, pos);
if (query.Count() > 0)
foreach ((string fuben, int pos) in query)
{
placestr = "在" + jsonData.instance.SceneNameJsonData[fuben]["MapName"].str.ToCN() + "的第" + pos.ToString() + "位置";
}
This query was originally a manual foreach, and then VS turned it into a Linq form. I'm not very good at linq and would like to convert it to Linq method lambda syntax.

IEnumerable<(string key, int pos)> query = fuBenNPCDictionary.Keys
.SelectMany(fuben => fuBenNPCDictionary[fuben]
.Where(fubenDict => fubenDict.Value.Contains(npc.ID))
.Select(fubenDict => (fuben, fubenDict.Key)));
Brief explanation
If you're unfamiliar with SelectMany, it might be easier to think about what this code will do instead for now:
IEnumerable<IEnumerable<(string key, int Key)>> query2 = fuBenNPCDictionary.Keys
.Select(fuben => fuBenNPCDictionary[fuben]
.Where(fubenDict => fubenDict.Value.Contains(npc.ID))
.Select(fubenDict => (fuben, fubenDict.Key)));
So first we start with a Select on the fuBenNPCDictionary.Keys, taking each string key, and mapping it to the return of the subsequent code. In other words, for each key in the dict, do some sort of calculation or procedure, and give me a new list with the return from that procedure.
Inside this Select we can think about just what we're doing with one of the keys. We'll take the corresponding value to the key: fuBenNPCDictionary[fuben], which is now the Dictionary<int, List<int>>. We want to filter the values in here to only include relevant List<int>s so we'll use the Where. Next we want to take those filtered Lists and Select the tuple (fuben, fubenDict.Key). Note that we get fubenDict from the direct scope of our current Select, but we can grab fuben from within the scope of our parent Select. It's a little easier to see that this is valid if we had written the statement like below (omitting the Where for brevity):
fuBenNPCDictionary.Keys.Select(fuben =>
{
return fuBenNPCDictionary[fuben]
// Note this is still inside the curly braces in which `fuben` is still in scope.
.Select(fubenDict => (fuben, fubenDict.Key));
});
Putting this all together, the last thing left is that our query2 contains an extra level of nesting that we didn't want: it's of type IEnumerable<IEnumerable<(string key, int Key)>> instead of IEnumerable<(string key, int pos)>. So instead of a single list we have a list of lists. The answer to this is to change the outer Select to SelectMany instead. A simple example for SelectMany would be that if you had an array that looked like
[ [1, 2], [1] ]
You could do a SelectMany(x => x) to get [1, 2, 1], flattening the jagged array to a single array.

Related

Convert ordered comma separated list into tuples with ordered element number (a la SQL SPLIT_STRING) using C# 6.0/.Net Framework 4.8

I can't seem to find a ready answer to this, or even if the question has ever been asked before, but I want functionality similar to the SQL STRING_SPLIT functions floating around, where each item in a comma separated list is identified by its ordinal in the string.
Given the string "abc,xyz,def,tuv", I want to get a list of tuples like:
<1, "abc">
<2, "xyz">
<3, "def">
<4, "tuv">
Order is important, and I need to preserve the order, and be able to take the list and further join it with another list using linq, and be able to preserve the order. For example, if a second list is <"tuv", "abc">, I want the final output of the join to be:
<1, "abc">
<4, "tuv">
Basically, I want the comma separated string to determine the ORDER of the end result, where the comma separated string contains ALL possible strings, and it is joined with an unordered list of a subset of strings, and the output is a list of ordered tuples that consists only of the elements in the second list, but in the order determined by the comma separated string at the beginning.
I could likely figure out all of this on my own if I could just get a C# equivalent to all the various SQL STRING_SPLIT functions out there, which do the split but also include the ordinal element number in the output. But I've searched, and I find nothing for C# but splitting a string into individual elements, or splitting them into tuples where both elements of the tuple are in the string itself, not generated integers to preserve order.
The order is the important thing to me here. So if an element number isn't readily possible, a way to inner join two lists and guarantee preserving the order of the first list while returning only those elements in the second list would be welcome. The tricky part for me is this last part: the result of a join needs a specific (not easy to sort by) order. The ordinal number would give me something to sort by, but if I can inner join with some guarantee the output is in the same order as the first input, that'd work too.
That should work on .NET framework.
using System.Linq;
string str = "abc,xyz,def,tuv";
string str2 = "abc,tuv";
IEnumerable< PretendFileObject> secondList = str2.Split(',').Select(x=> new PretendFileObject() { FileName = x}); //
var tups = str.Split(',')
.Select((x, i) => { return (i + 1, x); })
.Join(secondList, //Join Second list ON
item => item.Item2 //This is the filename in the tuples
,item2 => item2.FileName, // This is the filename property for a given object in the second list to join on
(item,item2) => new {Index = item.Item1,FileName = item.Item2, Obj = item2})
.OrderBy(JoinedObject=> JoinedObject.Index)
.ToList();
foreach (var tup in tups)
{
Console.WriteLine(tup.Obj.FileName);
}
public class PretendFileObject
{
public string FileName { get; set; }
public string Foo { get; set; }
}
Original Response Below
If you wanted to stick to something SQL like here is how to do it with linq operators. The Select method has a built in index param you can make use of. And you can use IntersectBy to perform an easy inner join.
using System.Linq;
string str = "abc,xyz,def,tuv";
string str2 = "abc,tuv";
var secondList = str2.Split(',');
var tups = str.Split(',')
.Select((x, i) => { return (i + 1, x); })
.IntersectBy(secondList, s=>s.Item2) //Filter down to only the strings found in both.
.ToList();
foreach(var tup in tups)
{
Console.WriteLine(tup);
}
This will get you list of tuples
var input = "abc,xyz,def,tuv";
string[] items = input.Split(',');
var tuples = new List<(int, string)>();
for (int i = 0; i < items.Length)
{
tuples.Add(((i + 1), items[i]));
}
if then you want to add list of "tuv" and "abc" and keep 1, you probably want to "Left Join". But I am not sure, how you can do using LINQ because you first need to iterate the original list of tuples and assign same int. Then join. Or, you can join first and then assign int but technically, order is not guaranteed. However, if you assign int first, you can sort by it in the end.
I am slightly confused by "and be able to take the list and further join it with another list using linq". Join usually means aggregate result. But in your case it seem you demanding segment, not joined data.
--
"I want to remove any items from the second list that are not in the first list, and then I need to iterate over the second list IN THE ORDER of the first list"
var input2 = "xxx,xyz,yyy,tuv,";
string[] items2 = input2.Split(',');
IEnumerable<(int, string)> finalTupleOutput =
tuples.Join(items2, t => t.Item2, i2 => i2, (t, i2) => (t.Item1, i2)).OrderBy(tpl => tpl.Item1);
This will give you what you want - matching items from L2 in the order from L1
with LINQ
string inputString = "abc,xyz,def,tuv";
var output = inputString.Split(',')
.Select((item, index) => { return (index + 1, item); });
now you can use the output list as you want to use.
Not 100% sure what you're after, but here's an attempt:
string[] vals = new[] { "abc", "xyz", "dev", "tuv"};
string[] results = new string[vals.Length];
int index = 0;
for (int i = 0; i < vals.Length; i++)
{
results[i] = $"<{++index},\"{vals[i]}\">";
}
foreach (var item in results)
{
Console.WriteLine(item);
}
This produces:
<1,"abc">
<2,"xyz">
<3,"dev">
<4,"tuv">
Given the example
For example, if a second list is <"tuv", "abc">, I want the final
output of the join to be:
<1, "abc"> <4, "tuv">
I think this might be close?
List<string> temp = new List<string>() { "abc", "def", "xyz", "tuv" };
List<string> temp2 = new List<string>() { "dbc", "ace", "zyw", "tke", "abc", "xyz" };
var intersect = temp.Intersect(temp2).Select((list, idx) => (idx+1, list));
This produces an intersect result that has the elements from list 1 that are also in list 2, which in this case would be:
<1, "abc">
<2, "xyz">
If you want all the elements from both lists, switch the Intersect to Union.

How get max value by lambda query in C#?

I want to get the last number in the code column in C# using the query lambda. Be careful that I want a numerical value, not a list. For example, if the last registered number was 50, I would like this number 50. That is, we can store the query result in a numerical variable so that I can use it elsewhere
var maxcode= dbContext.personel
.Select(a => a.Max(w => w.code))
.FirstOrDefault();
For example
code name old
-----------------
1 Amelia 18
2 Olivia 27
3 Emily 11
4 Amelia 99
I want to get number 4
If I want to use top(1) to improve the speed?
This should work:
var max = dbContext.personel.Max(x => x.code);
While SQL and LINQ share some similarities, they are quite different in how they work. It's best to start with how IEnumerable works with this type of query, then how that translates to IQueryable. In most simple cases the two look exactly the same, by design.
The Select extension for IEnumerable iterates through the sequence and passes each object to the supplied function, collecting the results in a new IEnumerable of the appropriate type. In your code a will be a record rather than a collection.
Essentially Select looks like this under the hood:
public static IEnumerable<TResult> Select<TElement, TResult>(this IEnumerable<TElement> seq, Func<TElement, TResult> selector)
{
foreach (var item in seq)
yield return selector(item);
}
In simple words Select is a transformation. It takes objects of one type and passes them through a transform function.
The Max extension - at least the relevant one - processes a sequence of objects, uses the supplied function to pull some value from each object, then returns the largest of those values. It looks a little bit like this pseudo-code:
public static TResult Max<TElement, TResult>(this IEnumerable<TElement> seq, Func<TElement, TResult> valueFunc)
{
var result = default(TResult);
foreach (var item in seq)
{
var curr = valueFunc(item);
if (result == default(TResult) || result < curr)
result = curr;
}
return curr;
}
OK that won't compile, but it shows the basic concept.
So if you had an array of Personel objects in memory and wanted to find the largest code then you'd do this:
var maxCode = personel.Max(p => p.code);
The nice thing about LinqToSQL and pretty much all LINQ-like ORMs (Entity Framework, LinqToDB, etc) is that the exact same thing works for IQueryable:
var maxCode = dbContext.personel.Max(p => p.code);
The actual SQL for that will look something like (actual output from LinqToDB code gen):
SELECT
Max([t1].[code]) as [c1]
FROM
[personel] [t1]
For more interesting queries the syntax differs.
You have two Amelia entries with different ages. Let's say you want to find the age range for each name in your list. This is where grouping comes in.
In LINQ query syntax the query would look something like this:
var nameAges =
from p in dbContext.personel
group p.old by p.name into grp
select new { name = grp.Key, lowAge = grp.Min(), highAge = grp.Max() };
Grouping is easier in that format. In fluent it looks more like:
var nameAges = dbContext.personel
.GroupBy(p => p.name, p => p.old)
.Select(grp => new { name = grp.Key, lowAge = grp.Min(), highAge = grp.Max() };
Or in SQL:
SELECT name, Min(code) AS lowAge, Max(code) AS highAge
FROM personel
GROUP BY name
The moral is, writing LINQ queries is not the same as writing SQL queries... but the concepts are similar. Play around with them, work out how they work. LINQ is a great tool once you understand it.

LINQ to Entities does not recognize the method 'System.Linq.IQueryable`

I want to run this LINQ simple code to have record number in LINQ but result is beneath error
var model = _db2.Persons.Select(
(x, index) => new
{
rn = index + 1,
col1 = x.Id
}).ToList();
Error:
LINQ to Entities does not recognize the method
'System.Linq.IQueryable1[<>f__AnonymousType22
[System.Int32,System.Int32]] Select[Person,<>f__AnonymousType22](System.Linq.IQueryable1
[MvcApplication27.Models.Person], System.Linq.Expressions.Expression1[System.Func3
[MvcApplication27.Models.Person,System.Int32,<>f__AnonymousType2`2
[System.Int32,System.Int32]]])' method, and this method cannot be translated into a store
expression.
The problem is that LINQ to Entities doesn't understand how to convert that Select overload (the one that gives you the index) into a SQL query. You can fix this by first selecting the portion from the DB you need (to avoid selecting every column unnecessarily), then doing AsEnumerable() to take it as an IEnumerable<T> instead of an IQueryable<T>, and then doing the Select purely in C# (in short, IQueryable<T>s are converted to SQL, while IEnumerable<T>s are run in code).
var model = _db2.Persons.Select(x => x.Id).AsEnumerable().Select(
(id, index) => new
{
rn = index + 1,
col1 = id
}).ToList();
Note that the query as you have it appears to be unordered, so the id/index pairings can change each time you call this. If you expected consistency, you should order by something (e.g. _db2.Persons.OrderBy(...)).
Edit
Adding comment from Scott:
As a nice reference here is the list of all Linq statements built in
to the framework and a listing if it is compatible or not.
You could just select the Id and after it create your own anonymous object using linq to objects, for sample:
var model = _db2.Persons.Select(x => x.Id)
.ToList() // return int[]
.Select((id, index) => new
{
rn = index + 1,
col1 = id
}) // return anonymous[] (with rn and col1)
.AsEnumerable(); // get an IEnumerable (readonly collection)
Problably this is happen because Entity Framework does not support this kind of query using linq as linq could do in memory, so, in this case, you could select just you need (id in your case) and execute it, using ToList() method to concretize your query and after that you will have a list on memory, so, you can use linq to objects and use the supported method as you want.

How to select a rectangle from List<Rectangle[]> with Linq

I have a list of DrawObject[]. Each DrawObject has a Rectangle property. Here is my event:
List<Canvas.DrawObject[]> matrix;
void Control_MouseMove ( object sender, MouseEventArgs e )
{
IEnumerable<Canvas.DrawObject> tile = Enumerable.Range( 0, matrix.Capacity - 1)
.Where(row => Enumerable.Range(0, matrix[row].Length -1)
.Where(column => this[column, row].Rectangle.Contains(e.Location)))
.????;
}
I am not sure exactly what my final select command should be in place of the "????". Also, I was getting an error: cannot convert IEnumerable<int> to bool.
I've read several questions about performing a linq query on a list of arrays, but I can't quite get what is going wrong with this. Any help?
Edit:
Apologies for not being clear in my intentions with the implementation.
I intend to select the DrawObject that currently contains the mouse location.
It's not at all clear what you're trying to do. I suspect you want something like:
var drawObjects = from array in matrix
from item in array
where item.Rectangle.Contains(e.Location)
select item;
... but maybe not. You haven't shown what you're trying to do with the result of the query, or what this[column, row] is there for.
You almost certainly don't want to be using the capacity of the list in the first place - it's more likely that you're interested in the Count, but using the list as an IEnumerable<T> is probably better anyway.
EDIT: Okay, so the above query finds all the drawObjects where the rectangle contains the given location. You almost certainly want to use something like First, FirstOrDefault, Single or SingleOrDefault. For example:
var drawObject = (from array in matrix
from item in array
where item.Rectangle.Contains(e.Location)
select item)
.SingleOrDefault();
if (drawObject != null) // We found one
{
...
}
var tile = matrix.SelectMany(x => x)
.Where(x => x.Rectangle.Contains(e.Location));
Maybe:
....Select(y => y);
But it is hard to really tell what you are doing. And your first Where clause will not work since the lambda expression in the clause must result in a bool, but your lambda expression is resulting in a IEnumerable<T>. If I'm not all wrong.

Implementing a "like" operator multiple times in one Linq to Entities query

We have a list of strings and we need to filter our results by that list. Example would be find all students who have SSNs that start with 465, 496, or 497 (plus x more)
List<string> list = GetPossibleStartsWithValues();
var qry = from student in entities.Students.WhereStartsWith(x=>x.SSN, list)
select new SearchedStudent
{
Name = student.Name,
SSN = student.SSN,
...
}
The code provided here is close to what we need, but we can't figure out how to impliment the StartsWith that we need using the Expression Class.
Well, you could try this:
public static IQueryable<T> WhereStartsWith<T>(this IQueryable<T> source,
Expression<Func<T, string>> projection,
List<T> list)
{
return source.Where(x => list.Any(y => projection(x).StartsWith(y)));
}
That may well not work, but it would be worth trying before you go into anything more complicated.
EDIT: As you say, the above won't compile - you basically need to build an expression tree representing the bit within the Where clause. Oops. However, before you start doing that, it would be worth seeing whether it'll work in the end. Try this:
List<string> list = GetPossibleStartsWithValues();
var qry = from student in entities.Students
.Where(student => list.Any(y => student.SSN.StartsWith(y)))
select new SearchedStudent
{
Name = student.Name,
SSN = student.SSN,
...
}
If that doesn't work, then making a more general method won't be any use :(
How about using a compound statement such as
var qry = from student in entities.Students.Where(
s => list.Where( x => s.StartsWith(x)).Count() != 0 )

Categories

Resources