EntityFrameWork学习

本文来源于博客园,转载请注明出处

这篇学习笔记是2015年写的,之前在CSDN里,花了不少时间整理,不忍丢弃,便手工搬到了这里。

前言

本文是我对照英文官网的学习记录,官方的网址在里。可以看到内容分为这么几大块:

Get Started
Past & Future Versions
API Documentation
Entity Framework 6.x
Learn More About Setting Up Your Model
Learn More About Using Your Model
Using EF With Other Technologies

下面开始:

Get Started

I just want to write code (使用代码)

I am creating a new database (创建新数据库)

新建项目

这一节讲的是用Code First的方式来整个小项目。为了简便,作者直接搞了个Console项目。

第一步,创建项目。
第二步,创建实体类。
第三步,创建一个Context:我们要派生出一个Context类来,它代表了与数据库的一个会话(Session),具体来说是从System.Data.Entity.DbContext派生。

  经过上面三步,整完后差不多是这个样子:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Data.Entity; 
 
namespace CodeFirstNewDatabaseSample 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
        } 
    } 
 
    public class Blog 
    { 
        public int BlogId { get; set; } 
        public string Name { get; set; } 
 
        public virtual List<Post> Posts { get; set; } 
    } 
 
    public class Post 
    { 
        public int PostId { get; set; } 
        public string Title { get; set; } 
        public string Content { get; set; } 
 
        public int BlogId { get; set; } 
        public virtual Blog Blog { get; set; } 
    } 
 
    public class BloggingContext : DbContext 
    { 
        public DbSet<Blog> Blogs { get; set; } 
        public DbSet<Post> Posts { get; set; } 
    } 
}

然后:

class Program 
{ 
    static void Main(string[] args) 
    { 
        using (var db = new BloggingContext()) 
        { 
            // Create and save a new Blog 
            Console.Write("Enter a name for a new Blog: "); 
            var name = Console.ReadLine(); 
 
            var blog = new Blog { Name = name }; 
            db.Blogs.Add(blog); 
            db.SaveChanges(); 
 
            // Display all Blogs from the database 
            var query = from b in db.Blogs 
                        orderby b.Name 
                        select b; 
 
            Console.WriteLine("All blogs in the database:"); 
            foreach (var item in query) 
            { 
                Console.WriteLine(item.Name); 
            } 
 
            Console.WriteLine("Press any key to exit..."); 
            Console.ReadKey(); 
        } 
    } 
}

默认情况下DbContext会创建一个数据库:

  • If a local SQL Express instance is available (installed by default with Visual Studio 2010) then Code First has created the database on that instance
  • If SQL Express isn’t available then Code First will try and use LocalDb (installed by default with Visual Studio 2012)
  • The database is named after the fully qualified name of the derived context, in our case that is CodeFirstNewDatabaseSample.BloggingContext

注:通过“SQL SERVER 对象资源管理器”=(localdb)\V11.0

简单说来上面的意思就是:如果你机器上有SQLExpress的数据库实例,Code First方式就会在该实例上创建数据库,否则就会试图使用LocalDb(可以看成一个改进的SQLExpress实例,详细的介绍)。数据库的名称会是Context的完全限定名:CodeFirstNewDatabaseSample.BloggingContext。

处理模型改变(Code Migration)

我们需要用到一种叫做Code First Migrations的特性。
1、打开程序包控制台
2、运行Enable-Migrations命令,会为当前项目生成一个Migrations文件夹,里面有两个文件,一个Configuration.cs ,这里可以为Context指定Seed。_InitialCreate.cs我觉得它就代表一次Mirgration。
现在给Blog类增加一个Url属性:

public class Blog 
{ 
    public int BlogId { get; set; } 
    public string Name { get; set; } 
    public string Url { get; set; } 
 
    public virtual List<Post> Posts { get; set; } 
}

3、在控制台中输入Add-Migration AddUrl
Add-Migration命令会对比上一次migration检查改变,如果有改变则会列出一个新的migration,这里我们给新的migration命名为AddUrl——看到这里,我觉得这很像SVN。

每个migration都会有一个时间戳前缀。当我们运行Update-database命令,就会把所有的migration都应用到数据库中(我的猜测是:按时间顺序)。

