SGwanmin

在学习C语言和数据结构,,, 日了,上学时候没感觉啥的,学的越多发现得调头翻大学教材了,啊,西八!

导航

EF Core 实战

EF Core 实战

前言

EF Core 官网:EF Core 基础的集成和配置就不在这里写了,自学一下就可以了,下面主要记录一下,我在实际开发中遇到的问题,需要对EF Core 有一定的了解,

不会像之前那样写的那么基础了~

 

1.DBContext非线程安全

以下来自官网:

编写使用 EF Core 的异步代码时需要注意的一些事项:
只有导致查询或发送数据库命令的语句才能以异步方式执行。 这包括 ToListAsync、SingleOrDefaultAsync、FirstOrDefaultAsync 和 SaveChangesAsync。 不包括只会更改 IQueryable 的语句,例如 var students = context.Students.Where(s => s.LastName == "Davolio")。
EF Core 上下文并非线程安全:请勿尝试并行执行多个操作。
若要利用异步代码的性能优势,请验证在调用向数据库发送查询的 EF Core 方法时,库程序包(如用于分页)是否使用异步。

异步使用DBContext时可能会出现两个错误提示:

System.ObjectDisposedException
  HResult=0x80131622
  Message=Cannot access a disposed context instance.
A common cause of this error is disposing a context instance that was resolved from dependency injection
and then later trying to use the same context instance elsewhere in your application. This may occur if you
are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection,
you should let the dependency injection container take care of disposing context instances.

这是因为http请求生成的DBContext(scoped)随着主线程的结束,而被dispose了~

System.InvalidOperationException: A second operation started on this context before a previous operation completed. 
This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

这个Error就简单了,完全字面意思,两个线程用了同一个DBContext。

以上我使用的DI注入的方式,生命周期是Scoped,在官网上默认也是Scoped,为什么在这里提一下生命周期的问题,因为如果将其改成Transient就可以解决线程问题了,但是,显然不能够哇~ 

     services.AddEntityFrameworkSqlServer()
                        .AddDbContext<CoreDBContext>(options =>
                        {
                            sqlProvider.Use(options, coreDBConn).UseLoggerFactory(AveLogger.AveLoggerFactory);
                        }, ServiceLifetime.Scoped)
services.AddSingleton<AutoMigration>();
services.AddScoped<IDBRepository, DBRepository>();
services.AddScoped<DBContextFactory>();/create DBContext

以下是我自己的理解:

1.翻了一下源码:DBContext的最大连接数默认是500,默认就是scoped,当然这个数是可调的,但不建议调。如果改成了Transient了,500显然不够用的,
2.官网所有的例子都是scoped,额外提到了线程问题,所以我不认为是人家没想到生命周期来解决问题。

所以解决他~~~

本质上,就是因为多线程使用了同一个DBContext的原因,所以通过ServiceProvider获取一个新的DBContext,给Service使用即可。

ps:微服务涉及到跨服务通信(CAP,gRPC),其中CAP属于异步操作,gRPC是http2,所以你懂得~

public SLALearningObjectConsumer(IServiceScopeFactory serviceScopeFactory)
     {
         _serviceScopeFactory = serviceScopeFactory;
     }

using (var serviceScope = _serviceScopeFactory.CreateScope())
{
    var db = serviceScope.ServiceProvider.GetService<IDBRepository>();
                 var dbcontext = serviceScope.ServiceProvider.GetService<DBContext>();
                 db.Context = dbcontext;
    _slaLearningObjectService.ChangeDBContext(coredb);
    //go on logic
}

 

2.EF Core 的Tracking机制

在介绍这个tracking之前,先看一下通过EFCore从DB中拉出来的实体对象的状态

Added:数据库中尚不存在实体。 SaveChanges 方法发出 INSERT 语句。
Unchanged:无需保存对该实体所做的任何更改。 从数据库中读取实体时,该实体具有此状态。
Modified:已修改实体的部分或全部属性值。 SaveChanges 方法发出 UPDATE 语句。
Deleted:已标记该实体进行删除。 SaveChanges 方法发出 DELETE 语句。
Detached:数据库上下文未跟踪该实体。

每一个查询默认为tracking,如果不需要跟踪则需要显示的调用AsNoTracking()

 public virtual IQueryable<TResult> Filter<T, TResult>(Expression<Func<T, bool>> expression, Expression<Func<T, TResult>> selector, bool isTracking = false) where T : class
        {
            return isTracking
                ? Context.Set<T>().Where<T>(expression).Select<T, TResult>(selector).AsQueryable<TResult>()
                : Context.Set<T>().Where<T>(expression).AsNoTracking<T>().Select<T, TResult>(selector).AsQueryable<TResult>();
        }

