Entity Framework Core(EF Core) 是一个轻量级的、可扩展的开源ORM(对象关系映射)框架,它允许开发者通过 POCO 类与数据库实体进行映射,并通过 DbContext 与数据库进行交互。本文将详细介绍如何使用 EF Core 进行数据库操作,包括创建类库、配置 DbContext、实现CRUD操作、查询、事务处理、日志记录和处理并发冲突。
首先,需要创建一个类库,并添加以下 NuGet 包:
接下来,创建一个代表数据库实体的类:
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
}
然后,创建一个继承自 DbContext 的类:
public class Database : DbContext
{
public Database(DbContextOptions options) : base(options) { }
public DbSet Actors { get; set; }
}
为了实现CRUD操作,需要创建一个ASP.NET CoreWeb 应用程序和一个 API 控制器。以下是一些基本的 CRUD 操作示例:
[HttpGet]
public async Task GetList()
{
var entities = await context.Actors.ToListAsync();
var outputModel = entities.Select(entity => new
{
entity.Id,
entity.Name,
});
return Ok(outputModel);
}
[HttpGet("{id}", Name = "GetActor")]
public IActionResult GetItem(int id)
{
var entity = context.Actors.Where(e => e.Id == id).FirstOrDefault();
if (entity == null) return NotFound();
var outputModel = new
{
entity.Id,
entity.Name,
};
return Ok(outputModel);
}
[HttpPost]
public async Task Create([FromBody]ActorCreateInputModel inputModel)
{
if (inputModel == null) return BadRequest();
var entity = new Actor
{
Name = inputModel.Name
};
this.context.Actors.Add(entity);
await this.context.SaveChangesAsync();
var outputModel = new
{
entity.Id,
entity.Name
};
return CreatedAtRoute("GetActor", new { id = outputModel.Id }, outputModel);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]ActorUpdateInputModel inputModel)
{
if (inputModel == null || id != inputModel.Id) return BadRequest();
var entity = new Actor
{
Id = inputModel.Id,
Name = inputModel.Name
};
this.context.Actors.Update(entity);
this.context.SaveChanges();
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var entity = context.Actors.Where(e => e.Id == id).FirstOrDefault();
if (entity == null) return NotFound();
this.context.Actors.Remove(entity);
this.context.SaveChanges();
return NoContent();
}
在 Startup 类中配置 DbContext:
public void ConfigureServices(IServiceCollection services)
{
var connection = "Data Source=...";
services.AddDbContext(options =>
options.UseSqlServer(connection));
services.AddMvc();
}
Entity Framework Core(EF) 是一个ORM,它通过使用 POCO 类映射数据库实体,并通过 DbContext 与它们进行交互,使得与数据库的工作变得更加简单。
如代码所示,最佳(且可测试)的配置 DbContext 子类的方式是在其构造函数中注入 DbContextOptions。NuGet 包 Microsoft.EntityFrameworkCore 提供了一个扩展方法 AddDbContext 来设置自定义 DbContext(如解决方案部分所示)。
DbContext 的子类将有一个 DbSet 实体,通过它可以添加、更新和删除数据库记录。DbContext 上的 SaveChanges() 方法触发数据库更改,这意味着也可以在一个事务中添加/更新/删除多个项目:
this.context.Actors.Add(entity1);
this.context.Actors.Add(entity2);
this.context.Actors.Add(entity3);
await this.context.SaveChangesAsync();
注意:SaveChanges() 方法有同步和异步版本。
使用 LINQ,可以查询 DbSet,这是 EF 的一个非常强大的特性。以下是一个更复杂的查询示例,用于检索电影及其导演和演员:
var entities = from movie in this.context.Movies
join director in this.context.Directors on movie.DirectorId equals director.Id
select new
{
movie.Id,
movie.Title,
movie.ReleaseYear,
movie.Summary,
Director = director.Name,
Actors = (
from actor in this.context.Actors
join movieActor in this.context.MovieActors on actor.Id equals movieActor.ActorId
where movieActor.MovieId == movie.Id
select actor.Name + " as " + movieActor.Role)
};
注意:这个查询可以用几种不同的方式编写,包括使用导航属性。然而,由于没有在这里讨论它们,使用了简单的 LINQ。
注意:ToList() 方法有同步和异步版本。
DbContext 上的 SaveChanges() 方法提供了事务性设施,但也可以通过使用 DbContext 上的 DatabaseFacade 类型显式创建事务。例如,下面首先保存电影(以获取其 Id),然后保存演员:
using (var transaction = this.context.Database.BeginTransaction())
{
try
{
// build movie entity
this.context.Movies.Add(movieEntity);
this.context.SaveChanges();
foreach (var actor in inputModel.Actors)
{
// build actor entity
this.context.MovieActors.Add(actorEntity);
}
this.context.SaveChanges();
transaction.Commit();
}
catch (System.Exception ex)
{
transaction.Rollback();
}
}
可以使用ASP.NET Core日志记录功能来查看/记录发送到 SQL Server 的 SQL。为了做到这一点,需要使用 ILoggerProvider 和 ILogger 接口:
public class EfLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
if (categoryName == typeof(IRelationalCommandBuilderFactory).FullName)
{
return new EfLogger();
}
return new NullLogger();
}
public void Dispose() { }
#region " EF Logger "
private class EfLogger : ILogger
{
public IDisposable BeginScope(TState state) => null;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
{
Console.WriteLine(formatter(state, exception));
}
}
#endregion
#region " Null Logger "
private class NullLogger : ILogger
{
public IDisposable BeginScope(TState state) => null;
public bool IsEnabled(LogLevel logLevel) => false;
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { }
}
#endregion
}
现在可以将这个日志记录器添加到工厂中:
public Startup(ILoggerFactory loggerFactory)
{
loggerFactory.AddProvider(new EfLoggerProvider());
}
通过命令行运行,将获得生成的 SQL:
注意:使用 DbContextOptionsBuilder 上的 EnableSensitiveDataLogging() 方法可以启用参数的日志记录,默认情况下,它们不会显示。
可以使用 ETag 来实现乐观并发,如这里所讨论的。然而,有时想要更多地控制过程,EF 提供了另一种选择。首先,在数据库(和实体 POCO)中创建一个字段,作为并发令牌,并用 [Timestamp] 属性注释:
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] Timestamp { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().Property(actor => actor.Timestamp).ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
}
try
{
this.context.Actors.Update(entity);
this.context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
var inEntry = ex.Entries.Single();
var dbEntry = inEntry.GetDatabaseValues();
if (dbEntry == null)
return StatusCode(StatusCodes.Status500InternalServerError, "Actor was deleted by another user");
var inModel = inEntry.Entity as Actor;
var dbModel = dbEntry.ToObject() as Actor;
var conflicts = new Dictionary();
if (inModel.Name != dbModel.Name)
conflicts.Add("Actor", $"Changed from '{inModel.Name}' to '{dbModel.Name}'");
return StatusCode(StatusCodes.Status412PreconditionFailed, conflicts);
}