EntityFramework 7.0之初探【基於VS 2015】(十)


前言

本篇作為EF 7.0的開篇也是Entity Framework目前系列末篇,因為關於EF 7.0學習資料實在是太少,我都是參考老外的資料花費了不少時間去研究去嘗試同時也失敗多次,個人覺得那是值得的,至少為今后在VS2015上來運用EF 7.0打下了堅定的基礎,但是有些很深入的層面還得待EF 7.0比較成熟時再來補充及學習,當然在前面EF 6.0或6.1中系列中如果我發現又有好的內容也會進行適當補充同時也和大家一起學習以及分享。文中若有不妥之處或錯處請指出。【注意】下面所演示代碼都是基於VS2015上的ASP.NET 5和EF 7.0 Beta4,之前想要用最新版本Beta 7來演示,需要安裝其擴展,但是安裝后導致項目直接打不開,卸載后又出毛病,於是放棄使用Beta 7,用法基本上是大同小異。

EF 7.0相關概念

簡介

EF 7.0利用了大量的依賴注入,也就意味着,EF是通過構造器注入來共同有效工作並且互相依賴的服務的集合,實現EF的提供者(Provider)就是真正根據需要去創建這些服務的特定提供者(Provider-Specific)的實現,一切都需手動去配置,且更加靈活和利於后期擴展。

上下文作用域(Context Scope)

單個EF 7.0應用程序能夠利用多個Provider,例如同一個應用程序能夠訪問SQL Server數據庫和SQLite數據庫 ,然而通過每個上下文實例定義的Session必須嚴格使用單個Provider。因此我們可以創建一個上下文實例去訪問SQL Server數據庫,創建另外一個實例上下文去訪問SQLite數據庫,但是我們不能用我們創建的單個上下文實例來同時去訪問SQL Server和SQLite數據庫。

服務類型(Types of Services)

  • Core services:核心服務對於不同的Provider(提供者)不打算實現不同的行為。

  • Provider-specific services:特定服務提供者沒有具體的實現,所有的提供者必須提供這些服務的實現,然而經常有抽象的基類被用來當做是特定提供者(Provider-specific)實現的基礎。

  • Provider-specific services:特定服務提供者有具體的實現,對於這些服務,如果共同實現需要以某種方式被改變,那么一個提供者僅僅只需提供一種實現。

數據存儲服務接口(IDataStoreServices)

對於關系提供者有一個 IRelationalDataStoreServices 接口,此接口通過EF 7.0關系架構添加了額外的服務。提供者若要連接一個關系數據庫應該要實現此接口。 IRelationalDataStoreServices 和 IDataStoreServices 有基本實現,其包含了有具體實現的特定提供者的預定義映射,提供者一般使用這些基類之一並且重寫其虛方法。 

例如,通過 SQLite 提供者來實現 IRelationalDataStoreServices ,大概代碼如下:

    public class SqliteDataStoreServices : RelationalDataStoreServices
    {
        public SqliteDataStoreServices(IServiceProvider services) : base(services) { }
       //ovveride methods from RelationalDataStoreServices
    }

注冊服務(Registering services) 

每個具體的服務必須被注冊到DI容器中,以至於在調用 GetService  調用時將作出相應的行為。該注冊通過在一個AddXxx擴展方法中進行,而Xxx是提供者的名稱,例如對於SQLite的擴展方法如下:

public static EntityFrameworkServicesBuilder AddSqlite(
    this EntityFrameworkServicesBuilder services)
{
    ((IAccessor<IServiceCollection>)services.AddRelational()).Service
        .AddSingleton<IDataStoreSource, SqliteDataStoreSource>()
        .TryAdd(new ServiceCollection()
            .AddSingleton<SqliteValueGeneratorCache>()
            .AddSingleton<SqliteSqlGenerator>()
            .AddScoped<SqliteTypeMapper>()
            .AddScoped<SqliteModificationCommandBatchFactory>()
            .AddScoped<SqliteCommandBatchPreparer>()
            .AddSingleton<SqliteModelSource>()
            .AddScoped<SqliteDataStoreServices>()
            .AddScoped<SqliteDataStore>()
            .AddScoped<SqliteDataStoreConnection>()
            .AddScoped<SqliteMigrationSqlGenerator>()
            .AddScoped<SqliteDataStoreCreator>()
            .AddScoped<SqliteHistoryRepository>());
 
    return services;
}

