EF Core – 大杂烩
前言
记入一些零零碎碎的知识。
Shadow Properties
参考:Docs – Shadow and Indexer Properties
Shadow Property 指的是那些在数据库有 Column 但是在 Entity Class 却没有 Property 的 Property。
Foreign key example
举例
public class Product { public int Id { get; set; } public string Name { get; set; } = ""; public List<Color> Colors = []; } public class Color { public int Id { get; set; } public string Name { get; set; } = ""; public int ProductId { get; set; } public Product Product { get; set; } = null!; }
有 2 个 Entity -- Product 和 Color,它们的关系是一对多。在数据库 Color Table 里一定要记入一个 foreign key ProductId,这样它俩才能关联在一起。
在 Entity Class,关联 Product 和 Color 的是 Product.List<Color> 和 Color.Product 属性,Color.ProductId 属性是多余的。
这个多余的 ProductId 就很符合 Shadow Property 的理念 -- 数据库有 Column 但是在 Entity Class 却没有 Property。
具体做法很简单,首先把 ProductId 从 class Color 中移除
public class Color { public int Id { get; set; } public string Name { get; set; } = ""; // public int ProductId { get; set; } // 不需要了 public Product Product { get; set; } = null!; }
然后在设置 Fluent API 时 HasForeignKey 直接写 string "ProductId"。
modelBuilder.Entity<Color>() .HasOne(e => e.Product) .WithMany(e => e.Colors) // .HasForeignKey(e => e.ProductId) 把 e => e.ProductId 改成 "ProductId" .HasForeignKey("ProductId") .OnDelete(DeleteBehavior.Cascade);
这样 ProductId 就变成 Shadow Property 了。
using var db = new ApplicationDbContext(); var colorEntity = db.Model.GetEntityTypes().Single(e => e.ClrType == typeof(Color)); var productIdProperty = colorEntity.GetProperty("ProductId"); Console.WriteLine("is shadow: " + productIdProperty.IsShadowProperty()); // is shadow: true
Config and access shadow properties
config shadow property
上面例子中,我们利用 HasForeignKey 间接设置了 Shadow Property。
我们来一个直接的。
modelBuilder.Entity<Color>().Property<DateTimeOffset>("LastUpdated");
使用 Property 方法,提供一个类型和一个 name 就可以了,后续要链接上 HasMaxLength,HasPrecision,HasColumnName 都可以。
access shadow property
LastUpdated shadow property 不在 class 里,那要怎样 set value 呢?
答案是通过 entry property 直接 set CurrentValue
using var db = new ApplicationDbContext(); var color = new Color { Name = "red" }; db.Entry(color).Property("LastUpdated").CurrentValue = DateTimeOffset.Now; db.Colors.Add(color); db.SaveChanges();
通常这些逻辑会写在 Entity Interception 里。
Backing Fields
下面是一个 Order Entity
public class Order { public int Id { get; set; } private string _name = ""; public string Name { get { return _name; } set { _name = value; } } }
它的特别之处在于,它有一个 field _name,然后有 getter setter Property Name。
有时候在 Application Level 我们想使用 getter setter 做一些额外处理,但又不希望 Database Layer 受到影响。
这时就可以使用 Backing Fields,它可以让 Entity 和 数据库映射的时候不使用 Name Property,而是直接使用 _name Field。
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Order>( builder => { builder.ToTable("Order"); builder.Property(e => e.Name).HasField("_name").UsePropertyAccessMode(PropertyAccessMode.Field); }); }
告诉它,Order 有一个 Property Name,它有一个 Field _name,访问的时候使用 Field。
query 数据后会实例化 Order,这个时候它会直接写入 _name 而不是通过 Name setter。
在 translate SQL command 的时候会直接访问 _name value,而不是 Name getter。
PropertyAccessMode 是 Enum,它有好几个选项可以分开控制 query 数据时和 translate SQL command 时要用 Field 还是 Property。

AsAsyncEnumerable
在 ASP.NET Core – ADO.NET 文章中,我们有提到

SQL 返回的 data 是 stream 来的,读的时候是一条一条异步读取的。
这种读取方式的好处是可以在数据还没有加载完之前,就开始对已加载的数据做处理。
然而 EF Core ToListAsync 是把 reader 读到完放进 List 里面以后才让我们使用。
var products = await db.Products.ToListAsync();
在 C# 8.0 之前,我们无法透过 EF Core 使用到 ADO.NET async read row by row 的好处。
直到 C# 8.0 推出 Iterating with Async Enumerables 后才可以。
using var db = new ApplicationDbContext(); var products = db.Products.AsAsyncEnumerable(); await foreach (var product in products) { Console.WriteLine(product.Name); }
AsAsyncEnumerable + await foreach 就等价于 ADO.NET 的 whilte (await reader.ReadAsync)。
在 ADO.NET reader 还没有读取完毕之前,我们就可以拿到已读取的 entity 做操作了。
IAsyncEnumerable to List
有些 Library 会因为担心性能而选择 return IAsyncEnumerable,出发点是好,但是对于使用者来说,未必是件好事,因为 IAsyncEnumerable 比 List 难用多了。
于是,就有了 IAsyncEnumerable 转换成 List 的需求。
我们可以使用一个 Library 做到这个事情 -- System.Linq.Async (它是 Rx.NET 派系的一员)
dotnet add package System.Linq.Async
var products = await db.Products.AsAsyncEnumerable().ToListAsync(); Console.WriteLine(products.Count);
它扩展了 IAsyncEnumerable 接口,让它多了 ToListAsync 方法可以用。
它的具体实现很简单,就是 for loop 放进 List 里面,源码在 ToList.cs


浙公网安备 33010602011771号