namespace CodeFirstNewDatabaseSample.Migrations 
{ 
    using System; 
    using System.Data.Entity.Migrations; 
     
    public partial class AddUrl : DbMigration 
    { 
        public override void Up() 
        { 
            AddColumn("dbo.Blogs", "Url", c => c.String()); 
        } 
         
        public override void Down() 
        { 
            DropColumn("dbo.Blogs", "Url"); 
        } 
    } 
}

要注意,要想Migration生效——哪怕最开始的那个Migration——一定先把代码跑起来,让数据生成一下,然后再去为我们Context开启Migration,再去改变模型,再去应用Migration……如果数据库里压根儿没有库和表,则这些都是不能完成的

数据迁移的使用心得

之前我们在使用EF开发的过程中,一直有一个比较头疼的问题:一旦实体类发生了改变,就需要手动改变数据库,还需要告知其他的开发人员作相应改变。而Code First Migration带来的好处就是,数据库的变化记录到了项目代码中,小组内的其他开发人员把项目更新后,直接运行Update-database即可更新自己本地的数据库。
不过,既然是小组合作,就有可能冲突,处理冲突的方法是:
拉取最新的代码,并作了合并后,得到最新的实体,这时候要根据项目需求处理好实体的冲突,然后我们需要重新生成一个新的Add-Migration,然后Update-Database

针对已有数据库

之前我们使用EF,但都没有使用Migration特性,实体变化了,要么手动删除数据库重新跑程序,要么手动更改相应字段。现在想要针对这些程序使用上Migration特性,怎么办呢:
1、首先,在实体所在的项目中的Package-Console中,运行:Enable-Migrations
2、然后运行 Add-Migration InitialCreate –IgnoreChanges (要确保没有更改任何实体)
3、再运行Update-Database
之后就可正常进行Migrate啦:更改实体,Add-Migration,Update-Database

在实际使用中失败了,然后在Entity项目中重新安装了最新的EF,成功了。

Data Annotations & Code First Fluent API(注解和流畅API)

大部分的模型配置可以通过注解完成,而Fluent API也可以做到,并且还可以完成一些高级配置,二者可以同时使用。EF支持的注解列表:

  • Index[IsUnique=true] 注意,只有6.1及以上支持
  • KeyAttribute
  • StringLengthAttribute
  • MaxLengthAttribute
  • ConcurrencyCheckAttribute
  • RequiredAttribute
  • TimestampAttribute
  • ComplexTypeAttribute
  • ColumnAttribute
  • TableAttribute
  • InversePropertyAttribute
  • ForeignKeyAttribute
  • DatabaseGeneratedAttribute
  • NotMappedAttribute

要使用Fluent API,需要覆写DbContext的OnModelCreating方法。
我们先用注解:

首先添加一个User模型,并用注解指定用户名为主键,代码如下:

public class User 
{ 
    public string Username { get; set; } 
    public string DisplayName { get; set; } 
}

public class BloggingContext : DbContext 
{ 
    public DbSet<Blog> Blogs { get; set; } 
    public DbSet<Post> Posts { get; set; } 
    public DbSet<User> Users { get; set; } 
}

public class User 
{ 
    [Key] 
    public string Username { get; set; } 
    public string DisplayName { get; set; } 
}

然后我们增加一个migration,并应用这个migration:
Add-Migration AddUser
Update-Database
两条命令都执行完后,我们就能看到最新的数据库。

我们再用Fluent API,更改在数据库表中User的一个DisplayName列:

public class BloggingContext : DbContext 
{ 
    public DbSet<Blog> Blogs { get; set; } 
    public DbSet<Post> Posts { get; set; } 
    public DbSet<User> Users { get; set; } 
 
    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
        modelBuilder.Entity<User>() 
            .Property(u => u.DisplayName) 
            .HasColumnName("display_name"); 
    } 
}

同样,也需要两条命令才能更新数据库:
Add-Migration ChangeDisplayName
Update-Database

Learn More About Setting Up Your Model

这一大部分其实相当于把Get Started的内容往细了讲,主要分两块,一块是Code First,一块是EF designer。

Connections and Models

这节涉及的内容包括:EF是如何找到一个可用的数据库连接的,我们如何才能改变默认的行为。

