PageRenderTime 47ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/ToSIC_SexyContent/Docs-Generator/specs/datasources/linq-guide.md

https://github.com/2sic/2sxc
Markdown | 223 lines | 174 code | 49 blank | 0 comment | 0 complexity | 7ecdd170aed7418c78429f3c095bfc56 MD5 | raw file
Possible License(s): AGPL-3.0
  1. ---
  2. uid: Specs.DataSources.LinqGuide
  3. ---
  4. # Guide to Working with LINQ and 2sxc/EAV Data
  5. > Warning: some of this is old and doesn't apply any more
  6. > we must update these docs
  7. > Especially the compare / contains etc. actually works now, even with dyn-objects
  8. In many cases you will want to sort, filter or group some data, or quickly check if any data was found. When using Razor or working in WebApi, this is best done with LINQ. This guide will assist you to get everything working.
  9. For a more API-oriented documentation, see [DotNet Query LINQ](xref:Specs.DataSources.Linq). We also recommend to play around with the [Razor Tutorial App](https://2sxc.org/en/apps/app/razor-tutorial)
  10. ## LINQ Basics
  11. The way LINQ works is that the namespace `System.Linq` contains a bunch of [extension methods](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) like `.Count()`, `.Where(...)` and more. So to use LINQ you need to add a `@using` statement to razor or just `using` in a WebApi class. Here's a simple razor example:
  12. ```razor
  13. @using System.Linq;
  14. @{
  15. var newestPosts = AsDynamic(App.Data["BlogPost"])
  16. .OrderByDescending(b => b.PublicationDate)
  17. .Take(3);
  18. }
  19. ```
  20. This demonstrates:
  21. 1. adding the `using` statement
  22. 1. getting all the _BlogPost_ items using `App.Data["BlogPost"]`
  23. 1. converting it to a list of `dynamic` objects which will allow the nice syntax using `AsDynamic(...)`
  24. 1. sorting these with newest on top using `.OrderByDescending(...)` on the property _PublicationDate_
  25. 1. keeping only the first 3 using `.Take(3)`
  26. 1. it also shows how placing the parts on separate lines makes the code easier to read
  27. ## Important: Working with LINQ and dynamic objects
  28. ### LINQ needs IEnumerable<...>
  29. Before we continue, it's important that you really understand that LINQ commands are stored as [extension methods](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) of `IEnumerable<T>`. So this works:
  30. ```razor
  31. @using System.Linq;
  32. @{
  33. var list = new List<string> { "word", "word" };
  34. var x = list.First();
  35. }
  36. ```
  37. ...whereas this does not:
  38. ```razor
  39. @using System.Linq;
  40. @{
  41. var y = 27.First();
  42. }
  43. ```
  44. This sounds obvious, but there's an important catch: if the compiler doesn't know that something is an `IEnumerable`, it will not even try to use the LINQ extension methods, because it doesn't know that it can. So let's look at that...
  45. ### LINQs Problems with dynamic objects #1
  46. Here's an example that would fail:
  47. ```razor
  48. @using System.Linq;
  49. @{
  50. dynamic list = new List<string> { "word", "word" };
  51. var x = list.First();
  52. }
  53. ```
  54. The only difference to before is that _list_ ist now `dynamic`. It contains the same object, but the compiler doesn't treat it that way. In Razor, we use `dynamic` objects all the time, where we run into this problem. Here's an example which fails:
  55. ```razor
  56. @using System.Linq;
  57. @{
  58. var books = AsDynamic(App.Data["Books"]);
  59. var booksWithoutAuthors = books
  60. .Where(b => !b.Authors.Any());
  61. }
  62. ```
  63. Internally the _b.Authors_ returns a list of authors, but the compiler doesn't know this, since it's treated as a `dynamic` object. You would get an error. To solve this, we must tell the compiler that _b.Authors_ is an IEnumerable, like this:
  64. ```razor
  65. @using System.Linq;
  66. @using System.Collections.Generic;
  67. @{
  68. var books = AsDynamic(App.Data["Books"]);
  69. var booksWithoutAuthors = books
  70. .Where(b => !(b.Authors as IEnumerable<dynamic>).Any());
  71. }
  72. ```
  73. But let's be honest - it's ugly, long and prone to typos. Especially in a complex query where you could have many of these. So we recommend to define a shorthand for it, like this:
  74. ```razor
  75. @using System.Linq;
  76. @using Dynlist = System.Collections.Generic.IEnumerable<dynamic>;
  77. @{
  78. var books = AsDynamic(App.Data["Books"]);
  79. var booksWithoutAuthors = books
  80. .Where(b => !(b.Authors as Dynlist).Any());
  81. }
  82. ```
  83. ### LINQs problem with dynamic objects #2
  84. LINQ methods often have multiple signatures. This means the same command can be written in different ways and with different parameters. To detect the right method, the compiler needs to know the data-types used in the parameters. This causes problem with `dynamic` objects because the compiler doesn't know what it is until runtime. Check this out:
  85. ```razor
  86. @using System.Linq;
  87. @{
  88. var dogString = "dog"
  89. dynamic dogDyn = "dog";
  90. var list = new List<string> { "dog", "cat", "hound" };
  91. var x = list.Contains(dogString); // this works
  92. var x = list.Contains(dogDyn); // this fails
  93. }
  94. ```
  95. To fix this, we must tell the compiler it's an object:
  96. ```razor
  97. @using System.Linq;
  98. @{
  99. dynamic dynDog = "dog";
  100. var list = new List<string> { "dog", "cat", "hound" };
  101. var x = list.Contains(dynDog as object);
  102. }
  103. ```
  104. The above example is a bit trivial but here's a real life example, taken from the [2sxc razor tutorial](https://2sxc.org/en/apps/app/razor-tutorial):
  105. ```razor
  106. @using System.Linq;
  107. @using Dynlist = System.Collections.Generic.IEnumerable<dynamic>;
  108. @{
  109. var persons = AsDynamic(App.Data["Persons"]);
  110. var books = AsDynamic(App.Data["Books"]);
  111. var booksWithAwardedAuthors = books
  112. .Where(b => (b.Authors as Dynlist)
  113. .SelectMany(a => a.Awards as Dynlist)
  114. .Any()
  115. );
  116. var otherBooks = books
  117. .Where(b => !(booksWithAwardedAuthors as Dynlist)
  118. .Contains(b as object)
  119. );
  120. }
  121. ```
  122. ### LINQs problem with dynamic object #3
  123. The last bit has to do with how `dynamic` objects are built, since they are usually wrapper-objects to help write nicer template code. As wrappers, they are different objects every time. This shows the problem:
  124. ```razor
  125. @using System.Linq;
  126. @using Dynlist = System.Collections.Generic.IEnumerable<dynamic>;
  127. @{
  128. // this is just the data object, "@bookData.Author" wouldn't work
  129. var bookData1 = App.Data["Books"].First();
  130. var bookData2 = App.Data["Books"].First();
  131. // this is now a dynamic object, allowing @bookDyn1.Author"
  132. var bookDyn1 = AsDynamic(bookData1);
  133. var bookDyn2 = AsDynamic(bookData2);
  134. var dataIsSame = bookData1 == bookData2; // true
  135. var dynIsSame = bookDyn1 == bookDyn2; // false before 2sxc 9.42
  136. }
  137. ```
  138. This doesn't sound like a big deal, but it is. Look at this code from the example above:
  139. ```razor
  140. var otherBooks = books
  141. .Where(b => !(booksWithAwardedAuthors as Dynlist)
  142. .Contains(b as object)
  143. );
  144. ```
  145. The `.Contains(...)` clause receives a variable `b` which is actually the dynamic wrapper, and will _not_ be the same as the dynamic wrapper of dynamic wrappers given in `booksWithAwardedAuthors`. So contains would always say "nope, didn't find it".
  146. Solving the comparison / equality problem requires the underlying wrapper object to tell the .net framework, that `==`, `!=` and a few internal methods must work differently. 2sxc 9.42 does this, so the above code would actually work in 2sxc 9.42, but not in previous versions. If another system gives you `dynamic` objects, you will probably have to write it like this:
  147. ```razor
  148. // this example is for non-2sxc objects or 2sxc before 9.42
  149. var otherBooks = books
  150. .Where(b => !(booksWithAwardedAuthors as Dynlist)
  151. .Contains(bookWithAward => bookWithAward != null && bookWithAward.SomeProperty == b.SomeProperty)
  152. );
  153. ```
  154. ### LINQs problem with boolean null-objects
  155. In many cases, dynamic objects could have a property like `Show` which could be a boolean, but it could also be `null`. So this could cause an error:
  156. ```razor
  157. var show = links.Where(x => x.Show);
  158. ```
  159. To fix this, the easiest way is to really compare it with `true` or `false` as you want, each way will result in treating the `null` as the opposite (so you decide if null should be yes or no):
  160. ```razor
  161. @using System.Linq;
  162. @using Dynlist = System.Collections.Generic.IEnumerable<dynamic>;
  163. Dynlist list;
  164. list = links.Where(x => x.Show == true); // take true, skip false & null
  165. list = links.Where(x => x.Show != true); // take false & null, skip true
  166. list = links.Where(x => x.Show == false); // take false, skip true & null
  167. list = links.Where(x => x.Show != false); // take true & null, skip false
  168. list = links.Where(x => x.Show == null); // take null, skip true & false
  169. ```
  170. ## Read also, Demo App and further links
  171. 1. [LINQ API Docs](xref:Specs.DataSources.Linq)
  172. 2. [Razor Tutorial App showing all kinds of Queries](https://2sxc.org/en/apps/app/razor-tutorial)
  173. ## History
  174. 1. Guide created 2019-03