C #. Net powerful LINQ

Time:2021-12-4

LINQ yes   Language   INtegrated   Query is the acronym of the word, which translates into language integration query. It provides a consistent model for querying data across various data sources and formats, so it is called integrated query. Because this query does not create a new language, but is only implemented on the basis of the existing language, it is called language integrated query.

Basics

Functionally, LINQ can be divided into two categories:

  • LINQ to object, query the memory collection, and directly compile the query into. Net code for execution.
  • LINQ to provider is used to query the user-defined data source. The developer provides the provider of the corresponding data source and translates and executes the user-defined query. For example, XML and JSON can be used as the data source corresponding to the provider. The LINQ query corresponding to the data source is called LINQ to, such as LINQ to XML.

Grammatically, LINQ can be divided into:

  • SQL style: the syntax is similar to SQL, and the semantics of some complex queries will be clearer with SQL style, such as selectmany and join queries. SQL style has absolute advantages in readability, but it does not support all standard LINQ functions and user-defined functions. Pure grammar sugar.
  • Function style: it is implemented in the form of c# extension method. The extension method can be provided by the standard library or implemented by itself. It is a complete native programming style. The compiled code is function call. All standard LINQ functions and any custom functions are supported. With the increase of query complexity, the readability is not as good as SQL style.

LINQ to object is mostly used to query the mapping database, and LINQ to XML is used to query XML element data. The premise of using LINQ query is that the object must be an IEnumerable collection (note that for convenience of description, the collection in this paper refers to IEnumerable objects, including literal icollection objects). In addition, most LINQ queries are chain queries, that is, the data source of the operation is  IEnumerable  Type, return is  IEnumerable  Type.

The following query is LINQ to object:

var list = from user in users
where user.Name.Contains("Wang")
select user.Id;

It is equivalent to using the following LINQ extension method:

var list = users
.Where(u => user.Name.Contains("Wang"))
.Select(u => u.id);

LINQ query supports defining variables as needed in the middle of the statement, such as taking out numbers in the array whose square value is greater than the average value:

int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = from number in numbers
let average = numbers.Average()
let squared = Math.Pow(number, 2)
where squared > average
select number;
//The average value is 4.5 and the result is {3, 4, 5, 6, 7, 8, 9}

The select method receives the most parameters  Func, it can also receive  Func  Parameters, example:

var collectionWithRowNumber = collection.
.Select((item, index) => new { Item = item, RowNumber =index })
.ToList();

Let’s take a look at the example of LINQ to XML. Suppose we have the following XML file:


<Employees>
<Employee>
<EmpId>1EmpId>
<Name>LiamName>
<Sex>maleSex>
Employee>
<Employee>
<EmpId>2EmpId>
...
Employee>
Employees>

Use LINQ to XML to query all elements with the specified node value:

XElement xelement = XElement.Load("Employees.xml");
var els = from el in xelement.Elements("Employee")
where (string)el.Element("Sex") == "Male"
select el;

Equivalent to using LINQ extension method:

var els = xelement.Elements("Employee")
.Where(el => (string)el.Element("Sex") == "Male");

LINQ to XML operation XML is very convenient and flexible. You can explore it when you use it. I won’t talk about it here.

There are many methods of LINQ query. Due to space reasons, we will not list and demonstrate them one by one. Here we only select some powerful query methods. If these methods are implemented by non LINQ, they may be troublesome.

LINQ is powerful because it can easily implement complex queries. Let’s summarize the strengths of c# LINQ.

First, last, single, etc

First, firstordefault, last, lastordefault, single, and singleordefault are methods for quickly querying the first or last element in a collection. If the collection is empty, first, last and single will all report errors. If you make it not report errors and use the default value when the collection is empty, you can use firstordefault, lastordefault and singleordefault. The difference between single / singleordefault and other methods is that it restricts the query result to only one element. If the query result set contains multiple elements, an error will be reported. See the following examples:

new[] { "a", "b" }.First(x => x.Equals("b")); //Return to "B"“
new[] { "a", "b" }.First(x => x.Equals("c")); //Throw invalidoperationexception
new[] { "a", "b" }.FirstOrDefault(x => x.Equals("c")); //Return null

new[] { "a", "b" }.Single(x => x.Equals("b")); //Return to "B"“
new[] { "a", "b" }.Single(x => x.Equals("c")); //Throw invalidoperationexception
new[] { "a", "b" }.SingleOrDefault(x => x.Equals("c")); //Return null
new[] { "a", "a" }.Single(); //Throw invalidoperationexception

In practical application, if you want to ensure the uniqueness of query results (such as querying users through mobile phone number), use single / singleordefaut. In other cases, try to use first / firstordefault. Although firstordefault can also judge whether an element exists according to conditions, it is more efficient to use any.

Except difference set

The exception method of LINQ is used to get the difference set, that is, to get the elements in the set that are different from all the elements in another set.

Example:

int[] first = { 1, 2, 3, 4 };
int[] second = { 0, 2, 3, 5 };
IEnumerable<int> result = first.Except(second);
// result = { 1, 4 }

Note that the exception method removes duplicate elements:

int[] second = { 0, 2, 3, 5 };
int[] third = { 1, 1, 1, 2, 3, 4 };
IEnumerable<int> result = third.Except(second);
// result = { 1, 4 }

It is easy to use exception for simple types (int, float, string, etc.), but how to use exception for objects of custom types (or composite types, the same below)? At this point, you need to implement the custom typeIEquatableInterface, example:

class User : IEquatable
{
public string Name { get; set; }

public bool Equals(User other)
{
return Name == other.Name;
}

public override int GetHashCode()
{
return Name?.GetHashCode() ?? 0;
}
}

class Program
{
static void Main(string[] args)
{
var list1 = new List
{
new User{ Name = "User1"},
new User{ Name = "User2"},
};

var list2 = new List
{
new User{ Name = "User2"},
new User{ Name = "User3"},
};

var result = list1.Except(list2);
result.ForEach(u => Console.WriteLine(u.Name));
//Output: user1
}
}

Dimensionality reduction of selectmany sets

Selectmany can reduce the dimension of multidimensional sets, such as tiling two-dimensional sets into one-dimensional sets. give an example:

var collection = new int[][]
{
new int[] {1, 2, 3},
new int[] {4, 5, 6},
};
var result = collection.SelectMany(x => x);
// result = [1, 2, 3, 4, 5, 6]

Let’s take a more practical example. For example, there are the following entity classes (a department has multiple employees):

class Department
{
public Employee[] Employees { get; set; }
}

class Employee
{
public string Name { get; set; }
}

At this point, we have such a data set:

var departments = new[]
{
new Department()
{
Employees = new []
{
new Employee { Name = "Bob" },
new Employee { Name = "Jack" }
}
},
new Department()
{
Employees = new []
{
new Employee { Name = "Jim" },
new Employee { Name = "John" }
}
}
};

Now we can use selectmany to query the employees of each department into a result set:

var allEmployees = departments.SelectMany(x => x.Employees);
foreach(var emp in allEmployees)
{
Console.WriteLine(emp.Name);
}
//Output in sequence: Bob Jack Jim John

Selectmany Cartesian product operation

Selectmany is not only applicable to the dimensionality reduction of a single multi-dimensional collection object, but also applicable to the two-to-two mutual operations before multiple collections, such as dikal product operation. For example, we have two sets:

var list1 = new List<string> { "a1", "a2" };
var list2 = new List<string> { "b1", "b2", "b3" };

Now we need to combine it in pairs. Using the common method, we need to implement it with nested loop statements:

var result = newList<string>();
foreach (var s1 in list1)
foreach (var s2 in list2)
result.Add($"{s1}{s2}");
// result = ["a1b1", "a1b2", "a1b3", "a2b1", "a2b2", "a2b3"]

Use the selectmany implementation instead:

var result = list1.SelectMany(x => list2.Select(y => $"{x}{y}"));
// result = ["a1b1", "a1b2", "a1b3", "a2b1", "a2b2", "a2b3"]