通常一个EF应用程序会使用一个DbContext的派生类,该派生类会调用DbContext基类的某个构造来控制:
a、context如何连接到一个数据库,比如,如何找到并使用一个连接字符串
b、context是以code first形式生成model还是直接加载EF designer创建的model
c、额外的高级选项
如下的一些代码片段展示了DbContext构造的一些使用方法

Use Code First with connection by convention

namespace Demo.EF 
{ 
    public class BloggingContext : DbContext 
    { 
        public BloggingContext() 
        // C# will call base class parameterless constructor by default 
        { 
        } 
    } 
}

在没有为程序作任何配置(尤其是数据库、数据源相关的配置)的情况下,像上面一样调用的是无参的默认构造,这样便会按照默认的Code First的方式来创建并使用一个数据库及数据库连接,在这个例子中,DbContext使用派生类的完全限定名Demo.EF.BlogginContext来作为生成的数据库的名称,并针对该数据库创建一个连接字符串(使用SQL Express或者LocalDb),SQL Express优先。默认情况下VS2010会安装一个SQL Express实例,而VS2012则是LocalDb。

Use Code First with connection by convention and specified database name

public class BloggingContext : DbContext 
{ 
    public BloggingContext() 
        : base("BloggingDatabase") 
    { 
    } 
}

在没有给程序作任何配置的情况下,像上面一样调用了父类带参的构造函数,这会使得DbContext以Code First的模式运行,默认情况下,会创建一个连接到参数所指定的数据库的连接。在该例中,数据库的名称是BloggingDatabase。

Use Code First with connection string in app.config/web.config file

<configuration> 
  <connectionStrings> 
    <add name="BloggingCompactDatabase" 
         providerName="System.Data.SqlServerCe.4.0" 
         connectionString="Data Source=Blogging.sdf"/> 
  </connectionStrings> 
</configuration>

上面这种方式就在告诉DbContext使用一个SQL Express 和 LocalDb之外的database server:Compact Edition database。
假设使用的是DbContext的无参构造,如果连接字符串的名称和派生类名(带不带空间前缀都行)相同,DbContext便会找到该配置中的连接字符串,如果和派生类名不同,你可以将配置中的连接字符串传给DbContext的参数:

public class BloggingContext : DbContext 
{ 
    public BloggingContext() 
        : base("BloggingCompactDatabase") 
    { 
    } 
}

更加严格的写法是这样:

public class BloggingContext : DbContext 
{ 
    public BloggingContext() 
        : base("name=BloggingCompactDatabase") 
    { 
    } 
}

这时,如果在配置中找不到名为BloggingCompactDatabase的连接字符串,则会抛异常。我个人喜欢这种方式。

Handling of Transaction Commit Failures(处理事务提交失败)

适用于+EF6.1。
如何处理连接失败对事务提交的影响,这篇文章介绍的很详细: SQL Database Connectivity and the Idempotency Issue。总而言之是两种情况:
1、事务提交在Server端失败了。
2、事务在Server端提交成功了但是由于连接中断客户端没有得到通过。
发生第1种情况,程序或者用户可以重新提交,发生第二种情况,就应该避免重新提交。EF的新特性可以帮助我们自动处理这些问题。

Using the feature

using System.Data.Entity; 
using System.Data.Entity.Infrastructure; 
using System.Data.Entity.SqlServer; 
 
public class MyConfiguration : DbConfiguration  
{ 
  public MyConfiguration()  
  {  
    SetTransactionHandler(SqlProviderServices.ProviderInvariantName, () => new CommitFailureHandler());  
    SetExecutionStrategy(SqlProviderServices.ProviderInvariantName, () => new SqlAzureExecutionStrategy());  
  }  
}

关于DbConfiguration,后面会说到。

How transactions are tracked

EF是怎么做到的呢,简单来说启用了这个特性,EF会生成一个 __Transactions表,每当有新的事务,就会添加一行。需要注意的是,该表的内容可能会越来越多,需要手动不定期清理。

Code-Based Configuration (EF6 onwards)

