microsoft entity framework 包含哪些功能
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了microsoft entity framework 包含哪些功能相关的知识,希望对你有一定的参考价值。
参考技术A Entity Framework 以 Entity Data Model (EDM) 为主,将数据逻辑层切分为三块,分别为 Conceptual Schema, Mapping Schema 与 Storage Schema 三层,其上还有 Entity Client,Object Context 以及 LINQ 可以使用。目前 ADO.NET Entity Framework 的开发,在 Visual Studio 2008 中有充份的支持,在安装 Visual Studio 2008 Service Pack 1 后,文件范本中即会出现 ADO.NET 实体数据模型 (ADO.NET Entity Data Model) 可让开发人员利用 Entity Model Designer 来设计 EDM,EDM 亦可由记事本或文本编辑器所编辑。
主条目:ADO.NET Data Services
ADO.NET Entity Model Designer
ADO.NET Entity Model Designer
微软特别针对了网络上各种不同的应用程序 (例如 AJAX, Silverlight, Mashup 应用程序) 开发了一个基于 ADO.NET Entity Framework 之上的服务,称为 ADO.NET Data Services (项目代号为 Astoria),并与 ADO.NET Entity Framework 一起包装在 .NET Framework 3.5 Service Pack 1 中发表。
目前已有数个数据库厂商或元件开发商宣布要支持 ADO.NET Entity Framework:
(1) Core Lab,支持Oracle、MySQL、PostgreSQL 与 SQLite 数据库。
(2) IBM,实现 DB2 使用的 LINQ Provider。
(3) MySQL,发展 MySQL Server 所用的 Provider。
(4) Npqsql,发展 PostgreSQL 所用的 Provider。
(5) OpenLink Software,发展支持多种数据库所用的 Provider。
(6) Phoenix Software International,发展支持 SQLite 数据库的 Provider。
(7) Sybase,将支持 Anywhere 数据库。
(8) VistaDB Software,将支持 VistaDB 数据库。
(9) DataDirect Technologies,发展支持多种数据库所用的 Provider。
(10) Firebird,支持 Firebird 数据库。
Entity Framework 利用了抽象化数据结构的方式,将每个数据库对象都转换成应用程序对象 (entity),而数据字段都转换为属性 (property),关系则转换为结合属性 (association),让数据库的 E/R 模型完全的转成对象模型,如此让程序设计师能用最熟悉的编程语言来调用访问。而在抽象化的结构之下,则是高度集成与对应结构的概念层、对应层和储存层,以 及支持 Entity Framework 的数据提供者 (provider),让数据访问的工作得以顺利与完整的进行。
(1) 概念层:负责向上的对象与属性显露与访问。
(2) 对应层:将上方的概念层和底下的储存层的数据结构对应在一起。
(3) 储存层:依不同数据库与数据结构,而显露出实体的数据结构体,和 Provider 一起,负责实际对数据库的访问和 SQL 的产生。
--------------------------
Entity Framework7 部分新功能
1、在Linq to Entity 查询中对列使用类型转换
2、Code-First下用数据迁移更新数据库时使用修改(Alter)代替删除(Dropping)后重新创建
3、删除孤儿(orphans)记录
4、日志记录
Entity Framework7 有哪些改变?
1、新特性
a、支持对关系型数据的批量更新。在这之前,就是说他的更新效率太低,如果要实现批量更新,特别插入时,需要借助sql语句或是第三方工具类。相信这是很多人期待的功能;
b、支持唯一约束。它允许你在实体内除主键外额外标识一个键,将他们用作外键。
2、行为(Behavior)改变
在EF6和前期的版本中,顶层API就有很多不直观的行为,虽然EF7尽可能是保持顶层API的相同,但仍去掉了一些限制并添加了一些我们期待的行为。什么意思呢?这听起来有点迷糊,举个例子来说明吧,以前的查询,虽然Linq给我们带来了很大方便,但限制多呀,整个Linq查询翻译成一条单独的sql查询,Linq查询中只能包含EF能翻译成sql的语句或方法;还有就是sql的生成,有时生成了很复杂、效率不高,且不是我们希望的sql语句。EF7改变这种情况,可以返回多结果集,sql评估工作也不是在数据库端来做了,变更到客户端。这样就为生成sql提供了很大的灵活性。如果还有点晕,没关系,先有个印象就行。
3、变得更加简单、灵活
直接使用一个例子来说明吧。我们想通过EF的元数据来获取Blog实体被映射到数据库中的哪一张表。在这之前,我们的代码会是这样:
4、去掉了一些特性
a、每类型映射多个实体集(MEST)。这个特性,估计用的人很少,正是因为使用的人少,所以才去掉。它是什么意思呢?就是一个类型对应数据库中的多张表,例如:表Product 和 RetriedProduct都映射到Product类。如果你还有这样的需求,使用继承是更好的选择。
b、非常复杂的类型映射。在EF6.x中,可能在一个继承映射中组合TPH,TPT和TPC。EF7不再支持这种复杂的映射了,它要求你的CLR 类型尽量跟表结构保持一至。至于为什么,不少人到现在都还没有弄明白什么是TPH,TPT,TPC,那更说不上灵活运行了,这也是导致EF6.x MetadataWorkspace异常复杂的主原之一。
c、去掉edmx建模。这可能会让很多人失望,因为它曾经给我们带来多么美好的回忆。但它有很多的不足,比较一些复杂的需求,不适应ddd分层设计,不符合现在流行的POCO等。最主要的是,有更好的选择code-based建模,这就是我们常说的code-first。 可能你会有疑问,怎么code-first和edmx是平级概念,它不是跟db-first、model-first平级的吗? 没错,它是跟edmx平级的,d、ObjectContext API。它陪着EF一起成长,到EF4.1时才被DbContext弄到幕后.不过DbContext只是它的外观模式,底层仍然是使用的它。有时需要使用一些高级的功能时,我们还得想办法把它找出来。去掉它并不意味着它以前的一些功能不能用了。EF7重写了底层,把之前一定需要ObjectContext才能使用的api包含在了DbContext中,并且让调用更加清晰,简单。
e、延迟加载。 这功能相信大家不陌生,它一直被当成EF的一大特点,但现在,它将要从EF7中去掉。不确定最终的版本微软会不会把它请回来,因为这一点存在很大的争论。无论是开发人员,还是EF的开发团队。一,不是所有的应用都需要延迟加载;二、不少的EF使用者对它没有深入的去了解,经常会有人问,为什么会出现"无法完成该操作,因为 DbContext 已释放"这样的问题。这说明这个功能反而给一部份使用者带来了困惑。
这些变化并不是最终的,也许文中说的,会发生改变。当然这里也不可能列出所有的变化点,毕竟EF7还在处于开发过程中。总之,它是一个革命性的版本,以至于有人在争论应该叫他EF7呢,还是EF1。
5、对非关系型数据库的支持,
6、官方支持SQLite;
以上均为网络收集而来,希望有用.如果没有很大用处,还请多多见谅
Entity Framework Core 中的日志记录与拦截器
https://docs.microsoft.com/zh-cn/ef/core/logging-events-diagnostics/
文章目录
1.机制
efcore包含一些用于生成日志、响应时间和或者诊断结果的机制。具体见下表:
机制名称 | 支持异步 | 范围 | 进行配置的位置 | 用途 |
---|---|---|---|---|
简单的日志记录 | 否 | 上下文 | 数据库上下文 | 开发时日志记录 |
Microsoft.Extensions.Logging | 否 | 上下文 | DI或数据库上下文 | 生产日志记录 |
事件 | 否 | 上下文 | 任何位置 | 对 EF 事件做出反应 |
拦截器 | 是 | 上下文 | 数据库上下文 | 进行 EF 操作 |
诊断侦听器 | 否 | 进程 | 全局 | 应用程序诊断 |
2. 简单的日志记录
直接在OnConfiguring
里配置LogTo
即可:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.LogTo(Console.WriteLine);
//或
optionsBuilder.LogTo(msg=>Console.WriteLine(msg));
}
2.1 日志的详细信息
默认efcore不会将数据的值包含在异常消息中,因为这类数据可能是敏感的。但是在调试时如果能够看到这些值将是非常非常有用的,通过EnableSensitiveDataLogging()
启用此功能:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
出于性能方面的考虑,efcore不会将每一次对数据库的调用都用try-catch包裹起来。但这样有时候遇到问题时会很难诊断,特别是当数据库返回null值而模型却不允许null的时候。可以使用EnableDetailedErrors
方法,让efcore返回具体的错误信息:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
2.2 日志过滤
- 通过LogTo的第二个参数控制日志级别:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
- 将EventId(可从
CoreEventId
类或RelationalEventId
类获取)传入LogTo的第二个参数,以输出指定类型的日志:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
- 也可以根据消息的类别进行过滤
每条日志消息都分配到一个命名的分层记录器类别。 类别为:
类别 | 说明 |
---|---|
Microsoft.EntityFrameworkCore | 所有 EF Core 消息 |
Microsoft.EntityFrameworkCore.Database | 所有数据库交互 |
Microsoft.EntityFrameworkCore.Database.Connection | 使用数据库连接 |
Microsoft.EntityFrameworkCore.Database.Command | 使用数据库命令 |
Microsoft.EntityFrameworkCore.Database.Transaction | 使用数据库事务 |
Microsoft.EntityFrameworkCore.Update | 保存实体,排除数据库交互 |
Microsoft.EntityFrameworkCore.Model | 所有模型和元数据交互 |
Microsoft.EntityFrameworkCore.Model.Validation | 模型验证 |
Microsoft.EntityFrameworkCore.Query | 查询,排除数据库交互 |
Microsoft.EntityFrameworkCore.Infrastructure | 常规事件,例如上下文创建 |
Microsoft.EntityFrameworkCore.Scaffolding | 数据库反向工程 |
Microsoft.EntityFrameworkCore.Migrations | 迁移 |
Microsoft.EntityFrameworkCore.ChangeTracking | 更改跟踪交互 |
比如记录数据库交互的日志:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
- 自定义过滤器
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
2.3 设置日志的内容和格式
默认日志内容是多行显示,第一行包括Loglevel、本地时间、EventId、消息类别,第二行是具体内容:
info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
设置DbContextLoggerOptions.DefaultWithUtcTime
使用UTC时间和单行显示
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
3. 使用Microsoft.Extensions.Logging
默认已支持
4. 事件
efcore包含有以下几个事件
事件 | 引入的efcore版本 | 触发时间 |
---|---|---|
DbContext.SavingChanges | 5.0 | 在调用SaveChanges 或SaveChangesAsync 前 |
DbContext.SavedChanges | 5.0 | 成功调用SaveChanges 或 SaveChangesAsync 之后 |
DbContext.SaveChangesFailed | 5.0 | 调用SaveChanges 或 SaveChangesAsync 失败时 |
ChangeTracker.Tracked | 2.1 | context首次跟踪实体后 |
ChangeTracker.StateChanged | 2.1 | 当被跟踪的实体更改其状态时(首次进行跟踪时不会触发) |
示例:
public interface IHasTimestamps
{
DateTime? Added { get; set; }
DateTime? Deleted { get; set; }
DateTime? Modified { get; set; }
}
public BlogsContext()
{
ChangeTracker.StateChanged += UpdateTimestamps;
ChangeTracker.Tracked += UpdateTimestamps;
}
private static void UpdateTimestamps(object sender, EntityEntryEventArgs e)
{
if (e.Entry.Entity is IHasTimestamps entityWithTimestamps)
{
switch (e.Entry.State)
{
case EntityState.Deleted:
entityWithTimestamps.Deleted = DateTime.UtcNow;
Console.WriteLine($"Stamped for delete: {e.Entry.Entity}");
break;
case EntityState.Modified:
entityWithTimestamps.Modified = DateTime.UtcNow;
Console.WriteLine($"Stamped for update: {e.Entry.Entity}");
break;
case EntityState.Added:
entityWithTimestamps.Added = DateTime.UtcNow;
Console.WriteLine($"Stamped for insert: {e.Entry.Entity}");
break;
}
}
}
5. 拦截器
可以拦截、修改、禁止efcore的操作。包括低级的数据库操作(如执行命令)和高级的数据库操作(如对SaveChanges的调用)。
5.1 注册拦截器
在OnConfiguring
里进行配置
public class ExampleContext : BlogsContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.AddInterceptors(new TaggedQueryCommandInterceptor());
}
5.2 实现数据库拦截器
低级别的数据库拦截涉及到三个接口:
接口 | efcore自带实现基类 | 已截获数据库操作 |
---|---|---|
IDbCommandInterceptor | DbCommandInterceptor | 创建命令 执行命令 执行失败 |
IDbConnectionInterceptor | DbConnectionInterceptor | 打开/关闭连接 连接失败 |
IDbTransactionInterceptor | DbTransactionInterceptor | 创建事务 提交事务 回退事务 创建和使用保存点 事务失败 |
然后使用TagWith
方法对查询进行标记:
var blogs1 = context.Blogs.TagWith("Use hint: robust plan").ToList();
创建上面的TaggedQueryCommandInterceptor
拦截器:
public class TaggedQueryCommandInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command,CommandEventData eventData,InterceptionResult<DbDataReader> result)
{
ManipulateCommand(command);
return result;
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,CommandEventData eventData,InterceptionResult<DbDataReader> result,CancellationToken cancellationToken = default)
{
ManipulateCommand(command);
return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
private static void ManipulateCommand(DbCommand command)
{
if (command.CommandText.StartsWith("-- Use hint: robust plan", StringComparison.Ordinal))
{
command.CommandText += " OPTION (ROBUST PLAN)";
}
}
}
拦截器同时实现了同步和异步的方法,可应用到同步或异步查询中。此时生成的sql如下:
-- Use hint: robust plan
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b] OPTION (ROBUST PLAN)
5.2.1 示例
需要实现的功能:当查询前先查下缓存,如有有缓存则直接返回。否则将从数据库查询出来的数据存放到缓存中。
以下示例用来获取最新的每天消息:
- 首先给查询打tag:
async Task<string> GetDailyMessage(DailyMessageContext context)
=> (await context.DailyMessages.TagWith("Get_Daily_Message").OrderBy(e => e.Id).LastAsync()).Message;
- 设置拦截器需要的一些参数
public class CachingCommandInterceptor : DbCommandInterceptor
{
private readonly object _lock = new object();
private int _id;
private string _message;
private DateTime _queriedAt;
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,CommandEventData eventData,InterceptionResult<DbDataReader> result,CancellationToken cancellationToken = default)
{
//...
}
public override async ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command,CommandExecutedEventData eventData,DbDataReader result,CancellationToken cancellationToken = default)
{
//...
}
}
因为拦截器是全局的而且可以应用到多个dbcotext上,所以需要定义一个锁。
3. 在数据库查询执行之前,拦截器根据之前打的tag来定位具体的查询,然后检查是否有缓存
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,CommandEventData eventData,InterceptionResult<DbDataReader> result,CancellationToken cancellationToken = default)
{
if (command.CommandText.StartsWith("-- Get_Daily_Message", StringComparison.Ordinal))
{
lock (_lock)
{
if (_message != null&& DateTime.UtcNow < _queriedAt + new TimeSpan(0, 0, 10))//查询10s以内的数据
{
//因为sql不会到数据库执行,所以可以加一些自定义的注释
command.CommandText = "-- Get_Daily_Message: Skipping DB call; using cache.";
result = InterceptionResult<DbDataReader>.SuppressWithResult(new CachedDailyMessageDataReader(_id, _message));
}
}
}
return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
- 如果没有发现缓存则会执行到
ReaderExecutedAsync
方法
public override async ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command,CommandExecutedEventData eventData,DbDataReader result,CancellationToken cancellationToken = default)
{
if (command.CommandText.StartsWith("-- Get_Daily_Message", StringComparison.Ordinal)&& !(result is CachedDailyMessageDataReader))
{
try
{
await result.ReadAsync(cancellationToken);
lock (_lock)
{
_id = result.GetInt32(0);
_message = result.GetString(1);
_queriedAt = DateTime.UtcNow;
return new CachedDailyMessageDataReader(_id, _message);
}
}
finally
{
await result.DisposeAsync();
}
}
return result;
}
5.3 对OnSaveChanges
的拦截
应用场景:记录某些人对数据做了哪些更改时。
实现方法:有两个数据库上下文,用户对BlogsContext
进行更改操作,并配置拦截。AuditContext
存储用户的具体操作。
- 首先配置
BlogsContext
public class BlogsContext : DbContext
{
private readonly AuditingInterceptor _auditingInterceptor = new AuditingInterceptor("DataSource=audit.db");
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.AddInterceptors(_auditingInterceptor)
.UseSqlite("DataSource=blogs.db");
public DbSet<Blog> Blogs { get; set; }
}
主要是配置拦截器
2. AuditContext
无须额外设置
public class AuditContext : DbContext
{
private readonly string _connectionString;
public AuditContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite(_connectionString);
public DbSet<SaveChangesAudit> SaveChangesAudits { get; set; }
}
public class SaveChangesAudit
{
public int Id { get; set; }
public Guid AuditId { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public bool Succeeded { get; set; }
public string ErrorMessage { get; set; }
public ICollection<EntityAudit> Entities { get; } = new List<EntityAudit>();
}
public class EntityAudit
{
public int Id { get; set; }
public EntityState State { get; set; }
public string AuditMessage { get; set; }
public SaveChangesAudit SaveChangesAudit { get; set; }
}
AuditingInterceptor
的实现逻辑
在SaveChanges
时就记录数据,并写入数据库。如果保存失败,则记录操作失败。如果保存成功,则记录操作成功。
实现ISaveChangesInterceptor
接口,实现SavingChangesAsync
、SavedChangesAsync
、SaveChangesFailedAsync
三个异步方法和对应的同步方法。
//保存时
public async ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
_audit = CreateAudit(eventData.Context);
using (var auditContext = new AuditContext(_connectionString))
{
auditContext.Add(_audit);
await auditContext.SaveChangesAsync();
}
return result;
}
//保存成功时
public async ValueTask<int> SavedChangesAsync(
SaveChangesCompletedEventData eventData,
int result,
CancellationToken cancellationToken = default)
{
using (var auditContext = new AuditContext(_connectionString))
{
auditContext.Attach(_audit);
_audit.Succeeded = true;
_audit.EndTime = DateTime.UtcNow;
await auditContext.SaveChangesAsync(cancellationToken);
}
return result;
}
//保存失败时
public async Task SaveChangesFailedAsync(
DbContextErrorEventData eventData,
CancellationToken cancellationToken = default)
{
using (var auditContext = new AuditContext(_connectionString))
{
auditContext.Attach(_audit);
_audit.Succeeded = false;
_audit.EndTime = DateTime.UtcNow;
_audit.ErrorMessage = eventData.Exception.InnerException?.Message;
await auditContext未能正确加载包“Microsoft.Data.Entity.Design.Package.MicrosoftDataEntityDesignPackage
未能正确加载包“Microsoft.Data.Entity.Design.Package.MicrosoftDataEntityDesignPackage
未能正确加载 Microsoft.Data.Entity.Design.BootstrapPackage
vs2010安装后打开提示 未能正确加载包“Microsoft.Data.Entity.Design