当前位置:网站首页 / .NET CORE / 正文

ay的C#8.0和net5高级编程笔记11-使用EntityFramework Core处理数据库

时间:2021年04月26日 | 作者 : aaronyang | 分类 : .NET CORE | 浏览: 215次 | 评论 0

EF6是最后一个NF版本

EF支持读写MSSQL,SQLite,Azure CosmosDB

目前EFC 6已发布(当前时间 2021-04-26)

EFCore文档数据库提供者:https://github.com/BlueshiftSoftware/EntityFrameworkCore

EFCore微软 https://github.com/dotnet/efcore/tree/main/docs


从SQLite学习使用EF

macOS,在/usr/bin/目录中,sqlite3的命令行应用程序


Windows到 https://www.sqlite.org/download.html

下载 第三个,解压到D盘

配置系统环境变量



下载Northwind.sql文件,ctrl+S另存为

https://raw.githubusercontent.com/markjprice/cs8dotnetcore3/master/sql-scripts/Northwind.sql

vs也可以ctrl+反引号,打开和vscode一样的终端

但是恢复数据库没用,提示<错误,打开cmd进行恢复

https://www.runoob.com/sqlite/sqlite-create-database.html


 书上说的使用SQLiteStudio管理Northwind库

http://sqlitestudio.pl


我就不用了,我使用Navicat,网上自己找安装教程

image.png

image.png

image.pngimage.png


EFCore(以后简称EFC)提供了操作的dll包

Microsoft.EntityFrameworkCore.SqlServer 操作MSSQL 2008+

Microsoft.EntityFrameworkCore.SQLite 操作SQLite

MySQL.Data.EntityFrameworkCore 操作MySQL

Microsoft.EntityFrameworkCore.InMemory 在内存中

更多第三方,链接


通过Nuget安装SQLite

image.png


编码约定:

表的名称与DbContext类的DbSet<T>属性的名称匹配

列的名称与类中的属性名称匹配

.NET类型string是数据库的nvarchar类型

.NET类型int是数据库的int类型

对于ID属性,如果类名为Product,则该属性重命名为ProductID。然后假定该属性是主键。如果该属性是整数或者Guid类型,那就可以假定为IDENTITY类型,插入时自动赋值的列类型


更多约定,查看

https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-5.0/whatsnew


EF Core注解特性

引入

using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations.Schema;


添加理解的代码

    public class Product
    {
        [Required]
        [StringLength(40)]
        public string ProductName { get; set; }

        [Column(TypeName ="money")]
        public decimal? UnitPrice { get; set; }

    }
    public class Category
    {
        /// <summary>
        /// 可以超过nvarchar的8000字符,用ntext映射
        /// </summary>
        [Column(TypeName = "ntext")]
        public string Description { get; set; }


        public decimal? UnitPrice { get; set; }

    }


EF Core Fluent API

定义模型另一种方法Fluent APi

比如ProductName上的特性,可以在 OnModelCreating 方法替换为等效的FluentAPI

        modelBuilder.Entity<Product>()
            .Property(product => product.ProductName)
            .IsRequired()
            .HasMaxLength(40)


理解数据播种

使用FluentAPI提供初始数据以填充数据库。EF Core会自动计算出需要执行哪些插入,更新或删除操作。如果想要确保新数据库在Product表中至少一行,就调用HasData方法,如下所示:

    modelBuilder.Entity<Product>()
            .HasData(new Product{
              ProductID=1,
               ProductName="CA1",
              UnitPrice=9.88M
        });

更多


构建EF Core模型

打开Categories有4列

image.png

然后映射这个表

    public class Category
    {
        public Category()
        {
            this.Products = new List<Product>();
        }
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        [Column(TypeName = "ntext")]
        public string Description { get; set; }
        public virtual ICollection<Product> Products { get; set; }

    }

这里Picture列不映射,Products体现一对多

CategoryName后面用FluentAPI映射 不为空,最多15字符

