我们使用EF Core操纵数据库,需要配置2个层面的东西
- 数据库层面:使用的数据库类型,连接字符串,超时时间,查询是否跟踪等。
- 数据表层面:模型类的创建以及配置,表关系等。
数据库上下文配置
为了配置数据库,EF Core提供了DbContextOptions。DbContextOptions可以通过构造函数从外部传入或者重写OnConfiguring来提供给DbContext:
- 构造函数
public class BloggingContext : DbContext { public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { } public DbSet<Blog> Blogs { get; set; } } //使用的时候从外部传入配置使用 var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer("server=.;database=SmallProgramAsset_db;user=sa;password=123456;", providerOptions => providerOptions.CommandTimeout(60));
using (var context = new BloggingContext(optionsBuilder.Options)) { // do stuff }
EF Core实体框架核心Sqlserver组件nuget: Install-Package Microsoft.EntityFrameworkCore.SqlServer
- 重写OnConfiguring
public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get; set; } //配置数据库连接串,超时时间,以及查询是否跟踪 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlServer("server=.;database=SmallProgramAsset_db;user=sa;password=123456;", providerOptions => providerOptions.CommandTimeout(60))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); } } //外部使用 using (var context = new BloggingContext(optionsBuilder.Options)) { // do stuff }
通过依赖注入使用
NetCore中的mvc和web api内置了IOC容器,EF Core支持通过使用依赖注入的方式来使用DbContext:
//StartUp文件在ConfigureServices方法中调用 AddDbContext<TContext>注入DbContext服务 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddDbContext<CoreDemoDbContext>(ServiceLifetime.Transient);//此处注册为瞬态,默认为Scoped也没问题 } //使用时通过构造函数注入 public class MyController { private readonly BloggingContext _context; public MyController(BloggingContext context) { _context = context; } }
并行问题避免
EF Core同样的一个DbContext实例不支持同时运行在多个线程,否则将导致程序崩溃和不可预料的数据破坏。常用的采坑点以及防止办法:
- EF Core支持异步方法,如果调用方不等待异步方法然后继续继续利用该DbContext执行其他方法,那么该实例可能就被破坏掉了。
- 依赖注入注入瞬态或者将作用域创建为每个线程。创建为Scored没问题,因为AspNet应用程序中在给定时间执行每个客户端请求创建了一个独立线程并且每个请求获取单独的依赖关系注入作用域 (单独
DbContext
实例)
模型配置
- 哪些模型会被当做实例映射写入到数据库?
- DbContext类中DbSet类型的属性公开的,如Blogs
- (1)中属性的导航属性发现的,如Blog.Posts
- OnModelCreating发放中提及的,如AuditEntry
class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<AuditEntry>(); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; } } public class AuditEntry { public int AuditEntryId { get; set; } public string Username { get; set; } public string Action { get; set; } }
属性的约定配置以及自定义配置
实体排除
- 数据注解:类上标注[NotMapped]
- 排除模型映射,不作为实例写入数据库 modelBuilder.Ignore<TModel>();
属性排除
- 数据注解:属性标注[NotMapped]
- 属性排除modelBuilder.Entity<Blog>().Ignore(b => b.LoadedFromDatabase);
主键:默认约定名为Id或者<type name>Id(后缀)的属性会配置为实体的主键;
//数据注解 class Car { [Key] public string LicensePlate { get; set; }//主键 public string Make { get; set; } public string Model { get; set; } } //api配置 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => c.LicensePlate); } //复合主键只能用api配置 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => new { c.State, c.LicensePlate }); }
生成值
- 无值生成:需始终提供要保存到数据库的有效值。 必须先将有效的值赋予新的实体,再将这些新的实体添加到上下文中。
- 添加时生成值
- 根据所用数据库提供程序的不同,值通过 EF 在客户端生成或者由数据库生成。数据库生成在实体添加到上下文是EF给定一个默认,然后SaveChanges的时候替换这个临时值;生成方式取决于数据库提供程序,数据库提供程序可能会为某些属性类型自动设置值的生成,但其他的属性类型可能要求你手动设置值的生成方式,如sqlserver的datetime类型。
public class Employee { public int EmployeeId { get; set; } public string Name { get; set; } public DateTime EmploymentStarted { get; set; } public int Salary { get; set; } public DateTime? LastPayRaise { get; set; } } //配置为由数据库为新实体生成值(使用默认值) //插入如果指定了显式值则自定义,否则则用CLR的默认值 //如果插入新行,但未指定值的列将插入值的列的默认值。 modelBuilder.Entity<Employee>() .Property(b => b.EmploymentStarted) .HasDefaultValueSql("CONVERT(date, GETDATE())"); modelBuilder.Entity<Blog>() .Property(b => b.Rating) .HasDefaultValue(3);
- 实体添加到已经为属性赋值的上下文,EF会尝试插入该值而非生成新值;默认被赋值的前提是属性被赋予CLR默认(string:null,int:0,Guid.Empty:Guid等)
//没有值生成 (数据批注) public class Blog { [DatabaseGenerated(DatabaseGeneratedOption.None)] public int BlogId { get; set; } public string Url { get; set; } } modelBuilder.Entity<Blog>() .Property(b => b.BlogId) .ValueGeneratedNever(); //在添加时生成值(数据注释) public class Blog { public int BlogId { get; set; } public string Url { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public DateTime Inserted { get; set; } } modelBuilder.Entity<Blog>() .Property(b => b.Inserted) .ValueGeneratedOnAdd(); //在添加或更新时生成值(数据注释 public class Blog { public int BlogId { get; set; } public string Url { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime LastUpdated { get; set; } } modelBuilder.Entity<Blog>() .Property(b => b.LastUpdated) .ValueGeneratedOnAddOrUpdate();
- 对于主键存储生成的IDEDTITY列,若要显式插入值,则必须在调用
SaveChanges()
之前手动启用IDENTITY_INSERT
using (var context = new EmployeeContext()) { context.Employees.Add(new Employee { EmployeeId = 100, Name = "John Doe" }); context.Employees.Add(new Employee { EmployeeId = 101, Name = "Jane Doe" }); context.Database.OpenConnection(); try { context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.Employees ON"); context.SaveChanges(); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.Employees OFF"); } finally { context.Database.CloseConnection(); } foreach (var employee in context.Employees) { Console.WriteLine(employee.EmployeeId + ": " + employee.Name); } }
-
在添加或更新时生成值:意味着在每次保存该记录(插入或更新)时生成新值。
最大长度
public class Blog { public int BlogId { get; set; } [MaxLength(500)] public string Url { get; set; } } modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasMaxLength(500);
并发
EF Core 实现_了乐观并发控制_,这意味着它将允许多个进程或用户独立进行更改,而不会产生同步或锁定的开销
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [Timestamp] public byte[] Timestamp { get; set; } } modelBuilder.Entity<Blog>() .Property(p => p.Timestamp) .IsRowVersion();
索引
//配置单一索引 modelBuilder.Entity<Blog>() .HasIndex(b => b.UserID); //通过多个列指定索引 modelBuilder.Entity<Person>() .HasIndex(p => new { p.FirstName, p.LastName });
- 实体继承结构确定基类不包含在模型中
modelBuilder.Entity<RssBlog>().HasBaseType<BaseBlog>();