如果是tracking则查出来的Entity状态为Unchanged,如果显示调用AsNoTracking(),查询Entity的状态为Detached, 这个状态有啥用问得好~
除了Detached状态的Entity,其他所有状态在直接,注意是直接SaveChange后,都会进行DB操作。
下面以Update为例,以下截取的是Microsoft.EntityFrameworkCore update summary中的一段,

   A recursive search of the navigation properties will be performed to find reachable
        //     entities that are not already being tracked by the context. All entities found
        //     will be tracked by the context.
        //     For entity types with generated keys if an entity has its primary key value set
        //     then it will be tracked in the Microsoft.EntityFrameworkCore.EntityState.Modified
        //     state. If the primary key value is not set then it will be tracked in the Microsoft.EntityFrameworkCore.EntityState.Added
        //     state. This helps ensure new entities will be inserted, while existing entities
        //     will be updated. An entity is considered to have its primary key value set if
        //     the primary key property is set to anything other than the CLR default for the
        //     property type.

update方法将会track Entity,如果这个Entity已经处于被Track状态(modified/Unchanged)则会报错!

综上应该可以清晰的认识到tracking在DB操作时所起的作用,当Entity被tracking时,直接SaveChange就会进行DB操作,而如果是Detached状态,需要先Tracking,才会进行DB操作,这里有EF Core会进行一个Check检查。

知识点:

当Entity被tracking时,所查询的Entity会加载关联表的数据,关联表的关联表属性也会被加载嗷~
那么问题就来了,当我只需要一个关联表中的个别属性,不要将全部的关联表都查询出来该怎么办呢,

AsNoTracking().xxxxx.include(x=>x) 

最后一个Tracking 问题,在上文也体现了好多次了,同一个Entity(主键一致)不允许被多次Tracking!为什么,,,问的好。

EF Core 的每次查询会为Entity生成一个代理类(proxy),我们操作的是proxy,而不是真正的Entity,那玩意躺在DB里呢,
一个Entity需要根据主键确定并唯一的对应一个Proxy,所以无法重复的Tracking,由此我们其实可以将Tracking理解成Entity与proxy之间的一个连接!

上Code~上图, 理解一下~

            var @class = _repository.GetDbSet<Class>().AsNoTracking().Where(c => c.DeleteStatus == DeleteStatus.Ok).Include(c => c.Module).FirstOrDefault();
            var classState = _repository.Context.Entry(@class).State;
            var moduleState = _repository.Context.Entry(@class.Module).State;

            var module = _repository.FirstOrDefault<Module>(m => m.Id == @class.ModuleId, true);
            var moduleState1 = _repository.Context.Entry(@class.Module).State;
            var moduleState2 = _repository.Context.Entry(module).State;

            @class.Module.Name = "modifiy name";

            var saveNumber =_repository.SaveChanges();

 

 

 

附送一个知识点:EF Core 的关联查询 使用的是Inner join ,每次查询的连接都会在开始 open,结束时close, 给图会看的比较清晰一点,只有当一个请求结束了这个DBContext才会被Dispose!!!可通过DBContext ID来验证!不写了怪简单的,嘿嘿嘿~

 

 

 

3.IQueryable与IEnumerable的区别

本来这俩玩意都没想说,但是工作中发现大多数人竟然不知道这两货的区别,所以才拎出来谈一下~
直接看字面意思,已经够清晰了~
IQueryable:可查询的   

IEnumerable:可枚举的
再看一眼他们的namespace 和summary

namespace System.Collections
{
    //
    // Summary:
    //     Exposes an enumerator, which supports a simple iteration over a non-generic collection.
    public interface IEnumerable
    {
        //
        // Summary:
        //     Returns an enumerator that iterates through a collection.
        //
        // Returns:
        //     An System.Collections.IEnumerator object that can be used to iterate through
        //     the collection.
        IEnumerator GetEnumerator();
    }
}
namespace System.Linq
{
    //
    // Summary:
    //     Provides functionality to evaluate queries against a specific data source wherein
    //     the type of the data is not specified.
    public interface IQueryable : IEnumerable
    {
         //玛卡巴卡
    }
}

以EF Core 使用为例子
IQueryable:是Linq下的,更多的作用在服务端。
IEnumerable:是Collections下的,是操作内存中的集合的。

在EF Core 中体现则是IQueryable.Where() 会生成sql语句,将条件带入filter。而IEnumerable.Where()操作的对象是内存,所以会查找到全部Entity在内存中Filter。

因为这俩货总是一起出现的,所以很多人才会混淆,这里特地说一下~

btw:经过我们大佬的测试发现,EF Core中的联表查询,不如拆成单表查更快~(这个我没有验证过啊,我也不确定)

 

 

posted on 2022-04-09 15:44  GwanMin  阅读(460)  评论(0)    收藏  举报