CategoryID按照主键约定来的

====================www.ayjs.net=====www.ayjs.net============


Product表

image.png

我的navicat不能打开这个表设计

我用sqlitestudio打开

image.png

image.png



映射需要的列

public class Product
    {
        public int ProductID { get; set; }
        [Required]
        [StringLength(40)]
        public string ProductName { get; set; }

        [Column("UnitPrice", TypeName = "money")]
        public decimal? Cost { get; set; }
        [Column("UnitsInStock")]
        public short? Stock { get; set; }
        public bool Discontinued { get; set; }

        public int CategoryID { get; set; }
        public virtual Category Category { get; set; }
    }

这里有被virtual修饰的属性,这允许EFC 实现延迟加载的功能,在NC2.0之前不可用。

注意这里的ID都是int类型的

定义Northwind数据库上下文

引入using Microsoft.EntityFrameworkCore;

DbContext提供操作数据库,动态生成SQL和操作数据。

DbSet<T>代表表

OnConfiguring重载,用于设置数据库连接字符串。

同样,DbContext派生类还可以有名为OnModelCreating的重载方法。在这里,可以编写Fluent API语句,作为用特性装饰实体类的替代选择。

    public class NorthWind:DbContext
    {
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
        {
            string path = @"D:\aycore\Code\Chapter11\Northwind.db";
            optionBuilder.UseSqlite($"FileName={path}");
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Category>()
                .Property(c => c.CategoryName)
                .IsRequired()
                .HasMaxLength(15);
        }
    }


查询EF Core模型

引入using System.Linq;

查询目录

    class Program
    {
        static void Main(string[] args)
        {

            QueryingCategories();

            Console.ReadKey();
        }
        static void QueryingCategories()
        {
            using (var db=new NorthWind())
            {
                Console.WriteLine("类别和产品");
                IQueryable<Category> cats = db.Categories.Include(c=>c.Products);
                foreach (var cat in cats)
                {
                    Console.WriteLine($"{cat.CategoryName} 有{cat.Products.Count}个产品");
                }
            } 
        }
    }

效果

image.png


过滤和排序产品

static void QueryingProducts()
        {
            using (var db=new NorthWind())
            {
                Console.WriteLine("价格大于50产品");
                decimal price = 50M;
                //IOrderedEnumerable<Product>
                var products = db.Products.AsEnumerable().Where(p => p.Cost > price).OrderByDescending(x => x.Cost);
                foreach (var item in products)
                {
                    Console.WriteLine("{0}:{1} 花费{2:$#,##0.00} 有{3}个stock",item.ProductID,item.ProductID,item.Cost,item.Stock);
                }
            }
        }

image.png

如果控制台不能显示Unicode字符,可以cmd内输入

chcp 65001

回车就行了


记录EF Core

监视EFC和数据库之间交互,启用日志记录功能

注册日志提供程序

实现日志提供程序


引入

using Microsoft.Extensions.Logging;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.EntityFrameworkCore.Infrastructure;


当日志级别为None,Trace和Information时候,禁用ConsoleLogger,然后的其他的启用。

ConsoleLogger通过向控制台写入日志来实现Log方法

public class ConsoleLoggerProvider : ILoggerProvider
    {
        public ILogger CreateLogger(string categoryName)
        {
            return new ConsoleLogger();
        }

        public void Dispose()
        {

        }
    }
    public class ConsoleLogger : ILogger
    {
        //如果你的logger使用了非托管资源,你返回个实现了IDisposable的类
        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Trace:
                case LogLevel.Information:
                case LogLevel.None:
                    return false;
                case LogLevel.Debug:
                case LogLevel.Warning:
                case LogLevel.Error:
                case LogLevel.Critical:
                default:
                    return true;
            };
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            Console.WriteLine($"Level:{logLevel},Event ID:{eventId.Id}");
            if(state != null)
            {
                Console.Write($",State{state}");
            }
            if (exception != null)
            {
                Console.Write($",Exception{exception.Message}");
            }
            Console.WriteLine();
        }
    }

