EFCore2.1的安装使用和其中遇到的那些坑

EFCore2.1的安装使用和其中遇到的那些坑

LazyLoading是EntityFramework受争议比较严重的特性,有些人爱它,没有它就活不下去了,有些人对它嗤之以鼻,因为这种不受控制的查询而感到焦虑。

我个人觉得如果要用EF那还是尽量要使用它尽可能多的特性,不然,你还不如去找其它更轻量级的ORM。

本人对EF的理解还是处于比较初级的阶段,但是CodeFirst的开发方式让我在三年前写MVC的时候为之惊叹。奈何各种搞Migration吐血,各种配置吐血,学习耗时太长,后来放弃,直到敬而远之。

这次由于自己喜欢的油管主播AngelSix在WPF项目中使用了EFCore访问本地Sqlite数据库,和SQL Server数据库,决定参考重新学习。这次本着边做边学的态度,接触EFCore,碰到不少坑,现在记录如下,后续可能会有更新,毕竟EFCore目前的版本是2.1,项目也正在不断演进。

  • EFCore的安装使用
  • 坑一:实体与实体间的关联关系,外键如何生成和映射
  • 坑二:System.InvalidCastException: 指定的转换无效
  • 坑三:数据存取集成测试如何不创建实体文件数据库进行测试
  • 坑四:怎样显示EFCore执行的Sql日志
  • 坑五:为何使用LazyLoad,如何使用
  • 方案一:使用Microsoft.EntityFrameworkCore.Proxies
  • 方案二:侵入式使用ILazyLoader注入Domain对象
  • 坑六:怎样实现一个完整的Clone数据库对象

EFCore的安装使用

EFCore同时支持传统.net framework和.net core架构,相关的架构依赖可以参考nuget上的说明文档。

安装Nuget包Microsoft.EntityFrameworkCore.Sqlite版本2.1.0

EFCore的主要配置代码都集中在DBContext继承类上

DBSet定义数据库表

OnConfiguring用来配置DBContext行为,比如下面代码就是使用本地testing.db文件数据库

OnModelCreating用来配置数据库的映射,这里没有吧映射加到Domain实体,因为这样Domain实体代码就要引用EF,全部映射都在ModelCreating完成

再通过DbContext.Database.EnsureCreatedAsync();创建数据库实例。

public class StockDbContext:DbContext
{
    #region DbSets
    public DbSet<Stock> Stocks { get; set; }
    public DbSet<Valuation> Valuations { get; set; }
    #endregion
    #region Constructor
    public StockDbContext(DbContextOptions<StockDbContext> options):base(options)
    {
    }
    #endregion
    #region Configure the path
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=testing.db");
    }
    #endregion
    #region Model Creating
    /// <summary>
    /// Configures the database structure and relationships
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //设置数据库主键
        modelBuilder.Entity<Stock>().HasKey(a => a.Id);
        //主键自增
        modelBuilder.Entity<Stock>().Property(x => x.Id).ValueGeneratedOnAdd();
        modelBuilder.Entity<Valuation>().HasKey(a => a.Id);
        modelBuilder.Entity<Valuation>().Property(x => x.Id).ValueGeneratedOnAd();
        //设置默认值
        modelBuilder.Entity<Valuation>().Property(x => x.Time).HasDefaultValueSq("strftime(\‘%Y-%m-%d %H:%M:%f\‘,\‘now\‘,\‘localtime\‘)");
    }
}

其中ModelCreating的各种数据库属性怎么映射可以参考这里

新增实体

public async Task<int> AddStock(Stock stock)
{
    mDbContext.Stocks.Add(stock);
    return await mDbContext.SaveChangesAsync();
}

更新实体

public async Task<int> UpdateStock(Stock stock)
{
    mDbContext.Stocks.Update(stock);
    // Save changes
    return await mDbContext.SaveChangesAsync();
}

删除实体

public async Task<int> Remove(Stock stock)
{
    mDbContext.Stocks.Remove(stock);
    // Save changes
    return await mDbContext.SaveChangesAsync();
}

查询实体

public Task<IQueryable<Stock>> GetStockAsync()
{
    return Task.FromResult(mDbContext.Stocks.AsQueryable());
}

坑一:实体与实体间的关联关系,外键如何生成和映射

EntityFrameWork实体之间的关系映射这篇文章已经讲的很清楚了,包括一对多、多对多关系。