上述AddXxx是在 EntityFrameworkServicesBuilder 上的擴展方法並且應該返回被傳遞進去的  EntityFrameworkServicesBuilder ,這將允許當在注冊DI服務一個AddEntityFramework調用時它將被連接到並且允許額外的擴展方法從它中被連接。

注冊范圍(Registration scopes)

在大部分情況下,特定提供者服務應該被注冊在DI中通過使用AddScoped方法,這是因為上下文創建Scope是為了在不同的上下文實例中使用不同的提供者,同時這也取決於在一個Scope 服務上的任何服務必須自己也作為Scope被注冊。

 

在少數情況下,服務被注冊使用 AddSingleton  在此種情況下的服務不依賴人任何其他的Scope服務,同時單個實例能同時被所有上下文所使用-例如上述的 SqliteSqlGenerator 。【注意】所有單例服務必須是線程安全的。

 

當前有 IModelSource 和 IValueGeneratorCache 兩種服務,此兩種服務表現在作為特定提供者緩存,這些服務必須作為單例服務來注冊那么緩存將一直始終能貫穿於上下文實例中,這是有緩存的某一點。他們因此不依賴於任何Scope服務,同時提供者對於這些服務要有自己的具體實現,所以每個提供者將獲得不同的實例同時避免了緩存沖突。

提供者的選擇(Provider selection)

EF 7.0應用程序選擇去使用哪個提供者取決於所給的上下文實例中的UserXxx方法所要調用的,主要是在上下文中的 OnConfiguring  方法中,例如在一個應用程序中要使用SQLite可能需要像如下這樣做:

protected override void OnConfiguring(
    DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlite("MyDb.db");
}

UseSqlite是在DbContextOptionsBuilder上的擴展方法,它的作用是創建或者更新一個已經存在的 IDbContextOptionsExtension 對象並且將其注冊在OptionsBuilder上,例如:

public static SqliteDbContextOptionsBuilder UseSqlite(
    this DbContextOptionsBuilder options,
    string connectionString)
{
    var extension = GetOrCreateExtension(options);
    extension.ConnectionString = connectionString;
    ((IOptionsBuilderExtender)options).AddOrUpdateExtension(extension);
 
    return new SqliteDbContextOptionsBuilder(options);
}

【注意】更新為可選的一個新的builder應該被返回以此來允許進一步的連接。

存在於option builder上的 IDbContextOptionsExtension 用途是通過EF堆棧來決定一個提供者是否已經被選擇。對於Session,它也被用來存儲特定提供者配置,這些配置,例如:連接字符串,通過關系基類被處理。

當EF正顧及所有內部DI注冊時, IDbContextOptionsExtension  也被用來自動注冊DI服務。一種簡單的實現對於SQLite如下:

public class SqliteOptionsExtension : RelationalOptionsExtension
{
    public override void ApplyServices(
        EntityFrameworkServicesBuilder builder)
        => builder.AddSqlite();
}

當EF需要注冊當前提供者的服務時通過調用ApplyServices,反過來說會調用以上被定義的AddXxx擴展方法。

總結

創建一個提供者的幾個步驟:

  • 通過使用IDataStoreServices和IRelationalDataStoreServices接口作為服務需要的前提來實現特定提供者。

  • 創建一個實現IDataStoreServices或者IRelationalDataStoreServices的接口來映射到你的實現,通過使用作為起始點的基類之一。

  • 創建一個AddXxx擴展方法在DI上來注冊你的服務。

  • 創建一個IDbContextOptionsExtension的實現來處理提供者的選擇和配置。

  • 創建UseXxx的擴展方法來使得應用程序去選擇你的提供者。

  • 創建一個IDataStoreSource接口的實現來將一切捆綁在一起。

