.Net Core 核心知识点(七)--熟悉EFCore(2)

EFCore 实体的配置

        约定大于配置:如果开发过程中基于框架默认的规则,就可以省去很多配置工作,如果默认的配置满足不了实际开发中的需求,再进行更精细的配置。常用的默认规则有:

        1:表名采用DbContext中的对应的Dbset<T>的属性名。如下图,如果没有在Config类中执行表名,EF Core就会默认用这些DbSet<T>属性的名字; 

        2:数据表列的名字采用实体类属性的名字,列的数据类型采用和实体类属性类型最兼容的类型,没有在Config类中设置长度或者不能为空时,框架会自动给max或者可为空。

         3:数据表列的可空性取决于对应实体类属性的可空性。

        4:名字为Id的属性为主键,如果主键为short,int 或者long类型,则默认采用自增字段,如果主键为Guid类型则默认采用默认的Guid生成机制生成主键值


两种配置方式

  • Data Annotation

        把配置以特性(Annotation)的形式标注在实体类的定义中,这种方式的优点是简单,不需要写太多代码,缺点就是程序耦合性太高,不利于修改;如下所示

  • Fluent Api

        通过在对应Config类中以代码的方式,利用EntityTypeBuilder来进行配置:优点就是程序耦合度降低了,可以灵活的修改,缺点是比较复杂

  • 推荐用FluentApi 的方式,   

        因为更具灵活性,比如我需要根据不同的数据库类型,来设置不同的字段长度,我就可以在Config类中增加if else 判断,来设置不同的字段长度。


  • 常用FluentApi 

  1. 视图与实体类映射:builder.ToView("blogsView")如果数据库中不是表,而是自定义的 视图,则就需要用到ToView方法,
  2. 排除属性映射:builder.lgnore(b => b. Name2);如果我们不需要将类的属性映射到数据库表的字段时,我们可以使用Ignore方法将此属性排除,不与数据库发生关系
  3. 配置列名:builder.Property(b=>b.BlogId).HasColumnName("blog_id");
  4. 配置列数据类型:builder.Property(e =>e.Title).HasColumnType("varchar(200)")
  5. 配置主键 默认把名字为Id或者“实体类型+Id“的属性作为主键,也可以用HasKey(来配置其他属性作为主键。builder.HasKey(c =>c.Number);
  6. 生成列的值 builder.Property(b =>b.Number).ValueGeneratedOnAdd();
  7. 可以用HasDefaultValue(为属性设定默认值builder.Property(b =>b.Age).HasDefaultValue(6);
  8. 索引
    1. builder.HasIndex(b => b.Url);
    2. 复合索引builder.HasIndex(p => new { p.FirstName,p.LastName });
    3. 唯一索引:IsUnique();
    4. 聚集索引:IsClustered();

  • EF Core 主键设置

  1. EF Core支持多种主键生成策略: 自动增长; Guid; Hi/Lo算法等。
  2. 自动增长。优点:简单;缺点:数据库迁移以及分布式系统中比较麻烦;并发性能差。long、int等类型主键,默认是自增。因为是数据库生成的值,所以SaveChanges后会自动把主键的值更新到Ia属性。试验一下。场景:插入帖子后,自动重定向帖子地址。
  3. 自增字段的代码中不能为Id赋值,必须保持默认值0.否则运行的时候就会报错。
  4. 在SQLserver数据库中,不要把Guid主键作为聚集索引,在Mysql中,插入频繁的表不要用Guid主键

  • 通过代码查看EFCore生成的sql语句

方式1.

        在继承自DBContext的类中生成一个LoggerFactory,然后在OnConfiguration重载方法中增加 optionsBuilder.UseLoggerFactory(loggerFactory);就可以实现将EFCore底层生成的SQL语句打印在控制台中(根据项目需要,如果需要打印在日志中,则LoggerFactory中添加生成到文件的方法即可)

方式2.

        利用DBContextOptionsBuilder.LogTo()方法输出简单日志

 

方式3.

        通过查询出的结果集的ToQueryString()方法查看 , 我们在使用查询Linq语句的where方式时,返回值是IQueryable<T>类型的结果集,它包含了一个ToQueryString()方法,可以打印出执行的SQL语句。(注意:该方法只能针对查询方法能获取SQL语句)

 static async Task Main(string[] args)
        {
            using (MyDBContext dBContext = new MyDBContext())
            {
                
                //查询
                IQueryable<Book> books = dBContext.Books.Where(b => b.Author == "liyumin");
                foreach (var book in books)
                {
                    Console.WriteLine(book.ToString());
                }
                Console.WriteLine(books.ToQueryString()); 
            }
        }

  • 同样的C#语句在不同的数据库中被EF Core翻译成不同的SQL语句

        比如取前三行数据,SQLserver,Mysql ,oracle三种数据库的语句各不相同

SqlServer : select top(3) * from t;
Mysql: select * from t limit 3;
Oracle : select * from t where rownum<=3;
  • EF Core 产生的数据迁移脚本适合数据库类型相关的,不同把与A类型的数据库产生的Migration数据库脚本用到与B类型数据库的对接中使用,如下,生成的Migration脚本都有很强的Mysql标记,对于其他类型数据库是使用不了的。


EFCore 一对多关系配置

        实际项目中,我们很少会只对一张表进行查询的操作,一般都是要关联多张业务表进行数据的查询,EFCore 不仅支持单实体类的操作,更支持多实体的关系操作。

  • 一对多的配置, 
  1. 例如一个篮球俱乐部队有多个球员,俱乐部和球员就是一对多的关系,建立相关实体类如下:
 /// <summary>
    /// 篮球队
    /// </summary>
    public class BasketBallTeam
    {
        /// <summary>
        /// 主键Id
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 球队代码
        /// </summary>
        public string TeamCode { get; set; }
        /// <summary>
        /// 球队名称
        /// </summary>
        public string TeamName { get; set; }
        /// <summary>
        /// 球队总经理
        /// </summary>
        public string TeamManager { get; set; }
        /// <summary>
        /// 球队所在城市
        /// </summary>
        public string TeamCity { get; set; }
         /// <summary>
        /// 旗下所有球员
        /// </summary>
        public List<BasketBallPlayer> players { get; set; } = new List<BasketBallPlayer>(); 
    }

// <summary>
    /// 篮球运动员
    /// </summary>
    public class BasketBallPlayer
    {
        public int Id { get; set; }
        /// <summary>
        /// 球员姓名
        /// </summary>
        public string PlayerName { get; set; }
        /// <summary>
        /// 球员场上位置
        /// </summary>
        public string PlayerPosition { get; set; }
        /// <summary>
        /// 所属球队
        /// </summary>
        public BasketBallTeam team { get; set; }
  • 配置一对多的关系

应用HasXXX().WithXXX()方法进行配置,

  1. 一对多:HasOne(...).WithMany(...);
  2. 一对一:HasOne(...).WithOne(...);
  3. 多对多:HasMany(...).WithMany(...);

当前例子中,我们是以一个球队对应多个球员的逻辑,那么就可以HasOne(球队).WithMany(球员),两表的关系配置,既可以配置在一端的Config类,也可以配置在多端的Config类,这里的端指的是继承了IEntityTypeConfiguration<T>的类:

我们这里在球员端配置一对多的关系;

public class BasketBallPlayerConfig : IEntityTypeConfiguration<BasketBallPlayer>
    {
        public void Configure(EntityTypeBuilder<BasketBallPlayer> builder)
        {
            builder.ToTable("BasketBall_Player");
            builder.Property(e => e.PlayerName).HasMaxLength(200).IsRequired();
            //进行一对多的配置
            builder.HasOne(c => c.team).WithMany(p => p.players).IsRequired();
        }
    }

然后我们执行数据库Migration迁移命令,生成球队表和球员表如下:可以看到,EFCore自动在球员表中增加了一个teamId字段,作为球员和球队关联的字段,也就是对应球队Team表中的Id字段的值。

我们通过代码的形式往两个表中插入数据,然后运行代码,看数据是否正常插入

static async Task Main(string[] args)
        {
            using (MyDBContext dBContext = new MyDBContext())
            {
                BasketBallTeam team = new BasketBallTeam()
                {
                    TeamCode = "NBA020",
                    TeamCity = "圣安东尼奥",
                    TeamName = "马刺队",
                    TeamManager = "RC-布福德",
                };
                team.players.Add(new BasketBallPlayer()
                {
                    PlayerName = "文班亚马",
                    PlayerPosition = "中锋",
                });
                team.players.Add(new BasketBallPlayer()
                {
                    PlayerName = "克里斯保罗",
                    PlayerPosition = "控卫",
                });
                team.players.Add(new BasketBallPlayer()
                {
                    PlayerName = "索汉",
                    PlayerPosition = "小前锋",
                });
                dBContext.basketBallTeams.Add(team);
                await dBContext.SaveChangesAsync();
            }
        }

  • 一对多关联数据的获取

利用QueryableExtensions扩展类的Include方法,可以将一对多关联的关系数据查询出来:

static async Task Main(string[] args)
        {
            using (MyDBContext dBContext = new MyDBContext())
            {
                BasketBallTeam team = dBContext.basketBallTeams.Include(t => t.players).Single(p => p.Id == 1);
                Console.WriteLine(team);
                Console.WriteLine("--------------------");
                foreach (var player in team.players)
                {
                    Console.WriteLine(player);
                }
            }
        }

我们把生成的SQL语句打印在控制台,可以看到使用了Left Join左连接查询的方式:

  • 额外的外键字段

上面我们用到的Include方法,用在关联查询时,会将两张关联表的所有字段全部查询出来,但是在实际项目开发中,我们大部分时间只需要查询关联表中的部分字段出来使用,如果还使用include方法,就会造成查询数据的冗余:

        1.我们给BasketBallPlayer表单独增加一个teamId属性,用来与team表就行关联

        2.然后再Config类中配置HasForeignKey(c=>c.teamId),指定这个属性为外键

         3.然后查询的时候,不需要使用Include方法,直接通过球员Id查询,即可带出球队Id,可以看出,查询的语句也没有了left join关键字,避免了数据的冗余;

         4.我们还可以利用Linq的Select投影机制,让EF Core只查询我们需要的playName字段:

var ballPlayer = dBContext.BasketBallPlayers
                    .Select(d => new { PlayerName = d.PlayerName, TeamId = d.TeamId })
                    .Single(p => p.PlayerName == "文班亚马");
Console.WriteLine("球员:" + ballPlayer.PlayerName + ",球队Id:" + ballPlayer.TeamId);


  • EF Core 单向导航属性

        前面我们在处理一对多的关系时,在Team表和Player表中都创建了一个指向关联表的实体类或者实体类集合的一个数据,这种在双方都创建属性的机制叫做双向导航属性。

但是在实际的业务中,我们往往会遇到这种情况,表A的一些字段要频繁的和其他业务表产生数据关联,比如一个用户表User,其他很多业务表中都有类似于操作员的字段,这个操作员是和用户表有关联的,这种情况下,如果在User表实体类中针对每个与它有关系的业务表都新增一个属性与之对应,那显然不太实际。此时我们只需在其他业务表中定义指向User的类作为属性即可,这种就叫做单向导航属性。

  1. 我们新建两个类,一个User类,一个下单的订单类Order,Order类中包含两个字段指向User表的,一个是下单人OrderCreator,一个是订单审核人OrderApprover
public class User
    {
        /// <summary>
        /// 用户Id
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 用户姓名
        /// </summary>
        public string UserName { get; set; }
    }
public class Order
    {
        /// <summary>
        /// 订单号
        /// </summary>
        public int OrderId { get; set; }
        /// <summary>
        /// 订单内容
        /// </summary>
        public string OrderContent { get; set; }
        /// <summary>
        /// 下单人
        /// </summary>
        public User OrderCreator { get; set; }
        /// <summary>
        /// 订单审核人
        /// </summary>
        public User OrderApprover { get; set; }
        
         public override string ToString()
        {
            return $"订单编号:{OrderId},订单内容:{OrderContent},下单人:{OrderCreator.UserName},审核人{OrderApprover.UserName}";
        }
    }

        2.对实体类新增Config实体配置类

        因为是配置单向导航属性,所以我们只需要在OrderConfig类中配置对应关系,并且WithMany()方法不用设置参数

public class UserConfig : IEntityTypeConfiguration<User>
    {
        public void Configure(EntityTypeBuilder<User> builder)
        {
            builder.ToTable("T_Users");
        }
    }
    
    public class OrderConfig : IEntityTypeConfiguration<Order>
    {
        public void Configure(EntityTypeBuilder<Order> builder)
        {
            builder.ToTable("T_Orders");
            builder.HasOne(o => o.OrderCreator).WithMany().IsRequired();
            builder.HasOne(o => o.OrderApprover).WithMany().IsRequired();
        }
    }

        3.执行数据库迁移命令,将表生成到数据库中

        4.插入相应的数据

 static async Task Main(string[] args)
        {
            using (MyDBContext dBContext = new MyDBContext())
            {
                User userKobe = new(){UserName="kobe"};
                User userJordon = new(){UserName = "Jordon"};
                Order order = new Order()
                {
                    OrderContent = "下单商品",
                    OrderCreator = userKobe,
                    OrderApprover = userJordon
                };
                dBContext.orders.Add(order);
                await dBContext.SaveChangesAsync();
            }
        }

 

        5.通过EFCore查询语句查询对应的数据

 Order order = dBContext.orders.Include(o=>o.OrderCreator)
                                 .Include(r=>r.OrderApprover)
                                 .Single(p => p.OrderId == 1);
 Console.WriteLine(order);


  • EF Core自引用结构树

        在我们实际的项目开发的业务中,会遇到这种实体类,它的上级只有一个,它的下级有多个的场景,比如省市区区域字典这种

 

        1.像这种实体在EFCore中就可以定义为自引用树的实体类,在该种类的定义中,包含一个定义为父类的属性,和一个定义为子节点集合的一个属性,如下代码的定义: 

public class AreaDict
    {
        /// <summary>
        /// 区域Id
        /// </summary>
        public int  AreaId {get;set;}
        /// <summary>
        /// 区域名称
        /// </summary>
        public string AreaName { get; set; }
        /// <summary>
        /// 区域级别
        /// </summary>
        public int AreaLevel { get; set; }
        /// <summary>
        /// 父级区域信息
        /// </summary>
        public AreaDict ParentArea { get; set; }
        /// <summary>
        /// 子节点区域
        /// </summary>
        public List<AreaDict> ChildAreas { get; set; }
    }

        2.在实体类对应的Config类中定义对应的引用关系

public class AreaDictConfig : IEntityTypeConfiguration<AreaDict>
    {
        public void Configure(EntityTypeBuilder<AreaDict> builder)
        {
            builder.ToTable("T_Area_Dicts");
            builder.HasKey(e => e.AreaId);
            //设置与父级和子级的关联关系
            builder.HasOne(a => a.ParentArea).WithMany(r => r.ChildAreas) ;
        }
    }

         3.执行数据库迁移命令,查看数据库表是否建立

         4.使用EFCore 语句插入一些数据:

static async Task Main(string[] args)
        {
            using (MyDBContext dBContext = new MyDBContext())
            {
               //省级
                AreaDict province = new AreaDict()
                {
                    AreaName = "江西省",
                    AreaLevel = 2,
                    ParentArea = null,
                    ChildAreas = new List<AreaDict>()
                };
                //地级市
                AreaDict cityNanchang = new AreaDict()
                {
                    AreaName = "南昌市",
                    AreaLevel = 3,
                    ParentArea = province,
                    ChildAreas = new List<AreaDict>()
                };
                AreaDict cityJiujiang = new AreaDict()
                {
                    AreaName = "九江市",
                    AreaLevel = 3,
                    ParentArea = province,
                    ChildAreas = new List<AreaDict>()
                };
                AreaDict cityYichun = new AreaDict()
                {
                    AreaName = "宜春市",
                    AreaLevel = 3,
                    ParentArea = province,
                    ChildAreas = new List<AreaDict>()
                };
                province.ChildAreas.Add(cityNanchang);
                province.ChildAreas.Add(cityJiujiang);
                province.ChildAreas.Add(cityYichun);

                //县级市
                AreaDict cityNanchangxian = new AreaDict()
                {
                    AreaName = "南昌县",
                    AreaLevel = 4,
                    ParentArea = cityNanchang,
                };
                AreaDict cityLushan = new AreaDict()
                {
                    AreaName = "庐山市",
                    AreaLevel = 4,
                    ParentArea = cityJiujiang
                };
                AreaDict cityZhangshushi = new AreaDict()
                {
                    AreaName = "樟树市",
                    AreaLevel = 4,
                    ParentArea = cityYichun
                };
                cityNanchang.ChildAreas.Add(cityNanchangxian);
                cityJiujiang.ChildAreas.Add(cityLushan);
                cityYichun.ChildAreas.Add(cityZhangshushi);

                await dBContext.areaDicts.AddAsync(province);//把book对象加入Books这个逻辑的表里面
                await dBContext.SaveChangesAsync();//update-database
            }
        }

可以看出,数据按照对应的父子级关系存到了数据库中

  • 我们来根据区域级别进行缩进的打印

        

static async Task Main(string[] args)
        {
            using (MyDBContext dBContext = new MyDBContext())
            {
                List<AreaDict> areaDicts = dBContext.areaDicts.ToList();
                PrintArea(2, areaDicts,null);
            }
        }

        /// <summary>
        /// 递归打印
        /// </summary>
        /// <param name="areaLevel"></param>
        /// <param name="areaDicts"></param>
        /// <param name="parent"></param>
        static void PrintArea(int areaLevel, List<AreaDict> areaDicts,AreaDict parent)
        {
            List<AreaDict> childAreas = areaDicts.Where(a => a.ParentArea == parent).ToList();
            foreach (AreaDict area in childAreas)
            {
                Console.WriteLine(new String('-', area.AreaLevel) +area.AreaName);
                if (area.ChildAreas is object && area.ChildAreas.Count > 0)
                {
                    PrintArea(area.AreaLevel + 1, area.ChildAreas,area);
                }
            }
        }

打印结果为:

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值