EF程序的配置文件可以写在app.config/web.config文件中,也可以写在代码中。如果两处重复配置,则文件里的配置最终生效。
代码中的配置主要通过System.Data.Entity.Config.DbConfiguration完成:

  • 在整个程序中只创建一个DbConfiguration
  • 将DbConfiguration和DbContext放在相同的程序集中
  • 让DbConfiguration有一个公有无参默认构造
  • 配置通过在构造中调用相关方法完成
 using System.Data.Entity; 
using System.Data.Entity.Infrastructure; 
using System.Data.Entity.SqlServer; 
 
namespace MyNamespace 
{ 
    public class MyConfiguration : DbConfiguration 
    { 
        public MyConfiguration() 
        { 
            SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy()); 
            SetDefaultConnectionFactory(new LocalDbConnectionFactory("v11.0")); 
        } 
    } 
}

Creating a Model with Code First

Conventions

Type Discovery:
我们先定义好自己的 conceptual (domain) model ,通过DbContext和DbSet可以对应到表中,如果model涉及到继承呢,直接上代码:

public class SchoolEntities : DbContext
{
    public DbSet<Department> Departments { get; set; }

    //下面这行和上面这行,只留一行即可,也可两行都留,EF会智能地创建出相关的表
    //public DbSet<Course> Courses { get; set; }
}

public class Department
{
    // Primary key 
    public int DepartmentID { get; set; }
    public string Name { get; set; }

    // Navigation property 
    public virtual ICollection<Course> Courses { get; set; }
}

public class Course
{
    // Primary key 
    public int CourseID { get; set; }

    public string Title { get; set; }
    public int Credits { get; set; }

    // Foreign key 
    public int DepartmentID { get; set; }

    // Navigation properties 
    public virtual Department Department { get; set; }
}

public partial class OnlineCourse : Course
{
    public string URL { get; set; }
}

[NotMapped]
public partial class OnsiteCourse : Course
{
    public string Location { get; set; }
    public string Days { get; set; }
    public System.DateTime Time { get; set; }
}

对于继承而言,子类的属性会添加到表中。若子类加了NotMapped则子类的属性不会添加到表中。
Primary Key Convention:
字段“ID"或者ClassName+"ID"会被看作主键。(忽略大小写)
Relationship Convention:
除了导航属性,从对象(对应的是从表)上最好还包含外键属性,如下面代码中的Course:

public class Department 
{ 
    // Primary key 
    public int DepartmentID { get; set; } 
    public string Name { get; set; } 
 
    // Navigation property 
    public virtual ICollection<Course> Courses { get; set; } 
} 
 
public class Course 
{ 
    // Primary key 
    public int CourseID { get; set; } 
 
    public string Title { get; set; } 
    public int Credits { get; set; } 
 
    // Foreign key 
    public int DepartmentID { get; set; } 
 
    // Navigation properties 
    public virtual Department Department { get; set; } 
}

从类中的属性,只要类型和主类中的主键属性类型一致,且属性名满足以下三种格式:

navigation property name+principal primary key property name:如DepartmentDepartmentID

principal class name+primary key property name:如DepartmentDepartmentID,注意,这里导航属性恰好和主类名相同强调内容,倘若导航属性是Dpt,则外键名称应该是DptDepartmentID

principal primary key property name:如DepartmentID
就会自动作为从表中的外键。

倘若从对象没有显示定义外键属性,则就会以"导航属性名+主对象主键属性名"为格式生成一个外键,如:Dpt_DepartmentID(假设Course中的导航属性是Dpt)
另外,外键名称的匹配检测都是大小写无关的。

  • 如果从类中的外键属性是不可空的,则Code First会在两表的关系上设置上级联删除,并且这种主从关系也是强制的,比如Course中的外键属性必须有值,否则就不设置级联删除,主从关系也不是强制的,如:course.DepartmentID = null,这就要求DepartmentID是可空的,如下面代码所示:
class Program
    {
        static void Main(string[] args)
        {

            using (var db = new TestContext())
            {
                db.Customer.Add(new Customer() { Name = "ddd" });
                db.SaveChanges();
            }

        }
    }

    public class TestContext : DbContext
    {
        public TestContext():base("name=Test")
        {

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

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

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }

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

        public string Name { get; set; }

        //由于这里的外键属性是可空的,所以可以直接向数据库中添加一个UserId为null的Customer
        public int? UserId { get; set; }
        public virtual User User { get; set; }
    }

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

        public string Name { get; set; }

        public virtual ICollection<Customer> Cusomters { get; set; }
    }
  • 如果在同类型对象上有多个关系,则主外键关系需要使用注解或者Fluent API来配置了。在下面的Data Annotatios的InverseProperty提到了通过注解的方法来完成配置。