當然,上述關於創建任何一個提供者實際要實現這些服務是比較艱難,同時我們可以為特定提供者模型構建創建一些擴展方法。

接下來我們將手動一步一步通過EF創建數據庫和表以及映射關系和實現增、刪等操作以及注意事項,請繼續往下看。

EF 7.0一步一步手動創建數據庫及表

我們創建一個空的應用程序來手動通過添加代碼來實現EF並了解下VS2015,如圖:

我們創建空的應用程序同時添加如上幾個文件夾為接下來的工作做好准備(至於為什么不創建非空的應用程序,個人覺得首先得了解各種配置文件的作用,因為在VS2015上為了跨平台都是基於配置,而不是一上來就創建非空的應用程序,以至於看到里面各式各樣的代碼都懵逼了)。下面首先對上述默認創建配置文件進行簡短說明(稍安勿躁,慢慢來)。

Startup

我們知道在ASP.NET 5版本之前global.asax被用來在不同啟動時刻來觸發不同的事件,所以我們能配置應用程序,而在ASP.NET 5中Startup就類似global.asax,但是不同的是該文件能對一切進行配置而不是局限於某個區間,也就是說其作用域更廣了。下面我們就對Startup里面的內容進行解讀。

構造函數Startup

首先在這個類中建立一個如下屬性

public IConfiguration Configuration { get; set; }

被創建的 IConfiguration  對象包含了來自 config.json 和執行環境的所有配置信息,我們在構造函數中通過類 Configuration 的實例來賦值給Configuration進行各種配置。也就是說通過Configuration配置的信息就代替了ASP.NET之前版本的Web.Config。在Web.Config中所有舊的配置被儲存,AppSettings我們經常使用但是其不支持其他架構,僅僅只支持字符串。當然,對於復雜的場景我們可以自己手動創建處理程序但是還有其他應用也需要我們添加節點,這無疑使得Web.Config漸漸的膨脹起來,所以對於此Web.Config變得不是太靈活以及代碼整體上來看似乎顯得非常臃腫。在ASP.NET 5中這就發生了改變,IConfiguration和Configuration是配置的中心,而不是集中的配置位置。我們來看看如何靈活,如下:

var Configuration = new Configuration()
    .AddJsonFile("config.json")
    .AddIniFile("system.ini")
    .AddCommandLine(args)
    .AddEnvironmentVariables();

通過這種流式語法可以使我們從各種來源來添加相應的配置信息,通過能添加其他不同類型的配置來源我們可以看出,這是可擴展的,這種模式的配置也就意味着我們能將配置選項合並到單一的配置源中。

ConfigureServices

一旦構造函數被觸發並讀取配置信息,該方法以及Configure方法(下面講)就會被執行以此來設置運行時環境, ConfigureServices 方法的作用是設置依賴注入(dependency injection),該方法中的參數 IServiceCollection 接口主要負責將我們注入的服務(如MVC、EntityFramework、Identity等)推入到運行的應用程序中,在此之外,ASP.NET 5有其自己內部的實現,盡管我們有其他的容器來提供相應的實現,即使我們不喜歡微軟內部對於依賴注入的實現,但是我們可以讓其默認存在,反正是無害的,何樂而不為呢!例如,如下:

public void ConfigureServices(IServiceCollection services)
{
  // 添加EF到服務容器中
  services.AddEntityFramework(Configuration) 
      .AddSqlServer()
      .AddDbContext<MyCountriesContext>();
 
  // 添加角色驗證到服務容器中
  services.AddIdentity<ApplicationUser, IdentityRole>(Configuration)
      .AddEntityFrameworkStores<MyCountriesContext>();
 
  // 添加MVC到服務容器中
  services.AddMvc();
 
  //添加其他的服務
  services.AddScoped<IEmailer, Emailer>();
 
}

