最新版本号[免费下载]

EntityFramework Core 学习扫盲 (上)

作者:本站编辑 发布时间:2018-09-30 来源:佚名 点击数:

0. 写在前面

本篇文章虽说是入门学习,但是也不会循规蹈矩地把EF1.0版本一直到现在即将到来的EF Core 2.0版本相关的所有历史和细节完完整整还原出来。在后文中,笔者会直接进入正题,所以这篇文章仍然还是需要一定的EF ORM基础。

对于纯新手用户,不妨先去看看文末链接中一些优秀博客,笔者当初也是从这些博客起家,也从中得到了巨大的帮助。当然了,官方教程同样至关重要,笔者之前也贡献过部分EF CORE 官方文档资料(基本都是勘误,逃…),本篇文章中很多内容都是撷取自官方的英文文档和示例。

下文示例中将使用Visual Studio自带的Local Sql Server作为演示数据库进行演示,不过可以放心的是,大部分示例都能流畅地在各种关系型数据库中实现运行,前提是更换不同的DATABASE PROVIDERS,如NPGSQLMYSQL等。

对于未涉及到的知识点(CLI工具,Shadow Property,Logging,从Exsiting Database反向工程生成Context等),只能说笔者最近一直在忙着毕业收尾的事情,有空的时候会把草稿整理下在博文中贴出的,一晃四年,终于也要毕业了。

1. 建立运行环境

  • 新建一个APS.NET CORE WEB模板项目

  • 安装相关Nuget包

//Sql Server Database ProviderInstall-Package Microsoft.EntityFrameworkCore.SqlServer
    
//提供熟悉的Add-Migration,Update-Database等Powershell命令,不区分关系型数据库类型Install-Package Microsoft.EntityFrameworkCore.Tools
  • 自定义Context

public class MyContext : DbContext{    public MyContext(DbContextOptions<MyContext> options):base(options)    {
    }    protected override void OnModelCreating(ModelBuilder modelBuilder)    {
    }
}
  • 在Startup的ConfigurationServices方法中添加EF CORE服务

public void ConfigureServices(IServiceCollection services)
{    // Add framework services.
    services.AddMvc();

    services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}        
// 需要在appsettings.json中新增一个ConnectionStrings节点,用于存放连接字符串。"ConnectionStrings": {    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication4;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  • 在powershell中运行相关迁移命令

Add-Migration InitializeUpdate-Database

2. 添加实体和映射数据库

使用EF CORE中添加实体,约束属性和关系,最后将其映射到数据库中的方式有两种,一种是Data Annotations,另一种是Fluent Api,这两种方式并没有优劣之分,全凭开发者喜好和需求,不过相对而言,Fluent Api提供的功能更多。

1. 准备工作

  • 新增三个实体

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; }
}

2. Data Annotations

  • 在自定义的MyContext中添加以下属性信息,并在每个自定义的实体名称上部增加[Table("XXX")],其中XXX为开发者指定的表名称。

//在自定义的MyContext中添加以下三行代码public DbSet<Blog> Blogs { get; set; }public DbSet<Post> Posts { get; set; }public DbSet<AuditEntry> AuditEntries { get; set; }//添加Table特性,第一个属性代表数据库表名称[Table("Blogs")]public class Blog{
    [Key]    public int BlogId { get; set; }    public string Url { get; set; }    public List<Post> Posts { get; set; }
}
  • 运行Add-Migration         InitializeUpdate-Database即可成功运行。虽然我们目前还没有添加任何约束,但是EF Core会自动地根据Id/XXId的命名方式生成自增主键,而且如果没有在实体上增加[Table]Attribute的话,表的命名也是根据属性命名而定。查询相关的Create Table语句,清晰易见,Identity(1,1)代表Id从1开始,每次插入递增1。

//BLOG TableCREATE TABLE [dbo].[Blogs] (
    [BlogId] INT            IDENTITY (1, 1) NOT NULL,
    [Url]    NVARCHAR (MAX) NULL,    CONSTRAINT [PK_Blogs] PRIMARY KEY CLUSTERED ([BlogId] ASC)
);CREATE TABLE [dbo].[Posts] (
    [PostId]  INT            IDENTITY (1, 1) NOT NULL,
    [BlogId]  INT            NULL,
    [Content] NVARCHAR (MAX) NULL,
    [Title]   NVARCHAR (MAX) NULL,    CONSTRAINT [PK_Posts] PRIMARY KEY CLUSTERED ([PostId] ASC),    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [dbo].[Blogs] ([BlogId])
);

//POST Table
GOCREATE NONCLUSTERED INDEX [IX_Posts_BlogId]    ON [dbo].[Posts]([BlogId] ASC);

3. Fluent Api

Fluent Api俗名流式接口,其实就是C#中的扩展接口形式而已,大家日常应该接触过很多了。还记得我们第一步中MyContext定义的OnModelCreating方法吗,Fluent Api就是在那里面使用的

  • 增加以下代码至OnModelCreating方法。

modelBuilder.Entity<Blog>().ToTable("Blogs").HasKey(c=>c.BlogId);
modelBuilder.Entity<Post>().ToTable("Posts").HasKey(c=>c.PostId);
modelBuilder.Entity<AuditEntry>().ToTable("AuditEntries").HasKey(c=>c.AuditEntryId);
  • 运行Add-Migration InitializeUpdate-Database即可成功运行。