The challenging problem is how to perform the dikal product operation on N sets. For example, there is such set data:

var arrList = new List<string[]>
{
new string[] { "a1", "a2" },
new string[] { "b1", "b2", "b3" },
new string[] { "c1" },
// ...
};

How to combine the sets in the above arrlist in pairs? This demand is common in product portfolio promotion in e-commerce business, especially retail business.

The following is an implementation using selectmany, which requires recursion:

class Program
{
static void Main(string[] args)
{
var arrList = new List<string[]>
{
new string[] { "a1", "a2" },
new string[] { "b1", "b2", "b3" },
new string[] { "c1" },
// ...
};

var result = Recursion(arrList, 0, new List<string>());
result.ForEach(x => Console.WriteLine(x));
}

static List<string> Recursion(List<string[]> list, int start, List<string> result)
{
if (start >= list.Count)
return result;

if (result.Count == 0)
result = list[start].ToList();
else
result = result.SelectMany(x => list[start].Select(y => x + y)).ToList();

result = Recursion(list, start + 1, result);

return result;
}
}

Output:

a1b1c1
a1b2c1
a1b3c1
a2b1c1
a2b2c1
a2b3c1

The operation of dikal product of similar sets can also be implemented by LINQ to object instead of selectmany:

result = result.SelectMany(x => list[start].Select(y => x + y)).ToList();
//Equivalent extension method:
result = (from a in result from b in list[start] select a + b).ToList();

LINQ to object looks easier to read than the extension method, but it is more convenient to write the extension method.

Aggregate aggregate

The aggregate extension method can perform accumulator like operations on a collection in turn, and gradually gather the data together like a snowball. For example, it is convenient to add 1 to 10 by using the aggregate extension method:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = numbers.Aggregate((prevSum, current) => prevSum + current);
// sum = 55

Let’s analyze its execution steps

  • In the first step, prevsum takes the value of the first element, that is, prevsum = 1
  • In the second step, add the value of prevsum obtained in the first step to the second element, that is, prevsum = prevsum + 2
  • By analogy, in step I, add the i-th element to the prevsum obtained in step I-1

Let’s take another example of a string to deepen our understanding:

string[] stringList = { "Hello", "World", "!" };
string joinedString = stringList.Aggregate((prev, current) => prev + " " + current);
// joinedString = "Hello World !"

Aggregate also has an overloaded method that specifies the initial value of the accumulator. Let’s take a more comprehensive and complex example. Suppose we have a number set of 1-12 as follows:

var items = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

Now we want to do the following calculation:

  • Calculates the total number of collection elements
  • Number of elements with even calculated value
  • Collect every 4th element

Of course, these three calculations can also be realized through ordinary loop traversal, but it is more concise to use aggregate. The following is the implementation of aggregate:

var result = items.Aggregate(new { Total = 0, Even = 0, FourthItems = new List<int>() },
(accum, item) =>
new
{
Total = accum.Total + 1,
Even = accum.Even + (item % 2 == 0 ? 1 : 0),
FourthItems = (accum.Total + 1) % 4 == 0 ? new List<int>(accum.FourthItems) { item } : accum.FourthItems
}
);

// result:
// Total = 12
// Even = 6
// FourthItems = [4, 8, 12]

For simplicity, the anonymous type is used as the initial value of the accumulator. Since the properties of the anonymous type are read-only, a new object is created during the accumulation process. If the initial value uses a custom type, there is no need for a new object during accumulation.

Join Association query

Like SQL queries, LINQ also supports inner join, left join, right join, cross join and full outer join. Sometimes you may see different expressions that actually mean the same thing. For example, left outer join is left join, join is inner join, and inner is omitted.

Suppose we have the following two sets, representing the data on the left and the data on the right.

var first = new List<string>() { "a","b","c" }; //Left
var second = new List<string>() { "a", "c", "d" }; //Right

The following takes this data as an example to demonstrate various associated queries.

Inner Join