上述代碼中我們添加了EF、MVC、Identity到DI容器中,在最底部有我們自己的服務並將其注入到容器中以供我們使用。通過一定量的配置也可以在此發生,比如設置EF環境的連接字符串以及對MVC的配置等等。基於這一點,我們所有的代碼都是使這些服務依賴注入到容器中並進行訪問。

Configure

這是當構造函數讀取配置並執行該方法設置其運行時的環境的第二種方法,在ASP.NET之前版本不會啟動任何其他的運行時框架,在ASP.NET 5中,你必須告訴將啟用哪種框架(如MVC、EF實體框架以及Identity身份驗證等框架),該方法是基於上述ConfigureServices方法,當Configure執行之后此方法然后將被調用如下:

public void Configure(IApplicationBuilder app,
                      IHostingEnvironment env,
                      ILoggerFactory loggerfactory)
{
// 添加日志到控制台輸出 loggerfactory.AddConsole();

// 添加靜態文件到請求管道 app.UseStaticFiles(); // 添加基於Cookie的授權到請求管道 app.UseIdentity(); // 添加MVC路由到請求管道 app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" }); }); }

  Configure 方法主要是通過 IApplicationBuilder  對象來允許我們加入到這些服務中。在上述中,我們可以選擇加入靜態文件、身份驗證以及使用MVC對MVC路由的定義,通過了解這些我們就能隨時跟蹤應用程序的配置以及設置。

通過對上述的詳細敘述,想必你應該對配置文件以及注入服務有了深刻的理解,接下來就是對於EF的實戰。

數據庫以及表生成

第一步

很顯然由於要連接數據庫以及初始化數據庫和表,我們當然需要配置文件以及讀取配置文件,默認創建空的應用程序是不會自動添加配置文件(config.json)的,我們得手動添加配置文件。如下:

在Statrup構造函數讀取配置文件

            var configurationBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
                  .AddJsonFile("config.json")
                  .AddEnvironmentVariables();

            Configuration = configurationBuilder.Build();

第二步

注入EntityFramework,添加我們需要使用的數據庫以及上下文並通過字符串連接數據庫

           var connectionString = Configuration.Get("Data:DefaultConnection:ConnectionString");

            services.AddEntityFramework()
                .AddSqlServer()
                .AddDbContext<EFDbContext>(options =>
                {
                    options.UseSqlServer(connectionString);
                });

第三步

在Models文件夾下添加Student和Flower(一個Student對應一個Flower,一個Flower對應多個Student即一對多)

    public class Student
    {
        public int Id { get; set; }

        public string Name { get; set; }


        public int FlowerId { get; set; }
        public virtual Flower Flower { get; set; }

    }


    public class Flower
    {
        public int Id { get; set; }

        public string Remark { get; set; }

        public virtual ICollection<Student> Students { get; set; }
    }

第四步

在Core文件夾建立EF上下文進行映射以及初始化數據庫以及表

    public class EFDbContext : DbContext
    {
        public EFDbContext()
        {

        }

        protected override void OnModelCreating(ModelBuilder builder)【改變】EF 7.0之前版本為DbModelBuilder
        {
            builder.Entity<Student>(d =>
            {
                d.Key(p => p.Id);

                d.Property(p => p.Name).Required();
                d.Reference(p => p.Flower).InverseCollection(p => p.Students).ForeignKey(p => p.FlowerId);
            });

            builder.Entity<Flower>(d =>
            {
                d.Key(p => p.Id);

                d.Property(p => p.Remark).Required();
            });

        }

    }

【注意】關於映射關系,下面講,稍安勿躁。

第五步 

通過PowerShell命令來生成數據庫和表你不得不知道的幾個命令以及有關DNX命令,如下:

安裝.NET Core以及CLR命令

dnvm install -r coreclr latest -u

dnvm install -r clr  latest -u

指定安裝32或者64位版本

dnvm install -r coreclr -arch x64 latest -u

或者 

dnvm install -r coreclr -arch x86 latest -u

指定運行時(執行環境)以及版本或者只需指定運行時(執行環境)

dnvm use -r coreclr -arch x86 1.0.0-beta5

或者

dnvm  use 1.0.0-beta5

 重新恢復包

dnu restore

用DNX運行應用程序

dnx . run

EF遷移命令

dnx . ef migration add MyGrations(自定義名稱)

在VS2015 RTM中為

dnx  ef migrations add MyGrations(自定義名稱)

更新數據庫

dnx . ef migration apply

在VS2015 RTM中為

dnx ef database update

鑒於上述描述命令,由於初始化數據庫是基於Migration,所以直接用 dnx . ef migration apply 無法創建數據庫,我們得首先用 dnvm . ef migration add MyGgrations 來進行遷移。【注意】VS2015默認運行時為beta5,若你為此之上,首先指定運行時為beta5,然后重新恢復包才行,如下:

//指定運行時
dnvm use 1.0.0-beta5

//重新恢復包
dnu restore

接下來就是提升逼格的時刻到了,全部用命令來執行,我們直接在NuGet管理控制台來進行遷移,不遷不知道,一遷嚇一跳,嚇死寶寶了,如下:

看那出錯意思就是:對於運行時為4.5.1,解析下列依賴失敗。我默認的運行時為beta6,剛開始以為是運行時的問題,后來指定運行時依然出錯。【注】這個問題糾結我一天,查找資料也有類似錯誤,但是在我這上面卻不適用,估計是別的問題導致同等錯誤。

*重要: 要進行遷移,必須將命令行移到項目文件夾中 project.json 文件所在位置才行,在NuGet控制台中操作行不通。

當你通過遷移命令進行到如圖,說明你已經成功一大半了。

接下來,快馬加鞭, dnx . ef migration apply 生成數據庫及表,至此我們生成數據庫才算告一段落,如下:

EF 7.0映射關系

一對一映射(one-one)

依然以上述兩個類(Student)和(Flower)為例來實現一對一映射,如下:

    public class Student
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int FloweId{ get; set; }
        public virtual Flower Flower { get; set; }

    }


    public class Flower
    {
        public int Id { get; set; }

        public string Remark { get; set; }

        public virtual Student Student { get; set; }
    }