在Querying的两个方法中加入下面代码,注册日志记录器

   static void QueryingProducts()
        {
            using (var db = new NorthWind())
            {
                var logfactory = db.GetService<ILoggerFactory>();
                logfactory.AddProvider(new ConsoleLoggerProvider());

                Console.WriteLine("价格大于50产品");
                decimal price = 50M;
                //IOrderedEnumerable<Product>
                var products = db.Products.AsEnumerable().Where(p => p.Cost > price).OrderByDescending(x => x.Cost);
                foreach (var item in products)
                {
                    Console.WriteLine("{0}:{1} 花费{2:$#,##0.00} 有{3}个库存", item.ProductID, item.ProductID, item.Cost, item.Stock);
                }
            }
        }

image.png

事件ID的值及含义特定于.NET数据提供程序。如果想知道LINQ查询是如何 转换成SQL语句并执行的,事件ID是20100    

image.png

过滤,只输出 20100的事件

   public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (eventId.Id == 20100)
            {
                Console.WriteLine($"Level:{logLevel},Event ID:{eventId.Id}");
                if (state != null)
                {
                    Console.Write($",State{state}");
                }
                if (exception != null)
                {
                    Console.Write($",Exception{exception.Message}");
                }
                Console.WriteLine();
            }
     
        }

image.png


使用查询标记进行日志记录

EFCore2.2 引入了查询标记特性,以允许向日志中添加SQL注释

  static void QueryingProducts()
        {
            using (var db = new NorthWind())
            {
                var logfactory = db.GetService<ILoggerFactory>();
                logfactory.AddProvider(new ConsoleLoggerProvider());

                Console.WriteLine("价格大于50产品");
                decimal price = 50M;
                //IOrderedEnumerable<Product>
                //var products = db.Products.AsEnumerable().Where(p => p.Cost > price).OrderByDescending(x => x.Cost);
                var products = db.Products.TagWith("产品价格筛选,然后排序").AsEnumerable().Where(p => p.Cost > price).OrderByDescending(x => x.Cost);
                foreach (var item in products)
                {
                    Console.WriteLine("{0}:{1} 花费{2:$#,##0.00} 有{3}个库存", item.ProductID, item.ProductName, item.Cost, item.Stock);
                }
            }
        }

image.png

更多:https://docs.microsoft.com/zh-cn/ef/core/querying/tags


模糊匹配与Like

     static void QueryingWithLike()
        {
            using (var db = new NorthWind())
            {
                var logfactory = db.GetService<ILoggerFactory>();
                logfactory.AddProvider(new ConsoleLoggerProvider());

                Console.WriteLine("搜索产品名:");
                string productname = "che";
                var products = db.Products.Where(p => EF.Functions.Like(p.ProductName, $"%{productname}%"));
                foreach (var item in products)
                {
                    Console.WriteLine("{0} 的库存数量{1}. 是否停产? {2}", item.ProductName, item.Stock, item.Discontinued);
                }
            }
        }

用到了EF.Functions.Like

image.png

====================www.ayjs.net=================



定义全局过滤器

产品可以停产,因此要确保停产的产品不会返回。

添加全局过滤器,删除停产的产品

在OnModelCreating方法添加个HasQueryFilter

    public class NorthWind : DbContext
    {
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
        {
            string path = @"D:\aycore\Code\Chapter11\Northwind.db";
            optionBuilder.UseSqlite($"FileName={path}");
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Category>()
                .Property(c => c.CategoryName)
                .IsRequired()
                .HasMaxLength(15);

            modelBuilder.Entity<Product>().HasQueryFilter(p => !p.Discontinued);

        }
    }

image.png

只剩3个了,那个Gumbo Mix过滤了



使用EFCore加载模式