var result = from f in first
join s in second on f equals s
select new { f, s };
//Equivalent extension method:
var result = first.Join(second,
f => f,
s => s,
(f, s) => new { f, s });

// result: {"a","a"}
// {"c","c"}

Left Join

var result = from f in first
join s in second on f equals s into temp
from t in temp.DefaultIfEmpty()
select new { First = f, Second = t };
//Or:
var result = from f in first
from s in second.Where(x => x == f).DefaultIfEmpty()
select new { First = f, Second = s };

//Equivalent extension method:
var result = first.GroupJoin(second,
f => f,
s => s,
(f, s) => new { First = f, Second = s })
.SelectMany(temp => temp.Second.DefaultIfEmpty(),
(f, s) => new { First = f.First, Second = s });

// result: {"a","a"}
// {"b", null}
// {"c","c"}

Right Join

var result = from s in second
join f in first on s equals f into temp
from t in temp.DefaultIfEmpty()
select new { First = t, Second = s };
//Others are similar to left join

// result: {"a","a"}
// {"c","c"}
// {null,"d"}

Cross Join

var result = from f in first
from s in second
select new { f, s };

// result: {"a","a"}
// {"a","c"}
// {"a","d"}
// {"b","a"}
// {"b","c"}
// {"b","d"}
// {"c","a"}
// {"c","c"}
// {"c","d"}

Full Outer Join

var leftJoin = from f in first
join s in second on f equals s into temp
from t in temp.DefaultIfEmpty()
select new { First = f, Second = t };
var rightJoin = from s in second
join f in first on s equals f into temp
from t in temp.DefaultIfEmpty()
select new { First = t, Second = s };
var fullOuterJoin = leftJoin.Union(rightJoin);

Associate according to multiple keys

In SQL, when a table is associated with a table, the on condition can specify the logical judgment of multiple keys and connect them with and or. However, LINQ of c# does not support the and keyword. If you want to associate multiple keys, you need to put the key values to be associated into the anonymous object with the same attribute name, and then use equals to compare whether the two anonymous objects are equal. Example:

var stringProps = typeof(string).GetProperties();
var builderProps = typeof(StringBuilder).GetProperties();
var query =
from s in stringProps
join b in builderProps
on new { s.Name, s.PropertyType } equals new { b.Name, b.PropertyType }
select new
{
s.Name,
s.PropertyType
};

The above two sets are used as examples. LINQ Association query also supports multiple set associations, just like SQL multi table associations. You only need to continue to add the join operation in the future.

LINQ Association query is similar to SQL, but it is very different in use. There are many uses of LINQ Association query, which are also very flexible. You don’t have to remember them deliberately. As long as you are familiar with the simple and commonly used ones, you can query the relevant documents when you actually use them.

Skip & take Pagination

The skip extension method is used to skip the reading collection of a specified number of elements starting from the starting position; The take extension method is used to read only a specified number of elements from the collection.

var values = new[] { 5, 4, 3, 2, 1 };
var skipTwo = values.Skip(2); // { 3, 2, 1 }
var takeThree = values.Take(3); // { 5, 4, 3 }
var skipOneTakeTwo = values.Skip(1).Take(2); // { 4, 3 }

The combination of skip and take can realize our common paging query:

public IEnumerable GetPage(this IEnumerable collection, int pageNumber, int pageSize)
{
int startIndex = (pageNumber - 1) * pageSize;
return collection.Skip(startIndex).Take(pageSize);
}

Students who have used ef (core) must be familiar with it.

In addition, there are  SkipWhile  and  TakeWhile  The extension method is different from skip and take in that their parameters are specific conditions. Skipwhile ignores elements from the starting position until the matching elements stop ignoring, and then the results of the query are displayed; TakeWhile starts reading qualified elements from the starting position. Once it encounters unqualified elements, it stops reading. Even if there are qualified elements behind, it will not be read. Example:

SkipWhile:

int[] list = { 42, 42, 6, 6, 6, 42 };
var result = list.SkipWhile(i => i == 42);
// result: 6, 6, 6, 42

TakeWhile:

int[] list = { 1, 10, 40, 50, 44, 70, 4 };
var result = list.TakeWhile(item => item < 50).ToList();
// result = { 1, 10, 40 }

Zip zipper

The objects operated by the zip extension method are two collections. Like a zipper, it pairs each element in the two series together in turn according to the position. The parameter it receives is a func instance that allows us to process elements in two collections in pairs. If the number of elements in the two sets is not equal, the extra elements will be ignored.

Example:

int[] numbers = { 3, 5, 7 };
string[] words = { "three", "five", "seven", "ignored" };
IEnumerable<string> zip = numbers.Zip(words, (n, w) => n + "=" + w);

foreach (string s in zip)
{
Console.WriteLine(s);
}

Output:

3=three
5=five
7=seven

Oftype and cast type filtering and conversion

Oftype is used to filter the elements of the specified type in the collection. Cast can convert the collection to the specified type, but the source type must be implicitly convertible to the target type. If there are the following data:

interface IFoo { }
class Foo : IFoo { }
class Bar : IFoo { }

var item0 = new Foo();
var item1 = new Foo();
var item2 = new Bar();
var item3 = new Bar();
var collection = new IFoo[] { item0, item1, item2, item3 };

Oftype example:

var foos = collection.OfType(); // result: item0, item1
var bars = collection.OfType(); // result: item2, item3
var foosAndBars = collection.OfType(); // result: item0, item1, item2, item3

//Equivalent to using where
var foos = collection.Where(item => item is Foo); // result: item0, item1
var bars = collection.Where(item => item is Bar); // result: item2, item3

Cast example:

var bars = collection.Cast();  //Invalidcastexception exception
var foos = collection.Cast(); //Invalidcastexception exception
var foosAndBars = collection.Cast(); // OK

Tolookup indexed lookup

The tolookup extension method returns an indexable data structure. It is an ilookup instance. All elements are grouped according to the specified key and can be indexed by pressing the key. This is a bit abstract. Let’s look at specific examples:

string[] array = { "one", "two", "three" };
//Creates a lookup object based on the length of the element string
var lookup = array.ToLookup(item => item.Length);

//Finds an element with a string length of 3
var result = lookup[3];
// result: one,two

Another example:

int[] array = { 1,2,3,4,5,6,7,8 };
//Create a parity lookup (keys 0 and 1)
var lookup = array.ToLookup(item => item % 2);

//Find even number
var even = lookup[0];
// even: 2,4,6,8

//Find odd
var odd = lookup[1];
// odd: 1,3,5,7

Distinct de duplication

The distinct method is used to remove duplicates, which is easy to understand. Example:

int[] array = { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
var distinct = array.Distinct();
// distinct = { 1, 2, 3, 4, 5 }

The distinct method called by a collection of simple types uses the default comparator. The distinct method uses this comparator to judge whether an element is repeated with other elements. However, for a user-defined type, a user-defined comparator is required to implement de duplication. Example:

public class IdEqualityComparer : IEqualityComparer
{
public bool Equals(Person x, Person y) => x.Id == y.Id;
public int GetHashCode(Person p) => p.Id;
}

public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
var people = new List();
var distinct = people.Distinct(new IdEqualityComparer());
}
}

Todictionary dictionary conversion

The todictionary extension method can put a collection  IEnumerable  Convert to  Dictionary  Structure, which receives a  Func  Parameter is used to return the specified key and value of each element. Example:

IEnumerable users = GetUsers();
Dictionary<int, User> usersById = users.ToDictionary(x => x.Id);

If you don’t use todictionary, you need to write this:

IEnumerable users = GetUsers();
Dictionary<int, User> usersById = new Dictionary<int, User>();
foreach (User u in users)
{
usersById.Add(u.Id, u);
}

The value in the dictionary data returned by todictionary above is the entire element. You can also customize the dictionary value through its second parameter. Example:

Dictionary<int, string> userNamesById = users.ToDictionary(x => x.Id, x => x.Name);

