EF 知识点

EntityFramework知识点记录

ORM

记录EF之前,首先讲讲什么是ORM(对象关系映射)

ORM:面向对象的对象模型和关系型数据库之间的相互转换。基于数据库的数据存储,实现的一个面向对象接口。

O(对象模型):根据数据库表建立的一个个实体

R(数据接口):我们建立的数据库表

M(映射):从数据库到对象模型的映射。

优点:不需要考虑SQL,大大提高工作效率。减少维护数据访问层的成本

发展史

EF1.0时,只支持Database First,数据库优先。必须将设计器指向一个现有的数据库。

EF4时,支持Model First,模型优先。可以使用设计面板来创建实体类。最终都会生成EDMX文件。

EF4.1时,支持Code First,代码优先。不需要依赖设计类,只需要编写类就行,会自动映射成表。

三种风格

EF有三种开发风格。

Database First:最适合于使用已经存在的数据库的应用。

如果数据库已经存在,只要花一点时间就可以编写数据访问层。实体数据模型(EDM)可以从数据库生成。

Model First:使用EDMX设计器来设计我们的模型。数据库和类,会从这个设计器来生成。

使用该EDM可以创建概念模型和数据库,使用这种方法原因是我们需要使用设计器来展示图形关系。

Code First:所有的领域模型都是以类编写。这些类会建立我们的EDM,数据库模式会从中创建。

对于所有业务逻辑,以类来进行实现。使用原因如下

1. 数据库只作为模型的持久化操作,没有数据逻辑。

2. 完全控制代码,没有自动生成的模型和上下文。

3. 数据库不会手动更改,模型类总是更改,数据库基于模型变更。

选择条件:如果想要类图使用Model First。已有数据库 使用Database First。已有模型类 使用Code First。

架构

EF构建在provider架构上,当创建LINQ查询是,EF引擎会链接provider并生成SQL。发送数据库。

概念数据模型

EDM全称Entity Data Model(实体数据模型)。使用EF,必须创建EDM,也就是.edmx文件。它定义了我们的概念模型类,类之间的关系与映射。

一旦创建的EDM就可以对概念模型执行所有的CRUD操作。EF会将操作翻译成SQL,并发送数据库。

ObjectContext:用来在创建的EDM上进行操作。它是EF的主要对象,负责:管理数据库连接、执行操作、追踪模型更改。

当我们想保存一个新的或者更改对象到数据库时,我们必须调用SaveChanges方法。

DbContext:ObjectContext的封装类,是一个更好的API。一般均使用此类,来进行操作。

DbContext:Code First的核心类,数据库抽象。包含表内容。

DbSet<T>:DbContext有泛型的集合属性,每个属性的类型是DbSet<T>对应于每个表。表中的数据定义在T的属性里面。

Migration迁移:如果需要添加或修改表,EF通过Migration迁移,功能来解决。允许你通过C#代码更改数据库结构,可以添加、删除、修改、添加索引。

数据库上下文

数据库上下文是数据库的抽象,它集成DbContext。需要传递链接字符串。需要使用DbSet来表示一个表。如不指定名称,则默认与上下文同名。

1  public class EFContext : DbContext
2     {
3         public EFContext() : base("EFConnection")
4         { }
5 
6         public DbSet<Donator> Donators { get; set; }
7     }

四种加载方式

EF加载数据的方式一共有四种:预加载、延迟加载、显示加载、按需加载。不同的加载方式适用于不同的情况。

延迟加载:

又叫惰性加载、懒加载。在需要或使用的时候,加载数据。默认情况下,EF会使用延迟加载方式。

在数据库上下文中表示为:Configuration.LazyLoadingEnabled = true; 如果false,则不会启用延迟加载。

属性或集合上添加virtual,会让此属性懒加载。就是访问时不会加载,只有请求他时,才会加载数据。

同时懒加载需要在数据库上下文中添加标记。默认是启用延迟加载状态

public UserDbContext()
    : base("name=UserDbContext")
{
    this.Configuration.LazyLoadingEnabled = true;
}

