查询表达式必须以 from 子句开头,且必须以 select 或 group 子句结尾。 在第一个 from 子句与最后一个 select 或 group 子句之间,可以包含以下这些可选子句中的一个或多个:where、orderby、join、let,甚至是其他 from 子句。 还可以使用 into 关键字,使 join 或 group 子句的结果可以充当相同查询表达式中的其他查询子句的源。
// Query Expression. IEnumerable<int> scoreQuery = //query variable from score in scores //required where score > 80// optional orderby score descending// optional select score; //must end with select or group
// Execute the query to produce the results foreach (int testScore in scoreQuery) { Console.WriteLine(testScore); }
通常提供查询变量的显式类型以便显示查询变量与 select 子句之间的类型关系。 但是,还可以使用 var 关键字指示编译器在编译时推断查询变量(或任何其他局部变量)的类型
开始查询表达式
查询表达式必须以 from 子句开头。 它指定数据源以及范围变量。 范围变量表示遍历源序列时,源序列中的每个连续元素。 范围变量基于数据源中元素的类型进行强类型化。 在下面的示例中,因为 countries 是 Country 对象的数组,所以范围变量也类型化为 Country。 因为范围变量是强类型,所以可以使用点运算符访问该类型的任何可用成员。
1 2 3 4
IEnumerable<Country> countryAreaQuery = from country in countries where country.Area > 500000//sq km select country;
范围变量一直处于范围中,直到查询使用分号或 continuation 子句退出。
查询表达式可能会包含多个 from 子句。 在源序列中的每个元素本身是集合或包含集合时,可使用其他 from 子句。 例如,假设具有 Country 对象的集合,其中每个对象都包含名为 Cities 的 City 对象集合。 若要查询每个 Country 中的 City 对象,请使用两个 from 子句,如下所示:
1 2 3 4 5
IEnumerable<City> cityQuery = from country in countries from city in country.Cities where city.Population > 10000 select city;
结束查询表达式
查询表达式必须以 group 子句或 select 子句结尾。
group 子句
使用 group 子句可生成按指定键组织的组的序列。 键可以是任何数据类型。 例如,以下查询会创建包含一个或多个 Country 对象,并且其关键值是数值为国家/地区名称首字母的 char 类型。
1 2 3
var queryCountryGroups = from country in countries group country by country.Name[0];
select 子句
使用 select 子句可生成所有其他类型的序列。 简单 select 子句只生成类型与数据源中包含的对象相同的对象的序列。 在此示例中,数据源包含 Country 对象。 orderby 子句只按新顺序对元素进行排序,而 select 子句生成重新排序的 Country 对象的序列。
1 2 3 4
IEnumerable<Country> sortedQuery = from country in countries orderby country.Area select country;
// Here var is required because the query // produces an anonymous type. var queryNameAndPop = from country in countries selectnew { Name = country.Name, Pop = country.Population };
选择每个源元素的子集 有两种主要方法来选择源序列中每个元素的子集:
若要仅选择源元素的一个成员,请使用点操作。 在以下示例中,假设 Customer 对象包含多个公共属性,包括名为 City 的字符串。 在执行时,此查询将生成字符串的输出序列。
1 2
var query = from cust in Customers select cust.City;
classXMLTransform { staticvoidMain() { // Create the data source by using a collection initializer. // The Student class was defined previously in this topic. List<Student> students = new List<Student>() { new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List<int>{97, 92, 81, 60}}, new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List<int>{75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List<int>{88, 94, 65, 91}}, };
// Create the query. var studentsToXML = new XElement("Root", from student in students let scores = string.Join(",", student.Scores) selectnewXElement("student", new XElement("First", student.First), newXElement("Last", student.Last), newXElement("Scores", scores) ) // end "student" ); // end "Root"
// Execute the query. Console.WriteLine(studentsToXML);
// Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
可以在 select 或 group 子句中使用 into 关键字创建存储查询的临时标识符。 如果在分组或选择操作之后必须对查询执行其他查询操作,则可以这样做。 在下面的示例中,countries 按 1000 万范围,根据人口进行分组。 创建这些组之后,附加子句会筛选出一些组,然后按升序对组进行排序。 若要执行这些附加操作,需要由 countryGroup 表示的延续。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// percentileQuery is an IEnumerable<IGrouping<int, Country>> var percentileQuery = from country in countries let percentile = (int)country.Population / 10_000_000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup;
// grouping is an IGrouping<int, Country> foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) { Console.WriteLine(country.Name + ":" + country.Population); } }
筛选、排序和联接
在开头 from 子句与结尾 select 或 group 子句之间,所有其他子句(where、join、orderby、from、let)都是可选的。 任何可选子句都可以在查询正文中使用零次或多次。
where 子句
使用 where 子句可基于一个或多个谓词表达式,从源数据中筛选出元素。 以下示例中的 where 子句具有一个谓词及两个条件。
1 2 3 4
IEnumerable<City> queryCityPop = from city in cities where city.Population < 200000 && city.Population > 100000 select city;
orderby 子句
使用 orderby 子句可按升序或降序对结果进行排序。 还可以指定次要排序顺序。 下面的示例使用 Area 属性对 country 对象执行主要排序。 然后使用 Population 属性执行次要排序。
1 2 3 4
IEnumerable<Country> querySortedCountries = from country in countries orderby country.Area, country.Population descending select country;
var categoryQuery = from cat in categories join prod in products on cat equals prod.Category selectnew { Category = cat, Name = prod.Name };
let 子句
使用 let 子句可将表达式(如方法调用)的结果存储在新范围变量中。 在下面的示例中,范围变量 firstName 存储 Split 返回的字符串数组的第一个元素。
1 2 3 4 5 6 7 8 9 10 11 12
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" }; IEnumerable<string> queryFirstNames = from name in names let firstName = name.Split(' ')[0] select firstName;
foreach (string s in queryFirstNames) { Console.Write(s + " "); }
//Output: Svetlana Claire Sven Cesar
查询表达式中的子查询
查询子句本身可能包含查询表达式,这有时称为子查询。 每个子查询都以自己的 from 子句开头,该子句不一定指向第一个 from 子句中的相同数据源。 例如,下面的查询演示在 select 语句用于检索分组操作结果的查询表达式。
1 2 3 4 5 6 7 8 9 10 11
var queryGroupMax = from student in students group student by student.Year into studentGroup selectnew { Level = studentGroup.Key, HighestScore = ( from student2 in studentGroup select student2.ExamScores.Average() ).Max() };
在真实的应用程序中你的实体可以相互引用. User 实体可以引用它的角色. 如果你想序列化用户,它的角色也必须是序列化的. Role 类可以有 List ,而 Permission 类可以有一个对 PermissionGroup 类的引用,依此类推…想象一下所有这些对象都被立即序列化了. 你可能会意外地序列化整个数据库! 同样,如果你的对象具有循环引用,则它们可能根本不会序列化成功.
几乎所有的O/RM框架都支持延迟加载. 此功能可在需要时从数据库加载实体. 假设 User 类具有对 Role 类的引用. 当你从数据库中获取用户时,Role 属性(或集合)不会被立即填充. 首次读取 Role 属性时,它是从数据库加载的. 因此如果将这样的实体返回到表示层,它将通过执行额外的查询从数据库中检索额外的实体. 如果序列化工具读取实体,它会递归读取所有属性,并且可以再次检索整个数据库(如果实体之间存在关系).
publicBook(Guid id, [NotNull] string name, BookType type, float? price = 0) { Id = id; Name = CheckName(name); Type = type; Price = price; }
publicvirtualvoidChangeName([NotNull] string name) { Name = CheckName(name); }
privatestaticstringCheckName(string name) { if (string.IsNullOrWhiteSpace(name)) { thrownew ArgumentException($"name can not be empty or white space!"); }
if (name.Length > MaxNameLength) { thrownew ArgumentException($"name can not be longer than {MaxNameLength} chars!"); }
IRepository<TEntity> 有一些缺失的方法, 通常与实体的 Id 属性一起使用. 由于实体在这种情况下没有 Id 属性, 因此这些方法不可用. 比如 Get 方法通过id获取具有指定id的实体. 不过, 你仍然可以使用IQueryable<TEntity>的功能通过标准LINQ方法查询实体。
publicOrder(Guid id, string referenceNo) { Check.NotNull(referenceNo, nameof(referenceNo)); Id = id; ReferenceNo = referenceNo; OrderLines = new List<OrderLine>(); }
publicvoidAddProduct(Guid productId, int count) { if (count <= 0) { thrownew ArgumentException( "You can not add zero or negative count of products!", nameof(count) ); }
var existingLine = OrderLines.FirstOrDefault(ol => ol.ProductId == productId);
protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder) { var converter = new ValueConverter<EquineBeast, string>( v => v.ToString(), v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder) { var converter = new ValueConverter<EquineBeast, string>( v => v.ToString(), v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v), new ConverterMappingHints(size: 20, unicode: false));
publicint Year { get; } public Money Income { get; } public Money Expenses { get; } public Money Revenue => new Money(Income.Amount - Expenses.Amount, Income.Currency); }
publicclassBlog { publicint Id { get; set; } publicstring Name { get; set; }
public IList<AnnualFinance> Finances { get; set; } }
接下来再次使用序列化来进行存储:
1 2 3 4 5 6 7 8 9
modelBuilder.Entity<Blog>() .Property(e => e.Finances) .HasConversion( v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null), v => JsonSerializer.Deserialize<List<AnnualFinance>>(v, (JsonSerializerOptions)null), new ValueComparer<IList<AnnualFinance>>( (c1, c2) => c1.SequenceEqual(c2), c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), c => (IList<AnnualFinance>)c.ToList()));
使用不区分大小写的字符串键
一些数据库(包括 SQL Server)默认执行不区分大小写的字符串比较。 另一方面,.NET 默认执行区分大小写的字符串比较。 这意味着,“DotNet”之类的外键值将与 SQL Server 上的主键值“dotnet”匹配,但与 EF Core 中的该值不匹配。 键的值比较器可用于强制 EF Core 执行不区分大小写的字符串比较,就像在数据库中那样。 例如,请考虑使用拥有字符串键的博客/文章模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
publicclassBlog { publicstring Id { get; set; } publicstring Name { get; set; }
public ICollection<Post> Posts { get; set; } }
publicclassPost { publicstring Id { get; set; } publicstring Title { get; set; } publicstring Content { get; set; }
publicstring BlogId { get; set; } public Blog Blog { get; set; } }
如果某些 Post.BlogId 值具有不同的大小写,此模型不会按预期工作。 此问题造成的错误取决于应用程序正在执行的操作,通常都涉及未正确修复的对象图和/或由于 FK 值错误而失败的更新。 值比较器可用于更正这种情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder) { var comparer = new ValueComparer<string>( (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase), v => v.ToUpper().GetHashCode(), v => v);
protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder) { var converter = new ValueConverter<string, string>( v => v, v => v.Trim()); var comparer = new ValueComparer<string>( (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase), v => v.ToUpper().GetHashCode(), v => v);