但是EFCore的多对多映射和EF略有不同

EF中:

this.HasMany(t => t.Users)
    .WithMany(t => t.Roles)
    .Map(m =>
        {
            m.ToTable("UserRole");
            m.MapLeftKey("RoleID");
            m.MapRightKey("UserID");
        });

EFCore中没有HasMany+WithMany这个API怎么办?

答案是手动创建关联实体,通过引入UserRole这个实体,来映射

public class UserRole(){
    public int UserID { get; set; }
    public virtual User User { get; set; }
    public int RoleID { get; set; }
    public virtual Role Role { get; set; }
}
public partial class User(){
    public virtual ICollection<UserRole> UserRoles { get; set;}
}
public partial class Role(){
    public virtual ICollection<UserRole> UserRoles { get; set;}
}

Map的时候使用UserRole进行两次一对多映射即可!

modelBuilder.Entity<UserRole>()
    .HasOne(x => x.Role)
    .WithMany(y => y.UserRoles)
    .HasForeignKey(z => z.RoleID)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<UserRole>()
    .HasOne(x => x.User)
    .WithMany(y => y.UserRoles)
    .HasForeignKey(z => z.UserID)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

坑二:System.InvalidCastException: 指定的转换无效

这个其实是一个不太容易发现问题原因的异常,因为很多原因可以导致这个异常,我这次的错误是把枚举类型以声明形式转换为数据库字段INTEGER导致

public enum Urgency : short{/*...*/}
//...OnModelCreating
modelBuilder.Entity<Task>().Property(x => x.Urgency).HasColumnType("INTEGER");

在创建数据库时无问题,但是在添加或查询数据时报错

其实如果不显式标注INTEGER的类型,在创建数据库时还是INTEGER类型,区别是一个可空一个不可空

估计在这里做实体映射的时候出错了,然后这个问题在EFCore2.0.3是没有的,汗。。。

本人在调试这个问题的时候猜测问题出在OnModelCreating上,然后不停的注释取注跑单元测试,最终定位问题出在这里。

坑三:数据存取集成测试如何不创建实体文件数据库进行测试

单元测试跑文件数据库需要每次都删除重来,搞起Setup、TearDown都是异常麻烦。

还好Sqlite有内存数据库,但是内存数据库的效用只在一次连接内。

也就是说,如果连接关闭了,你的表就都没了,即使dbcontext已经执行过了EnsureDBCreate方法

参考文献

public static StockDbContext GetMemorySqlDatabase()
{
    var connectionStringBuilder =
        new SqliteConnectionStringBuilder { DataSource = ":memory:" };
    var connectionString = connectionStringBuilder.ToString();
    var connection = new SqliteConnection(connectionString);
    var builder = new DbContextOptionsBuilder<StockDbContext>();
    builder.UseSqlite(connection);
    DbContextOptions<StockDbContext> options = builder.Options;
    return new StockDbContext(options);
}
public async Task UseMemoryContextRun(Func<StockDbContext, Task> function)
{
    //In-Memory sqlite db will vanish per connection
    using (var context = StockDbContext.GetMemorySqlDatabase())
    {
        if (context == null) return;
        context.Database.OpenConnection();
        context.Database.EnsureCreated();
        //Do that task
        await function.Invoke(context);
        context.Database.CloseConnection();
    }
}

坑四:怎样显示EFCore执行的Sql日志

很多时候需要排错,EF的最大问题是,我都不知道框架帮我生成的语句是什么

这个时候可以借助

public static readonly LoggerFactory MyLoggerFactory
    = new LoggerFactory(new[] { new DebugLoggerProvider((_, __) => true) });
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseLoggerFactory(MyLoggerFactory);
}

将详细日志打印到Debug日志里,参考官方文档

需要去nuget上安装对应的LogProvider,也可以使用自己的logprovider,我自己安装Microsoft.Extensions.Logging.Debug觉得够用了

坑五:为何使用LazyLoad,如何使用

由于CodeFirst生成的关联关系,在查询的时候默认都是空的

例如:

var fs = Stock.FirstOrDefault(x=>x.StockID = 1);

即使fs为1的对象有关联的Valuation数据在数据库中,查询出来的对象Valuation这一属性将会为空

只有显式的声明Include、ThenInclude才能一并加载,这对某些一对多自关联的对象来说很恐怖,所以LazyLoad可以说是省时省力的好工具