下面展示一个延迟加载的例子

using (var dbcontext=  new ModelFirstDemoEntities())
{
            #region 延迟加载:用的时候加载
            var customers = from c in dbcontext.Customer
                            select c;

            foreach (var cus in customers)
            {
                Console.WriteLine(cus.Id);
                foreach (var order in cus.Order)  //根据导航属性,自动查询客户的订单
                {
                    Console.WriteLine(order.OrderContent);
                }
            }
            #endregion
}

上文中的例子中,对表Customer进行了一次查询。然后每循环访问一次Order就会进行一次查询。

预加载:

又叫贪婪加载,如果想让数据一次性全部加载到内存中,那么需要使用.Include(T)方法。

using (var dbcontext=  new ModelFirstDemoEntities())
{
            #region 贪婪加载: 一次查询加载全部数据
            var q = from t in dbcontext.Customer.Include("Order")
                    select t;

            foreach (var cus in q)
            {
                Console.WriteLine("Teacher : {0}", cus.Id);
                Console.WriteLine("Respective Courses...");
                foreach (var order in cus.Order)
                {
                    Console.WriteLine("Course name : {0}", order.OrderContent);
                }
                Console.WriteLine();
            }
            #endregion

}

Include方法,是针对IQueryble的扩展方法。只有一次数据交互过程。可以减少与数据库交互次数。

但如果数据量很大,一次性将所有数据载入内存是不明智的。

显示加载:

显示加载和延迟加载类似,不过需要手动关闭EF的延迟加载属性。通过Configuration.LazyLoadingEnabled = false

以下代码中,首先关闭延迟加载,然后使用Load(),启用延迟加载。那么会形成4次查询。

可以通过判断条件,对加载方式进行控制,过滤掉无用的数据,从而减少数据的交互次数。

using (var dbcontext= new ModelFirstDemoEntities())
{
    dbcontext.Configuration.LazyLoadingEnabled = false;
            #region 显式加载:查询部分列数据,前提关闭 懒加载
            //查询表中部分列的数据
            var items = from c in dbcontext.Customer
                        select c;
            foreach (var item in items)
            {
                //条件判断,只加载满足条件的数据,减少访问数据库的次数
                if (item.Id < 5)
                {
                    dbcontext.Entry(item).Collection(c => c.Order).Load();
                    Console.WriteLine(item.CusName);
                }

                foreach (var order in item.Order)
                {
                    Console.WriteLine("Course name : {0}", order.OrderContent);
                }
            }
            #endregion
}

按需加载:

按需加载,就只是单纯的加载需要的列

   #region 按需加载:查询部分列数据
            //查询表中部分列的数据
            var items = from c in dbcontext.Customer
                        where c.Id < 10
                        select new { Id = c.Id, CName = c.CusName, OrderCount = c.Order.Count() };
            foreach (var item in items)
            {
                Console.WriteLine(item.CName);
            }
            #endregion

封装方法

先看一个标准的查询封装。

#region 按条件查询:LoadItems(Func<T, bool> whereLambda)
        public IQueryable<T> LoadItems(Func<T, bool> whereLambda)
        {
            return MyBaseDbContext.Set<T>().Where(whereLambda).AsQueryable();
        }
 #endregion

这样写的Func委托,会先查出所有的数据,然后根据条件where多次。

对于这种情况,我们需要改成 Expression<Func<TObject, bool>>

使用表达式树,会带上查询生成一条SQL。

AutoMapper

使用按需加载会投影出对应的类,如果表字段非常多,导航属性也非常多,那么手动映射就不方便了。

接下来,我们使用AutoMapper来完成映射。

 

 

 

创建数据库

使用两种方法可以创建数据库。1. 通过数据迁移。2. 通过EF数据库的API

通过API创建