You can also specify whether the keys of the converted dictionary are case sensitive, that is, the IComparer of the custom dictionary, for example:

Dictionary<string, User> usersByCaseInsenstiveName = users.ToDictionary(x =>x.Name,
StringComparer.InvariantCultureIgnoreCase);

var user1 =usersByCaseInsenstiveName["liam"];
var user2 =usersByCaseInsenstiveName["LIAM"];
user1 == user2; // true

Note that the dictionary type requires that all keys cannot be repeated, so when using the todictionary method, ensure that the element attribute as the key of the dictionary cannot have duplicate values, otherwise an exception will be thrown.

Other common extension methods

There are many other common extension methods for LINQ, which you should use more at ordinary times, such as where, any, all, etc. here are some simple examples to introduce.

Range and repeat

Range and repeat are used to generate a simple series of numbers or strings. Example:

//Generate numbers from 1 to 100, that is, the result is [1, 2,..., 99, 100]
var range = Enumerable.Range(1, 100);

//Three duplicate strings "a" are generated, that is, the result is ["a", "a", "a"]
var repeatedValues = Enumerable.Repeat("a", 3);

Any and all

Any is used to judge whether any element in the set meets the conditions, and all is used to judge whether all elements in the set meet the conditions. Example:

var numbers = new int[] {1, 2, 3, 4, 5 };
bool result = numbers.Any(); // true
bool result = numbers.Any(x => x == 6); // false
bool result = numbers.All(x => x > 0); // true
bool result = numbers.All(x => x > 1); // false

Concat and union

Concat is used to splice two sets without removing duplicate elements. Example:

List<int> foo = newList<int> { 1, 2, 3 };
List<int> bar = newList<int> { 3, 4, 5 };
//Through the static method of enumerable class
var result = Enumerable.Concat(foo, bar).ToList(); // 1,2,3,3,4,5
//By extension method
var result = foo.Concat(bar).ToList(); // 1,2,3,3,4,5

Union is also used to splice two sets. Unlike concat, it removes duplicates. For example:

var result = foo.Union(bar); // 1,2,3,4,5

Groupby grouping

The groupby extension method is used to group collections. The following is an example of grouping by parity:

var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var grouped = list.GroupBy(x => x % 2 == 0);
//Grouped: [1, 3, 5, 7, 9] and [2, 4, 6, 8]

You can also group according to the specified attributes:

public class Person
{
public int Age { get; set; }
public string Name { get; set; }
}

var people = new List();
var query = people
.GroupBy(x => x.Age)
.Select(g => { Age = g.Key, Count = g.Count() });

Defaultifempty replace

In the above Association query, we used the defaultifempty extension method, which means that when no element with specified conditions is queried, the default value of the element is used instead. In fact, defaultifempty can also specify other default values, for example:

var chars = new List<string>() { "a", "b", "c", "d" };
chars.Where(s => s.Length > 1).DefaultIfEmpty().First(); //Return null
chars.DefaultIfEmpty("N/A").FirstOrDefault(); //Return to "a"
chars.Where(s => s.Length > 1).DefaultIfEmpty("N/A").FirstOrDefault(); //Return to "n / a"

Sequenceequal sets are equal

The sequenceequal extension method is used to compare whether the elements at the same position in the collection series are equal. Example:

int[] a = new int[] {1, 2, 3};
int[] b = new int[] {1, 2, 3};
int[] c = new int[] {1, 3, 2};

bool result1 = a.SequenceEqual(b); // true
bool result2 = a.SequenceEqual(c); // false

last

There are some common and simple extension methods, such as orderby, sum, count, reverse, etc. at the same time, you are welcome to supplement the powerful or easy-to-use LINQ syntax sugar omitted in this article.


Source: fine code farmer, author Liam Wang

Recommended Today

Hive built-in function summary

1. Related help operation functions View built-in functions: Show functions; Display function details: desc function ABS; Display function extension information: desc function extended concat; 2. Learn the ultimate mental method of built-in function Step 1: carefully read all the functions of the show functions command to establish an overall understanding and impression Step 2: use […]