1. 程式人生 > >Entity Framework 查漏補缺 (一)

Entity Framework 查漏補缺 (一)

自動遷移 idt tex 方式 sch 行遷移 步驟 targe 問題

明確EF建立的數據庫和對象之間的關系

EF也是一種ORM技術框架, 將對象模型和關系型數據庫的數據結構對應起來,開發人員不在利用sql去操作數據相關結構和數據。
以下是EF建立的數據庫和對象之間關系

關系數據庫 對象
數據庫 DbContext類
DbContext中的DbSet<實體類名>
表間的關聯 實體類之間的關聯
字段 實體類的公有屬性
單條數據 單個實體類的對象
約束(主鍵、外鍵默認值) 實體類中的特性

了解EDM( 實體數據模型)

EF使用概念模型、 映射和存儲模型。三個模型來描述映射關系

  • 概念模型 (.csdl) ︰ 即為直接使用的對象類,並包含它們之間的關系。
  • 存儲模型 (.ssdl) ︰ 數據庫設計結構,包括表、 視圖、 存儲的過程和他們的關系和鍵。
  • 映射 (.msl) ︰ 包含將概念模型(對象類)映射到存儲模型(關系數據庫)的信息。

CodeFirst :實體結構發生變化,如何更新數據庫結構?

  • 數據準備
public class Place
{
    [Key]
    public int PlaceID { get; set;}

    public string Provice { get; set; }

    public string City { get; set; }
    //導航屬性
    public List<People> Population { get; set; }
}

public class People
{
    [Key]
    public int PeopleID{ get; set; }

    public string Name{ get; set; }

    public int Age{ get; set;}

    //外鍵,對應導航屬性對象中的標識
    [ForeignKey("Place")]
    public int PlaceID{ get; set;}

    //導航屬性
    public Place Place { get; set; }
}
  • 創建DbContext派生類
public class TestDB:DbContext
{
    public TestDB():base("name=Test") { }

    public DbSet<Place> Place { get; set; }

    public DbSet<People> People { get; set; }
}
  • 執行一次代碼
class Program
{
    static void Main(string[] args)
    {
        using (var context = new TestDB())
        {
            context.Place.Add(new Place {
                PlaceID = 2,
                Provice="jiangsu",
                City="wuxi"
            });
            context.SaveChanges();

        }
    }
}

發現在數據庫已經建好了相應的表,這時在people類增加Sex屬性,隨便增加一條數據執行代碼,報了如下錯,很顯然EF並不會幫我們更新數據庫結構:

技術分享圖片

有兩種情況處理這種情況,手動和自動遷移方式去更新數據庫

自動遷移

1、選擇所在的項目,在VS的程序包管理控制臺輸入命令(Tools菜單中打開Package Manager Console):

enable-migrations –EnableAutomaticMigrations

2、執行完畢,項目目錄中多出一個名為Migrations的文件夾,裏面有個Configuration.cs文件,打開它看到如下代碼:

internal sealed class Configuration : DbMigrationsConfiguration<EF1.Model.TestDB>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        ContextKey = "EF1.Model.TestDB";
    }

    protected override void Seed(EF1.Model.TestDB context)
    {
        //  This method will be called after migrating to the latest version.

        //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
        //  to avoid creating duplicate seed data.
    }
}

ContextKey屬性指定了要執行遷移的DbContext,以便多個DbContext共存遷移不會產生沖突。

在遷移過程成功後會執行Seed方法,可以利用這個方法添加一些初始化數據

在刪除屬性的時候,會遷移失敗,提示操作會造成數據丟失,可以在構造函數中添加如下代碼,忽略數據丟失。

public Configuration()
{
    AutomaticMigrationsEnabled = true;
    AutomaticMigrationDataLossAllowed = true;
    ContextKey = "EF1.Model.TestDB";
}

3.以上步驟已開啟支持遷移,每次更新實體結構時,在項目執行前,需要在管理控制臺輸入以下命令先更新數據庫

update-database

或是在DbContext構造函數中添加以下代碼,每次將自動遷移至最新的數據庫Schema

public class TestDB:DbContext
{
    public TestDB():base("name=Test") {
        //TestDB:dbContext;
//Test:連接字符串名稱
//遷移至最新版本 Database.SetInitializer(new MigrateDatabaseToLatestVersion<TestDB, Configuration>("Test")); } public DbSet<Place> Place { get; set; } public DbSet<People> People { get; set; } }

手動遷移

兩種遷移方式基本相同,只是相比自動遷移,手動遷移會記錄每次更新的情況,允許回滾數據庫到某個指定版本,適合於團隊開發。

1.一樣要先開啟支持遷移,並吧生成的Configuration構造函數中AutomaticMigrationsEnabled置為false,表示不使用自動遷移。

2.每次更改實體結構或映射配置時,在程序包管理控制臺運行以下代碼

Add-Migration ChangeSet1

會自動在前面生成的Migrations文件下生成一個ChangeSet1遷移文件

技術分享圖片

類的內容就是對於我們所更改的實體結構或映射配置,EF遷移要執行的內容,以下就是我增加了一個Phone屬性,運行Add-Migration ChangeSet1所生成的。此遷移文件後面可以用來回滾到某個版本

public partial class ChangeSet1 : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.People", "Phone", c => c.String());
    }
        
    public override void Down()
    {
        DropColumn("dbo.People", "Phone");
    }
}

3.成功遷移文件後,運行下面代碼更新至最新的遷移文件對應的版本。

Update-Database

或是回滾到指定版本

Update-Database -TargetMigration ChangeSet1

多種方式的增刪改查

基礎知識