1 using (var context = new EFContext())
2             {
3                 context.Database.CreateIfNotExists();
4

通过数据迁移

开启数据迁移:Enable-Migrations。开启后,会生成Configuration文件。可以在此文件中,进行种子填充。

显示迁移:Add-Migration。当开启数据迁移后,在需要修改数据库,则可以选择显示迁移,会生成多个迁移文件。

退回指定迁移:Update-Database  -TargetMigration 迁移名称。

更新数据库:update-database

注意:如需进行种子填充,需要开始Configuration文件中的,AutomaticMigrationDataLossAllowed 和 AutomaticMigrationsEnabled

CURD

写一个增删改查的小例子

 1 public async Task<string> Index()
 2         {
 3             StringBuilder strText = new StringBuilder();
 4             using (var context = new EFContext())
 5             {
 6                 var donators = new List<Donator>{
 7                     new Donator { Name="A", Amount=1.00m, DonatorDate=DateTime.Now },
 8                     new Donator { Name="B", Amount=2.00m, DonatorDate=DateTime.Now },
 9                     new Donator { Name="C", Amount=3.00m, DonatorDate=DateTime.Now }
10                 };
11                 context.Donators.AddRange(donators);
12                 await context.SaveChangesAsync();
13                 strText.Append("添加完成 \n");
14 
15                 if (context.Donators.Any())
16                 {
17                     var updateDonators = context.Donators.First(p => p.Name == "A");
18                     updateDonators.Name = "AA";
19                     await context.SaveChangesAsync();
20                 }
21 
22                 context.Donators.Remove(context.Donators.First(p => p.Name == "B"));
23                 await context.SaveChangesAsync();
24 
25                 foreach (var donator in context.Donators)
26                 {
27                     strText.Append(donator.Name + "\n");
28                 }
29 
30             }
31             return strText.ToString();
32         }

初始化器

初始化器会在首次实例化过程期间或者EF首次访问数据库时运行。初始化器有三种:

CreateDatabaseIfNotExists: 指如果数据库不存在则创建。

DropCreateDatabaseIfModelChanges:指如果模型改变了就销毁之前的数据库在创建。

DropCreateDatabaseAlways:首次在应用程序域中使用上下文时,重新创建数据库。

MigrateDatabaseToLatestVersion:在应用程序启动时自动升级

在方法中重写Seed,可以设置数据库种子,并且进行初始化数据。

 1 //模型改变就销毁数据库
 2     public class InitializerDrop : DropCreateDatabaseIfModelChanges<EFContext>
 3     {
 4         protected override void Seed(EFContext context)
 5         {
 6             base.Seed(context);
 7         }
 8 
 9     }
10     //如果数据库不存在创建
11     public class InitializerCreate : CreateDatabaseIfNotExists<EFContext>
12     {
13         protected override void Seed(EFContext context)
14         {
15             base.Seed(context);
16         }
17     }
18     //总是销毁再创建
19     public class InitializerDropAlways : DropCreateDatabaseAlways<EFContext>
20     {
21         protected override void Seed(EFContext context)
22         {
23             context.PayWays.AddRange(new List<PayWay> {
24                 new PayWay { Name="支付宝" },
25                 new PayWay { Name="微信" },
26                 new PayWay { Name="QQ红包" }
27             });
28         }
29     }

可以设置默认初始化器

在数据库上下文中,进行定义即可。

MigrateDatabaseToLatestVersion:当你发布部署应用程序的时候,可能希望当程序启动的时候它自动更新数据库(更新应用任何未更新的迁移),
你可以通过注册 MigrateDatabaseToLatestVersion 数据库初始化器来实现这一点,数据库初始化器只包含一些逻辑检查用于确保数据库被正确设置,
这个逻辑检查将会在AppDomain 的 context 第一次被使用的时候执行。
 public EFContext() : base("name=EFConnection")
        {
            Database.SetInitializer(new InitializerDropAlways());
       Database.SetInitializer(new MigrateDatabaseToLatestVersion<EFContext, Configuration>("")); }

属性配置

类的特性配置,主要是两个命名空间:System.ComponentModel.DataAnnotations.Schema  / System.ComponentModel.DataAnnotations

代码中设置了,表明、主键、列名、长度等

 [Table("CHENXY_PAYWAY")]
    public class PayWay
    {
        [Key]
        public int Id { get; set; }
        [MaxLength(8,ErrorMessage = "支付方式的名称不能大于8")]
        public string Name { get; set; }
    }

[Table("CHENXY_DONATOR")]
    public class Donator
    {
        [Key]
        [Column]
        public int DonatorId { get; set; }
        [StringLength(10, MinimumLength = 2)]
        public string Name { get; set; }
        public decimal Amount { get; set; }
        public DateTime DonatorDate { get; set; }
    }

数据库映射

DbContext类有一个OnModelCreating方法,用于配置数据库模式的映射。

 1  public class EFContext : DbContext
 2     {
 3         public EFContext() : base("name=EFConnection")
 4         { }
 5 
 6         public DbSet<Donator> Donators { get; set; }
 7         public DbSet<PayWay> PayWays { get; set; }
 8 
 9         protected override void OnModelCreating(DbModelBuilder modelBuilder)
10         {
11             //设置表名和主键
12             modelBuilder.Entity<Donator>().ToTable("CHENXY_DONATOR").HasKey(m => m.DonatorId);
13             //列名映射
14             modelBuilder.Entity<Donator>().Property(m => m.DonatorId).HasColumnName("Id");
15             modelBuilder.Entity<Donator>().Property(m => m.Name).IsRequired().IsUnicode().HasMaxLength(10);
16             base.OnModelCreating(modelBuilder);
17         }
18     }

伙伴类 

数据库映射的方法,只适合于少量的表。如果表数据比较多,可以使用伙伴类。

为每个实体类单独创建一个伙伴类,然后在OnModelCreating方法中调用这些伙伴类。

public class DonatorMap:EntityTypeConfiguration<Donator>
    {
        public DonatorMap() {
            ToTable("DonatorFromConfig");
            Property(m => m.Name).IsRequired().HasColumnName("DonatorName");
        }
    }

 public class EFContext : DbContext
    {
        public EFContext() : base("name=EFConnection")
        { }

        public DbSet<Donator> Donators { get; set; }
        public DbSet<PayWay> PayWays { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new DonatorMap());
            base.OnModelCreating(modelBuilder);
        }
    }