参考官方文档

一共有三种方式实现LazyLoad,都需要EFCore版本2.1以上

  • Nuget安装使用Microsoft.EntityFrameworkCore.Proxies
  • 使用ILazyLoader注入Domain对象
  • 非侵入式使用ILazyLoader注入Domain对象

Domain对象肯定不能侵入式注入,所以我尝试了方法1和方法3,都可以成功

方案一:使用Microsoft.EntityFrameworkCore.Proxies

实现细节参考文档,这里说下坑

首先所有关联属性必须用virtual,不然代理不能注入

其次代理注入将改变对象的类型

比如我注入了一个UserRole对象,那这个对象的GetType将会是UserRoleProxy

这就导致这个对象在和另一个UserRole进行比较的时候可能出现,对象判等失败

obj.GetType() != GetType()

方案二:侵入式使用ILazyLoader注入Domain对象

因为方案一实现过程中出现了坑二的问题,导致我又尝试了ILazyLoader注入

No field was found backing property ‘xxxxx‘ of entity type ‘xxxxx‘. Lazy-loaded navigation properties must have backing fields. Either name the backing field so that it is picked up by convention or configure the backing field to use.

只有一个关联属性xxxx报了这个错,关联属性这么多,怎么偏偏你报错呢?

仔细看了下,是拼写问题,private field的拼写要和public property的拼写一致。虽然Intelisense没有错误代表编译是可以通过的,汗。。。

坑六:怎样实现一个完整的Clone数据库对象

要Clone数据首先要使用AsNoTracking方法

var originalEntity = mDbContext.Memos.AsNoTracking()
    .Include(r => r.MemoTaggers)
    .Include(x => x.TaskMemos)
    .FirstOrDefault(e => string.Equals(e.MemoId, memoid, StringComparison.Ordinal));
if (originalEntity != null)
{
    originalEntity.MemoId = null;
    foreach (var originalEntityMemoTagger in originalEntity.MemoTaggers)
    {
        originalEntityMemoTagger.MemoId = null;
        originalEntityMemoTagger.MemoTaggerId = null;
    }
    foreach (var originalEntityTaskMemo in originalEntity.TaskMemos)
    {
        originalEntityTaskMemo.MemoId = null;
        originalEntityTaskMemo.TaskMemoId = null;
    }
    mDbContext.Memos.Add(originalEntity);
    await mDbContext.SaveChangesAsync();
    return originalEntity;
}

问题来了,LazyLoad引入后调用关联属性会报错

Error generated for warning ‘Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: An attempt was made to lazy-load navigation property ‘MemoTaggers‘ on detached entity of type ‘CNMemoProxy‘. Lazy-loading is not supported for detached entities or entities that are loaded with ‘AsNoTracking()‘.‘. This exception can be suppressed or logged by passing event ID ‘CoreEventId.DetachedLazyLoadingWarning‘ to the ‘ConfigureWarnings‘ method in ‘DbContext.OnConfiguring‘ or ‘AddDbContext‘.

根据提示OnConfiguration中加入这段后,就可以Suppress这个报错。

optionsBuilder
.ConfigureWarnings(warnnings=>warnnings.Lo(CoreEventId.DetachedLazyLoadingWarning))

原文地址:https://www.cnblogs.com/enigmaxp/p/9159391.html

时间: 2024-10-12 11:03:56

EFCore2.1的安装使用和其中遇到的那些坑的相关文章

ubuntu 14.04中安装 ruby on rails 环境(填坑版) 呕血推荐

环境:在win7 上Vmware虚拟机环境中安装的ubuntu 14.04 开发相关: ruby 2.2.0 rails 4.2.0 sublime text 3 本文说明:所有的命令均在$ 之后,若$前边带有信息,只是为了方便你理解和与自己对照. 安装过程中由于这样那样的原因,产生许多坑,所谓坑是对初学者来说,大牛们飘过吧. 步骤1.从官网下载ubuntu 14.04 X64 http://124.205.69.136/files/2013000000502943/202.141.176.11

利用nodejs安装并运行express的三个坑

概述 这是我安装并运行express的三个坑,应该是比较常见的,在此记录一下. 内容 express不是内部或外部命令 输入命令:express -V 报错:'express' 不是内部或外部命令,也不是可运行的程序或批处理文件. 解决方法: 最新express4.0版本中将命令工具分家出来了,所以我们还需要安装一个命令工具,命令是:npm install -g express-generator.如果在AppData\Roaming\npm目录下生成了express.express.cmd两个