Removing Conventions:
下面的代码移除了复数表名的约定

public class SchoolEntities : DbContext 
{ 
     . . . 
 
    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
        // Configure Code First to ignore PluralizingTableName convention 
        // If you keep this convention, the generated tables  
        // will have pluralized names. 
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 
    } 
}

Data Annotations

Key:
默认情况下,EF把"
Id"或者"classNameId"来作为主键。而Key注解可以改变这一点。
Required:
对于非空类型来说,生成的数据库的对应字段是not null的,不管有没有Required注解。
NotMapped:
不言自明。
ConcurrencyCheck:

[ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)] 
public string BloggerName { get; set; }

当调用saveChanges保存对BloggerName的更新时,Id和旧值都会经你Server,Server如果发现与此同时BloggerName已经更新了,客户端就会引发一个DbUpdateConcurrencyException异常。注意上面其实一次性给BloggerName加了三个注解,这里我们关注的仅是ConcurrencyCheck。通过处理异常可以提示进行相关其他操作。
InverseProperty :
假设一篇文章有两个作者,一个作者可发表多篇文章,则这种外键关系可以这样建立:

public class Post
{ 
        public int Id { get; set; } 
        public Person CreatedBy { get; set; }
        public Person UpdatedBy { get; set; }
}
public class Person 
{ 
        public int Id { get; set; } 
        public string Name { get; set; } 

        [InverseProperty("CreatedBy")] 
        public List<Post> PostsWritten { get; set; } 
        
        [InverseProperty("CreatedBy")] 
        public List<Post> PostsUpdated { get; set; } 
}

Fluent API - Configuring Relationships

特殊的一对一

有一对一、一对多、多对多三种关系,对于一对一来说,主键也起到了外键的作用,所以没有另外的外键。对于一对一来说下面的代码依赖convention是不能正常工作的:

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

    public String MbrName { get; set; }
    public virtual Address Address { get; set; }
}

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

    public virtual Member Member { get; set; }

    public String Addr { get; set; }
}

运行会抛异常:无法确定类型“Address”与“Member”之间的关联的主体端。必须使用关系 Fluent API 或数据注释显式配置此关联的主体端。
正确的代码:

public class SchoolDb : DbContext
{
    public DbSet<Member> Members { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Address>()
            .HasKey(t => t.MemberId);

        modelBuilder
            .Entity<Member>()
            .HasRequired(t => t.Address)
            .WithRequiredPrincipal();
    }
}

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

    public String MbrName { get; set; }
    public virtual Address Address { get; set; }
}

public class Address
{
    public int MemberId { get; set; }

    public String Addr { get; set; }
}

上面的代码确定了一对一中的主体表是Member,从表中不要显示定义主键,否则抛异常,EF会将外键MemberId作为主键。

概述

用fluent API配置relationship的时候,就是在EntityTypeConfiguration上调用HasRequired, HasOptional, 或者HasMany方法来指定相应Entity的relationship类型:HasRequired和HasOptional指定的是reference navigation 属性,HasMany指定的是collection navigation 属性,随后便可以使用WithRequired, WithOptional, 和WithMany 来配置反向的导航属性。这些函数都有无参重载,可以用来指定单向导航属性的基数。最后可以用HasForeignKey方法来指定外键。

Configuring a Required-to-Optional Relationship (One-to–Zero-or-One)
// Configure the primary key for the OfficeAssignment 
modelBuilder.Entity<OfficeAssignment>() 
    .HasKey(t => t.InstructorID); 
 
// Map one-to-zero or one relationship 
modelBuilder.Entity<OfficeAssignment>() 
    .HasRequired(t => t.Instructor) 
    .WithOptional(t => t.OfficeAssignment);

由于OfficeAssignment的主键属性名未遵从约定,所以第一句先配置主键。第二句是在配置关系。

Configuring a Relationship Where Both Ends Are Required (One-to-One)