可空类型 

EF会自动推断类型,如果希望创建的是可空类型。一、使用DateTime?。二、使用 IsRequire() 必填,使用 IsOptional() 选填。

一对多

public class Donator
    {
        public Donator() {
            PayWays = new HashSet<PayWay>();
        }

        public int DonatorId { get; set; }
        public string Name { get; set; }
        public decimal Amount { get; set; }
        public DateTime DonatorDate { get; set; }
        public virtual ICollection<PayWay> PayWays { get; set; }
    }

此时会在PayWay表中,添加DonatorId字段。也可以指定外键字段,使用HasForeignKey

 public DonatorMap()
        {
            ToTable("DonatorFromConfig");
            Property(m => m.Name).IsRequired();
            HasMany(d => d.PayWays)
                .WithRequired()
                .HasForeignKey(p => p.DonatorId);
        }

或者使用 [ForeignKey("DonatorId2")]

一对一

 public class Person
    {
        public int PersonId { get; set; }
        public string Name { get; set; }
        public bool IsActive { get; set; }
        public virtual Student Student { get; set; }
    }


    public class Student
    {
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
        public string CollegeName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }

可以使用WithOption来制定一对一的关系

public class StudentMap : EntityTypeConfiguration<Student>
    {
        public StudentMap()
        {
            HasRequired(p => p.Person).WithOptional(s => s.Student);
            HasKey(s => s.PersonId);
        }
    }

多对多

HasMany(p => p.Companies)
            .WithMany(c => c.Persons)
            .Map(m =>
            {
                m.MapLeftKey("PersonId");
                m.MapRightKey("CompanyId");
            });

 

 

 

 

zhanweifu

转载于:https://www.cnblogs.com/chenxygx/p/5084976.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值