微软的最新版本的实体框架。它是轻量级的、可扩展的,支持跨平台开发。
而且使用简单,性能超过先前版本的实体框架。
它是一个对象-关系映射框架(ORM)。
ORM是一种技术使开发者能够以面向对象语言的方式来工作,并以关系数据的方式来存储。
大多数的开发框架包含一些库,这些库通过数据结构这样的结果集来访问关系数据库。如以下:
using(var conn = new SqlConnection(connectionString))
using(var cmd = new SqlCommand("select * from Products", conn))
{
var dt = new DataTable();
using(var da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
DataTable里面的数据可以通过数字或字符串下标来访问,但必须从object类型转化为正确的类型。
foreach(DataRow row in dt.Rows)
{
int productId = Convert.ToInt32(row[0]);
string productName = row["ProductName"].ToString();
}
这种后期绑定获取“弱类型”的数据访问方式易于错误。问题通常发生于拼错列的名字,或者发现列的名字已经被数据端改变,或者sql语句的字段顺序的变化在代码端没有相应的变化。同样的,数据类型也可能错误。代码依然能编译通过,但运行时就会错误。因此,专业开发者更倾向于使用强数据类型来工作。
当使用强数据类型操作数据,是以这些面向对象的域模型的预定义类的属性的方式:
public class Product
{
int ProductId { get; set; }
string ProductName { get; set; }
}
int productId = myProduct.ProductId;
string productName = myProduct.ProductName;
一个域对象从数据库的获取和映射依然需要来做。一个可选的方式是自己写代码来管理。
但是,当域模型增长,代码量的需求也增长,接着会需要越来越多的开发时间来来维护。这个会增加完成整个应用程序完工的时间。
ORMs是预先写好的代码库来为你工作。完整特性的ORMs也做了很多事情。他们能:
*.映射域模型和数据库对象
*.创建数据库和维护数据库和模型的一致性
*.生成SQL语句并对数据库加以执行
*.管理事务
*.保持跟踪你以获取的对象
二.DbContext
1.ef.core DbContext表示和数据库的一次会话,并提供了一个API和数据库通讯,它拥有以下的能力:
*.数据库链接
*.数据库操作诸如查询和持久化
*.变化跟踪
*.模型建立
*.数据映射
*.对象缓存
*.事务管理
2.数据库链接
DbContext负责操作和管理连接数据库
3.数据操作
DbContext提供以下直接的数据操作方法:
1)增加数据
通过DbContext添加实体的关键方法:
Add(TEntity entity) Add(object entity) AddRange(IEnumerable
增加多个记录:
var context = new SampleContext();
var author = new Author { FirstName = "Stephen", LastName = "King" };
var books = new List {
new Book { Title = "It", Author = author },
new Book { Title = "Carrie", Author = author },
new Book { Title = "Misery", Author = author }
};
context.AddRange(books);
context.SaveChanges();
var context = new SampleContext();
var author = new Author { FirstName = "William", LastName = "Shakespeare" };
var book = new Book { Title = "Adventures of Huckleberry Finn" };
context.AddRange(author, book);
context.SaveChanges();
2)修改数据
var author = context.Authors.First(a => a.AuthorId == 1); author.FirstName = "Bill"; context.SaveChanges();
当实体从dbContext查询出来,便由变化跟踪者记录着(这个实体的字段的值的变化),当调用SaveChange时,并触发语句(只修改变化的字段)。
exec sp_executesql N'SET NOCOUNT ON; UPDATe [Authors] SET [FirstName] = @p0 WHERe [AuthorId] = @p1; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Bill'
实体的改变可能发生在Controller或service里面,dbContext要聪明的感知变化,并更新。
public void Save(Author author)
{
context.Entry(author).State = EntityState.Modified;
context.SaveChanges();
}
以上会更新所有的字段。
Update方法和UpdateRange方法:
public void Save(Author author)
{
context.Update(author);
context.SaveChanges();
}
会更新所有各个字段。
Attach:
var context = new TestContext();
var author = new Author {
AuthorId = 1,
FirstName = "William",
LastName = "Shakespeare"
};
author.Books.Add(new Book {BookId = 1, Title = "Othello" });
context.Attach(author);
context.Entry(author).Property("FirstName").IsModified = true;
context.SaveChanges();
只有FirstName字段被标识为修改,所以更新语句只更新FirstName这个字段。
TrackGraph :
var author = new Author {
AuthorId = 1,
FirstName = "William",
LastName = "Shakespeare"
};
author.Books.Add(new Book { AuthorId = 1, BookId = 1, Title = "Hamlet", Isbn = "1234" });
author.Books.Add(new Book { AuthorId = 1, BookId = 2, Title = "Othello", Isbn = "4321" });
author.Books.Add(new Book { AuthorId = 1, BookId = 3, Title = "MacBeth", Isbn = "5678" });
var context = new TestContext();
context.ChangeTracker.TrackGraph(author, e => {
if((e.Entry.Entity as Author) != null)
{
e.Entry.State = EntityState.Unchanged;
}
else
{
e.Entry.State = EntityState.Modified;
}
});
context.SaveChanges();
这个例子对应的SQL语句如下:
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Books] SET [AuthorId] = @p0, [Isbn] = @p1, [Title] = @p2 WHERe [BookId] = @p3; SELECT @@ROWCOUNT; UPDATE [Books] SET [AuthorId] = @p4, [Isbn] = @p5, [Title] = @p6 WHERe [BookId] = @p7; SELECT @@ROWCOUNT; UPDATE [Books] SET [AuthorId] = @p8, [Isbn] = @p9, [Title] = @p10 WHERe [BookId] = @p11; SELECT @@ROWCOUNT; ',N'@p3 int,@p0 int,@p1 nvarchar(4000),@p2 nvarchar(150),@p7 int,@p4 int,@p5 nvarchar(4000), @p6 nvarchar(150),@p11 int,@p8 int,@p9 nvarchar(4000),@p10 nvarchar(150)', @p3=1,@p0=1,@p1=N'1234',@p2=N'Hamlet', @p7=2,@p4=1,@p5=N'4321',@p6=N'Othello', @p113,@p8=1,@p9=N'5678',@p10=N'MacBeth'
var author = new Author {
AuthorId = 1,
FirstName = "William",
LastName = "Shakespeare"
};
author.Books.Add(new Book { BookId = 1, Isbn = "1234" });
author.Books.Add(new Book { BookId = 2, Isbn = "4321" });
author.Books.Add(new Book { BookId = 3, Isbn = "5678" });
var context = new TestContext();
context.ChangeTracker.TrackGraph(author, e => {
e.Entry.State = EntityState.Unchanged; //starts tracking
if((e.Entry.Entity as Book) != null)
{
context.Entry(e.Entry.Entity as Book).Property("Isbn").IsModified = true;
}
});
这个例子对应的SQL语句如下:
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Books] SET [Isbn] = @p0 WHERe [BookId] = @p1; SELECT @@ROWCOUNT; UPDATE [Books] SET [Isbn] = @p2 WHERe [BookId] = @p3; SELECT @@ROWCOUNT; UPDATE [Books] SET [Isbn] = @p4 WHERe [BookId] = @p5; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000),@p3 int,@p2 nvarchar(4000),@p5 int,@p4 nvarchar(4000)', @p1=1,@p0=N'1234', @p3=2,@p2=N'4321', @p5=3,@p4=N'5678'
3)删除数据
context.Remove(context.Authors.Single(a => a.AuthorId == 1)); context.SaveChanges();
exec sp_executesql N'SET NOCOUNT ON; DELETE FROM [Authors] WHERe [AuthorId] = @p0; SELECt @@ROWCOUNT; ',N'@p0 int',@p0=1
以上的结果是执行两条语句:一条先获取记录,一条删除
var context = new SampleContext();
var author = new Author { AuthorId = 1 };
context.Remove(author);
context.SaveChanges();
而这个的结果是一条语句。
var context = new SampleContext();
var author = new Author { AuthorId = 1 };
context.Entry(author).State = EntityState.Deleted;
context.SaveChanges();
关联数据的删除:
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
}
var context = new SampleContext();
var author = context.Authors.Single(a => a.AuthorId == 1);
var books = context.Books.Where(b => EF.Property(b, "AuthorId") == 1);//EF.core提供的一种叫做影子属性的东西来删除数据
foreach (var book in books)
{
author.Books.Remove(book);
}
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Books] SET [AuthorId] = @p0 WHERe [BookId] = @p1; SELECT @@ROWCOUNT; UPDATE [Books] SET [AuthorId] = @p2 WHERe [BookId] = @p3; SELECT @@ROWCOUNT; UPDATE [Books] SET [AuthorId] = @p4 WHERe [BookId] = @p5; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 int,@p3 int,@p2 int,@p5 int,@p4 int',@p1=1,@p0=NULL,@p3=2,@p2=NULL,@p5=3,@p4=NULL exec sp_executesql N'SET NOCOUNT ON; DELETE FROM [Authors] WHERe [AuthorId] = @p6; SELECt @@ROWCOUNT; ',N'@p6 int',@p6=1
但是这种删除仅仅是把AuthId设置为null
三.Fluent API 配置
1.写在一起的的配置:
public class SampleContext : DbContext
{
// Specify DbSet properties etc
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// add your own configuration here
// series of statements
modelBuilder.Entity().Property(t => t.OrderDate).IsRequired();
modelBuilder.Entity().Property(t => t.OrderDate).HasColumnType("Date");
modelBuilder.Entity().Property(t => t.OrderDate).HasDefaultValueSql("GetDate()");
// fluent api chained calls
modelBuilder.Entity()
.Property(t => t.OrderDate)
.IsRequired()
.HasColumnType("Date")
.HasDefaultValueSql("GetDate()");
}
}
2:独立类的配置
public class OrderConfiguration : IEntityTypeConfiguration{ public void Configure(EntityTypeBuilder builder) { builder.HasKey(o => o.OrderNumber); builder.Property(t => t.OrderDate) .IsRequired() .HasColumnType("Date") .HasDefaultValueSql("GetDate())" } }
应用只需要如下写:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new OrderConfiguration());
}
四、数据注释属性Data Annotations Attributes
Table
Column
ForeignKey
DatabaseGenerated
NotMapped
InverseProperty
ComplexType
Key
Timestamp
ConcurrencyCheck
Required
MaxLength
StringLength
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("server=.;database=myDb;trusted_connection=true;");
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options => {
options.UseSqlServer("server=.;database=myDb;trusted_connection=true;"));
});
}
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MyConnection": "server=.;database=myDb;trusted_connection=true;"
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options => {
options.UseSqlServer(Configuration.GetConnectionString("MyConnection"));
});
}
六、继承
public class Contract
{
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public int Months { get; set;}
public decimal Charge { get; set; }
}
public class MobileContract : Contract
{
public string MobileNumber { get; set; }
}
public class TvContract : Contract
{
public PackageType PackageType { get; set; }
}
public class BroadBandContract : Contract
{
public int DownloadSpeed { get; set; }
}
public enum PackageType
{
S, M, L, XL
}
1)Table Per Hierarchy (TPH)
这种方式是当前EFcore的仅有的实现方式
public class SampleContext : DbContext
{
public DbSet Contracts { get; set; }
public DbSet MobileContracts { get; set; }
public DbSet TvContracts { get; set; }
public DbSet BroadBandContracts { get; set; }
}
可以直接访问:
var mobileContracts = context.MobileContracts.ToList();
public class SampleContext : DbContext
{
public DbSet Contracts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity();
modelBuilder.Entity();
modelBuilder.Entity();
}
}
转换访问:
var tvContracts = context.Contracts.OfType().ToList(); public class SampleContext : DbContext { public DbSet MobileContracts { get; set; } public DbSet TvContracts { get; set; } public DbSet BroadbandContracts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity ().ToTable("Contracts"); } } public abstract class Contract { public int ContractId { get; set; } public DateTime StartDate { get; set; } public int Months { get; set;} public decimal Charge { get; set; } } public class MobileContract : Contract { public string MobileNumber { get; set; } } public class TvContract : Contract { public PackageType PackageType { get; set; } } public class BroadbandContract : Contract { public int DownloadSpeed { get; set; } } public enum PackageType { S, M, L, XL }
没有DbSet的写法:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.ToTable("Contracts")
.HasDiscriminator("ContractType")
.HasValue(1)
.HasValue(2)
.HasValue(3);
}
2)Table Per Type (TPT)
3)Table Per Concrete Type (TPC)
1.后进有效
以最后更新的为准
2.悲观者
对访问的对象先加锁
3.乐观者
可能会异常产生。EFCore采取的是这种方式。
(比如:有两个用户修改同一个记录,第一个用户添加一个未设置的电话号码,第二个用户修改这条记录的地址,两者同时获取了这条记录,是一样的。假设第一个用户先提交了,电话被设置。但是第二个用户获取的记录的电话是空字段,后者提交后,电话被置为空了。这样就不符合预期了。)
为了使这种不合预期长生异常可以如下操作:
1)可以通过特性设置并发属性:
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
[ConcurrencyCheck]
public string LastName { get; set; }
public ICollection Books { get; set; }
}
public class SampleContext : DbContext
{
public DbSet Authors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.Property(a => a.LastName).IsConcurrencyToken();
}
}
2)增加一个RowVersion属性
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection Books { get; set; }
[TimeStamp]
public byte[] RowVersion { get; set; }
}
public class SampleContext : DbContext
{
public DbSet Authors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.Property(a => a.RowVersion).IsRowVersion();
}
}
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection Books { get; set; }
public byte[] RowVersion { get; set; }
}
八、数据迁移
1.创建
[Command Line]
dotnet ef migrations add
[Package Manager console]
add-migration
2.删除
[Command Line]
dotnet ef migrations remove
[Package Manager Console]
remove-migration
3.应用
Applying A Migration
[Command line]
dotnet ef database update
[Package Manager Console]
update-database
4、反转
[Command line]
dotnet ef database update Create
[Package Manager Console]
update-database Create
5、应用一次迁移到远程数据库
[Command Line]
dotnet ef migrations script
[Package Manager Console]
script-migration using the -to and -from options.
6.执行自定义SQL
public partial class CreateDatabase : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("Some custom SQL statement");
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
AuthorId = table.Column(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
FirstName = table.Column(nullable: true),
LastName = table.Column(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.AuthorId);
});
7.对应多个提供给者
public partial class CreateDatabase : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if(ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer")
{
// do something SQL Server - specific
}
if(ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite")
{
// do something SqLite - specific
}
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
AuthorId = table.Column(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
FirstName = table.Column(nullable: true),
LastName = table.Column(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.AuthorId);
});
8.设置迁移的超时时间
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace EFCoreSample.Model
{
public class SampleContextFactory : IDesignTimeDbContextFactory
{
public SampleContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(@"Server=.;Database=db;Trusted_Connection=True;", opts => opts.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds));
return new SampleContext(optionsBuilder.Options);
}
}
}
Make sure that your existing DbContext has a constructor that takes a DbContextOptions object as a parameter:(确保你已有的DbContext构造函数有一个DbContextOptions参数)
public AdventureContext(DbContextOptions options) : base(options){}
九、执行原始SQL语句public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
public int AuthorId{ get; set; }
public string Isbn { get; set; }
}
...
public class SampleContext : DbContext
{
public DbSet Books { get; set; }
}
...
using (var context = new SampleContext())
{
var books = context.Books.FromSqlRaw("SELECt BookId, Title, AuthorId, Isbn FROM Books").ToList();
}
带参数的查询:
// Format string
var author = db.Authors.FromSqlRaw("SELECt * From Authors Where AuthorId = {0}", id).FirstOrDefault();
// String interpolation
var author = db.Authors.FromSqlInterpolated($"SELECt * From Authors Where AuthorId = {id}").FirstOrDefault();
var p1 = new SqliteParameter("@Id", id);
var author = db.Authors.FromSqlRaw($"SELECt * From Authors Where AuthorId = @Id", p1).FirstOrDefault();
var p1 = new SqlParameter("@Id", id);
var author = db.Authors.FromSqlRaw($"SELECt * From Authors Where AuthorId = @Id", p1).FirstOrDefault();
存储过程:
using (var context = new SampleContext())
{
var books = context.Books
.FromSqlRaw("EXEC GetAllBooks")
.ToList();
}
using (var context = new SampleContext())
{
var authorId = new SqlParameter("@AuthorId", 1);
var books = context.Books
.FromSqlRaw("EXEC GetBooksByAuthor @AuthorId" , authorId)
.ToList();
}
非实体类型的投影:
using(var context = new SampleContext())
{
var books = context.Books.FromSqlRaw("SELECt BookId, Title FROM Books").ToList();
}
这个在ef.core2.1之前是不允许的,但是可以通过投影:
using(var context = new SampleContext())
{
var books = context.Books
.FromSql("SELECt * FROM Books")
.Select(b => new {
BookId = b.BookId,
Title = b.Title
}).ToList();
}
ExecuteSqlCommand:
using(var context = new SampleContext())
{
var commandText = "INSERT Categories (CategoryName) VALUES (@CategoryName)";
var name = new SqlParameter("@CategoryName", "Test");
context.Database.ExecuteSqlCommand(commandText, name);
}
Note: You will need to add using Microsoft.Data.SqlClient; to make the SqlParameter type available to your code.
存储规程:
using(var context = new SampleContext())
{
var name = new SqlParameter("@CategoryName", "Test");
context.Database.ExecuteSqlCommand("EXEC AddCategory @CategoryName", name);
}
通过返回connection给ADO.net使用:
using (var context = new SampleContext())
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "SELECT * From Table1";
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
// do something with result
}
}
十、Query Types查询类型
2.1以后:Mapping to DbQuery
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public ICollection Orders { get; set; } = new HashSet();
}
public class Order
{
public int OrderId { get; set; }
public DateTime DateCreated { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public ICollection OrderItems { get; set; } = new HashSet();
}
public class OrderItem
{
public int OrderItemId { get; set; }
public string Item { get; set; }
public decimal Price { get; set; }
}
create view OrderHeaders as
select c.Name as CustomerName,
o.DateCreated,
sum(oi.Price) as TotalPrice,
count(oi.Price) as TotalItems
from OrderItems oi
inner join Orders o on oi.OrderId = o.OrderId
inner join Customers c on o.CustomerId = c.CustomerId
group by oi.OrderId, c.Name, o.DateCreated
public class OrderHeader
{
public string CustomerName { get; set; }
public DateTime DateCreated { get; set; }
public int TotalItems { get; set; }
public decimal TotalPrice { get; set; }
}
public class SampleContext : DbContext
{
public DbSet Orders { get; set; }
public DbSet OrderItems { get; set; }
public DbSet Customers { get; set; }
public DbQuery OrderHeaders { get; set; }
...
}
var orderHeaders = db.OrderHeaders.ToList();
var orderHeaders = db.OrderHeaders.Where(x => x.TotalItems > 15).ToList();
[SampleContext.cs]
public partial class SampleContext : DbContext
{
public DbSet Orders { get; set; }
public DbSet OrderItems { get; set; }
public DbSet Customers { get; set; }
...
}
[SampleContextDbQuery.cs]
public partial class SampleContext : DbContext
{
public DbQuery OrderHeaders { get; set; }
public DbQuery OrderTotals { get; set; }
...
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query().ToView("OrderHeaders");
}
select c.Name as CustomerName,
c.CustomerId,
o.DateCreated,
sum(oi.Price) as TotalPrice,
count(oi.Price) as TotalItems
from OrderItems oi
inner join Orders o on oi.OrderId = o.OrderId
inner join Customers c on o.CustomerId = c.CustomerId
group by oi.OrderId, c.Name, o.DateCreated
public class OrderHeader
{
public string CustomerName { get; set; }
public DateTime DateCreated { get; set; }
public int TotalItems { get; set; }
public decimal TotalPrice { get; set; }
public Customer Customer { get; set; }
}
var orderHeaders = db.Query().Include(o => o.Customer).ToList();
var orderHeaders = db.OrderHeaders.FromSql(
@"select c.Name as CustomerName, o.DateCreated, sum(oi.Price) as TotalPrice,
count(oi.Price) as TotalItems
from OrderItems oi
inner join Orders o on oi.OrderId = o.OrderId
inner join Customers c on o.CustomerId = c.CustomerId
group by oi.OrderId, c.Name, o.DateCreated");
十一、延迟加载
1.Proxies
1)
[Package Manager Console]
install-package Microsoft.EntityFrameworkCore.Proxies
[Dotnet CLI]
add package Microsoft.EntityFrameworkCore.Proxies
2).
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
3)
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List Books { get; set; } = new List();
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; }
public virtual Author Author { get; set; }
}
2.ILazyLoader
using Microsoft.EntityFrameworkCore.Infrastructure;
public class Author
{
private readonly ILazyLoader _lazyLoader;
public Author()
{
}
public Author(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}
private List _books;
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List Books
{
get => _lazyLoader.Load(this, ref _books);
set => _books = value;
}
}
using(var db = new BookContext())
{
var authors = db.Authors;
foreach(var author in authors)
{
Console.WriteLine($"Name: {author.FirstName} {author.LastName}");
foreach(var book in author.Books) // lazy loading initiated
{
Console.WriteLine($"t{book.Title}");
}
}
}
var authors = db.Authors.Include(a => a.Books); //一次加载,解决n+1次查询问题
foreach(var author in authors)
{
Console.WriteLine($"Name: {author.FirstName} {author.LastName}");
foreach(var book in author.Books)
{
Console.WriteLine($"t{book.Title}");
}
}