映射如下:

         builder.Entity<Student>(d =>
            {
                d.Key(p => p.Id);

                d.Property(p => p.Name).Required();
                d.Reference(p => p.Flower).InverseReference(p => p.Student);
            });

            builder.Entity<Flower>(d =>
            {
                d.Key(p => p.Id);

                d.Property(p => p.Remark).Required();
            });

上述 Reference 表示Student關聯到Flower即指向Flower,通過 InverseReference 說明二者為一對一(one-one)關系。通過SQL Profiler監控生成的代碼如下:

那問題來了,雖然表明這二者為一對一關系,但是怎么知道外鍵是FlowerId呢? 

因為默認情況下EF將會去查找Student類中有無Flower類的類名+Id的屬性(不區分大小寫),如果有則將其作為外鍵,如果沒有則將Student類的主鍵作為外鍵。

不信我們將其外鍵屬性修改如下:

public int FId{ get; set; }

映射如下:

驗證了我的結論,有人就說了,這EF太平洋的警察-管的也太寬了,要是迫於無奈,無法將其外鍵屬性進行上述約定呢?你想到的EF 7.0也早就想到了,就以修改的為例,我們只需將映射進行如下修改即可。

d.Reference(p => p.Flower).InverseReference(p => p.Student).ForeignKey(typeof(Student), "FId").PrincipalKey(typeof(Flower), "Id");

用 ForeignKey 方法顯式指定其FId再通過 PrincipalKey 方法將FId作為Flower的外鍵即可。

【建議】:一般情況下建議用約定來映射主要是可讀性強且易於理解,當然,你也手動去指定外鍵,可能會更靈活一點。

一對多映射(one-many) 

我們將Flower中的導航屬性變成集合即可 :

 public virtual ICollection<Student> Students { get; set; }