EF通常三种加载模式 : 

    立即加载 Eager Loading 

    延迟加载Lazy Loading  

    显式加载Explicit Loading


立即加载实体

在QueryingCategories方法中使用了 Include ,这是立即加载(也被称为早期加载)

image.png

删掉Include代码,执行下

image.png

image.png

现在的products都为空了


EFC2.1引入延迟加载,从而能够自动加载缺失的相关数据。


启用延迟加载

要启用延迟加载,开发人员必须

    为代理引用NuGet包

    配置延迟加载以使用代理


安装 Microsoft.EntityFrameworkCore.Proxies

image.png

  protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
        {
            string path = @"D:\aycore\Code\Chapter11\Northwind.db";
            optionBuilder.UseLazyLoadingProxies().UseSqlite($"FileName={path}");
        }

image.png

看日志,每次都多了个select查询

image.png

====================www.ayjs.net=================


显式加载实体

与延迟加载相似,不同之处在于可以控制加载哪些相关数据以及何时加载。

   static void QueryingCategories()
        {
            using (var db = new NorthWind())
            {
                var logfactory = db.GetService<ILoggerFactory>();
                logfactory.AddProvider(new ConsoleLoggerProvider());

                //Console.WriteLine("类别和产品");
                //IQueryable<Category> cats = db.Categories.Include(c => c.Products);

                //IQueryable<Category> cats = db.Categories;

                //foreach (var cat in cats)
                //{
                //    Console.WriteLine($"{cat.CategoryName} 有{cat.Products.Count}个产品");
                //}

                //显示加载实体
                IQueryable<Category> cats;
                db.ChangeTracker.LazyLoadingEnabled = false;

                Console.Write("是否启用贪婪加载?(Y/N): ");
                bool eagerloading = (Console.ReadKey().Key == ConsoleKey.Y);
                bool explicitloading = false;
                Console.WriteLine();
                if (eagerloading)
                {
                    cats = db.Categories.Include(c => c.Products);
                }
                else
                {
                    cats = db.Categories;
                    Console.Write("是否启用显示加载?(Y/N):");
                    Console.WriteLine();
                    explicitloading = (Console.ReadKey().Key == ConsoleKey.Y);

                    foreach (var cat in cats)
                    {
                        if (explicitloading)
                        {
                            Console.Write("是否为" + cat.CategoryName + "加载显示加载产品?(Y/N):");
                            Console.WriteLine();
                            var c = (Console.ReadKey().Key == ConsoleKey.Y);
                            if (c)
                            {
                                var products = db.Entry(cat).Collection(c2 => c2.Products);
                                if (!products.IsLoaded) products.Load();
                            }
                        }
                        Console.WriteLine($"{cat.CategoryName} 有{cat.Products.Count}个产品");
                    }


                }
            }
        }

image.png

主要代码:

第一步禁用

  db.ChangeTracker.LazyLoadingEnabled = false;

第二步:

 var products = db.Entry(cat).Collection(c2 => c2.Products);
 if (!products.IsLoaded) products.Load();


使用EF Core操作数据

增删改过后,请调用SaveChanges方法


插入实体,显示产品

 static bool AddProduct(int categoryID, string productName, decimal? price)
        {
            using (var db = new NorthWind())
            {
                var newp = new Product
                {
                    CategoryID=categoryID,
                    ProductName=productName,
                    Cost=price
                };
                db.Products.Add(newp);
                int affected = db.SaveChanges();
                return affected == 1;
            }
        }
        static void ListProducts()
        {
            using (var db = new NorthWind())
            {
                Console.WriteLine("{0,-3} {1,-35} {2,8} {3,5} {4}","ID","Product Name","Cost","Stock","Disc.");
                var _p = db.Products.OrderByDescending(x=>x.Cost);
                foreach (var item in _p)
                {
                    Console.WriteLine("{0:000} {1,-35} {2,8:$#,##0.00} {3,5} {4}",item.ProductID,item.ProductName,item.Cost,item.Stock,item.Discontinued);
                }
            }
        }