安装CentOS 7.4 可能会出现的坑以及解决方案

安装CentOS 7.4 可能会出现的坑以及解决方案 (解决方法不唯一,如果行不通的话emmmm~~, 百度会啥你会啥~~) 坑.0X01 解决: 退出虚拟机,以管理员权限运行 坑.0X02 解决: 物理机的CPU不支持虚拟64位虚拟化,有些CPU是直接不支持,有些可以虚拟64位CPU,只需要在bios里面开启虚拟化功能即可. 坑.0X03 解决:  找不到光盘,重新选择正确的镜像光盘 坑.0X04  设置linux系统网卡时无法连接网络??? 解决1: 可能是物理机(windows)里相关服务

安装VMware Tools的步骤和那些坑

背景环境:VMware workstation 12.5+Ubuntu16.04 首先VMware Tools在ubuntu中是及其不稳定的,也就是说,当你点击菜单栏中的install vmware tools后,在CD中可能看不到相应的.tar.gz的包,当然也是有可能看到的. 下面的解决办法也就是在能看到.tar.gz的包的情况下的: 可以先把.tar.gz的包拷贝到Desktop中,然后打开终端,执行如下命令(最好在root权限下操作): 进入Desktop目录: cd Desktop 解

nvm安装node和npm,个人踩坑记录

我采用nvm-setup安装windows版本的nvm nvm安装node出现的问题: 1.node成功了,npm没成功 解决:在nvm 安装了node之后,输入npm找不到该命令,当时安装报错如下: 报错其实也看不明白,大概感觉是npm包下载的地址没连上,出错了,查看nvm目录文件夹,有一个temp文件夹,里面存放着npm的压缩包,把temp整个文件夹删除,执行uninstall v6.10.2的时候,提示删除失败,需要手动删除,其实他是只删除了存放nodejs的文件夹,但是相应版本删除失败,

有关mac下安装双系统的。。。坑?

前言:我装win系统的原因很简单,就是某天突然想玩qq宠物了(不要嘲笑,自行尴尬一波)... 下面进入正题: 1.我的当前系统版本: 其实App Store 上新版本的os系统也已经出来很长一段时间了(如下图),不过看了网上的一些评论,以及身边朋友的亲身实践(被坑),本人对新版本还是持观望态度,所以暂时不予考虑. 2.windows ISO镜像文件 这里要首先提醒一下,新版本的Mac已经不支持win8以下的系统了,所以和我一样版本但喜欢win7的小伙伴们要失望了,因为你是用不上win7了,除非(

ubuntu系统中的VMware 安装win7 Ghost镜像的几个坑

1.ghost镜像安装时要先分区 2.分区后要激活 3.VM(虚拟机安装win7 提示 :units specified don't exist, SHSUCDX can't install)解决方法 设置虚拟机的光驱--> 高级设置 -->改成IDE的模式

Python,Pycharm,Anaconda等的关系与安装过程~为初学者跳过各种坑

1.致欢迎词 我将详讲讲述在学Python初期的各种手忙脚乱的问题的解决,通过这些步骤的操作,让你的注意力集中在Python的语法上以及后面利用Python所解决的项目问题上.而我自己作为小白,很不幸的没有错过任何的坑,都跳了进去,所以在这里写下经验贴,一方面希望能给后来的学者能够高效的避开这些坑,另一方面也算是自己的总结与警告. 2.内容大纲 2.1 安装顺序 能够使用Python的安装过程我建议这样:Anaconda-Pycharm Anaconda我建议安装Anaconda3 原因后面会解

前端开发工具Brackets介绍,安装及安装Emme插件时踩过的坑

对于前端开发的园友来说有可能IDE工具有很多,层次不穷,还有每个人的喜好及习惯也不一样,因为我是一名后端开发的.Net程序员,但是大家都知道,现在都提倡什么全栈工程师,所以也得会点前端开发,所以我对于前端来说可能是个菜鸟,大神绕过,勿喷!我刚接触程序,开发网站时主要用Dreamweaver,后来也用过WebStorm和Sublime2,不过在学习Bootstrap3的时候偶然的机会接触到Brackets就个人比较喜欢他,就不想用别的IDE了,我这里不做这些工具的比较,说哪一个更好,我觉得各有所爱