• EntityFramework笔记


    参照文档: http://www.cnblogs.com/farb/p/ABPAdvancedTheoryContent.html

    案例:http://pan.baidu.com/s/1c1Qgg28

    一、领域建模和管理实体关系

    二、 使用LINQ to Entities操作实体

    三、预加载

    四、CURD

    五、EF使用视图

    六、EF使用存储过程

    七、异步API

    八、管理并发

    九、事务

    十、数据库迁移

    十一、应用迁移

    十二、EF的其他功能

    一、领域建模和管理实体关系

     1,流利地配置领域类到数据库模式的映射

    namespace FirstCodeFirstApp
    {
        public class Context:DbContext
        {
            public Context()
                : base("name=FirstCodeFirstApp")
            {
            }
    
            public DbSet<Donator> Donators { get; set; }
            public DbSet<PayWay> PayWays { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Donator>().ToTable("Donators").HasKey(m => m.DonatorId);//映射到表Donators,DonatorId当作主键对待
                modelBuilder.Entity<Donator>().Property(m => m.DonatorId).HasColumnName("Id");//映射到数据表中的主键名为Id而不是DonatorId
                modelBuilder.Entity<Donator>().Property(m => m.Name)
                    .IsRequired()//设置Name是必须的,即不为null,默认是可为null的
                    .IsUnicode()//设置Name列为Unicode字符,实际上默认就是unicode,所以该方法可不写
                    .HasMaxLength(10);//最大长度为10
    
                base.OnModelCreating(modelBuilder);
            }
        }
    }

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

    public class DonatorMap:EntityTypeConfiguration<Donator>
    {
        public DonatorMap()
        {
            ToTable("DonatorFromConfig");//为了区分之前的结果
            Property(m => m.Name)
                .IsRequired()//将Name设置为必须的
                .HasColumnName("DonatorName");//为了区别之前的结果,将Name映射到数据表的DonatorName
        }
    }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
           modelBuilder.Configurations.Add(new DonatorMap());
           base.OnModelCreating(modelBuilder);
    }

     2,一对多关系

            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Donator>().HasMany(r => r.PayWays)//HasMany方法告诉EF在Donator和Payway类之间有一个一对多的关系
                    .WithRequired()//WithRequired方法表明链接在PayWays属性上的Donator是必须的,换言之,Payway对象不是独立的对象,必须要链接到一个Donator
                    .HasForeignKey(r => r.DonatorId);//HasForeignKey方法会识别哪一个属性会作为链接
                base.OnModelCreating(modelBuilder);
            }

    2.1,指定约束的删除规则

    public class DonatorTypeMap:EntityTypeConfiguration<DonatorType>
    {
        public DonatorTypeMap()
        {
            HasMany(dt=>dt.Donators)
                .WithOptional(d=>d.DonatorType)
                .HasForeignKey(d=>d.DonatorTypeId)
                .WillCascadeOnDelete(false);//指定约束的删除规则
        }
    }

    2.2调用WillCascadeOnDelete的另一种选择是,从 model builder中移除全局的约定,在数据库上下文的OnModelCreating方法中关闭整个数据库模型的级联删除规则

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    
        modelBuilder.Configurations.Add(new DonatorMap());
        modelBuilder.Configurations.Add(new DonatorTypeMap());
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
        base.OnModelCreating(modelBuilder);
    }

     2.3创建一对多关系的代码

    #region 6.1 一对多关系 例子2
    
    var donatorType = new DonatorType
    {
        Name = "博客园园友",
        Donators = new List<Donator>
        {
            new Donator
            {
                Amount =6,Name = "键盘里的鼠标",DonateDate =DateTime.Parse("2016-4-13"),
                PayWays = new List<PayWay>{new PayWay{Name = "支付宝"},new PayWay{Name = "微信"}}
            }     
        }
    };
    var donatorType2 = new DonatorType
    {
        Name = "非博客园园友",
        Donators = new List<Donator>
        {
    
             new Donator
            {
                Amount =10,Name = "待赞助",DonateDate =DateTime.Parse("2016-4-27"),
                PayWays = new List<PayWay>{new PayWay{Name = "支付宝"},new PayWay{Name = "微信"}}
            }
            
        }
    };
    context.DonatorTypes.Add(donatorType);
    context.DonatorTypes.Add(donatorType2);
    context.SaveChanges();

     

    3,一对一关系

    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; }
    }
    public class StudentMap:EntityTypeConfiguration<Student>
    {
        public StudentMap()
        {
            HasRequired(s=>s.Person)
                .WithOptional(p=>p.Student);//一或零对一
            HasKey(s => s.PersonId);
            Property(s => s.CollegeName)
                .HasMaxLength(50)
                .IsRequired();
        }
    }
    var student = new Student
    {
        CollegeName = "XX大学",
        EnrollmentDate = DateTime.Parse("2011-11-11"),
        Person = new Person
        {
            Name = "Farb",
        }
    };
    
    context.Students.Add(student);
    context.SaveChanges();

    4,多对多

    public class Company
    {
         public Company()
         {
            Persons = new HashSet<Person>();
         }
        public int CompanyId { get; set; }
        public string CompanyName { get; set; }
        public virtual ICollection <Person> Persons { get; set; }
    }
    
    public class Person
    {
        public Person()
        {
            Companies=new HashSet<Company>();
        }
        public int PersonId { get; set; }
        public string Name { get; set; }
        public bool IsActive { get; set; }
        public virtual Student Student { get; set; }
        public virtual ICollection<Company> Companies { get; set; }
    }
    
    public class PersonMap:EntityTypeConfiguration<Person>
    {
        public PersonMap()
        {
            HasMany(p => p.Companies)
                .WithMany(c => c.Persons)
                .Map(m =>
                {
                    m.MapLeftKey("PersonId");
                    m.MapRightKey("CompanyId");
                });
        }
    }
    #region 8 多对多关系
    
    var person = new Person
    {
        Name = "比尔盖茨",
    };
    var person2 = new Person
    {
        Name = "乔布斯",
    };
    context.People.Add(person);
    context.People.Add(person2);
    var company = new Company
    {
        CompanyName = "微软"
    };
    company.Persons.Add(person);
    context.Companies.Add(company);
    context.SaveChanges();
    
    #endregion

     如果我们连接表需要保存更多的数据怎么办?比如当每个人开始为公司干活时,我们想为他们添加雇佣日期。这样的话,实际上我们需要创建一个类来模型化该连接表,我们暂且称为PersonCompany吧。它仍然具有两个的主键属性,PersonId和CompanyId,它还有Person和Company的属性以及雇佣日期的属性。此外,Person和Company类分别都有PersonCompanies的集合属性而不是单独的Person和Company集合属性。

     5,Table per Type(TPT)继承

     派生类加上数据注解,表明他们是独立的表

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string PhoneNumber { get; set; }
    }
    
    [Table("Employees")]
    public class Employee : Person
    {
        public decimal Salary { get; set; }
    }
    
    [Table("Vendors")]
     public class Vendor : Person
     {
         public decimal HourlyRate { get; set; }
     }
     public class Context:DbContext
     {
         public virtual DbSet<Person> People { get; set; }//上面的上下文中,我们只添加了实体Person的DbSet。因为其它的两个领域模型都是从这个模型派生的,所以我们也就相当于将其它两个类添加到了DbSet集合中了,这样EF会使用多 态性来使用实际的领域模型
     }
    #region 1.0  TPT继承
    
    var employee = new Employee
    {
        Name = "farb",
        Email = "farbguo@qq.com",
        PhoneNumber = "12345678",
        Salary = 1234m
    };
    
    var vendor = new Vendor
    {
        Name = "tkb至简",
        Email = "farbguo@outlook.com",
        PhoneNumber = "78956131",
        HourlyRate = 4567m
    };
    
    context.People.Add(employee);
    context.People.Add(vendor);
    context.SaveChanges();
    #endregion

     

    6,Table per Class Hierarchy(TPH)继承

     未使用数据注解指点派生类的表明

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string PhoneNumber { get; set; }
    }
    
    
     public class Employee : Person
     {
         public decimal Salary { get; set; }
     }
    
    
    public class Vendor : Person
    {
        public decimal HourlyRate { get; set; }
    }
    public class Context:DbContext
    {
        public Context():base("ThreeInheritance")
        {
            
        }
    
        public virtual DbSet<Person> Person { get; set; }
    }
    #region 2.0 TPH 继承
      var employee = new Employee
      {
          Name = "farb",
          Email = "farbguo@qq.com",
          PhoneNumber = "12345678",
          Salary = 1234m
      };
     
      var vendor = new Vendor
      {
          Name = "tkb至简",
          Email = "farbguo@outlook.com",
          PhoneNumber = "78956131",
          HourlyRate = 4567m
      };
     
      context.Person.Add(employee);
      context.Person.Add(vendor);
      context.SaveChanges();
     #endregion

    7,Table per Concrete Class(TPC)继承

    如果我们想使用TPC继承,那么要么使用基于GUID的Id,要么从应用程序中传入Id,或者使用能够维护对多张表自动生成的列的唯一性的某些数据库机制

    public abstract class Person
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string PhoneNumber { get; set; }
    }
    
    public class Vendor : Person
    {
        public decimal HourlyRate { get; set; }
    }
    
    public class Employee : Person
    {
        public decimal Salary { get; set; }
    }
    public virtual DbSet<Person> People { get; set; }
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Employee>().Map(m =>
        {
            m.MapInheritedProperties();//MapInheritedProperties方法将继承的属性映射到表中,然后我们根据不同的对象类型映射到不同的表中
            m.ToTable("Employees");
        });
    
        modelBuilder.Entity<Vendor>().Map(m =>
        {
            m.MapInheritedProperties();
            m.ToTable("Vendors");
        });
        base.OnModelCreating(modelBuilder);
    }

    二、 使用LINQ to Entities操作实体

     1,实现多表连接join

    var join1 = from province in db.Provinces
                join donator in db.Donators on province.Id equals donator.Province.Id
                into donatorList//注意,这里的donatorList是属于某个省份的所有打赏者,很多人会误解为这是两张表join之后的结果集
                select new
                {
                    ProvinceName = province.ProvinceName,
                    DonatorList = donatorList
                };
    
    var join2 = db.Provinces.GroupJoin(db.Donators,//Provinces集合要连接的Donators实体集合
        province => province.Id,//左表要连接的键
        donator => donator.Province.Id,//右表要连接的键
        (province, donatorGroup) => new//返回的结果集
        {
            ProvinceName = province.ProvinceName,
            DonatorList = donatorGroup
        }
        );

    三、预加载

     预加载是这样一种过程,当我们要加载查询中的主要实体时,同时也加载与之相关的实体。要实现预加载,我们要使用Include方法。下面我们看一下如何在加载Donator数据的时候,同时也预先加载所有的Provinces数据:

    //预加载,以下两种方式都可以
    var donators2 = db.Donators.Include(d => d.Province).ToList();
    var donators3 = db.Donators.Include("Provinces").ToList();

    这样,当我们从数据库中取到Donators集合时,也取到了Provinces集合。

    四、CURD

    状态描述
    Added 添加了一个新的实体。该状态会导致一个插入操作。
    Deleted 将一个实体标记为删除。设置该状态时,该实体会从DbSet中移除。该状态会导致删除操作。
    Detached DbContext不再追踪该实体。
    Modified 自从DbContext开始追踪该实体,该实体的一个或多个属性已经更改了。该状态会导致更新操作。
    Unchanged 自从DbContext开始追踪该实体以来,它的任何属性都没有改变。

     1,DbSet的Attach方法本质上是将实体的状态设置为Unchanged

    var donator = new Donator
    {
        Id = 4,
        Name = "雪茄",
        Amount = 18.80m,
        DonateDate = DateTime.Parse("2016/4/15 0:00:00")
    };
    using (var db = new DonatorsContext())
    {
        db.Donators.Attach(donator);
        //db.Entry(donator).State=EntityState.Modified;//这句可以作为第二种方法替换上面一句代码
        donator.Name = "秦皇岛-雪茄";
        db.SaveChanges();
    }

    2,可以使用RemoveRange方法删除多个实体 

    3,DbSet的Local属性强制执行一个只针对内存数据的查询

    var query= db.Provinces.Local.Where(p => p.ProvinceName.Contains("")).ToList();

    4,Find方法在构建数据库查询之前,会先去本地的上下文中搜索

    5,通过ChangeTracker对象,我们可以访问内存中所有实体的状态

    using (var db=new DonatorsContext())
    {
        //14.1 证明Find方法先去内存中寻找数据
        var provinces = db.Provinces.ToList();
        //var query = db.Provinces.Find(3);//还剩Id=3和4的两条数据了
    
        //14.2 ChangeTracker的使用
        foreach (var dbEntityEntry in db.ChangeTracker.Entries<Province>())
        {
            Console.WriteLine(dbEntityEntry.State);
            Console.WriteLine(dbEntityEntry.Entity.ProvinceName);
        }
    }
    #endregio

    五、EF使用视图

    使用Database对象的另一个方法SqlQuery查询数据,该方法和ExecuteSqlCommand方法有相同的形参,但是最终返回一个结果集

    var sql = @"SELECT DonatorId ,DonatorName ,Amount ,DonateDate ,ProvinceName from dbo.DonatorViews where ProvinceName={0}";
    var donatorsViaCommand = db.Database.SqlQuery<DonatorViewInfo>(sql,"河北省");
    foreach (var donator in donatorsViaCommand)
    {
        Console.WriteLine(donator.ProvinceName + "	" + donator.DonatorId + "	" + donator.DonatorName + "	" + donator.Amount + "	" + donator.DonateDate);
    }

    SqlQuery方法的泛型参数不一定非得是一个类,也可以.Net的基本类型,如string或者int

    六、EF使用存储过程

    1,使用SqlQuery方法

    using (var db=new DonatorsContext())
    {
        var sql = "SelectDonators {0}";
        var donators = db.Database.SqlQuery<DonatorFromStoreProcedure>(sql,"山东省");
        foreach (var donator in donators)
        {
            Console.WriteLine(donator.ProvinceName+"	"+donator.Name+"	"+donator.Amount+"	"+donator.DonateDate);
        }
    }

    假如要提供多个参数的话,多个格式化占位符必须用逗号分隔,还要给SqlQuery提供值的数组

    2,使用ExecuteSqlCommand方法

    using (var db = new DonatorsContext())
    {
        var sql = "UpdateDonator {0},{1}";
        var rowsAffected = db.Database.ExecuteSqlCommand(sql, "Update", 10m);
    }

    3,使用存储过程CUD

    EF Code First全面支持这些查询。我们可以使用熟悉的EntityTypeConfiguration类来给存储过程配置该支持,只需要简单地调用MapToStoredProcedures方法就可以了

        public class UserMap:EntityTypeConfiguration<User>
        {
            public UserMap()
            {
                //只需要简单地调用MapToStoredProcedures方法就可以了
                //MapToStoredProcedures();
    
                //自定义配置
                MapToStoredProcedures(config =>
                {
                    //将删除打赏者的默认存储过程名称更改为“DonatorDelete”,
                    //同时将该存储过程的参数名称更改为“donatorId”,并指定该值来自Id属性
                    config.Delete(
                        procConfig =>
                        {
                            procConfig.HasName("UserDelete");
                            procConfig.Parameter(d => d.Id, "userId");
                        });
    
                    //将默认的插入存储过程名称更改为“DonatorInsert”
                    config.Insert(
                        procConfig =>
                        {
                            procConfig.HasName("UserInsert");
                        });
                    //将默认的更新存储过程名称更改为“DonatorUpdate”
                    config.Update(procConfig =>
                    {
                        procConfig.HasName("UserUpdate");
                    });
                });
            }
        }

    七、异步API

    例如,如果使用了异步的方式在创建一个Web应用,当我们等待数据库完成处理一个请求(无论它是一个保存还是检索操作)时,通过将web工作线程释放回线程池,就可以更有效地利用服务器资源

     1,异步地从数据库中获取对象的列表

    //3.1 异步查询对象列表
    static async Task<IEnumerable<Donator>> GetDonatorsAsync()
    {
        using (var db = new DonatorsContext())
        {
            return await db.Donators.ToListAsync();
        }
    }  

    2, 异步定位一条记录

    我们可以异步定位一条记录,可以使用很多方法,比如SingleFirst,这两个方法都有异步版本。

    //3.3 异步定位一条记录
    static async Task<Donator> FindDonatorAsync(int donatorId)
    {
        using (var db = new DonatorsContext())
        {
          return await db.Donators.FindAsync(donatorId);
        }
    }

    3,异步聚合函数

    对应于同步版本,异步聚合函数包括这么几个方法,MaxAsync,MinAsync,CountAsync,SumAsync,AverageAsync

    //3.4 异步聚合函数
    static async Task<int> GetDonatorCountAsync()
    {
        using (var db = new DonatorsContext())
        {
            return await db.Donators.CountAsync();
        }
    }

    4,异步遍历查询结果

    如果要对查询结果进行异步遍历,可以使用ForEachAsync

    //3.5 异步遍历查询结果
    static async Task LoopDonatorsAsync()
    {
        using (var db = new DonatorsContext())
        {
            await db.Donators.ForEachAsync(d =>
            {
                d.DonateDate=DateTime.Today;
            });
        }
    }

    八、管理并发

     1,为并发实现RowVersion

    RowVersion机制使用了一种数据库功能,每当更新行的时候,就会创建一个新的行值

    [Timestamp]
    public byte[] RowVersion { get; set; }
    
    //修改上下文
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Donator>().Property(d => d.RowVersion).IsRowVersion();
        base.OnModelCreating(modelBuilder);
    }

    现在,EF就会为并发控制追踪RowVersion列值。接下来尝试更新不同的列:

    try{
      //1.用户甲获取id=1的打赏者
      var donator1 = GetDonator(1);
      //2.用户乙也获取id=1的打赏者
      var donator2 = GetDonator(1);
      //3.用户甲只更新这个实体的Name字段
      donator1.Name = "用户甲";
      UpdateDonator(donator1);
      //4.用户乙只更新这个实体的Amount字段
      donator2.Amount = 100m;
      UpdateDonator(donator2);   
    }
    catch (DbUpdateConcurrencyException ex)
    {
        Console.WriteLine("发生并发更新");
    }

    九、事务

     1,EF的默认事务处理

    int outputId = 2,inputId=1;
    decimal transferAmount = 1000m;
    using (var db=new Context())
    {
        //1 检索事务中涉及的账户
        var outputAccount = db.OutputAccounts.Find(outputId);
        var inputAccount = db.InputAccounts.Find(inputId);
        //2 从输出账户上扣除1000
        outputAccount.Balance -= transferAmount;
        //3 从输入账户上增加1000
        inputAccount.Balance += transferAmount;
    
        //4 提交事务
        db.SaveChanges();
    }

    2,使用TransactionScope处理事务

     int outputId = 2, inputId = 1;
     decimal transferAmount = 1000m;
     using (var ts=new TransactionScope(TransactionScopeOption.Required))
     {
         var db1=new Context();
         var db2=new Context();
         //1 检索事务中涉及的账户
         var outputAccount = db1.OutputAccounts.Find(outputId);
         var inputAccount = db2.InputAccounts.Find(inputId);
         //2 从输出账户上扣除1000
         outputAccount.Balance -= transferAmount;
         //3 从输入账户上增加1000
         inputAccount.Balance += transferAmount;
    
         db1.SaveChanges();
         db2.SaveChanges();
    
         ts.Complete();
     }

    3,使用EF6管理事务

     int outputId = 2, inputId = 1;
     decimal transferAmount = 1000m;
     using (var db=new Context())
     {
         using (var trans=db.Database.BeginTransaction())
         {
             try
             {
                 var sql = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@outputId";
                 db.Database.ExecuteSqlCommand(sql, new SqlParameter("@amountToDebit", transferAmount), new SqlParameter("@outputId",outputId));
    
                 var inputAccount = db.InputAccounts.Find(inputId);
                 inputAccount.Balance += transferAmount;
                 db.SaveChanges();
    
                 trans.Commit();
             }
             catch (Exception ex)
             {
                 trans.Rollback();
             }
         }
     }

    4,使用已存在的事务

    //模拟老项目的类库
    static bool DebitOutputAccount(SqlConnection conn, SqlTransaction trans, int accountId, decimal amountToDebit)
    {
        int affectedRows = 0;
        var command = conn.CreateCommand();
        command.Transaction = trans;
        command.CommandType=CommandType.Text;
        command.CommandText = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@accountId";
        command.Parameters.AddRange(new SqlParameter[]
        {
            new SqlParameter("@amountToDebit",amountToDebit), 
            new SqlParameter("@accountId",accountId) 
        });
    
        try
        {
            affectedRows= command.ExecuteNonQuery();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return affectedRows == 1;
    }
        #region 7.0 使用已存在的事务
        int outputId = 2, inputId = 1;
        decimal transferAmount = 1000m;
        var connectionString = ConfigurationManager.ConnectionStrings["ConcurrencyAndTransactionManagementConn"].ConnectionString;
        using (var conn=new SqlConnection(connectionString))
        {
            conn.Open();
            using (var trans=conn.BeginTransaction())
            {
                try
                {
                    var result = DebitOutputAccount(conn, trans, outputId, transferAmount);
                    if (!result)
                    {
                        throw new Exception("不能正常扣款!");
                    }
                    using (var db=new Context(conn,contextOwnsConnection:false))
                    {
                        db.Database.UseTransaction(trans);
                        var inputAccount=db.InputAccounts.Find(inputId);
                        inputAccount.Balance += transferAmount;
                        db.SaveChanges();
                    }
                    trans.Commit();
                }
                catch (Exception ex) 
                {
                    trans.Rollback();
                }
            }
        }
    
        #endregion

    db.Database.UseTransaction(trans):这句话的意思是,EF执行的操作都在外部传入的事务中执行

    contextOwnsConnection的值为false:表示上下文和数据库连接没有关系,上下文释放了,数据库连接还没释放;反之为true的话,上下文释放了,数据库连接也就释放了

    5,选择合适的事务管理

    目前,我们已经知道了好几种使用EF处理事务的方法,下面一一对号入座:

    • 如果只有一个DbContext类,那么应该尽力使用EF的默认事务管理。我们总应该将所有的操作组成一个在相同的DbContext对象的作用域中执行的工作单元,SaveChanges()方法会处理提交事务。
    • 如果使用了多个DbContext对象,那么管理事务的最佳方法可能就是把调用放到TransactionScope对象的作用域中了。
    • 如果要执行原生SQL,并想把这些操作和事务关联起来,那么应该使用EF提供的Database.BeginTransaction()方法。然而这种方法只支持EF6,不支持之前的版本。
    • 如果想为要求SqlTransaction的老项目使用EF,那么可以使用Database.UseTransaction()方法,在EF6中可用。

    十、数据库迁移

    1,我们要给Message属性添加一个限制,即最大长度为50(默认的长度是MAX),然后更新数据库,结果会报错

    产生这个错误的道理很简单,字符串长度从最大变成50,肯定会造成数据丢失的。如果你知道会造成数据丢失,还要这么做,可以在后面加参数-Force,这个参数会强制更新数据库。或者,我们可以开启数据丢失支持,正如EF暴露的这个设置Set AutomaticMigrationDataLossAllowed to 'true'(错误信息中提到的)。

    2,UpDown方法

    Up方法将数据库结构向前移动,例如,我们这里创建了两张新的表;Down方法帮助我们撤销更改,以防我们发现了软件问题需要回滚到之前的数据库结构

    3,__MigrationHistory表

    __MigrationHistory这张表,顾名思义,是记录迁移历史的。可以看到,MigrationId对应于初次迁移的文件名,Model列包含了上下文的哈希值,ContextKey包含了上下文配置类的类名

    4,defaultValueSql

    要指定硬编码默认值,我们可以使用defaultValue

     public override void Up()
     {
         AddColumn("dbo.Donators", "CreationTime", c => c.DateTime(nullable: false,defaultValueSql:"GetDate()"));
     }

    上面的代码中,我们使用了SQL Server中的GetDate函数使用当前日期填充新加入的列

    5,要创建迁移,我们不一定非要有未处理的更改。我们仍然使用之前的指令Add-Migration,这样就给项目添加了一个迁移,但是Up和Down方法都是空的。现在,我们需要添加创建索引的自定义的代码,如下所示

    public partial class Donator_Add_Index_Name : DbMigration
    {
        public override void Up()
        {
            CreateIndex(
                "Donators",
                new []{"Name"},
                name:"Index_Donator_Name"
                );
        }
        
        public override void Down()
        {
            DropIndex("Donators", "Index_Donator_Name");
        }
    }

    6,回滚

    现在,尝试通过指定创建索引迁移之前的目标迁移删除该索引,创建索引之前的迁移名称是Donator_Add_CreationTime,要退回上一个迁移,我们可以使用下面的指令:

    Update-Database -TargetMigration Donator_Add_CreationTime 

    十一、应用迁移

     1,通过脚本应用迁移

    包管理器控制台窗口中,我们可以通过Update-Database -Script生成脚本,该命令一执行完成,生成的脚本就会在VS中打开

    需要注意的是,我们要指定匹配目标环境的数据库的正确连接字符串,因为迁移API会使用上下文比较实时数据库。我们要么在Update-Database后面带上连接字符串,要么在配置文件中使用正确的连接字符串。

    2,给已存在的数据库添加迁移

    有时,我们想为一个已存在的数据库添加EF迁移,为的是将处理模式变化从一种方式移动到迁移API。当然,因为数据库已存在于生产环境,所以我们需要让迁移知道迁移起始的已知状态。使用Add-Migration -IgnoreChanges指令处理这个是相当简单的,当执行该命令时,EF会创建一个空的迁移,它会假设上下文和实体定义的模型和数据库是兼容的。一旦通过运行这个迁移更新了数据库,数据库模式不会发生变化,但是会在_MigrationHistory表中添加一条新的数据来对应初次迁移。这个完成之后,我们就可以安全地切换到EF的迁移API来维护数据库模式变化了。

    十二、EF的其他功能

    1, 应用于很多实体类型或者表的全局更改(比如,下面是如何设置所有的string属性在数据库中存储为非Unicode列)

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Properties<string>().Configure(config=>config.IsUnicode(false));
    }

    我们也可以写相同的代码作为自定义约定,然后在model builder中加入到约定集合中。要这么做的话,首先创建一个继承自Convention的类,重写构造函数,然后使用之前相同的代码,调用Convention类的Properties方法。代码如下:

    public class CustomConventions:Convention
    {
        public CustomConventions()
        {
            Properties<string>().Configure(config=>config.IsUnicode(false));
        }
    }
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //modelBuilder.Properties<string>().Configure(config=>config.IsUnicode(false));
        modelBuilder.Conventions.Add<CustomConventions>();
    }

    2,地理空间数据

    除了标量类型数据,如string或decimal,EF也通过.Net中的DbGeometryDbGeography支持地理空间数据。这些类型有支持地理空间查询的内置支持和正确翻译,例如地图上两点之间的距离。这些特定的查询方法对于具有地理空间属性的实体的查询很有用,换言之,当使用空间类型时,我们仍编写.Net代码。

    ----------------------------------------------------------------------------------------

    Local只能跟踪CUR操作。Local只能针对某一个DbSet而言
    ChangeTracker可以跟踪整个DBContext
    Entry只能跟踪某个一实体

    继承IDbCommonInterceptor进行拦截EF执行

    Timestamp特性标记字段为时间戳(该字段类型必须是byte[])
    ConcurrencyCheck特性并发检测
    NotMapped特性:使该字段不生成数据库字段

    Codefirst四大初始化策略
    Database.SetInitializer<Entity>(new DropCreateDatabaseIfModelChanges<Entity>())

    1,CreateDatabaseIfNotExists:默认策略
    ①数据库不存在,创建数据库
    ②model修改,执行抛出异常


    2,DropCreateDatabaseIfModelChanges:
    model一旦修改,我们将会执行dropdatabase操作

    3,DropCreateDatabaseAlways:
    每一次执行就重新创建表

    4,Customer DB Initializer:
    自定义初始化


    5,禁用数据库初始化策略
    Database.SetInitializer<Entity>(null)

    使用EFProf工具监视ef支持的sql
    https://www.codeproject.com/Articles/101214/EFProf-Profiler-Tool-for-Entity-Framework

  • 相关阅读:
    【Unity】校验身份证号有效性
    【Unity】敏感词过滤
    【C#】2.C#程序结构
    常用Git命令手册
    Android删除指定路径下指定前缀或后缀的文件
    《Android源码设计模式》学习笔记之ImageLoader
    Android截屏的几种实现
    react 项目 合并单元格解决方案
    iconfont字体图标的使用方法(转)
    如何让antd的Modal组件的确认和取消不显示(或自定义按钮)(转载)
  • 原文地址:https://www.cnblogs.com/zd1994/p/7417959.html
Copyright © 2020-2023  润新知