无论是使用DbSet< TEntity >的形式抑或是使用modelBuilder.Entity< TEntity >的形式都能将定义的实体映射到数据库中,下文也会继续做出说明。

3. 包含和排除实体类型

将实体在Context中映射到数据库有多种方式:

  • 使用DbSet< TEntity >定义属性。

  • 在OnModelCreating方法中使用Fluent Api配置。

  • 假如导航属性中存在对其他实体的引用,那么即便不把被引用实体配置为显式引用,被引用实体也可以隐式地映射到数据库中。

如以下代码所示。Blog实体包含对Post实体的引用,而独立的AuditEntry则可以在OnModelCreating方法中进行配置。

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; }
}

以上内容指的是“包含实体”的操作,“排除实体”操作也十分地简便。通过以下两种配置方式,在运行了迁移命令后,BlogMetadata实体是不会映射到数据库中的。

1. Data Annotations [NotMapped] 排除实体和属性

public class Blog{    public int BlogId { get; set; }    public string Url { get; set; }    public BlogMetadata Metadata { get; set; }
}

[NotMapped]public class BlogMetadata{    public DateTime LoadedFromDatabase { get; set; }
}

2. Fluent API [Ignore] 排除实体和属性

class MyContext : DbContext{    public DbSet<Blog> Blogs { get; set; }    protected override void OnModelCreating(ModelBuilder modelBuilder)    {
        modelBuilder.Ignore<BlogMetadata>();
    }
}public class Blog{    public int BlogId { get; set; }    public string Url { get; set; }    public BlogMetadata Metadata { get; set; }
}public class BlogMetadata{    public DateTime LoadedFromDatabase { get; set; }
}

NotMapped特性不仅可以用在实体类上,也可以用在指定的属性上。而在Fluent Api中,对应的操作则是
modelBuilder.Entity<Blog>().Ignore(b => b.LoadedFromDatabase);

4. 列名称和类型映射

Property方法对应数据库中的Column。

默认情况下,我们不需要更改任何实体中包含的属性名,EF CORE会自动地根据属性名称映射到数据库中的列名。当开发者需要进行自定义修改名称时( 比如每种关系型数据库的命名规则不一样,虽然笔者一直喜欢使用帕斯卡命名以保持和项目代码结构中的统一),可以使用以下的方式。

少数的几个CLR类型在不做处理的情况下,映射到数据库中时将存在可空选项,如string,int?,这种情况也在下列方式中做了说明。其中Data Annotations对应[Required]特性,Fluent API对应IsRequired方法。

1. Data Annotations

Column特性可用于属性上,它接收多个参数,其中比较重要的是Name和TypeName,前者表示数据库表映射的列名,后者表示数据类型和格式。假如不指定Url属性上的[Column(TypeName="varchar(200)")],数据库Blog表的Url列默认数据格式将为varchar(max)

public class Blog{
    [Column("blog_id")]    public int BlogId { get; set; }
    
    [Required]
    [Column(TypeName = "varchar(200)")]    public string Url { get; set; }
}

2. Fluent API

class MyContext : DbContext{    public DbSet<Blog> Blogs { get; set; }    protected override void OnModelCreating(ModelBuilder modelBuilder)    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.BlogId)
            .HasColumnName("blog_id");
            
            modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .HasColumnType("varchar(200)")
            .IsRequired();
    }
}public class Blog{    public int BlogId { get; set; }    public string Url { get; set; }
}

由于各种关系型数据库对于数据类型的名称有所区别,所以自定义数据类型时,一定要参阅目标数据库的数据类型定义。比如PostgreSql支持Json格式,那么就需要添加以下代码——builder.Entity().Property(b => b.SomeStringProperty).HasColumnType("jsonb");

5. 主键

默认情况下,EF CORE会将实体中命名为Id或者[TypeName]Id的属性映射为数据库表中的主键。当然有些开发者不喜欢将主键命名为Id,EF CORE也提供了两种方式进行主键的相关设置。

1. Data Annotations [Key]

Data Annotations方式不支持定义外键名称,主键配置如下代码所示。

class Car{
    [Key]    public string LicensePlate { get; set; }    public string Make { get; set; }    public string Model { get; set; }
}

2. Fluent API [HasKey]

Fluent Api方式中的HasKey方法可以将属性映射为主键,对于复合主键(多个属性组合而成的主键标识)也可以很容易地进行表示。

class MyContext : DbContext{    public DbSet<Car> Cars { get; set; }    protected override void OnModelCreating(ModelBuilder modelBuilder)    {
        modelBuilder.Entity<Car>()
            .HasKey(c => c.LicensePlate);        
        //复合主键
        //modelBuilder.Entity<Car>()
            //.HasKey(c => new { c.State, c.LicensePlate });
            
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogId)
            .HasConstraintName("ForeignKey_Post_Blog");
    }
}class Car{    public string State { get; set; }    public string LicensePlate { get; set; }    public string Make { get; set; }    public string Model { get; set; }
}


本文责任编辑: 加入会员收藏夹 点此参与评论>>
复制本网址-发给QQ/微信上的朋友
AI智能听书
选取音色