大部分情况下EF可以推断出关系中的主实体和从实体,不过,当两个实体都是必须的时候,或者两个实体都是可选的时候,EF就无法进行主从判定了,对于前者,在HasRequired之后使用WithRequiredPrinciple或者WithRequiredDependent,对于后者,在HasOptional方法后使用WithOptionalPrincipal或者WithOptionalDependent

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>() 
    .HasKey(t => t.InstructorID); 
 
modelBuilder.Entity<Instructor>() 
    .HasRequired(t => t.OfficeAssignment) 
    .WithRequiredPrincipal(t => t.Instructor);
Configuring a Many-to-Many Relationship

下面的代码在Course和Instructor之间配置了多对多的属性,需要注意的是,中间表是依赖于约定创建的,表名是CourseInstructor,两个列名分别为Course_CourseID和Instructor_InstructorID。

modelBuilder.Entity<Course>() 
    .HasMany(t => t.Instructors) 
    .WithMany(t => t.Courses)

如果要指定中间表名和列名,则需要使用Map方法:

modelBuilder.Entity<Course>() 
    .HasMany(t => t.Instructors) 
    .WithMany(t => t.Courses) 
    .Map(m => 
    { 
        m.ToTable("CourseInstructor"); 
        m.MapLeftKey("CourseID"); 
        m.MapRightKey("InstructorID"); 
    });
Configuring a Relationship with One Navigation Property

单向导航指的是只能从一方导到另一方。默认情况下,EF将单向导航看成一对多。比如,你想让Instructor和OfficeAssignment有一对一的关系,只有Instructor定义了导航属性,那么fluent API的配置则是:

// Configure the primary Key for the OfficeAssignment 
modelBuilder.Entity<OfficeAssignment>() 
    .HasKey(t => t.InstructorID); 
 
modelBuilder.Entity<Instructor>() 
    .HasRequired(t => t.OfficeAssignment) 
    .WithRequiredPrincipal();
Configuring a Foreign Key Name That Does Not Follow the Code First Convention
modelBuilder.Entity<Course>() 
         .HasRequired(c => c.Department) 
         .WithMany(d => d.Courses) 
         .HasForeignKey(c => c.SomeDepartmentID);

Enum Support

using LearnEasyUIEntity;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;

namespace Console
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new EnumTestContext())
            {
                context.Departments.Add(new Department { Name = DepartmentNames.English });

                context.SaveChanges();

                var department = (from d in context.Departments
                                  where d.Name == DepartmentNames.English
                                  select d).FirstOrDefault();

                Console.WriteLine(
                    "DepartmentID: {0} Name: {1}",
                    department.DepartmentID,
                    department.Name);
            }
        }
    }

    public partial class EnumTestContext : DbContext
    {
        public DbSet<Department> Departments { get; set; }
    }
    public enum DepartmentNames
    {
        English,
        Math,
        Economics
    }

    public partial class Department
    {
        public int DepartmentID { get; set; }
        public DepartmentNames Name { get; set; }
        public decimal Budget { get; set; }
    }


}

Learn More About Using Your Model

Working with DbContext

使用建议:

  • 使用using,尽量缩短DbContext的生命周期
  • 在Web应用中,为每个请求new一个DbContext中

Querying/Finding Entities

使用Query:

using (var context = new BloggingContext()) 
{ 
    // Query for all blogs with names starting with B 
    var blogs = from b in context.Blogs 
                   where b.Name.StartsWith("B") 
                   select b; 
     
    // Query for the Blog named ADO.NET Blog 
    var blog = context.Blogs 
                    .Where(b => b.Name == "ADO.NET Blog") 
                    .FirstOrDefault(); 
}

使用Find方法:

using (var context = new BloggingContext()) 
{ 
    // Will hit the database 
    var blog = context.Blogs.Find(3); 
 
    // Will return the same instance without hitting the database 
    var blogAgain = context.Blogs.Find(3); 
 
    context.Blogs.Add(new Blog { Id = -1 }); 
 
    // Will find the new blog even though it does not exist in the database 
    var newBlog = context.Blogs.Find(-1); 
 
    // Will find a User which has a string primary key 
    var user = context.Users.Find("johndoe1987"); 
}