上下文是根據檢測實體的EntityState枚舉狀態,來執行相應的增/刪/改操作。

EntityState枚舉狀態如下:

  • Detached:對象存在,但未由對象服務跟蹤。在創建實體之後、但將其添加到對象上下文之前,該實體處於此狀態;
  • Unchanged:對象添加到上下文中後,還未被修改過;
  • Added:對象已添加到對象上下文,還沒有調用SaveChanges() 方法;
  • Deleted:將對象從上下文中刪除;
  • Modified:對象已更改,還沒有調用SaveChanges() 方法;

DBcontext類中的方法:

  • Entry:將對象加入EF容器,並獲取當前實體對象的狀態管理對象
  • Set<T>:獲取實體相應的DbSet類對象,如context.Set<Student>().Attach(student);

DbSet類

  • Attach:是把一個已存在於數據庫中,但沒有被 dbContext 跟蹤的對象,以EntityState.Unchanged 狀態附加到 dbCotext 中,對於已有相同key的對象存在於EF Context的情況,如果這個已存在對象狀態為Unchanged則不進行任何操作,否則將其狀態更改為Unchanged。
  • Add:將一個已存在於數據庫中的對象,以Added實體狀態添加到EF Context中。
  • Remove:將一個已存在於EF Context中的對象標記為Deleted,當SaveChanges(已經存在於EF Context)時,這個對象對應的數據庫條目被刪除。
  • Find:按主鍵去獲取一個實體,首先在EF Context中查找是否有被緩存過的實體,如果查找不到再去數據庫查找,如果數據庫中存在則緩存到EF Context並返回,否則返回null。
  • AsNoTracking:無跟蹤查詢,不受EFcontext管理,所以查詢出來的數據不能做修改,對於只查詢顯示的功能,加上AsNoTracking可以提升效率
  • 。。。。。

增加

基本方式

static void Main(string[] args)
{
    using (var context = new TestDB())
    {
            context.Place.Add(new Place
            {
                PlaceID = 8,
                Provice = "jiangsu",
                City = "wuxi",
            });
            context.SaveChanges();
    }
}

Entry方式

附:用attch附加到上下文保存無效,因為附加後的狀態是unchange,需要使用ChangeObjectState方法更改實體狀態,或是再次使用Entry獲取狀態管理對象更新

static void Main(string[] args)
{
    using (var context = new TestDB())
    {
        var obj = new Place()
        {
            PlaceID =9,
            Provice = "jiangsu",
            City = "wuxi",
        };
        //加入EF容器,並獲取當前實體對象的狀態管理對象
        var entity = context.Entry<Place>(obj);
        entity.State = System.Data.Entity.EntityState.Added;
        context.SaveChanges();
    }
}

更新

先查詢後更新:

static void Main(string[] args)
{
    using (var context = new TestDB())
    {
        var query = context.Place.Where(p => p.PlaceID == 9).FirstOrDefault();
        query.City = "suzhou";
        context.SaveChanges();
    }
}

Entry

using (var context = new TestDB())
{
    var obj = new Place
    {
        PlaceID=9,
        City="changzhou"
    };
    //將obj加入到上下文,並去獲取實體對象的狀態管理對象
    var entity = context.Entry<Place>(obj);
    //置為未被修改過
    entity.State = System.Data.Entity.EntityState.Unchanged;
    //設置該對象的City屬性為修改狀態,同時 entity.State由Unchanged-> Modified 狀態
    entity.Property("City").IsModified = true;
    context.SaveChanges();
}

Attach

using (var context = new TestDB())
{
    var obj = new Place
    {
        PlaceID=9
    };
    //將obj附加到上下文,此時實體狀態為Unchanged
    var newobj=context.Place.Attach(obj);
    //這時City屬性為修改狀態,同時entity.State自動由Unchanged-> Modified 狀態
    newobj.City = "yangzhou";
    context.SaveChanges();
}

刪除

常規操作:先查詢後刪除

using (var context = new TestDB())
{
    var queryObj = context.Place.Where(q=>q.PlaceID==8).FirstOrDefault();
    //當前的實體對象已置為Deleted狀態
    context.Place.Remove(queryObj);
    context.SaveChanges();
}

Entry

using (var context = new TestDB())
{
    var obj = new Place
    {
        PlaceID = 6
    };
    //將obj附加到上下文,並獲取實體對象的狀態管理對象
    var entity =context.Entry<Place>(obj);
    //將狀態置為Deleted
    entity.State = System.Data.Entity.EntityState.Deleted;
    context.SaveChanges();
}

Attach

using (var context = new TestDB())
{
    var obj = new Place
    {
        PlaceID = 7
    };
    //將obj附加到上下文,此時實體狀態為Unchanged
    var newobj = context.Place.Attach(obj);
    context.Place.Remove(newobj);
    context.SaveChanges();
}

查詢

對於簡單查詢,不需要進行修改操作,建議使用AsNoTracking,無跟蹤查詢來提升效率

using (var context = new TestDB())
{
    var obj = context.Place.Where(p => p.PlaceID == 9).AsNoTracking().ToList();
}

Find方法 :之前一直使用lambda查詢(where,FirstOrDefault),有一個問題就是不管我們要查詢的實體是否被dbconext緩存,都會去查詢數據庫,而使用Find通過主鍵來查詢,會首先在EF Context中查找是否有被緩存過的實體,如果查找不到才去數據庫查找

using (var context = new TestDB())
{
    var obj = context.Place.Find(9);
}

註:更詳細的EF查詢學習會在查漏補缺(二) 數據加載 中繼續記錄

Entity Framework 查漏補缺 (一)