修改映射關系:

 d.Reference(p => p.Flower).InverseCollection(p => p.Students).ForeignKey(p => p.FlowerId);

 Reference 指向其導航屬性Flower,並用 InverseCollection 來說明二者關系為一對多(one-many)。【注意】關於外鍵上述已經說明,如果依據約定則無需指定,否則需要顯式指定。

 我們進行如下查詢並監控

 var list = ctx.Set<Flower>().Include(d => d.Students).FirstOrDefault(d => d.Id == 1);

此時生成的SQL如下,滿足要求:

此時我們反過來查詢Student對應的是哪個Flower,並進行監控

 var list = ctx.Set<Student>().Include(d => d.Flower).FirstOrDefault(d => d.Id == 1);

監控如下:

如我們所預期一樣得到了相應的結果。

*多對多映射(many-many)【重要】

在EF 7.0之前版本能夠利用API來映射多對多關系 ,但是在EF 7.0版本中只有一對一(one-one)和一對多(one-many)的映射關系,卻沒有處理多對多(many-many)關系的API,雖然利用API實現不了,但是在EF 7.0中利用實體之間的關系來將自動完成映射。

我們假設有如下場景:一個產品(Product)多個分類(Category),同時一個分類對應多個多個產品 ,同時用第三類(Product_Category)來維護這兩者之間的關系。鑒於此,給出所需類,如下:

產品類(Product)

    public class Product
    {
        public int Id { get; set; }

        public string ProductName { get; set; }

        public ICollection<Product_Category> Categorizations { get; set; }
    }

分類(Category)

    public class Category
    {
        public int Id { get; set; }

        public string CategoryName { get; set; }

        public ICollection<Product_Category> Categorizations { get; set; }
    }

產品分類(Product_Category)

    public class Product_Category
    {

        public int Product_Category_Id { get; set; }

        public int ProductId { get; set; }
        public Product Product { get; set; }

        public int CategoryId { get; set; }
        public Category Category { get; set; }
    }

接下來必須指定(Product_Category)中的主鍵(當然你也可以指定主鍵為外鍵屬性),如下:

            builder.Entity<Product_Category>(d=> {
                d.Key(p => p.Product_Category_Id);
            });

此時通過SQL Profiler監控生成SQL如下:

接下來我們通過向表中插入數據,並進行相應的測試:

第一:找出單個產品對應的多個分類 

 var list = ctx.Set<Product>().Include(d => d.Categorizations).FirstOrDefault(d => d.Id == 3);

輸出數據,如我們預期,如下:

 第二:找出單個分類來對應的多個產品

對應代碼如下:

                var Catgorylist = ctx.Set<Category>().Include(d => d.Categorizations).FirstOrDefault(d => d.Id == 1).Categorizations.ToList();
                var ProductList = ctx.Set<Product>().Include(d => d.Categorizations).ToList();
                var pList = from p in ProductList join c in Catgorylist on p.Id equals c.ProductId select p;

查詢數據如下,so  perfect,完成!

補充

突然發現EF 7.0在多對多的映射關系上的強大,為何如此說呢?當我將(Product_Category)關系類進行如下修改時(去掉上述對應的外鍵屬性):

    public class Product_Category
    {

        public int Product_Category_Id { get; set; }

        public Product Product { get; set; }

        public Category Category { get; set; }
    }

生成表字段時會自動生成對應的表外鍵屬性:

 總結

對於上述敘述我們需要多對多(many-many)映射關系做個總結:

  • 在兩個類的關系類中(如上述Product_Category)只需給定需要建立關系的類,無需指定外鍵屬性,EF 7.0會根據類名+Id自動生成。

  • 在EF 7.0上下文中初始化模型時必須要顯式指定主鍵。

 EF 7.0增、刪等操作中的注意事項

連接數據庫問題

當用EF上下文進行數據操作時首先得在上下文中的 OnConfiguring 方法中連接數據庫,否則會出錯。(這也是我納悶的地方,明明在Startup類中配置了上下文以及數據庫的連接,為什么還要配置一遍),如下即可:

        protected override void OnConfiguring(EntityOptionsBuilder optionBuilder)
        {
            optionBuilder.UseSqlServer("Server=.;Database=EF7.0-Beta4-EntityFramework;Trusted_Connection=True;uid=sa;pwd=sa123");
        }

