使用Entity-Framework编程(3)

上传人:彩*** 文档编号:67781882 上传时间:2022-04-01 格式:DOC 页数:28 大小:1.41MB
返回 下载 相关 举报
使用Entity-Framework编程(3)_第1页
第1页 / 共28页
使用Entity-Framework编程(3)_第2页
第2页 / 共28页
使用Entity-Framework编程(3)_第3页
第3页 / 共28页
点击查看更多>>
资源描述
使用 Entity-Framework编程 (3)Convention默认规则DataAnnotationFluentKey Properties : not null in database键属性:在数据库中为非空Reference Types (String, arrays): null in thedatabase引用类型(String,数组):在数据库中可空Value Types (all numeric types, DateTime, bool,char) : not null in database值类型(所有数字类型,日期,布尔,字符):在数据库为非空Nullable Value Types : null in databaseNullable值类型(可空类型):在数据库可空RequiredEntity.Property(t=t.PropertyName).IsRequired默认规则约定确保非可空的 .Net 类型要映射到数据库的非可空字段,除此以外,任何键属性都只能映射到非可空数据库字段。如果你使用.Net的泛型Nullable指定一个值类型(如int )为可空,将会映射到数据库的一个可空字段。在第 2 章您已看到如何使用配置指定一个属性为必须项。使用Data Annotation的Required标记和Fluent的 IsRequired属性都可强制Lodging.Name属性为必须项。在保存数据到数据库之前, EF 运行时会对必须属性进行验证;如果属性没有赋值就会抛出一个异常。另一个效果是,数据库相应字段为非空。映射键Properties named IdConvention属性名为Id默认规则Properties named TypeName + Id属性名为 类型名 +IdData AnnotationKeyFluentEntity.HasKey(t=t.PropertyNameEF 框架要求每个实体都有一个键。这个键用于上下文以保持每个独立对象的跟踪。键是唯一的而且经常由数据库生成。Code First默认规则作出了同样的预设。回忆一下由 Destination 和 Lodging 类生成的数据库, DestinationId 和 LodgingId 的整型字段都被标记为主键和非空字段。如果进一步观察二者的列属性,你会发现这些字段是自增长的标识字段,如图 3-1 所示,这是默认规则将整型量作为主键来管理。大多数情况下,数据库中的主键不是int就是 GUID类型,尽管任意类型都可以作为键属性。数据库中的主键会是多个表的组成字段,类似地,一个实体的键也是某个类中的多个属性之一。在本节结束的时候,你会看到如何配置复合键。Code First默认规则对不合规键属性的响应如果在我们的类中我们意指的键碰巧满足Code First默认规则,那么一切顺利。但是如果不满足规则呢?我们向模型添加一个新类,但我们的意图是IdentifierTrip,见代码3-1.Trip属性应该作为键。类没有任何满足实体键默认规则的属性,Example 3-1. The Trip class without an obvious key propertypublicclassTrippublicGuid Identifier get;set; publicDateTime StartDate get ;set ; publicDateTime EndDate get;set; publicdecimal CostUSD get;set; 伴随这个新类,我们需要在BrakAwayContext中添加一个DbSet数据集:publicDbSet Trips get ;set ; 我们再次运行程序,在尝试从类中创建模型时DbModel Builder抛出一个异常:在模型生成过程中检测到一个或多个验证错误:实体类型 Trip还没有定义 key。请为这个实体类型定义Key.由于没有找到期望的默认明确的是,类型(GUIDKey 属性( Id 或 TripId ) ,Code First 无法继续创建模型。需要)与这个问题无关。如前所述,您可以使用任何的原始类型作为键。使用Data Annotations配置KeyData Annotation标识一个键只需要简单的一个Key.Key特性位于中,也被其他API所使用(如ASP.Net MVC使用的集的引用,你就不能添加它。对这个特定的特性不需要引用,由于它已经被添加到了.Net4Key ) . 如果你的项目尚未包含此程序EntityFramwork.dll。之KeypublicGuid Identifier get ;set ; 在Flurent API中使用HasKey来配置Key属性使用Fluent API来配置Key属性与前面几个Fluent配置不同。这一配置直接添加到实体上。为了配置一个key,你需要使用HasKey方法,如代码3-2。Example 3-2. The HasKey Fluent configuration in OnModelCreatingmodelBuilder.Entity().HasKey(t = t.Identifier)如果将代码配置进EntityTypeConfiguration类中,正如你在第2 章学到的,应该开始于HasKey或This.HasKey(代码3-3)Example 3-3. HasKey inside of an EntityTypeConfiguration classHasKey(t = t.Identifier)配置数据库生成的属性ConventioInteger keys:Identityn整型键值:标识列默认规则DataDatabaseGenerated(DatabaseGeneratedOption)AnnotationEntity.Property(t=t.PropertyName)Fluent.HasDatabaseGeneratedOption(DatabaseGeneratedOption)在前面部分里,你已经看到默认情况整型键值会被我们自己创建的Guid型的键值怎么办?GuidEF 框架生成标识字段,需要特殊的处置,包含在由数据库生成值。而DatabaseGenerated配置。为了展示,我们添加一个新方法,InsertTrip(代码3-4)到控制台程序,然后在主模型进行调用。Example 3-4. The InsertTrip methodprivatestaticvoidInsertTrip()var trip =new TripCostUSD =800 ,StartDate =new DateTime(2011 , 9, 1),EndDate =new DateTime(2011 , 9, 14);using ( varcontext =new BreakAwayContext()context.SaveChanges();运行程序会导致数据库卸载并增加新的Trips表后重新创建,如图3-2.Identifier是主键,唯一标识,非空列。回到本章前面的内容,你知道值类型默认是 required 。在此也会看到同样的效果,StartDate,EndDtat 和 CostUSD属性都是值类型,默认情况下,在数据库也都是非空字段。然后在新行中我们看到Guid值被填充为很多个0. 如图 3-3数据库和 EF 框架都不知道我们想让他们之一为新添加的Trips 生成一个新的Guid。由于这个属性没有一个生成新Guid 的逻辑方法,就会默认以0值填入。如果你尝试以同样的值插入另一个记录,数据库会抛出一个错误,因为期待一个唯一值。当然可以配置数据库自动生成一个新的Guid (通过设置默认值为newid()。不管你在数据库中手动操作还希望CodeFirst插入此逻辑,你必须让Code First知道数据库将要处理Guid.解决方案是让Code First知道数据库将要生成这个键值通过使用另一个annotation:DatabaseGenerated.这一配置有三个选项 None,Identity和Computed.我们想要Identifier字段被标识为Identity,才能确保数据库在加入新行时自动生成标识字段的值,正如整型类型的键值自动生成一样。使用 Data Annotations配置数据库 - 生成选项修改类代码告诉Code First让数据库生成一个唯一的键值:Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)publicGuid Identifier get ;set ; 当键字段为整数时,Code First默认选择DatabaseGeneratedOption.Identity。而对Guid,你需要显示进行配置。这是唯一一种可以通过Identify来配置Code First的数据类型。如果映射到一现有的数据库,任何在插入数据可生成值的列都可以标识为Identify.再次运行程序,如图3-4,输出了新生成的标识。你可能对查看SQL语句感兴趣, 代码3-5显示的EF框架发送给数据库的INSERT语句, 其中要求数据库为Idenfifier属性生成Guid值Example 3-5. SQL for inserting a new Tripdeclaregenerated_keystable( Identifieruniqueidentifier)insertdbo. Trips( StartDate,EndDate,CostUSD)output inserted.Identifierintogenerated_keysvalues(0,1,2)selectt.Identifierfromgenerated_keysas gjoindbo . Tripsas ton g.Identifier= t. IdentifierwhereROWCOUNT0 ,N 0 datetime2(7 ),1 datetime2(7),2 decimal( 18, 2) ,0= 2011 - 09 - 0100 : 00 : 00 ,1=2011 - 09 - 14 00 : 00 : 00 ,2=800.00DatabaseGeneratedOption例证明None是有用的。 代码3-6还有两个枚举值:None显示了另一个新类,和 Coumputed。下面就有一个示Person,SocialSecurityNumber属性已经被配置为此类的键属性。Example 3-6. Person class with unconventional key propertynamespace Modelpublicclass PersonKeypublicintSocialSecurityNumber get;set ; publicpublicstring FirstName get; string LastName get;set ; set ; 记得要在BreakAwayContext类中添加DbSetpublicDbSet People get;set ; 最后,将一个新方法,InsertPerson(见代码3-7调用这个方法,就会向数据库中添加一个新的person)添加到控制台程序中,在。Main方法中Example 3-7. InsertPerson methodprivate static void InsertPerson()varperson= new PersonFirstName= Rowan,LastName= Miller,SocialSecurityNumber= 12345678;using (varcontext= new BreakAwayContext()context.People. context.SaveChanges();Add (person);再次运行程序,让我们再看看数据库新添加的一行,如图3-5.SocialSecurityNumber的值是1,不是12345678.为什么?由于Code First根据key 是一个整型这个事实告知数据库这是一个标识字段, 因此在 INSERT 语句中提供正确的 SocialSecurityNumber 的值,而是让数据库自行生成。从而在EF框架没有SaveChanges完成后查看person实例中SocialSecurityNumber的值,此值已经被更新为数据库生成的值,1.为修正这一点,我们需要添加一些配置覆写默认标识规则,在这种情况下,DatabaseGeneratedOption.Identity是不对的,应该用None:Key, DatabaseGenerated(DatabaseGeneratedOption.None)publicintSocialSecurityNumber get;set ; 然后再运行程序,如图3-6 ,数据库正确插入了有关数据。DatabaseGeneratedOption.Computed得到的。例如,如果有一个FullName字段在用于指定一个映射到数据库的字段是通过计算People表中,是用一个公式将FirstName和LastName组合起来得到的,你就应该让EF框架知道以便其不会尝试存储数据到此列中。你不能指定一个公式用来计算Code First中列的值,因此当映射到一个现存的数据库中你只能使用Computed。要不然,在试图创建数据库时如果遇到Computed配置,数据库引擎就会抛出运行时异常。使用 Fluent API来配置数据库生成选项DatabaseGeneratedOption可以配置为一种特殊的属性,你可以将配置附加HasKey后面,如:modelBuilder.Entity().HasKey(t= t.Identifier).Property(t= t.Identifier).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);或者创建一个独立的语句:modelBuilder.Entity().HasKey(p= t.SocialSecurityNumber);modelBuilder.Entity().Property(p= p.SocialSecurityNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);你会注意到DatabaseGeneratedOption枚举位于名称空间, 在EntityFramework.dll中。需要在context类的文件头部添加using引用。为开放式并发环境配置时间戳或行版本字段ConventioNonen无默认规则DataAnnotatioTimeStampnEntity.Property(t=t.PropertyName).IsRowVersiFluenton()EF 框架从第一版本开始就支持开放式并发环境。Programming Entity Framework书的第二版在第23章深入探讨了开放式并发。在这里我我们教你如何配置类映射到这本RowVersion(或称作TimeStamp,时间戳)字段,同时通知EF框架在进行更新或删除数据库操作时使用这些字段进行并发检查。使用 Code First 你需要指定一个字段使用开放式并发检查,与映射到数据库的类型无关,或者你可以进一步指定并发的字段映射到一个 TimeStamp 字段。一个类只能有一个属性可以配置为TimeStamp特性。RRowVersion和 TimeStamp是两个具有相同类型的项。 Sql Server 使用 TimeStamp, 而其他数据库使用更恰当的名称为 RowVersion.d SQLServer2008 中,timestamp数据类型也调整为 rowversion ,但是大多数工具(如 Sql Server Management Studio,vs 等)仍然显示为 timestamp.Code First的默认规则与TimeStamp字段默认情况下,Code First并不识别时间戳属性,因此没有默认约定行为,获得此行为必须配置此属性。使用Data Annotations配置时间戳并非任何属性都可以映射到一个程很简单,将TimeStamptimestamp特性加到Trip和数据库类型。必须是byte数组才可以。配置过Personal类中的下列属性中。TimestamppublicbyteRowVersion get;set ; 然后运行控制台程序, 确保在数据库中你会看到新生成的InserTrip和RowVersionInsertPerson列(图 3-7方法都在Main方法中进行调用。),类型为非可空timestamp类型。任何时候行内数据被修改时数据库都会自动为此属性创建新值。但 TimeStamp不仅影响数据库的映射,还会导致属性被EF 框架视作并发的令牌。如果你使用EDMX文件,这就等同于设置了一个属性的ConcurrencyMode(并发模式 ) 。 EF 框架在执行插入、更新或删除数据库时,就会考虑并发字段,返回每个INSERT和 UPDATE更新数据库的值,并传回到每个UPDATE和 DELETE的相关属性的原始位置。例3-8显示了当执行InsertPerson方法后保存设置时的SQL语句:Example 3-8. INSERT combined with SELECT to return new RowVersionexecsp_executesql Ninsert dbo.People(SocialSecurityNumber, FirstName, LastName)values (0, 1, 2)select RowVersionfrom dbo.Peoplewhere ROWCOUNT 0 and SocialSecurityNumber = 0,N 0 int,1 nvarchar(max) ,2 nvarchar(max) , 0 = 12345678,1= NRowan, 2 = N MillerEF 框架不仅通知数据库执行插入,而且还请求返回RowVersion的值。一旦属性被标记为并发, EF 就总会这样做,即使它并不是一个timestamp类型数据。对更新和删除语句更是如此,因为在这会有并发检查产生。我们添加一个新的方法,UpdatePerson到程序中,见代码3-9Example 3-9. The UpdateTrip methodprivatestaticvoidUpdateTrip()using(varcontext =new BreakAwayContext()vartrip.CostUSD =750 ;context.SaveChanges();代码 3-10显示了当调用UpdatePerson时的 SQL语句:Example 3-10. UPDATE that filters on original RowVersion and returns newRowVersionexecsp_executesql Nupdate dbo.Tripsset CostUSD = 0where (Identifier = 1) and (RowVersion = 2)select RowVersionfrom dbo.Tripswhere ROWCOUNT 0 and Identifier = 1,N0 decimal(18,2),1 uniqueidentifier,2 binary(8)0=750.00, 1=D1086EFE-5C5B-405D-9F09-688981BB5B41, 2=0x0000000000001773注意谓词 Where用于定位 trip 的语句被更新 过滤器包括了Identifier和 Rowversion两个参数。 如果另外的人更改了行程就会被我们的方法检索到,由于 RowVersion已经更改,将不会再有行匹配过滤器。更新就会失败,EF 框架会抛出OptimisticConcurrencyException的异常。使用 Fluent API配置 TimeStamp/RowVersionFluent使用 RowVersion来配置,要指定一个 RowVersion属性,需要将IsRowVersion()方法附加到属性上。使用 DbModelBuilder,需要对属性作如下配置:modelBuilder.Entity().Property(p = p.RowVersion).IsRowVersion();在 EnityTypeConfiguration类中配置如下:Property(p=p.RowVersion).IsRowVersion();配置并发非时间戳字段ConventiNoneon无默认规则DataAnnotatiConcurrencyCheckonEntity.Property(t=t.PropertyName).IsConcurrenFluentcyToken()一个不太常见的方式是并发检查是通过字段为非行版本类型进行的。例如,许多数据库可能并没有行版本数据类型。因此你不能指定一个行版本属性,但你仍需要对一个或多个数据库字段进行并发检查。Person类当前使用属性SocialSecurityNumber作为其标识键。设想类使用了PersionId属性作为标识键而将SocialSecurityNumber简单地视作整型数据而不作为标识跟踪。在这种情况下,你可能想有一种方法避免在SocialSecurityNum ber进行改变时的冲突,因为在美国,每个公民的社会保险号码是唯一的。因此,如果一个一个用户编辑了一个人的记录,可能更改的FirstName的拼写, 但同时, 另外的人想更改此人的社会保险号码,前者在尝试存储更改时就会遇到一个冲突。指定SocialSecurityNumber属性为一个并发检查字段将提供这种检查(避免这种事情发生)。使用Data Annotations配置开放式并发代码3-11显示了修改的类为SocialSecurityNumber配置并发检查Example 3-11. Modified Person class with a ConcurrencyCheckpublicclassPersonpublicintPersonId get ;set ; ConcurrencyCheckpublicint SocialSecurityNumber get ; set ; publicstringFirstName get;set; publicstringLastName get;set; 例 3-12显示了一个方法试图更新一个Person.如果调用这个方法,就需要先调用InsertPerson以确保数据库内存在一个Person数据。Example 3-12. The UpdatePerson methodprivatestaticvoidUpdatePerson()using(varcontext =new BreakAwayContext()varperson.FirstName =Rowena;context.SaveChanges();正如您在Trip.RowVersion字段中看到的(代码3-10),当一个更新或删除请求发送以数据库时, SLQ语句(见代码3-13)不仅查找匹配的Key ( PersonId),还要匹配原始并发字段值( SocialSecurityNumber) .Example 3-13. SQL providing concurrency checking onSocialSecurityNumberexecsp_executesql Nupdate dbo.Peopleset FirstName = 0where (PersonId = 1) and (SocialSecurityNumber = 2) ,N 0 nvarchar(max) ,1 int,2 int, 0= NRowena, 1=1, 2=12345678如果匹配没有发现(也就是说SocialSecurityNumber已经在数据库中变更了),更新失败抛出OptimisticConcurrencyException异常。使用 Fluent API的开放式并发配置Fluent API使用 IsConcurrencyToken方法配置并发,并应用于属性。如代码3-14所示Example 3-14. Configuring concurrency checking fluentlypublicclass PersonConfiguration : EntityTypeConfigurationpublicPersonConfiguration()Property(p= p.SocialSecurityNumber).IsConcurrencyToken();我们为Person提供其自己的配置类,也就是这个新类。不要忘记在OnModelCreating方法中将PersonConfiguration添加到modelBuilder.Configurations集合里。映射到非 -Unicode数据库类型ConventiAll strings map to Unicode-encoded database typeson所有的字符串都映射到Unicode数据库类型默认规则DataAnnotati不可用onEntity.Property(t=t.PropertyName).IsUnicode(bFluentoolean)默认情况下,Code First会将所有字符串都映射到数据库中的Unicode字符串类型。你可以使用IsUnicod方法指定一个字符串是否映射到数据库码添加到LodgingConfiguation中告诉Code First类型:Unicode不要将 Owner字符串类型。下列代属性作为UnicodeProperty(l=l.Owner).IsUnicode(false);对 Decimal固定有效位数和小数位数的影响ConventiDecimals are 18, 2on默认规则Data不可用AnnotationEntity.Property(t=t.PropertyName).HasPrecisioFluentn(n,n)固定有效位数 (一个数字中数的位数)和小数位数 (小数点右侧的位数)可以使用Fluent API进行配置,而不能用Data Annotations配置。为了观察其如何工作,我们向Lodging类中添加一个新的decmial属性:MilesFromNearestAirport:publicdecimalMilesFromNearestAirport get;set ; 默认设置默认情况下,固定有效位数为18 ,小数位为2 ,如图 3-8所示。使用 Flurent API,可以对固定有效位和小数位进行配置,使用的是HasPrecison方法。即使默认值之一是想要设置的的,也需要将两个值都指定:Property(l= l.MilesFromNearestAirport).HasPrecision(8 ,1);图 3-9显示了MilesFromNearestAirport有效位和小数位的更改情况。在 Code First使用复杂类型EF 框架从第一版开始就支持复杂类型。复杂类型也可视作值类型(?)可以作为附加属性添加到其他类。复杂类型与实体类型的区别在于复杂类型没有其自己的键。它是依赖于其宿主类型跟踪变化和持久化。一个没有Key属性的类型,并且作为属性映射到一个或多个类型中,Code First就会将其视作为复杂类型。Code First将预设复杂类型的属性出现在宿主类型映射到数据库的表中。在 People 表中如何将 Person 中的 Address 包含进来,将 Address 的属性都映射到People表中?可以直接将所有相关属性都纳入Person类中,见代码3-15:Example 3-15. Individual properties representing an address in Personpublicclass PersonpublicintPersonId get;set; publicintSocialSecurityNumber get;set ; publicstring FirstName get;set ; publicstring LastName get;set; publicstring StreetAddress get;set ; publicstring City get;set ; publicstring State get;set; publicstring ZipCode get;set; 但在你的模型中如果使用Address类作为分割类,就可以简化Person类,如代码3-16所示:Example 3-16. Address type as a property of Personpublicclass AddresspublicintAddressId get;set; publicstring StreetAddress get;set ; publicstring City get;set ; publicstring State get;set; publicstring ZipCode get;set ; publicclass PersonpublicintPersonId get;set; publicintSocialSecurityNumber get;set ; publicstring FirstName get;set ; publicstring LastName get;set; publicAddress Address get;set; 但是如果这样分割,使用默认规则,会产生一个单独的表:Addresses。而我们目标是让People表中拥有一系列地址字段。如果Adress是一个复杂类型就可以达到这个目的。如果你有其他表中也包含相同的属性,你也可以在那些类中使用Address复杂类型。定义默认复杂类型最方便的方法将Address转化为复杂类型是移除AddressId属性。现在注释掉它:/ public int AddressId get; set; 在重新运行程序之前,你需要考虑InsertPerson方法(代码3-7)之前Address是否存在。因为Address属性没有处理将会成为null值,将会造成SaveChanges抛出DbUpdateException 中实例化一个新的异常。现在可以向代码中插入一个新的Address。Person,并且在Person类Example 3-17. Instantiating the A
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 办公文档 > 演讲稿件


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!