使用Query和Find两种方式作查询,有如下不同:

  • 使用Query每次查询都会操作数据库,就算查出来的实体们在Context中都存在。从数据库查询出来的结果集会和Context中的对象们对比,如果对象不存在则添加到Context中,如果存在就以存在的对象作为最终结果,但是不会覆写Context的对象,而且添加到Context中但是没有保存到数据库的对象不会被Query。

  • 使用Find方法,Find以主键查询,如果Context已经有该对象,则不会发生数据库操作,而且Find会查询添加到Context但未保存到数据库的对象。

    Async Query & Save

    依赖于.NET4.5,在.NET4.5中,异步编程的语法简单许多:Task.Wait、await。

    Relationships & Navigation Properties

    在多的一方删除关系时
    .NET4.0:
context.Entry(course).Reference(c => c.Department).Load();
course.Department = null;

.NET4.5:
context.Entry(course).Reference(c => c.Department).CurrentValue = null;

原生sql语句

大部分操作也可以用原生的sql语句代替,比如执行一个存储过程:

using (var context = new BloggingContext()) 
{ 
    var blogs = context.Blogs.SqlQuery("dbo.GetBlogs").ToList(); 
}

或者作更新操作:

using (var context = new BloggingContext()) 
{ 
    context.Database.SqlCommand( 
        "UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1"); 
}

Optimistic Concurrency Patterns(乐观并发)

对EF来说,乐观并发的意思是当试图把更新保存到数据库的时候,EF总是“乐观”地认为数据并没有被别人改变过。倘若已经改变了,则会抛出异常:DbUpdateConcurrencyException。注意:只有当更新带有ConcurrencyCheck标记的属性(下例的Name属性)的时候,才会抛异常 。

捕获异常后一般来说有两种处理方式:

1、要么让数据库赢


 using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
    blog.Name = "The New ADO.NET Blog"; 
 
    bool saveFailed; 
    do 
    { 
        saveFailed = false; 
 
        try 
        { 
            context.SaveChanges(); 
        } 
        catch (DbUpdateConcurrencyException ex) 
        { 
            saveFailed = true; 
 
            // Update the values of the entity that failed to save from the store 
            ex.Entries.Single().Reload(); 
        } 
 
    } while (saveFailed); 
}

2、 要么让客户端赢


 using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
    blog.Name = "The New ADO.NET Blog"; 
 
    bool saveFailed; 
    do 
    { 
        saveFailed = false; 
        try 
        { 
            context.SaveChanges(); 
        } 
        catch (DbUpdateConcurrencyException ex) 
        { 
            saveFailed = true; 
 
            // Update original values from the database 
            var entry = ex.Entries.Single(); 
            entry.OriginalValues.SetValues(entry.GetDatabaseValues()); 
        } 
 
    } while (saveFailed); 
}

在第二段代码中,倘若不把原始值设置为数据库最新值,则依然会抛异常,由此可以猜测EF并发控制的一些机制:把更新保存到数据库之前,先查询最新的值,若最新的值可原始值一样,则说明数据并未发生改变,那么就把此次更改存入数据库,否则就认为有其他人已经更改过,从而抛出DbUpdateConcurrencyException 异常。下面的代码的可以验证我们的猜测:

 try
    {
        entry.Name = "suaaaack";
        //只要旧值设置的和数据库中不一样就会抛异常
        logDb.Entry<Fake>(entry).Property(a => a.Name).OriginalValue = "xxx";
        logDb.SaveChanges();
        saveFailed = false;
    }
    catch (DbUpdateConcurrencyException ex)
    {
    }

Entity Framework Automatic Detect Changes

面几个方法调用时,会检测改变:
DbSet.Find
DbSet.Local
DbSet.Remove
DbSet.Add
DbSet.Attach
DbContext.SaveChanges
DbContext.GetValidationErrors
DbContext.Entry
DbChangeTracker.Entries
当在一循环中大量调用上述方法,跟踪大量实体时,检测改变会浪费性能,为了提高性能可以这样做:

using (var context = new BloggingContext()) 
{ 
    try 
    { 
        context.Configuration.AutoDetectChangesEnabled = false; 
 
        // Make many calls in a loop 
        foreach (var blog in aLotOfBlogs) 
        { 
            context.Blogs.Add(blog); 
        } 
    } 
    finally 
    { 
        context.Configuration.AutoDetectChangesEnabled = true; 
    } 
}