*添加數據操作(注意)

我們借助之前利用的(Student)和(Flower)來進行數據添加操作,代碼如下:

         using (var ctx = new EFDbContext())
            {
                var flower = new Flower()
                {
                    Id = 1,
                    Remark = "so bad",
                    Students = new List<Student>() {
                        new Student() {Id=1,Name="xpy0928",FlowerId=1 }
                    }
                };

                ctx.Set<Flower>().Add(flower);
                ctx.SaveChanges();


            }

以我們之前的經驗通過上述操作肯定在Student表和Flower表各自添加了一條數據,那你就錯了,結果卻不是你想象的那樣!!!空口無憑,用事實說話。如下:

 當我們在上述基礎上添加如下操作下時才可成功添加到數據庫

 ctx.Set<Student>().Add(flower.Students.FirstOrDefault());

通過上述我們知道,之前對於添加只需一步則其關聯的對象也會相應的進行添加,但是現在必須顯式的去添加相應的對象。對於此EF官方的解釋是:

不像EF之前版本,在當前版本上對於一個對象上調用Add()方法則不會標記它的關聯對象為Added狀態。但是此種情況是EF團隊想要暴露的,因為在過去的EF版本中收到許多開發者回饋不應該將關聯的對象標記為Added狀態。開發者中對於要使用無連接圖的呼聲非常高,因為在EF中沒有提供對於無連接圖的高級API,所以在此種情況下,將通過使用一種算法將每個對象標記為特定的狀態,因此不會對其他關聯對象產生其他影響。【注意】EF團隊可能在未來會針對不同的行為給出相應的解決方案。

 延遲加載

 上述導航屬性標記為virtual,數據庫插入數據並進行查找,如下:

  var stu = ctx.Set<Student>().Where(d => d.Flower.Remark == "so bad").ToList();

此時獲得數據卻為空,如下:

總結

在EF 7.0中不再支持延遲加載,即獲得導航屬性必須通過顯示指定 Inlude 來獲取,通過其不存在 LazyLoadingEnabled 和 ProxyCreationEnabled 方法也可得知。

 變更追蹤(Change Tracking)

之前版本關閉變更追蹤是通過 Configuration.DetectChanges 來進行設置,在EF 7.0版本中將通過 ChangeTracker.AutoDetectChangesEnabled 來進行追蹤。同時有關變更追蹤的方法及屬性都通過屬性 ChangeTracker 來進行訪問。其變更追蹤原理和之前版本原理一樣,未發生改變。

我們接下來看如下操作:

首先關閉變更追蹤

        public EFDbContext()
        {
            this.ChangeTracker.AutoDetectChangesEnabled = false;
        }

修改數據並保存

            using (var ctx = new EFDbContext())
            {

                var stu = ctx.Set<Student>().FirstOrDefault(d => d.Name == "xpy0928");


                stu.Name = "xpy0929";

                ctx.Entry(stu).State = EntityState.Modified;

                ctx.SaveChanges();

            }

因為關閉了變更追蹤所以需要手動修改其狀態自動調用DetectChanges方法檢測其狀態,最后更新到數據庫。這是我們在之前版本的做法,但是在EF 7.0中無需這么麻煩。將上述修改操作進行如下修改即可:

  ctx.Set<Student>().Update(stu);

此時調用Update方法進行更改將自動調用DetectChanges方法,最后進行更新。EF 7.0中對實體以及實體集合批量的基本操作都已經實現,通過對應的方法進行操作即可。

總結

以上就是對EF 7.0和VS2015做了一個初步的了解,相信通過在VS2015對EF 7.0的操作會為以后在VS2015上熟練使用EF 7.0做一個鋪墊。本人不才,同時也希望通過本人的學習及分享有能夠幫助到大家的地方。

 敬請期待【Web API】系列。。。。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM