Code First :使用Entity. Framework编程(3)

CHAPTER 3

Using Conventions and Configurationsfor Property Attributes

第三章

对属性使用约定和配置

In Chapter 2, you got your first look at some of Code First's conventions and how to override Code First's default behavior with configuration. You not only saw how to configure with Data Annotations, but we also applied the same configuration using the Fluent API so you could see a direct comparison.

在第2章,您已经对Code First的约定以及如何通过配置重载默认约定行为有了大致的了解。你你不仅看到如果使用Data Annotations配置,也学习如何使用Fluent API作出相同的配置,并对两者进行了对比。

In this and the next few chapters, we'll walk you through a variety of areas where you can configure your model. For each topic you'll see what Code First does by convention and then you'll learn how to override the convention with Data Annotations and the Fluent API. As you've learned, there are a number of configurations you can apply using the Fluent API that are not available with Data Annotations. We'll be sure to point out those cases when it's time to dig into them.

在本章乃至以后几章里,我们带您进入各种可以配置您的模型的领域。对每个主题你会看到Code First如何通过默认规则进行工作,也会学到如何通过Data Annotations和Fluent API来覆写这些规则。正如你已经学到的,在Fluent API中可以实现的很多配置在Data Annotations无法实现。我们会在适当的时机指出这些差异

We'll kick things off in this chapter by focusing on Code First conventions and configuration that affect attributes of the properties and their related database columns. You'll learn about working with attributes such as the length of a string or byte array and precision of numeric values. You'll work with key properties and properties that are involved with optimistic concurrency. Finally, you'll learn about how Code First detects when a property is in fact a complex type (aka value type), as well as how to help when it's unable to infer these complex types from your domain classes.

本章我们专注于对类中属性的配置,以观察默认规则和配置对数据库列的影响。你将会学习到诸如如何控制字符串长度,byte 数组,数值的精确性等方面的知识。你也可以学到键属性以及包含在其中的最优一致性。最后,您还可以学到有关Code First检测一个属性是否是复杂类型(aka值类型),如果Code First无法从您的域类中推断出复杂类型时,我们将介绍您如何对Code First提供帮助。

Working with Property Attributes in Code First

Code First中的属性

In Chapter 2, you saw some of Code First's conventions and configuration options that apply to string properties. We'll provide a quick review here before moving on to new attributes.

在第2章里,您已经看到一些应用于字符串属性的规则和配置选项,在进入新的选择我们快速回顾一下。

Length

字长

Convention

默认规则

max (type specified by database)

max(类型由数据库指定)

Data Annotation

MinLength(nn)

MaxLength(nn)

StringLength(nn) 

Fluent 

Entity<T>.Property(t=>t.PropertyName).HasMaxLength(nn) 

Length is used to describe the length of arrays. Currently that encompasses string and Byte array.

字长用于描述数组的长度。这里包括对字符串和byte数组。

Code First convention specifies that the length of string or byte array should be max.The database provider then determines what type should be used. In SQL Server, string becomes nvarchar(max) and byte array becomes varbinary(max).

Code First的默认规则string 或者byte数组的长度应为最大。根据不同的数据库类型确定在数据库最终的类型。对SQL Server而言,string 会生成nvarchar(max),而byte数组会生成varbinary(max).

You can override the default length, which will impact the length used in the database. The maximum length of a property is also validated by Entity Framework at runtime before pushing changes to the database. With Data Annotations, you can also configure a MinLength attribute for an array. MinLength will get validated by Entity Framework's Validation API but will not impact the database.

你可以覆写默认长度,就会影响在数据库中的实际字长。长度的最大值会在EF框架更新数据存入数据库之前的运行时进行验证。如果使用Data Annotation来配置,还可以为数组配置MinLength(最小长度)特性。最小长度特性也会得到EF验证API的验证,但不会影响数据库。

The StringLength Alternative 

可选的StringLength特性 

MinLength and MaxLength are part of the EntityFramework.dll. There is also a String Length annotation that is part of the System.ComponentModel.DataAnnotations.dll: 

MinLength和MaxLength都包含在EntityFramework.dll中。还有一个字符串长度特性包含在System.ComponentModel.DataAnnotations.dll里: 

[StringLength(500,MinimumLength= 10)] 

public string Description { get; set; } 

Code First will recognize this annotation if you prefer to use it. If you are working with ASP.NET MVC or Dynamic Data, they will recognize StringLength, but they will not know about MinLength and MaxLength from the EntityFramework.dll. 

如果你愿意用这种特性标永,Code First也是可以识别的。如果你在ASP.NET MVC或动态数据下工作,则只认StringLength,而不认来自于EntityFramework.dll里的MinLength和MaxLength.(如果使用EF框架来开发就认了!---译者注) 

When running against SQL Compact Edition, there is an additional convention that replaces the default maximum array length with 4000 rather than Max. 

使用SQL Compact Edition,有一个附加的默认规则将默认的最大字长替换为4000. 

Data Type

数据类型

Convention

默认规则

The default column data type is determined by the database provider you are using. For SQL Server some example default data types are:

默认的列数据类型由数据库决定,对SQL Server而言如下:

String : nvarchar(max)

Integer:int

Byte Array:varbinary(max)

Boolen:bit

Data Annotation 

Column(TypeName="XXX")

Fluent 

Entity<T>.Property(t=>t.PropertyName).HasColumnType("XXX")

In Chapter 2, you saw a number of examples of how Code First maps .NET types to data types. The Destination and Lodging classes contain Integers, Strings, a Byte array, and a Boolean. Code First lets the database provider select the appropriate data type to use for each column. The application is using the SQL Server database provider, which mapped those to nvarchar(max), int, varbinary(max), and bit, respectively.

第2章,您已经看到了几个如何映射.Net类型到数据库数据类型的例子。Destination和Lodging类包含有整型,字符串,Byte数组,布尔型变量。Code First通知数据库选择合适的数据类型匹配每一列。由于使用的是SQL Server数据库,因此分别映射到nvarchar(max), int, varbinary(max)和bit类型。

It's possible to map to a different database type using configurations as long as you choose a database type that can be cast automatically. For example, if you try to set the data type of a String to a database int, at runtime the DbModelBuilder will throw an error saying Member Mapping specified is not valid, followed by details of the .NET type and the database type you were attempting to coerce it to.

根据您选择的配置当然也可以指定到不同类型。例如,如果你想将字符串映射到数据库的int数据类型,运行时DbModelBuilder就会抛出一个错误告知映射非法,后而会有些细节,都是些有关于你要尝试创建的类型信息。

In Chapter 2, we changed the database type of the Photo property to be an image type rather than a varbinary(max)

在第2章,我们将Photo属性映射到了image类型而不是varbinary(max)类型。

Nullablitity and the Required Configuration

可空性和必需项配置

Convention

默认规则

Key Properties : not null in database

键属性:在数据库中为非空

Reference Types (String, arrays): null in the database

引用类型(String,数组):在数据库中可空

Value Types (all numeric types, DateTime, bool, char) : not null in database

值类型(所有数字类型,日期,布尔,字符):在数据库为非空

Nullable<T> Value Types : null in database

Nullable<T>值类型(可空类型):在数据库可空

Data Annotation 

Required

Fluent 

Entity<T>.Property(t=>t.PropertyName).IsRequired

Convention will ensure that .NET types that are non-nullable map to non-nullable fields in the database. Additionally, any properties that are part of the key will map to non-nullable database fields.

规则约定确保非可空的.Net类型要映射到数据库的非可空字段,除此以外,任何键属性都只能映射到非可空数据库字段。

If you use .NET to specify a value type (such as int) to be nullable using the generic Nullable<T>, it will map to a nullable database field.

如果你使用.Net的范围Nullable<T>指定一个值类型(如int)为可空,将会映射到数据库的一个可空字段。

You saw in Chapter 2 how to specify that a property is required using configuration. You used the Required Data Annotation and then the IsRequired Fluent configuration to force the Lodging.Name property to be required. Required properties are validated by the Entity Framework at runtime before saving data to the database; an exception is thrown if the property has not been populated. Another effect is that when you mark a property as Required, its database field will become not null.

在第2章您已看到如何使用配置指定一个属性为必须项。使用Data Annotation的Required标记和Fluent的IsRequired属性都可强制Lodging.Name属性为必须项。在保存数据到数据库之前,EF运行时会对必须属性进行验证;如果属性没有赋值就会抛出一个异常。另一个效果是,数据库相应字段为非空。

Mapping Keys

映射键

Convention

默认规则

Properties named Id

属性名为Id

Properties named [TypeName] + Id

属性名为[类型名]+Id

Data Annotation

Key

Fluent 

Entity<T>.HasKey(t=>t.PropertyName

Entity Framework requires every entity to have a key. This key is used by the context to keep track of individual objects. Keys are unique and often generated by the database. Code First convention makes these same presumptions.

Recall that when Code First created the database from the Destination and Lodging classes, the DestinationId and LodgingId int fields in the resulting tables were marked PK and not null. If you look further into the column properties for DestinationId and LodgingId, you'll see that these two fields are also Auto-Incremented Identity fields, as shown in Figure 3-1. This, too, is by convention for integers that are primary keys.

EF框架要求每个实体都有一个键。这个键用于上下文以保持每个独立对象的跟踪。键是唯一的而且经常上数据库生成。Code First默认规则作出了同样的预设。

回忆一下由Destination和Lodging类生成的数据库,DestinationId和LodgingId的整型字段都被标记为主键和非空字段。如果进一步观察二者的列属性,你会发现这些字段是自增长的标识字段,如图3-1所示,这是默认规则将整型量作为主键来管理。

Most commonly, primary keys in the database are either int or GUID types, although any primitive type can be used as a key property. A primary key in the database can be composed from multiple fields in the table, and, similarly, an entity's key can be composed of multiple properties in a particular class. At the end of this section, you'll see how to configure composite keys.

大多数情况下,数据库中的主键不是int就是GUID类型,尽管任意类型都可以作为键属性。数据库中的主键会是多个表的组成字段,类似地,一个实体的键也是某个类中的多个属性。在本节结束的时候,你会看到如何配置复合键。

Code First Convention Response to Unconventional Key Properties

Code First默认规则对不合规键属性的响应

In the case of our two classes, the properties that are meant to be keys happen to meet Code First convention, so everything worked out nicely. What if they did not meet convention?

Let's add a new class to the model, Trip, shown in Example 3-1. The Trip class does not have any properties that meet the convention for an entity key, but our intent is that the Identifier property be used as the key.

如果在我们的类中我们意指的键碰巧满足Code First默认规则,那么一切顺利。但是如果不满足规则呢?

我们向模型添加一个新类,Trip,见代码3-1.Trip类没有任何满足实体键默认规则的属性,但我们的意图是Identifier属性应该作为键。

Example 3-1. The Trip class without an obvious key property

public class Trip

{

public Guid Identifier { getset; }

public DateTime StartDate { getset; }

public DateTime EndDate { getset; }

public decimal CostUSD { getset; }

}

Along with the new class, we'll need a DbSet<Trip> added into BreakAwayContext:

伴随这个新类,我们需要在BrakAwayContext中添加一个DbSet<Trip>数据集:

public DbSet<Trip> Trips { getset; }

When running the application again, an exception will be thrown as the DbModel Builder attempts to construct a model from the classes:

我们再次运行程序,在尝试从类中创建模型时DbModel Builder抛出一个异常:

One or more validation errors were detected during model generation: System.Data.Edm.EdmEntityType: : 

EntityType 'Trip' has no key defined. Define the key for this EntityType. 

在模型生成过程中检测到一个或多个验证错误: 

System.Data.Edm.EdmEntityType: : 

实体类型"Trip"还没有定义key。请为这个实体类型定义Key. 

Because there was no key property that met the pattern expected by convention (in this case either Id or TripId), Code First could not go forward with building the model. To be clear, the type (Guid) has nothing to do with this problem. As stated earlier, you can use any primitive type for a key.

由于没有找到期望的默认Key属性(Id或TripId),Code First无法继续创建模型。需要明确的是,类型(GUID)与这个问题无关。如前所述,您可以使用任何的原始类型作为键。

Configuring the Key with Data Annotations

使用Data Annotations配置Key

The Data Annotation for identifying a key is simply Key. Key exists in the System.ComponentModel.DataAnnotations.dll because it has been added to .NET 4 for use by other APIs (for example, ASP.NET MVC uses Key). If your project didn't already contain a reference to this assembly, you'd need to add it. The EntityFramework.dll reference is not required for this particular annotation, although you might be using others from that assembly:

Data Annotation标识一个键只需要简单的一个Key.Key特性位于System.ComponentModel.DataAnnotations.dll,由于它已经被添加到了.Net4之中,也被其他API所使用(如ASP.Net MVC使用的Key).如果你的项目尚未包含此程序集的引用,你就不能添加它。对这个特定的特性不需要引用EntityFramwork.dl,尽管你可能有其他用途。

[Key]

public Guid Identifier { getset; }

Using HasKey to Configure a Key Property in the Fluent API

在Flurent API中使用HasKey来配置Key属性

Configuring a Key property with the Fluent API is a bit different than the few Fluent configurations you used in Chapter 2. Rather than configuring a particular property, this configuration is added directly to the Entity. To configure a key, you use the HasKey method, as shown in Example 3-2.

使用Fluent API来配置Key属性与前面几个Fluent配置不同。这一配置直接添加到实体上。为了配置一个key,你需要使用HasKey方法,如代码3-2。

Example 3-2. The HasKey Fluent configuration in OnModelCreating

modelBuilder.Entity<Trip>().HasKey(t => t.Identifier)

If you are encapsulating the configurations within EntityTypeConfiguration classes, as you learned about in Chapter 2, you begin with HasKey or this.HasKey (Example 3-3).

如果将代码配置进EntityTypeConfiguration类中,正如你在第2章学到的,应该开始于HasKey或This.HasKey(代码3-3)

Example 3-3. HasKey inside of an EntityTypeConfiguration class

HasKey(t => t.Identifier)

Configuring Database-Generated Properties

配置数据库生成的属性

Convention

默认规则

Integer keys:Identity

整型键值:标识列

Data Annotation

DatabaseGenerated(DatabaseGeneratedOption)

Fluent 

Entity<T>.Property(t=>t.PropertyName)

.HasDatabaseGeneratedOption(DatabaseGeneratedOption)

In the previous section, you learned that by default, Code First will flag int key properties so that Entity Framework is aware that the database will generate the values. What about the Guid key that we just created? Guids require special handling, which involves the DatabaseGenerated configuration.

To demonstrate, we'll add a new method, InsertTrip (Example 3-4) to the console application and call it from the Main module.

在前面部分里,你已经看到默认情况整型键值会被EF框架生成标识字段,由数据库生成值。而我们自己创建的Guid型的键值怎么办?Guid需要特殊的处置,需要包含在DatabaseGenerated配置。

为了展示,我们会添加一个新方法,InsertTrip(代码3-4)到控制台程序,然后在主模型进行调用。

Example 3-4. The InsertTrip method

private static void InsertTrip()

{

var trip = new Trip

{

CostUSD = 800,

StartDate = new DateTime(2011, 9, 1),

EndDate = new DateTime(2011, 9, 14)

};

using (var context = new BreakAwayContext())

{

context.Trips.Add(trip);

context.SaveChanges();

}

}

Running the application will cause the database to be dropped and recreated with the new Trips table shown in Figure 3-2.

Identifier is a primary key, unique identifier, not null column.

运行程序会导致数据库卸载并增加新的Trips表后重新创建,如图3-2.Identifier是主键,唯一标识,非空列。

Recall that earlier in the chapter, you learned that value types are required by convention. You can see the effect of this. The StartDate, EndDate, and CostUSD properties of the Trip class are all value types and therefore, by default, not null in the Trips table in the database. 

回到本章前面的内容,你知道值类型默认是required。在此也会看到同样的效果,StartDate,EndDtat和CostUSD属性都是值类型,默认情况下,在数据库也都是非空字段。 

However, an empty Guid value got entered into the new row. As you can see in Figure 3-3, it's filled with 0s.

然后在新行中我们看到Guid值被填充为很多个0.如图3-3

Neither the database nor Entity Framework is aware that we'd like one of them to generate a new Guid for new Trips. With no logic to generate a new Guid for this property, it inserted the Guid default value—the zeros.

数据库和EF框架都不知道我们想让他们之一为新添加的Trips生成一个新的Guid。由于这个属性没有一个生成新Guid的逻辑方法,就会默认以0值填入。

If you attempt to insert another record with the same value in Identifier, the database will throw an error because it expects a unique value.It is possible to configure the database to automatically generate a new Guid, by setting the default value to newid(). Whether you do this manually in the database or expect Code First to inject this logic, you must let Code First know that the database will be handling the Guid.

如果你尝试以同样的值插入另一个记录,数据库会抛出一个错误,因为期待一个唯一值。当然可以配置数据库自动生成一个新的Guid(通过设置默认值为newid()).i不客你在数据库中手动操作还希望CodeFirst插入此逻辑,你必须让Code First知道数据库将要处理Guid.

The solution is to let Code First know that the database will generate this key using another annotation: DatabaseGenerated. This configuration has three options—None, Identity, and Computed. We want the identifier to be treated as an Identity key by the database, forcing the database to generate the identity key values for new rows, just as it does by default with keys that are integers.

解决方案是让Code First 知道数据库将要生成这个键值通过使用另一个annotation:DatabaseGenerated.这一配置有三个选项—None,Identity和Computed.我们想要Identifier字段被标识为Identity,才能确保数据库在加入新行时自动生成标识字段的值,正如整型类型的键值自动生成一样。

Configuring Database-Generated Options with Data Annotations

使用Data Annotations配置数据库-生成选项

Modify the class to tell Code First that the database will generate an identity key on your behalf:

修改类型代码告诉Code First让数据库生成一个唯一的键值:

[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]

public Guid Identifier { getset; }

In the case where the Key field is an Integer, Code First defaults to DatabaseGeneratedOption.Identity. With a Guid, you need to explicitly configure this. These are the only types that you can configure to be Identity when Code First is generating the database. If you are mapping to an existing database, any column where the database generates a value on insert can be marked as Identity. 

当键字段为整数时,Code First默认选择DatabaseGeneratedOption.Identity。而对Guid,你需要显示进行配置。这是唯一一种可以通过Identify来配置Code First的数据类型。如果映射到一现有的数据库,任何在插入数据可生成值的列都可以标识为Identify. 

After running the application again, Figure 3-4 shows the newly generated Identifier.

再次运行程序,较长3-4输出的新生成的标识。

You might be interested in seeing the SQL, shown in Example 3-5, sent to the database for the INSERT where Entity Framework expects the database to generate the Guid value for the Identifier property.

你可对查看SQL感兴趣,代码3-5显示的EF框架发送给数据库的INSERT语句,其中要求数据库为Idenfifier属性生成Guid值

Example 3-5. SQL for inserting a new Trip

declare @generated_keys table([Identifier] uniqueidentifier)

insert [dbo].[Trips]([StartDate], [EndDate], [CostUSD])

output inserted.[Identifier] into @generated_keys

values (@0, @1, @2)

select t.[Identifier]

from @generated_keys as g

join [dbo].[Trips] as t on g.[Identifier] = t.[Identifier]

where @@ROWCOUNT > 0',

N'@0 datetime2(7),@1 datetime2(7),@2 decimal(18,2)',

@0='2011-09-01 00:00:00',@1='2011-09-14 00:00:00',@2=800.00

There are two other enums for DatabaseGeneratedOption: None and Computed. Following is an example of where None is useful.Example 3-6 shows another new class for our model, Person. The SocialSecurityNumber property has been configured as the Key property for the class.

DatabaseGeneratedOption还有两个枚举值:None和Coumputed。下面就有一个示例证明None是有用的。代码3-6显示了另一个新类,Person,SocialSecurityNumber属性已经被配置为此类的键属性。

Example 3-6. Person class with unconventional key property

using System.ComponentModel.DataAnnotations;

namespace Model

{

public class Person

{

[Key]

public int SocialSecurityNumber { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

}

}

Remember to add a DbSet<Person> to BreakAwayContext:

记得要在BreakAwayContext类中添加DbSet<Person>

public DbSet<Person> People { get; set; }

And finally, a new method, InsertPerson (shown in Example 3-7) is added to the console app, along with a call to this from the Main method, which inserts a new person into the database.

最后,将一个新方法,InsertPerson(见代码3-7)需要添加到控制台程序中,在Main方法中调用这个方法,就会向数的中添加一个新的person。

Example 3-7. InsertPerson method

private static void InsertPerson()

{

var person = new Person

{

FirstName = "Rowan",

LastName = "Miller",

SocialSecurityNumber = 12345678

};

using (var context = new BreakAwayContext())

{

context.People.Add(person);

context.SaveChanges();

}

}

再次运行程序,让我们再看看数据库新添加的一行,如图3-5.

The SocialSecurityNumber is 1, not 12345678. Why? Because Code First followed its convention that a Key that is an integer is presumed to be a database identity field and therefore, Entity Framework did not provide the SocialSecurityNumber value in the INSERT command. It let the database generate the value. In fact, if you look at the SocialSecurityNumber value of the person instance after SaveChanges is called, it has been updated to reflect the database-generated value, 1.

SocialSecurityNumber 的值是 1, 不是12345678.为什么?由于Code First根据key是一个整型这个事实告知数据库这是一个标识字段,因此在INSERT语句中EF框架没有提供正确的SocialSecurityNumber 的值,而是让数据库自行生成。事实上,如果在SaveChanges完成后查看 person实例中SocialSecurityNumber 的值,此值已经被更新为数据库生成的值,1.

To fix this, we need to add some configuration to override the Identity convention, because in this case DatabaseGeneratedOption.Identity is wrong. Instead we want None:

为修正这一点,我们需要添加一些配置覆写默认标识规则,在这种情况下,DatabaseGeneratedOption.Identity是不对的,应该用None:

[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]

public int SocialSecurityNumber { get; set; }

Then, running the app again, you can see in Figure 3-6 that the value provided by the application was inserted into the database as expected.

然后再运行程序,如图3-6,数据库正确插入了有关数据。

DatabaseGeneratedOption.Computed is used to specify a mapping to a database field that is computed. For example, if there was a FullName field in the People table with a formula that combined the values of FirstName and LastName, you would want to let Entity Framework know so that it would not attempt to save data into that column. You cannot specify the formula to use for a computed column in Code First and, there fore, you can only use Computed when mapping to an existing database. Otherwise, the database provider will throw a runtime exception when it encounters the Computed configuration while trying to create the database.

DatabaseGeneratedOption.Computed用于指定一个映射到数据库的字段是通过计算得到的。例如,如果有一个FullName字段在People表中,是用一个公式将FirstName和LastName组合起来得到的,你就应该让EF框架知道以便其不会尝试存储数据到此列中。你不能指定一个公式用来计算Code First中列的值,因此当映射到一个现存的数据库中你只能使用Coumputediv。要不然,在试图创建数据库时如果遇到Computed配置,数据库引擎就会抛出运行时异常。

Configuring Database-Generated Options with the Fluent API

使用Fluent API来配置数据库生成选项

The DatabaseGeneratedOption can be configured on a particular property. You can append this configuration to the HasKey you applied earlier, for example:

DatabaseGeneratedOption可以配置为一种特殊的属性,你可以将配置附加HasKey后面,如:

modelBuilder.Entity<Trip>()

.HasKey(t => t.Identifier)

.Property(t => t.Identifier)

.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

或者创建一个独立的语句:

modelBuilder.Entity<Person>()

.HasKey(p => t.SocialSecurityNumber);

modelBuilder.Entity<Person>()

.Property(p => p.SocialSecurityNumber)

.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

You'll notice that the DatabaseGeneratedOption enums are within the System.ComponentModel.DataAnnotations namespace in EntityFramework.dll. You'll also need to have a using statement for this namespace at the top of the context class file.

你会注意到DatabaseGeneratedOption枚举位于System.ComponentModel.DataAnnotations名称空间,在EntityFramework.dll中。需要在context类的文件头部添加using引用。

Configuring TimeStamp/RowVersion Fields for Optimistic Concurrency

为开放式并发环境配置时间戳或行版本字段

Convention

默认规则

None

Data Annotation

TimeStamp

Fluent 

Entity<T>.Property(t=>t.PropertyName).IsRowVersion()

Entity Framework has supported optimistic concurrency since the first version. Chapter 23 of the second edition of Programming Entity Framework covers optimistic concurrency in depth. Here we'll show you how to configure your classes to map to RowVersion (also known as TimeStamp) fields and at the same time instruct Entity Framework to use these fields for concurrency checking when performing updates or deletes on the database.

With Code First you can flag a field to be used in optimistic concurrency checks regardless of what type it maps to in the database, or you can take it a step further and specify that the concurrency field maps to a TimeStamp field.

Only one property in a class can be configured as a TimeStamp property.

EF框架从第一版本开始就支持开放式并发环境。Programming Entity Framework这本书的第二版在第23章深入探讨了开放式并发。在这里我我们教你如何配置类映射到RowVersion(或称作TimeStamp,时间戳)字段,同时通知EF框架在进行更新或删除数据库操作时使用这些字段进行并发检查。

使用Code First你需要指定一个字段使用开放式并发检查,与映射到数据库的类型无关,或者你可以进一步指定并发的字段映射到一个TimeStamp字段。

一个类只能有一个属性可以配置为TimeStamp特性。

RowVersion and TimeStamp are two terms for the same data type. SQL Server has always used TimeStamp, while many other databases use the more aptly named RowVersion. As of SQL Server 2008, the timestamp data type was changed to be called rowversion, but most of the tools (e.g., SQL Server Management Studio, Visual Studio) continue to display this as timestamp. 

RowVersion和TimeStamp是两个具有相同类型的项。Sql Server使用TimeStamp,而其他数据库使用更恰当的名称为RowVersion.d SQL Server2008中,timestamp数据类型也调整为rowversion,但是大多数工具(如Sql Server Management Studio,vs等)仍然显示为timestamp. 

Code First Convention and TimeStamp fields

Code First的默认规则与TimeStamp字段

By default, Code First does not recognize TimeStamp properties, so there is no conventional behavior. You must configure properties to get the behavior.

默认情况,Code First并不识国时间戳属性,因此没有默认约定行为,获得此行为必须配置此属性。

Using Data Annotations to Configure TimeStamp

使用Data Annotations配置时间戳

Not just any property can be mapped to a timestamp database type. You must use a byte array. With that, the Data Annotation is simple: TimeStamp.Add the following property to both the Trip and Person classes:

并非任何属性都可以映射到一个timestamp数据库类型。必须是byte数组才可以。配置过程很简单,将TimeStamp特性加到Trip和Personal类中的下列属性中。

[Timestamp]

public byte[] RowVersion { getset; }

Then run the console app again, ensuring that both the InsertTrip and InsertPerson methods are called from the Main method. In the database you'll see that the new RowVersion column (Figure 3-7) has been added to both tables and its type is a non-nullable timestamp.

The database will automatically create a new value for these fields any time the row is modified. But TimeStamp doesn't only affect the database mapping. It also causes the properties to be seen by Entity Framework as concurrency tokens. If you have worked with an EDMX file, this is the equivalent of setting the property's ConcurrencyMode to Fixed. Any time Entity Framework performs an insert, update, or delete to the database, it will take the concurrency field into account, returning the updated database value on every INSERT and UPDATE and passing in the original value from the property with every UPDATE and DELETE.

然后运行控制台程序,确保InserTrip和InsertPerson方法都在Main方法中进行调用。在数据库中你会看到新生成的RowVersion列(图3-7),类型为非可空timestamp类型。

任何时候行内数据被修改时数据库会自动为此属性创建新值。但TimeStamp不仅影响数据库的映射,还会导致属性被EF框架视作并放的令牌。如果你使用EDMX文件,这就等同于设置了一个属性的ConcurrencyMode(并发模式)。EF框架在执行插入、更新或删除数据库时,就会考虑并发字段,返回每个INSERT和UPDATE更新数据库的值,并传回到每个UPDATE和DELETE的相属性的原始位置。

For example, Example 3-8 shows the SQL that was sent to the database when SaveChanges was called in the InsertPerson method:

例3-8显示了当执行InsertPerson方法后保存设置时的SQL语句:

Example 3-8. INSERT combined with SELECT to return new RowVersion

exec sp_executesql N

'insert [dbo].[People]([SocialSecurityNumber], [FirstName], [LastName])

values (@0, @1, @2)

select [RowVersion]

from [dbo].[People]

where @@ROWCOUNT > 0 and [SocialSecurityNumber] = @0',

N'@0 int,@1 nvarchar(max) ,@2 nvarchar(max) ',@0=12345678,@1=N'Rowan',@2=N'Miller'

Not only does Entity Framework tell the database to perform the INSERT, but it also requests the RowVersion value back. EF will always do this with a property that is flagged for concurrency, even if it is not a timestamp value. Even more critical are the UPDATE and DELETE commands, because here is where the concurrency check occurs. We've added a new method to the app, UpdatePerson, shown in Example 3-9.

EF框架不仅通知数据库执行插入,而且还请求返回RowVersion的值。一旦属性被标记为并发,EF就总会这样做,即使它并不是一个timestamp类型数据。对更新和删除语句更是如此,因为在这会有并发检查产生。我们添加一个新的方法,UpdatePerson到程序中,见代码3-9

Example 3-9. The UpdateTrip method

private static void UpdateTrip()

{

using (var context = new BreakAwayContext())

{

var trip = context.Trips.FirstOrDefault();

trip.CostUSD = 750;

context.SaveChanges();

}

}

代码3-10显示了当调用UpdatePerson时的SQL语句:

Example 3-10. UPDATE that filters on original RowVersion and returns new RowVersion

exec sp_executesql N'update [dbo].[Trips]

set [CostUSD] = @0

where (([Identifier] = @1) and ([RowVersion] = @2))

select [RowVersion]

from [dbo].[Trips]

where @@ROWCOUNT > 0 and [Identifier] = @1',

N'@0 decimal(18,2),@1 uniqueidentifier,@2 binary(8)',

@0=750.00,@1='D1086EFE-5C5B-405D-9F09-688981BB5B41',@2=0x0000000000001773

Notice the where predicate used to locate the trip being updated—it filters on the Identifier and the RowVersion. If someone else has modified the trip since it was retrieved by our method, the RowVersion will have changed and there will be no row that matches the filter. The UPDATE will fail and Entity Framework will throw an OptimisticConcurrencyException.

注意谓词Where用于定位trip的语句被更新—过滤器包括了Identifier和Rowversion两个参数。如果另外的人更改了行程就会被我们的方法检索到,由于RowVersion已经更改,将不会再有行匹配过滤器。更新就会失败,EF框架会抛出OptimisticConcurrencyException的异常。

Configuring TimeStamp/RowVersion with Fluent API

使用Fluent API配置TimeStamp/RowVersion

While the Data Annotation uses the term TimeStamp, the Fluent configuration uses the term RowVersion. To specify a RowVersion property, append the IsRowVersion() method to the Property.

With DbModelBuilder, you configure the Property like this:

Fluent 使用RowVersion来配置,要指定一个RowVersion属性,需要将IsRowVersion()方法附加到属性上。

使用DbModelBuilder,需要对属性作如下配置:

modelBuilder.Entity<Person>()

.Property(p => p.RowVersion).IsRowVersion();

在EnityTypeConfiguration<T>类中配置如下:

Property(p=>p.RowVersion).IsRowVersion();

Configuring Non-Timestamp Fields for Concurrency

配置并发非时间戳字段

Convention

默认规则

None

Data Annotation

ConcurrencyCheck

Fluent 

Entity<T>.Property(t=>t.PropertyName).IsConcurrencyToken()

A less common way to provide concurrency checking involves fields that are not row versioning types. For example, some databases do not even have a row version type.So though you won't be able to specifically configure a row version property, you may still want the concurrency checking on one or more database fields.

The Person class currently uses the property SocialSecurityNumber as its identity key. Perhaps the class used a PersonId property for its identity key and SocialSecurityNumber was simply an integer property not used for identity tracking. In that case, you might want to have a way to avoid conflicts in case the SocialSecurityNumber is changed, because in the United States (not taking illegal activity into account) a social security number uniquely identifies a citizen. Therefore, if a user is editing a Person record, perhaps changing the spelling of the FirstName, but in the meantime, someone else changes that person's Social Security Number, the user changing the FirstName should be alerted of a conflict when she attempts to save her changes. Flagging the SocialSecurityNumber property as a concurrency checking field will provide this check.

一个不太常见的方式是并发检查是通过字段为非行版本类型进行的。例如,许多数据库可能并没有行版本数据类型。因此你不能指定一个行版本属性,但你仍需要对一个或多个数据库字段进行并发检查。

Person类当前使用属性SocialSecurityNumber作为其标识键。设想类使用了PersionId属性作为标识键而将SocialSecurityNumber简单地视作整型数据而不作为标识跟踪。在这种情况下,你可能想有一种方法避免在SocialSecurityNum ber进行改变时的冲突,因为在美国,每个公民的社会保险号码是唯一的。因此,如果一个一个用户编辑了一个人的记录,可能更改的FirstName的拼写,但同时,另外的人想更改此人的社会保险号码,前者在尝试存储更改时就会遇到一个冲突。指定SocialSecurityNumber属性为一个并发检查字段将提供这种检查(避免这种事情发生)。

Configuring for Optimistic Concurrency with Data Annotations

使用Data Annotations配置开放式并发

Example 3-11 shows the modified class with the SocialSecurityNumber configured with the ConcurrencyCheck annotation.

代码3-11显示了修改的类为SocialSecurityNumber配置并发检查

Example 3-11. Modified Person class with a ConcurrencyCheck

public class Person

{

public int PersonId { getset; }

[ConcurrencyCheck]

public int SocialSecurityNumber { getset; }

public string FirstName { getset; }

public string LastName { getset; }

}

Example 3-12 shows a new method to force an update to a Person. If you call this method, you will need to call InsertPerson first to ensure there is an existing Person in the database.

例3-12显示了一个方法试图更新一个Person.如果调用这个方法,就需要先调用InsertPerson以确保数据库内存在一个Person数据。

Example 3-12. The UpdatePerson method

private static void UpdatePerson()

{

using (var context = new BreakAwayContext())

{

var person = context.People.FirstOrDefault();

person.FirstName = "Rowena";

context.SaveChanges();

}

}

Just as you saw with the Trip.RowVersion field in Example 3-10, when an update or delete is sent to the database, the SQL (shown in Example 3-13) looks for the row with not only the matching key (PersonId) but also with the concurrency field (SocialSecurityNumber) that matches the originally retrieved value.

正如您在Trip.RowVersion字段中看到的(代码3-10),当一个更新或删除请求发送以数据库时,SLQ语句(见代码3-13)不仅查找匹配的Key(PersonId),还要匹配原始并发字段值(SocialSecurityNumber).

Example 3-13. SQL providing concurrency checking on SocialSecurityNumber

exec sp_executesql N'update [dbo].[People]

set [FirstName] = @0

where (([PersonId] = @1) and ([SocialSecurityNumber] = @2))

',N'@0 nvarchar(max) ,@1 int,@2 int',@0=N'Rowena',@1=1,@2=12345678

If no match is found (meaning that the SocialSecurityNumber has changed in the database), the update will fail and an OptimisticConcurrencyException will be thrown.

如果匹配没有发现(也就是说SocialSecurityNumber已经在数据库中变更了),更新失败抛出OptimisticConcurrencyException异常。

Configuring for Optimistic Concurrency with Fluent API

使用Fluent API的开放式并发配置

The Fluent API method for concurrency is IsConcurrencyToken and gets applied to a Property as shown in Example 3-14.

Fluent API使用IsConcurrencyToken方法配置并发,并应用于属性。如代码3-14所示

Example 3-14. Configuring concurrency checking fluently

public class PersonConfiguration : EntityTypeConfiguration<Person>

{

public PersonConfiguration()

{

Property(p => p.SocialSecurityNumber).IsConcurrencyToken();

}

}

We've decided it's time for Person to have its own configuration class, so the configuration is inside this new class. Don't forget to add the PersonConfiguration to the modelBuilder.Configurations collection in your OnModelCreating method.

我们为Person提供其自己的配置类,也就是这个新类。不要忘记在OnModelCreating方法中将PersonConfiguration添加到modelBuilder.Configurations集合里。

Mapping to Non-Unicode Database Types

映射到非-Unicode数据库类型

Convention

默认规则

All strings map to Unicode-encoded database types

所有的字符串都映射到Unicode数据库类型

Data Annotation

不可用

Fluent 

Entity<T>.Property(t=>t.PropertyName).IsUnicode(boolean)

By default, Code First convention presumes that all strings map to Unicode string types in a database.

默认情况下,Code First会将所有字符串都映射到数据库中的Unicode字符串类型。

You can specify whether or not a string maps to a Unicode string type in the database with the IsUnicode method. The following code added to the LodgingConfiguration tells Code First to not map the Owner property as a Unicode encoded type:

你可以使用IsUnicod方法指定一个字符串是否映射到数据库Unicode字符串类型。下列代码添加到LodgingConfiguation中告诉Code First不要将Owner属性作为Unicode 类型:

Property(l=>l.Owner).IsUnicode(false);

Affecting the Precision and Scale of Decimals

对Decimal固定有效位数和小数位数的影响

Convention

默认规则

Decimals are 18, 2

 

Data Annotation

不可用

Fluent 

Entity<T>.Property(t=>t.PropertyName).HasPrecision(n,n)

Precision (the number of digits in a number) and Scale (the number of digits to the right of the decimal point in a number) are property attributes that can be configured with the Fluent API, though not with Data Annotations.

To see how it works, we'll add a new decimal property to the Lodging class: MilesFromNearestAirport:

固定有效位数(一个数字中数的位数)和小数位数(小数点右侧的位数)可以使用Fluent API进行配置,而不能用Data Annotations配置。

为了观察其如何工作,我们向Lodging 类中添加一个新的decmial属性:MilesFromNearestAirport:

public decimal MilesFromNearestAirport { getset; }

Convention for Precision and Scale

默认设置

By default, Decimal types have a Precision of 18 and a Scale of 2, as shown in Figure 3-8.

默认情况下,固定有效位数为18,小数位为2,如图3-8所示。

With the Fluent API, you configure both the precision and scale with a single method,HasPrecision. Even if one of the defaults is correct, you need to include both:

命名用Flurent API,可以用一个方法对固定有效位和小数位进行配置,方法为HasPrecison.即使默认值之一是正确的,也需要将两个值都指定:

Property(l => l.MilesFromNearestAirport).HasPrecision(8, 1);

As a result, Figure 3-9 shows the MilesFromNearestAirport database column with the specified precision.

图3-9显示了MilesFromNearestAirport有效位和小数位的更改情况。

Working with Complex Types in Code First

在Code First使用复杂类型

Entity Framework has supported using complex types since the first version. Complex types are also known as value types and can be used to add additional properties to another class. What differentiates complex types from entity types is that a complex type does not have its own key. It is dependent on its "host" type for change tracking and persistence.

A type that has no key property and is used as a property in one or more mapped types will be recognized by Code First convention as a complex type. Code First will presume that the properties of the complex type are contained in the table to which the host type maps.

What if the People table in an existing database included properties to represent a person's address? A class that mapped directly to that table might look like the Person class in Example 3-15.

EF框架从第一版开始就支持复杂类型。复杂类型也被称为值类型(?)可以作为附加属性添加到其他类。复杂类型与实体类型的区别在于复杂类型没有其自己的键。它是依赖于其"宿主"类型跟踪变化 和持久化。

一个没有Key属性的类型,并且作为属性映射到一个或多个类型中,Code First就会将其视作为复杂类型。Code First将预设复杂类型的属性出现在宿主类型映射到数据库的表中。

在一现在的数据库中People表如何将Person中的Address包含进来?将一个类直接映射到表内,见代码3-15:

Example 3-15. Individual properties representing an address in Person

public class Person

{

public int PersonId { getset; }

public int SocialSecurityNumber { getset; }

public string FirstName { getset; }

public string LastName { getset; }

public string StreetAddress { getset; }

public string City { getset; }

public string State { getset; }

public string ZipCode { getset; }

}

But in your model you prefer to have Address as a separate class and be simply a value type property of Person, as shown in Example 3-16.

但在你的模型中更想要Address作为一个分离的类而简化为一个Person类的值类型,如代码3-16所示:

Example 3-16. Address type as a property of Person

public class Address

{

public int AddressId { getset; }

public string StreetAddress { getset; }

public string City { getset; }

public string State { getset; }

public string ZipCode { getset; }

}

public class Person

{

public int PersonId { getset; }

public int SocialSecurityNumber { getset; }

public string FirstName { getset; }

public string LastName { getset; }

public Address Address { getset; }

}

By convention this setup would result in a separate table, Addresses, for the Address data. But the goal is to have the properties of Address be fields of the People table. You can achieve this if Address is a complex type. And if you have other tables that also contain these same properties, you can use the Address complex type in their classes as well.

默认情况下,这种设置会产生一个单独的表:Addresses。但是目标是让People表中拥有一个地址字段。如果Adress是一个复杂类型就可以达到这个目的。如果你有其他表中也包含相同的属性,你也可以在那些类中使用Address复杂类型。

Defining Complex Types by Convention

定义默认复杂类型

The conventional way to turn the Address class into a complex type is to remove the AddressId property. Let's comment that out for now:

最方便的方法将Address转化为复杂类型是移除AddressId 属性。现在注释掉它:

// public int AddressId { get; set; }

Before rerunning the application, you'll need to consider the InsertPerson method listed in Example 3-7 before Address even existed. Because the Address property is not handled and will therefore be null, it will cause a DbUpdateException to be thrown by SaveChanges. Rather than worry about that in any code that inserts a new Person, you can instantiate a new Address in the constructor of the Person class (Example 3-17).

在重新运行程序之前,你需要考虑InsertPerson方法(代码3-7)之前 Address是否存在。因为Address属性没有处理将会成为null值,将会造成SaveChanges抛出DbUpdateException异常。现在可以向代码中插入一个新的Person,并且在Person类中实例化一个新的Address。

Example 3-17. Instantiating the Address property in the constructor of the Person class

public class Person

{

public Person()

{

Address = new Address();

}

//…

}

In addition to the rule that a complex type does not have a key, Code First has two other rules that must be satisfied for detecting complex types. The complex type can only contain primitive properties and, when used in another class, it can only be used as a non-collection type. In other words, if you want a property in the Person class that is a List<Address> or some other type that results in a collection of Address types, Address cannot be a complex type.

除了复杂类型不能有Key以外,Code First中还有两条规则用于检测复杂类型是否满足要求。复杂类型在应用于其他类时只能包含原始属性,只能被用作为非集合类型。换句话说,如果 想要Person类中有一个List<Address>或其他Address类型的集合类型属性,Address不能作为复杂类型。

Conventional Complex Type Rules 

1. Complex types have no key property. 

2. Complex types can only contain primitive properties. 

3. When used as a property in another class, the property must represent a single instance. It cannot be a collection type. 

复杂类型的默认规则 

  1. 复杂类型无Key属性 
  2. 复杂类型只包含原始属性 
  3. 用作其他类的属性时,属性必须是一个单一实例,不能用于集合类型 

After running the application, Figure 3-10 shows that the Address fields are part of the People table. The Code First convention recognized that Address was meant to be a complex type and responded in the new model that it generated.

运行程序后,图3-10显示了Address字段成为了People表的一部分。Code First认定Address是一个复杂类型,在新模型生成得到响应:

Notice how the Address fields are named: HostPropertyName_Property Name, for example. This is the Code First convention. In Chapter 5, you'll learn how to configure column names for complex type properties. 

注意Address 字段的命名:HostPropertyName_Property。这是Code First 的默认设置。第5章,你就会学到如何为复杂属性配置列名。 

Configuring Unconventional Complex Types

配置非默认复杂类型

What if your intended complex type, Address, did not follow convention? Perhaps you want to have an AddressId property even though you know that an individual Address instance will not be change-tracked by Entity Framework?.

If we add the AddressId property back into the Address class and rerun the application, Code First convention will not be able to infer your intent and will go back to creating a separate Addresses table that has a PK/FK relationship to the People table. You can fix this by explicitly configuring the complex type.

如果要使用复杂类型,必须要遵循这些规则吗?可能您想要有一个AddressId属性,尽管你知道一个单独的地址实例不会变更,也不需要EF框架对其跟踪。

如果我们添加AddressId属性重新运行程序,Code First不能推断出你的意图,然后会创建一个单独的Addersses表,并建立与People表的主外键关系。你可以显示地配置复杂类型来修正。

Specifying complex types with Data Annotations

使用Data Annotations指定复杂类型

There is a ComplexType Data Annotation that you can apply to a class.

Data Annotation提供了ComplexType特性应用于类上。

Example 3-18. Address with AddressId reinstated and a ComplexType configuration

[ComplexType]

public class Address

{

public int AddressId { getset; }

public string StreetAddress { getset; }

public string City { getset; }

public string State { getset; }

public string ZipCode { getset; }

}

With this in place, when you run the application again, the model will be rebuilt and the resulting database schema will once again match Figure 3-10, with the addition of a new int field called Address_AddressId.

使用这种方式,再次运行程序,模型将重建,最终的数据库架构一次同图3-10一致,另外附加了一个新的int字字段,命名为Address_AddressId.

Specifying complex types with the Fluent API

使用Fluent API指定复杂类型

To instruct Code First that a type is a complex type using the Fluent API, you must use the DbModelBuilder.ComplexType method.

为了通过Fluent API向Code First指明一个类型为复杂类型,你必须使用DbModelBuilder.ComplexType 方法。

modelBuilder.ComplexType<Address>();

代码3-19显示了对OnModelCreating方法的修改:

Example 3-19. Specifying a complex type fluently

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

modelBuilder.Configurations.Add(new DestinationConfiguration());

modelBuilder.Configurations.Add(new LodgingConfiguration());

modelBuilder.Configurations.Add(new PersonConfiguration());

modelBuilder.Configurations.Add(new TripConfiguration());

modelBuilder.ComplexType<Address>();

}

This modelBuilder configuration is intentionally positioned after the code for adding configurations. In-line configurations, those which are called directly against the modelBuilder instance inside the OnModelCreating method, must come after any code that adds configuration classes to the Configurations collection. 

本modelBuilder的配置故意将新增加的配置代码放在后面。那些直接通过内建在OmodelCreating方法内部建立的modelBuilder类的实例,称为内联配置,必须将这些代码写在添加的配置类集合的后面。 

Working with More Complicated Complex Types

处理更多的杂乱的复杂类型

Recall that one of the conventions for complex types is that the type can only contain primitive types. If your complex type doesn't satisfy this rule, you will have to configure the type. Here is an example.

We've created two new types, PersonalInfo and Measurement, shown in Example 3-20. PersonalInfo contains two Measurement properties. Notice there is no identity property in either type. Our intent is for both PersonalInfo and Measurement to be complex types. The PersonalInfo complex type makes use of the Measurement complex type; this is known as a nested complex type.

回想默认配置的复杂类型规定类型只能包含原始类型。如果你的复杂类型不符合这一规范,必须进行配置,这里有一些例子。

我们创建了两个新类,PersonalInfo和Measuremet,见代码3-20. PersonalInfo包含有两Measurement属性。注意到在两个类都没有标识属性。我们的意图是两个类都成为复杂类型。PeersonalInfo复杂类型使用Measurment复杂类型,这就是所谓的嵌套复杂类型。

Example 3-20. New classes: PersonalInfo and Measurement

public class PersonalInfo

{

public Measurement Weight { getset; }

public Measurement Height { getset; }

public string DietryRestrictions { getset; }

}

public class Measurement

{

public decimal Reading { getset; }

public string Units { getset; }

}

当我们向Person类中添加新的PersonInfo属性后:

public PersonalInfo Info { getset; }

我们也需要添加一些逻辑到Person的构造器,用具体实例说明这些属性:

public Person()

{

Address = new Address();

Info = new PersonalInfo

{

Weight = new Measurement(),

Height = new Measurement()

};

}

If you go ahead and run the application, the model builder will throw an exception:

EntityType 'PersonalInfo' has no key defined. Define the key for this EntityType.

Code First does not recognize that we want PersonalInfo to be a complex type. The reason is that we broke one of the rules: the complex type must contain only primitive types. There are two Measurement properties in PersonalInfo. Because those are not primitive types, convention did not see PersonalInfo as a complex type.

If you add the ComplexType configuration to the PersonalInfo class, Code First will be able to properly build the model. You don't need to configure the Measurement class since it follows convention for Complex Types.

如果此时继续运行程序,Model builder会抛出异常:

实体类"PersonInfo"没有定义键。请为此实体类定义键。

Code First 并没有将PersonalInfo识虽为复杂类型。原因是我们打破了规则:复杂类型必须只包含原生类型。在PersonalInfo类中有两个Measurement类型的属性。由于这是非原生类型,规则不能将PersonalInfo作为复杂类型。

如果添加ComplexType配置到PersonalInfo类,Code First就能够将属性建立到模型中。你不必配置Measurement类,因为它遵循复杂类的规则。

Configuring Properties of Complex Types

配置复杂类型的属性

Code First will treat complex type properties in the same way as any other type and you can configure them with Data Annotations or fluently.

Code First将复杂类型属性与其他类型以相同的方式处理,你可以用Data Annotations或Fluently来配置。

Configuring Complex Types with Data Annotations

使用Data Annotations来配置复杂类型

Recall that Code First convention named them using the pattern ComplexTypeName_PropertyName (see Figure 3-10). You can apply Data Annotations to complex types just as you use them for the other classes. Example 3-21 uses an annotation you are already familiar with, MaxLength, to affect a property in the Address type.

Code First默认命名列类似:ComplexTypeName_PropertyName(见图3-10)。你可以应用Data Annotations在复杂类型上正如你在其他类上使用的那样。例3-21使用了一个你熟悉的特性标记,MaxLength,去影响 Address类型的中的属性。

Example 3-21. Configuring the StreetAddress property of the Address

[ComplexType]

public class Address

{

public int AddressId { getset; }

[MaxLength(150)]

public string StreetAddress { getset; }

public string City { getset; }

public string State { getset; }

public string ZipCode { getset; }

}

Figure 3-11 shows the People table of the database with the modified Address_StreetAddress field. You can also see the Address_AddressId field that came from reinstating AddressId and the fields added as a result of the PersonalInfo complex type and its Measurement subtype.

图3-11显示了数据库的People表,其Address_streetAddress字段已经被修改.你也可以看到Address_AddressId字段来自于AddressId,作为结果,新添加的了PersonalInfo复杂类型和它的Measurment子类型.

In Chapter 5, we'll revisit the column names of this complex type and fix them up when you learn about configuring column names. 

第5章,我们重新检视复杂类型的列名,然后你会学习如何通过配置调整列名. 

Configuring Complex Type Properties with the Fluent API

使用Fluent API来配置复杂类型

There are two ways to configure properties of complex types with the Fluent API. You can start with the host entity or you can start with the complex type itself. According to the Entity Framework team, the latter is the preferred way to configure property attributes. MaxLength falls into this category. When we get to the examples of configuring column names in Chapter 5, we'll configure from the Person entity since the goal will be to impact how the Person mapping handles the naming of its fields.

使用Fluent API有两种方法配置复杂类型属性.你可以开始于宿主实体也可以开始于复杂类型本身.根据EF构架团队的报告,后者是更好的配置方式.MaxLength属于这一类.当我们在第5章讨论列名时,我们会从Person实体中进行配置,以期对Person映射到字段的名字产生影响.

The model builder recognizes the difference between complex types and entity types.

If you want to configure directly from the DbModelBuilder, you must begin with its Complex<T> method, instead of the Entity<T> method you've used so far. Example 3-22 demonstrates configuring a Complex Type directly from the modelBuilder instance in OnCreatingModel.

Model builder能够识别复杂类和实体类的差异.

如果你想直接从DbModelBuilder配置,必须开始于Compex<T>方法,而不是一直在用的Entity<T>方法.代码3-22展示了直接在OnCreatingModel中的ModelBuilder实例配置复杂类型.

Example 3-22. Configuring a property of the Address complex type

modelBuilder.ComplexType<Address>()

.Property(p => p.StreetAddress).HasMaxLength(150);

If you prefer to encapsulate your configurations, you'll need to inherit from ComplexTy peConfiguration class rather than EntityTypeConfiguration, as shown in Example 3-23.

如果你更喜欢封装配置,你需要继承自ComplexTypeConfiguation类而不是EntityTypeConfigration,如代码3-23.

Example 3-23. Configuring the length of StreetAddress in the Address ComplexType

public class AddressConfiguration :

ComplexTypeConfiguration<Address>

{

public AddressConfiguration()

{

Property(a => a.StreetAddress).HasMaxLength(150);

}

}

You'll also need to be sure that the you add this AddressConfiguration to the model:

你还要确保在模型中添加AddressConfiguration:

modelBuilder.Configurations.Add(new AddressConfiguration());

Summary

小结

In this chapter you've seen many of the presumptions that Code First makes about what the model should look like based on what it sees in your classes. Strings become nvarchar(max) in SQL Server. Numbers acquire a precision that lets them have up to 18 digits (which will enable you to keep track of values in the quadrillions!) and 2 decimal places. While these and other defaults may be useful for very generic scenarios, you do have the ability to apply configurations to specify the sizes you prefer. You've also learned how to ensure that Entity Framework is aware that values should be treated as timestamps or at least concurrency fields. You've also worked with properties that point to complex types—types that do not have keys and can only be tracked when they are a property of another class that is tracked.

在本章中你已经看到很多Code First创建的预设模型,这些模型都是基于你自己的类创建的.Strings在SQL Server中变成nvarchar(max).数字的固定位数设置为18位(可以确保你可以跟踪1024的数据)和2位小数.这些及其他默认值在很广泛的场合中很有关,你也可以根据需要通过应用配置指定位数.你已经学到如何确保EF框架知道如何将值设定为timestamps或至少是一个并发字段.你已经开始接触到复杂类型,这些类型没有键,只有它们是其他类的属性时才能被跟踪到.

Code First conventions take care of a good portion of common scenarios, but it is the ability to override these conventions with your own configurations that affords you great control over how your classes are managed by Entity Framework.

Code First的约定在大量通用场景都发挥了很好的作用,但通过你的配置来覆写这些约定可以很好地控制如何由EF框架来管理你的类.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值