No-Tracking Queries

在只读的环境下,查询大量实体时,以下代码可以提高性能:

 using (var context = new BloggingContext()) 
{ 
    // Query for all blogs without tracking them 
    var blogs1 = context.Blogs.AsNoTracking(); 
 
    // Query for some blogs without tracking them 
    var blogs2 = context.Blogs 
                        .Where(b => b.Name.Contains(".NET")) 
                        .AsNoTracking() 
                        .ToList(); 
}

Add/Attach and Entity States

实体的状态通过EntitySate枚举来标识,共5种:

  • Added:没被context 维护,但是数据库中没有。
  • Unchanged:被context维护,且属性值和从数据库查询出时一样。
  • Modified:和Unchanged不同的是,属性值和从数据库查询出来的时候,发生了变化。
  • Deleted:和Unchanged不同的是,该实体被标记为删除,SaveChanges的时候,会从数据库中删除掉。
  • Detached:从context中分离出来,context不再追踪它了。

三种添加新Entity的方式:

using (var context = new BloggingContext()) 
{ 
    var blog = new Blog { Name = "ADO.NET Blog" }; 
    context.Blogs.Add(blog); 
    context.SaveChanges(); 
}
using (var context = new BloggingContext()) 
{ 
    var blog = new Blog { Name = "ADO.NET Blog" }; 
    context.Entry(blog).State = EntityState.Added; 
    context.SaveChanges(); 
}
using (var context = new BloggingContext()) 
{ 
    // Add a new User by setting a reference from a tracked Blog 
    var blog = context.Blogs.Find(1); 
    blog.Owner = new User { UserName = "johndoe1987" }; 
 
    // Add a new Post by adding to the collection of a tracked Blog 
    var blog = context.Blogs.Find(2); 
    blog.Posts.Add(new Post { Name = "How to Add Entities" }); 
 
    context.SaveChanges(); 
}

Insert/Update模式:

public void InsertOrUpdate(Blog blog) 
{ 
    using (var context = new BloggingContext()) 
    { 
        context.Entry(blog).State = blog.BlogId == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 
 
        context.SaveChanges(); 
    } 
}

Working with Transactions

EF对事务的默认处理:
如果没有事务,SaveChanges()会开启事务,执行完毕之后就关闭了。如果显示使用了事务,则SaveChanges执行后不会直接入库,会使用该事务。类似的ExecuteSqlCommand和EntityFramework.Extended的批量更新和删除也是如此。
事务的隔离级别则是 database provider 默认的隔离级别,比如,在SQL Server上,隔离级别是:Read Committed,意思是:一个事务内,可以读到另一个事务已经提交的数据,这是否成为一个问题,和项目需求有关,一般来说,这不会是问题,而且EF对此也提供了并发处理机制ConcurrencyCheck。
对于查询,EF是不开启事务的。

EF的这些默认处理足以应付大多数开发需求。

显式使用事务:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 
 
namespace TransactionsExamples 
{ 
    class TransactionsExample 
    { 
        static void StartOwnTransactionWithinContext() 
        { 
            using (var context = new BloggingContext()) 
            { 
                using (var dbContextTransaction = context.Database.BeginTransaction()) 
                { 
                    try 
                    { 
                        context.Database.ExecuteSqlCommand( 
                            @"UPDATE Blogs SET Rating = 5" + 
                                " WHERE Name LIKE '%Entity Framework%'" 
                            ); 
 
                        var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
                        foreach (var post in query) 
                        { 
                            post.Title += "[Cool Blog]"; 
                        } 
 
                        context.SaveChanges(); 
 
                        dbContextTransaction.Commit(); 
                    } 
                    catch (Exception) 
                    { 
                        dbContextTransaction.Rollback(); 
                    } 
                } 
            } 
        } 
    } 
}

BeginTransaction()方法会按需打开数据库链接。

跨库的事务:
上面所说的事务,都是针对同一个数据库,如果操作多个数据库该怎么使用事务呢?
这便是所谓的分布式事务,它并不是EF的一部分。待以后研究一下ADO.NET再说。

本文来源于博客园,转载请注明出处

转载于:https://www.cnblogs.com/qzhforthelife/p/6680744.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值