在Main方法测试

   AddProduct(6, "杨洋界面", 599M);
            ListProducts();

由于我测试,运行了2次,所以有两条记录

image.png



更新实体

先获取一遍,然后修改属性

  using (var db=new NorthWind())
            {
                var p = db.Products.FirstOrDefault(x => x.ProductName == "杨洋界面");
                if (p != null)
                {
                    p.Cost = 2021M;
                }
                db.SaveChanges();
            }
            ListProducts();

image.png


删除实体

删除实体,先找出 需要删除的 实体集合,然后RemoveRange

 ListProducts();
            using (var db = new NorthWind())
            {
                var p = db.Products.Where(x => x.ProductName.StartsWith("杨洋"));
                db.Products.RemoveRange(p);
                db.SaveChanges();
            }
            ListProducts();

先列出来一遍

image.png

删除后

image.png


池化数据库环境

DbContext类是可以销毁的,并且是按照单一工作单元原则设计的。

ASP.NET CORE与EF CORE的相关的特点:在构建Web应用程序和Web服务时,可通过汇集数据库上下文来提高代码的效率。这将允许创建和释放尽可能多的DbContext派生对象,从而确保代码仍然是有效的。

https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-2.0/#dbcontext-pooling


事务

每次SaveChanges方法,都会启动隐式事务,以便错误后回滚。

原子性,一致性,隔离性,持久性,ACID


隔离级别                                锁                                                                                             允许的完整性问题

ReadUncommitted     无                                                                                                       脏读,不可重复读,幻像数据

ReadCommitted         当编辑时,应用读取锁以组织其他用户读取记录,直到事务结束            不可重复读和幻像数据

RepeatableRead         当读取时,应用编辑锁以阻止其他用户编辑记录,知道事务结束            幻影数据

Searializable               应用键范围的锁以防止任何可能影响结果的操作,包括插入和删除        无

Snapshot                    无                                                                                                       无


定义显式事务

引入using Microsoft.EntityFrameworkCore.Storage;

    AddProduct(6, "杨洋界面1", 599M);
            AddProduct(6, "杨洋界面2", 599M);
            ListProducts();
            using (var db = new NorthWind())
            {
                using (IDbContextTransaction t=db.Database.BeginTransaction())
                {
                    Console.WriteLine("隔离级别:"+t.GetDbTransaction().IsolationLevel);
                    var p = db.Products.Where(x => x.ProductName.StartsWith("杨洋"));
                    db.Products.RemoveRange(p);
                    db.SaveChanges();
                    t.Commit();
                }
            }
            ListProducts();

效果:

新增成功

image.png

然后定义显示事务,删除产品

image.png



====================www.ayjs.net=================


推荐您阅读更多有关于“C#8.0core3,”的文章

猜你喜欢

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

  查看权限

合肥科大智能常年招聘C# .NET CORE,vue前端,JAVA开发,有想换工作的私聊我AY唯一QQ:875556003和AY交流

抖音号:wpfui 工作是wpf,不接活,目前主要折腾maui

AYUI8社区版Github地址:前往获取

杨洋(AaronYang简称AY,安徽六安人)目前是个人,还没公司AY唯一QQ:875556003和AY交流

高中学历,2010年开始web开发,2015年1月17日开始,兴趣学习研究WPF

声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费.AY主要靠卖技术服务挣钱

不是从我处购买的ayui7源码,我不提供任何技术服务,如果你举报从哪里买的,我可以帮你转正为我的客户,并送demo

查看捐赠

AYUI7.X MVC教程 更新如下:

第一课 第二课 程序加密教程

vs2015 企业版密钥HM6NR-QXX7C-DFW2Y-8B82K-WTYJV

vs2017 企业版密钥NJVYC-BMHX2-G77MM-4XJMR-6Q8QF

标签列表