某个朋友问为什么不推首页或者允许评论,我说一直没怎么写博客,也习惯了先随便乱画再开始写文档,担心公开后一些不经意的"呓语“中得出的错误的结论会给别人错误的观点,所以这个系列只是当做熟悉写博客的习作和研究笔记。等过后有时间了再修改完善或者另开一个更系统的专题。
EF出来不短的时间了,为什么一直没用?因为其实有很长一段时间没有负责架构搭建的工作了,更多的时间花在前端,数据库,文档,客户需求方面。当然精力不足只是借口,更多的是形成了一种不好的技术惰性,总认为能理解是怎样的就行了,如果没有项目需求,没必要去了解技术细节。
使用之前做过一些相关资料的搜集,了解到EF其实是非常棒的一个框架组件。以前开发的项目,绝大部分是先从数据库设计开始,用工具画实体图或者数据库设计图,生成数据库,修改好后,用自己编写的代码生成工具或者脚本工具,生成相应ORM框架的基础代码。用这种方式其实有不少问题:开发中后期数据库结构已经和最初的设计有很大的不同,即使开始想得再完美(很多时候并没有那么多时间去想完美),中间文档再完善,后期进入的开发人员,想要通过设计图去理解数据结构,都会变得很难,这其实也是和项目控制有关,绝大多数公司都没有DBA,对项目开发过程中的数据库更改没有进行太严格的审核。另外,设计和实际的代码编辑的分离,即使将设计文档和项目放在一起做源代码管理,做控制的时候也不方便,毕竟一个是文档,一个是代码,再加上数据库,三者在项目过程中是紧密结合的,但是在修改上又是相互独立的。比如新需求需要对数据库结构进行调整,大多数情况下我需要先对设计图进行修改,然后手动修改数据库结构,再修改相应的代码实现。整个过程是脱离的,如果一个公司或者项目组有完善的管理机制,这并不是太大的问题,相应的人员管理相应的步奏就行,但是实际开发中,即使再严格按照规范来管理,还是会出现类似的情况,因为开发过程还是控制在开发人员手中,有时候为了调试方便,直接修改数据库而不做更改记录的情况屡见不鲜。EF其实在某种程度上缓和了这种涉及需求更改的问题,无论是codefirst还是dbfirst,更改过程都可以直观的展示在模板中,而通过对模板的源代码管理,可以很直观的看到数据库或者数据实体设计上的变更。
我们还是转到实际使用上来,怎么使用dbfirst或者codefirst,园子里面已经有很多的文章,我这里就不一步一步展示了,说一下我的测试项目中几个小小的改造
1.T4脚本分离
使用EF自带的模板文件,生成代码的T4模板其实有点太臃肿了,代码不复杂,但是就是因为将生成代码和基础函数以及模板代码放在一起,让很多人觉得看起来很麻烦。这是自动生成的T4模板。
1 <#@ template language="C#" debug="false" hostspecific="true"#> 2 <#@ include file="EF.Utility.CS.ttinclude"#><#@ 3 output extension=".cs"#><# 4 5 const string inputFile = @"SqlServer.edmx"; 6 var textTransform = DynamicTextTransformation.Create(this); 7 var code = new CodeGenerationTools(this); 8 var ef = new MetadataTools(this); 9 var typeMapper = new TypeMapper(code, ef, textTransform.Errors); 10 var fileManager = EntityFrameworkTemplateFileManager.Create(this); 11 var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); 12 var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); 13 14 if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) 15 { 16 return string.Empty; 17 } 18 19 WriteHeader(codeStringGenerator, fileManager); 20 21 foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) 22 { 23 fileManager.StartNewFile(entity.Name + ".cs"); 24 BeginNamespace(code); 25 #> 26 <#=codeStringGenerator.UsingDirectives(inHeader: false)#> 27 <#=codeStringGenerator.EntityClassOpening(entity)#> 28 { 29 <# 30 var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity); 31 var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity); 32 var complexProperties = typeMapper.GetComplexProperties(entity); 33 34 if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any()) 35 { 36 #> 37 public <#=code.Escape(entity)#>() 38 { 39 <# 40 foreach (var edmProperty in propertiesWithDefaultValues) 41 { 42 #> 43 this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>; 44 <# 45 } 46 47 foreach (var navigationProperty in collectionNavigationProperties) 48 { 49 #> 50 this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>(); 51 <# 52 } 53 54 foreach (var complexProperty in complexProperties) 55 { 56 #> 57 this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>(); 58 <# 59 } 60 #> 61 } 62 63 <# 64 } 65 66 var simpleProperties = typeMapper.GetSimpleProperties(entity); 67 if (simpleProperties.Any()) 68 { 69 foreach (var edmProperty in simpleProperties) 70 { 71 #> 72 <#=codeStringGenerator.Property(edmProperty)#> 73 <# 74 } 75 } 76 77 if (complexProperties.Any()) 78 { 79 #> 80 81 <# 82 foreach(var complexProperty in complexProperties) 83 { 84 #> 85 <#=codeStringGenerator.Property(complexProperty)#> 86 <# 87 } 88 } 89 90 var navigationProperties = typeMapper.GetNavigationProperties(entity); 91 if (navigationProperties.Any()) 92 { 93 #> 94 95 <# 96 foreach (var navigationProperty in navigationProperties) 97 { 98 #> 99 <#=codeStringGenerator.NavigationProperty(navigationProperty)#> 100 <# 101 } 102 } 103 #> 104 } 105 <# 106 EndNamespace(code); 107 } 108 109 foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection)) 110 { 111 fileManager.StartNewFile(complex.Name + ".cs"); 112 BeginNamespace(code); 113 #> 114 <#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#> 115 <#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> 116 { 117 <# 118 var complexProperties = typeMapper.GetComplexProperties(complex); 119 var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex); 120 121 if (propertiesWithDefaultValues.Any() || complexProperties.Any()) 122 { 123 #> 124 public <#=code.Escape(complex)#>() 125 { 126 <# 127 foreach (var edmProperty in propertiesWithDefaultValues) 128 { 129 #> 130 this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>; 131 <# 132 } 133 134 foreach (var complexProperty in complexProperties) 135 { 136 #> 137 this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>(); 138 <# 139 } 140 #> 141 } 142 143 <# 144 } 145 146 var simpleProperties = typeMapper.GetSimpleProperties(complex); 147 if (simpleProperties.Any()) 148 { 149 foreach(var edmProperty in simpleProperties) 150 { 151 #> 152 <#=codeStringGenerator.Property(edmProperty)#> 153 <# 154 } 155 } 156 157 if (complexProperties.Any()) 158 { 159 #> 160 161 <# 162 foreach(var edmProperty in complexProperties) 163 { 164 #> 165 <#=codeStringGenerator.Property(edmProperty)#> 166 <# 167 } 168 } 169 #> 170 } 171 <# 172 EndNamespace(code); 173 } 174 175 foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection)) 176 { 177 fileManager.StartNewFile(enumType.Name + ".cs"); 178 BeginNamespace(code); 179 #> 180 <#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#> 181 <# 182 if (typeMapper.EnumIsFlags(enumType)) 183 { 184 #> 185 [Flags] 186 <# 187 } 188 #> 189 <#=codeStringGenerator.EnumOpening(enumType)#> 190 { 191 <# 192 var foundOne = false; 193 194 foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType)) 195 { 196 foundOne = true; 197 #> 198 <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>, 199 <# 200 } 201 202 if (foundOne) 203 { 204 this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1); 205 } 206 #> 207 } 208 <# 209 EndNamespace(code); 210 } 211 212 fileManager.Process(); 213 214 #> 215 <#+ 216 217 public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager) 218 { 219 fileManager.StartHeader(); 220 #> 221 //------------------------------------------------------------------------------ 222 // <auto-generated> 223 // <#=GetResourceString("Template_GeneratedCodeCommentLine1")#> 224 // 225 // <#=GetResourceString("Template_GeneratedCodeCommentLine2")#> 226 // <#=GetResourceString("Template_GeneratedCodeCommentLine3")#> 227 // </auto-generated> 228 //------------------------------------------------------------------------------ 229 <#=codeStringGenerator.UsingDirectives(inHeader: true)#> 230 <#+ 231 fileManager.EndBlock(); 232 } 233 234 public void BeginNamespace(CodeGenerationTools code) 235 { 236 var codeNamespace = code.VsNamespaceSuggestion(); 237 if (!String.IsNullOrEmpty(codeNamespace)) 238 { 239 #> 240 namespace <#=code.EscapeNamespace(codeNamespace)#> 241 { 242 <#+ 243 PushIndent(" "); 244 } 245 } 246 247 public void EndNamespace(CodeGenerationTools code) 248 { 249 if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion())) 250 { 251 PopIndent(); 252 #> 253 } 254 <#+ 255 } 256 } 257 258 public const string TemplateId = "CSharp_DbContext_Types_EF5"; 259 260 public class CodeStringGenerator 261 { 262 private readonly CodeGenerationTools _code; 263 private readonly TypeMapper _typeMapper; 264 private readonly MetadataTools _ef; 265 266 public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef) 267 { 268 ArgumentNotNull(code, "code"); 269 ArgumentNotNull(typeMapper, "typeMapper"); 270 ArgumentNotNull(ef, "ef"); 271 272 _code = code; 273 _typeMapper = typeMapper; 274 _ef = ef; 275 } 276 277 public string Property(EdmProperty edmProperty) 278 { 279 return string.Format( 280 CultureInfo.InvariantCulture, 281 "{0} {1} {2} {{ {3}get; {4}set; }}", 282 Accessibility.ForProperty(edmProperty), 283 _typeMapper.GetTypeName(edmProperty.TypeUsage), 284 _code.Escape(edmProperty), 285 _code.SpaceAfter(Accessibility.ForGetter(edmProperty)), 286 _code.SpaceAfter(Accessibility.ForSetter(edmProperty))); 287 } 288 289 public string NavigationProperty(NavigationProperty navigationProperty) 290 { 291 var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); 292 return string.Format( 293 CultureInfo.InvariantCulture, 294 "{0} {1} {2} {{ {3}get; {4}set; }}", 295 AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), 296 navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, 297 _code.Escape(navigationProperty), 298 _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), 299 _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); 300 } 301 302 public string AccessibilityAndVirtual(string accessibility) 303 { 304 return accessibility + (accessibility != "private" ? " virtual" : ""); 305 } 306 307 public string EntityClassOpening(EntityType entity) 308 { 309 return string.Format( 310 CultureInfo.InvariantCulture, 311 "{0} {1}partial class {2}{3}", 312 Accessibility.ForType(entity), 313 _code.SpaceAfter(_code.AbstractOption(entity)), 314 _code.Escape(entity), 315 _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType))); 316 } 317 318 public string EnumOpening(SimpleType enumType) 319 { 320 return string.Format( 321 CultureInfo.InvariantCulture, 322 "{0} enum {1} : {2}", 323 Accessibility.ForType(enumType), 324 _code.Escape(enumType), 325 _code.Escape(_typeMapper.UnderlyingClrType(enumType))); 326 } 327 328 public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter) 329 { 330 var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); 331 foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable)) 332 { 333 var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"; 334 var notNullInit = "new ObjectParameter("" + parameter.EsqlParameterName + "", " + parameter.FunctionParameterName + ")"; 335 var nullInit = "new ObjectParameter("" + parameter.EsqlParameterName + "", typeof(" + parameter.RawClrTypeName + "))"; 336 writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit); 337 } 338 } 339 340 public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace) 341 { 342 var parameters = _typeMapper.GetParameters(edmFunction); 343 344 return string.Format( 345 CultureInfo.InvariantCulture, 346 "{0} IQueryable<{1}> {2}({3})", 347 AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), 348 _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), 349 _code.Escape(edmFunction), 350 string.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray())); 351 } 352 353 public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace) 354 { 355 var parameters = _typeMapper.GetParameters(edmFunction); 356 357 return string.Format( 358 CultureInfo.InvariantCulture, 359 "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>("[{1}].[{2}]({3})"{4});", 360 _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), 361 edmFunction.NamespaceName, 362 edmFunction.Name, 363 string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()), 364 _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))); 365 } 366 367 public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) 368 { 369 var parameters = _typeMapper.GetParameters(edmFunction); 370 var returnType = _typeMapper.GetReturnType(edmFunction); 371 372 var paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray()); 373 if (includeMergeOption) 374 { 375 paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption"; 376 } 377 378 return string.Format( 379 CultureInfo.InvariantCulture, 380 "{0} {1} {2}({3})", 381 AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), 382 returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", 383 _code.Escape(edmFunction), 384 paramList); 385 } 386 387 public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) 388 { 389 var parameters = _typeMapper.GetParameters(edmFunction); 390 var returnType = _typeMapper.GetReturnType(edmFunction); 391 392 var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())); 393 if (includeMergeOption) 394 { 395 callParams = ", mergeOption" + callParams; 396 } 397 398 return string.Format( 399 CultureInfo.InvariantCulture, 400 "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}("{1}"{2});", 401 returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", 402 edmFunction.Name, 403 callParams); 404 } 405 406 public string DbSet(EntitySet entitySet) 407 { 408 return string.Format( 409 CultureInfo.InvariantCulture, 410 "{0} DbSet<{1}> {2} {{ get; set; }}", 411 Accessibility.ForReadOnlyProperty(entitySet), 412 _typeMapper.GetTypeName(entitySet.ElementType), 413 _code.Escape(entitySet)); 414 } 415 416 public string UsingDirectives(bool inHeader, bool includeCollections = true) 417 { 418 return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion()) 419 ? string.Format( 420 CultureInfo.InvariantCulture, 421 "{0}using System;{1}" + 422 "{2}", 423 inHeader ? Environment.NewLine : "", 424 includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "", 425 inHeader ? "" : Environment.NewLine) 426 : ""; 427 } 428 } 429 430 public class TypeMapper 431 { 432 private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName"; 433 434 private readonly System.Collections.IList _errors; 435 private readonly CodeGenerationTools _code; 436 private readonly MetadataTools _ef; 437 438 public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors) 439 { 440 ArgumentNotNull(code, "code"); 441 ArgumentNotNull(ef, "ef"); 442 ArgumentNotNull(errors, "errors"); 443 444 _code = code; 445 _ef = ef; 446 _errors = errors; 447 } 448 449 public string GetTypeName(TypeUsage typeUsage) 450 { 451 return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null); 452 } 453 454 public string GetTypeName(EdmType edmType) 455 { 456 return GetTypeName(edmType, isNullable: null, modelNamespace: null); 457 } 458 459 public string GetTypeName(TypeUsage typeUsage, string modelNamespace) 460 { 461 return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace); 462 } 463 464 public string GetTypeName(EdmType edmType, string modelNamespace) 465 { 466 return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace); 467 } 468 469 public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace) 470 { 471 if (edmType == null) 472 { 473 return null; 474 } 475 476 var collectionType = edmType as CollectionType; 477 if (collectionType != null) 478 { 479 return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace)); 480 } 481 482 var typeName = _code.Escape(edmType.MetadataProperties 483 .Where(p => p.Name == ExternalTypeNameAttributeName) 484 .Select(p => (string)p.Value) 485 .FirstOrDefault()) 486 ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ? 487 _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) : 488 _code.Escape(edmType)); 489 490 if (edmType is StructuralType) 491 { 492 return typeName; 493 } 494 495 if (edmType is SimpleType) 496 { 497 var clrType = UnderlyingClrType(edmType); 498 if (!IsEnumType(edmType)) 499 { 500 typeName = _code.Escape(clrType); 501 } 502 503 return clrType.IsValueType && isNullable == true ? 504 String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) : 505 typeName; 506 } 507 508 throw new ArgumentException("edmType"); 509 } 510 511 public Type UnderlyingClrType(EdmType edmType) 512 { 513 ArgumentNotNull(edmType, "edmType"); 514 515 var primitiveType = edmType as PrimitiveType; 516 if (primitiveType != null) 517 { 518 return primitiveType.ClrEquivalentType; 519 } 520 521 if (IsEnumType(edmType)) 522 { 523 return GetEnumUnderlyingType(edmType).ClrEquivalentType; 524 } 525 526 return typeof(object); 527 } 528 529 public object GetEnumMemberValue(MetadataItem enumMember) 530 { 531 ArgumentNotNull(enumMember, "enumMember"); 532 533 var valueProperty = enumMember.GetType().GetProperty("Value"); 534 return valueProperty == null ? null : valueProperty.GetValue(enumMember, null); 535 } 536 537 public string GetEnumMemberName(MetadataItem enumMember) 538 { 539 ArgumentNotNull(enumMember, "enumMember"); 540 541 var nameProperty = enumMember.GetType().GetProperty("Name"); 542 return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null); 543 } 544 545 public System.Collections.IEnumerable GetEnumMembers(EdmType enumType) 546 { 547 ArgumentNotNull(enumType, "enumType"); 548 549 var membersProperty = enumType.GetType().GetProperty("Members"); 550 return membersProperty != null 551 ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null) 552 : Enumerable.Empty<MetadataItem>(); 553 } 554 555 public bool EnumIsFlags(EdmType enumType) 556 { 557 ArgumentNotNull(enumType, "enumType"); 558 559 var isFlagsProperty = enumType.GetType().GetProperty("IsFlags"); 560 return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null); 561 } 562 563 public bool IsEnumType(GlobalItem edmType) 564 { 565 ArgumentNotNull(edmType, "edmType"); 566 567 return edmType.GetType().Name == "EnumType"; 568 } 569 570 public PrimitiveType GetEnumUnderlyingType(EdmType enumType) 571 { 572 ArgumentNotNull(enumType, "enumType"); 573 574 return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null); 575 } 576 577 public string CreateLiteral(object value) 578 { 579 if (value == null || value.GetType() != typeof(TimeSpan)) 580 { 581 return _code.CreateLiteral(value); 582 } 583 584 return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks); 585 } 586 587 public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile) 588 { 589 ArgumentNotNull(types, "types"); 590 ArgumentNotNull(sourceFile, "sourceFile"); 591 592 var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); 593 if (types.Any(item => !hash.Add(item))) 594 { 595 _errors.Add( 596 new CompilerError(sourceFile, -1, -1, "6023", 597 String.Format(CultureInfo.CurrentCulture, GetResourceString("Template_CaseInsensitiveTypeConflict")))); 598 return false; 599 } 600 return true; 601 } 602 603 public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection) 604 { 605 return GetItemsToGenerate<SimpleType>(itemCollection) 606 .Where(e => IsEnumType(e)); 607 } 608 609 public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType 610 { 611 return itemCollection 612 .OfType<T>() 613 .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName)) 614 .OrderBy(i => i.Name); 615 } 616 617 public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection) 618 { 619 return itemCollection 620 .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i)) 621 .Select(g => GetGlobalItemName(g)); 622 } 623 624 public string GetGlobalItemName(GlobalItem item) 625 { 626 if (item is EdmType) 627 { 628 return ((EdmType)item).Name; 629 } 630 else 631 { 632 return ((EntityContainer)item).Name; 633 } 634 } 635 636 public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type) 637 { 638 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); 639 } 640 641 public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type) 642 { 643 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); 644 } 645 646 public IEnumerable<EdmProperty> GetComplexProperties(EntityType type) 647 { 648 return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); 649 } 650 651 public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type) 652 { 653 return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); 654 } 655 656 public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type) 657 { 658 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); 659 } 660 661 public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type) 662 { 663 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); 664 } 665 666 public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type) 667 { 668 return type.NavigationProperties.Where(np => np.DeclaringType == type); 669 } 670 671 public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type) 672 { 673 return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many); 674 } 675 676 public FunctionParameter GetReturnParameter(EdmFunction edmFunction) 677 { 678 ArgumentNotNull(edmFunction, "edmFunction"); 679 680 var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters"); 681 return returnParamsProperty == null 682 ? edmFunction.ReturnParameter 683 : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault(); 684 } 685 686 public bool IsComposable(EdmFunction edmFunction) 687 { 688 ArgumentNotNull(edmFunction, "edmFunction"); 689 690 var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute"); 691 return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null); 692 } 693 694 public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction) 695 { 696 return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); 697 } 698 699 public TypeUsage GetReturnType(EdmFunction edmFunction) 700 { 701 var returnParam = GetReturnParameter(edmFunction); 702 return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage); 703 } 704 705 public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption) 706 { 707 var returnType = GetReturnType(edmFunction); 708 return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType; 709 } 710 } 711 712 public class EdmMetadataLoader 713 { 714 private readonly IDynamicHost _host; 715 private readonly System.Collections.IList _errors; 716 717 public EdmMetadataLoader(IDynamicHost host, System.Collections.IList errors) 718 { 719 ArgumentNotNull(host, "host"); 720 ArgumentNotNull(errors, "errors"); 721 722 _host = host; 723 _errors = errors; 724 } 725 726 public IEnumerable<GlobalItem> CreateEdmItemCollection(string sourcePath) 727 { 728 ArgumentNotNull(sourcePath, "sourcePath"); 729 730 if (!ValidateInputPath(sourcePath)) 731 { 732 return new EdmItemCollection(); 733 } 734 735 var schemaElement = LoadRootElement(_host.ResolvePath(sourcePath)); 736 if (schemaElement != null) 737 { 738 using (var reader = schemaElement.CreateReader()) 739 { 740 IList<EdmSchemaError> errors; 741 var itemCollection = MetadataItemCollectionFactory.CreateEdmItemCollection(new[] { reader }, out errors); 742 743 ProcessErrors(errors, sourcePath); 744 745 return itemCollection; 746 } 747 } 748 return new EdmItemCollection(); 749 } 750 751 public string GetModelNamespace(string sourcePath) 752 { 753 ArgumentNotNull(sourcePath, "sourcePath"); 754 755 if (!ValidateInputPath(sourcePath)) 756 { 757 return string.Empty; 758 } 759 760 var model = LoadRootElement(_host.ResolvePath(sourcePath)); 761 if (model == null) 762 { 763 return string.Empty; 764 } 765 766 var attribute = model.Attribute("Namespace"); 767 return attribute != null ? attribute.Value : ""; 768 } 769 770 private bool ValidateInputPath(string sourcePath) 771 { 772 if (sourcePath == "$" + "edmxInputFile" + "$") 773 { 774 _errors.Add( 775 new CompilerError(_host.TemplateFile ?? sourcePath, 0, 0, string.Empty, 776 GetResourceString("Template_ReplaceVsItemTemplateToken"))); 777 return false; 778 } 779 780 return true; 781 } 782 783 public XElement LoadRootElement(string sourcePath) 784 { 785 ArgumentNotNull(sourcePath, "sourcePath"); 786 787 var root = XElement.Load(sourcePath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); 788 return root.Elements() 789 .Where(e => e.Name.LocalName == "Runtime") 790 .Elements() 791 .Where(e => e.Name.LocalName == "ConceptualModels") 792 .Elements() 793 .Where(e => e.Name.LocalName == "Schema") 794 .FirstOrDefault() 795 ?? root; 796 } 797 798 private void ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath) 799 { 800 foreach (var error in errors) 801 { 802 _errors.Add( 803 new CompilerError( 804 error.SchemaLocation ?? sourceFilePath, 805 error.Line, 806 error.Column, 807 error.ErrorCode.ToString(CultureInfo.InvariantCulture), 808 error.Message) 809 { 810 IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning 811 }); 812 } 813 } 814 815 public bool IsLazyLoadingEnabled(EntityContainer container) 816 { 817 string lazyLoadingAttributeValue; 818 var lazyLoadingAttributeName = MetadataConstants.EDM_ANNOTATION_09_02 + ":LazyLoadingEnabled"; 819 bool isLazyLoading; 820 return !MetadataTools.TryGetStringMetadataPropertySetting(container, lazyLoadingAttributeName, out lazyLoadingAttributeValue) 821 || !bool.TryParse(lazyLoadingAttributeValue, out isLazyLoading) 822 || isLazyLoading; 823 } 824 } 825 826 public static void ArgumentNotNull<T>(T arg, string name) where T : class 827 { 828 if (arg == null) 829 { 830 throw new ArgumentNullException(name); 831 } 832 } 833 834 private static readonly Lazy<System.Resources.ResourceManager> ResourceManager = 835 new Lazy<System.Resources.ResourceManager>( 836 () => new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(MetadataItemCollectionFactory).Assembly), isThreadSafe: true); 837 838 public static string GetResourceString(string resourceName) 839 { 840 ArgumentNotNull(resourceName, "resourceName"); 841 842 return ResourceManager.Value.GetString(resourceName, null); 843 } 844 845 #>
我进行了小小的改造,当然最终代码不是这样的,我先从原有的代码上进行分离,基本能满足需求
1 <#@ template language="C#" debug="false" hostspecific="true"#> 2 <#@ include file="EF.Utility.CS.ttinclude"#> 3 <#+ 4 public class CodeStringGenerator 5 { 6 private readonly CodeGenerationTools _code; 7 private readonly TypeMapper _typeMapper; 8 private readonly MetadataTools _ef; 9 10 public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef) 11 { 12 ArgumentNotNull(code, "code"); 13 ArgumentNotNull(typeMapper, "typeMapper"); 14 ArgumentNotNull(ef, "ef"); 15 16 _code = code; 17 _typeMapper = typeMapper; 18 _ef = ef; 19 } 20 21 public string Property(EdmProperty edmProperty) 22 { 23 return string.Format( 24 CultureInfo.InvariantCulture, 25 "{0} {1} {2} {{ {3}get; {4}set; }}", 26 Accessibility.ForProperty(edmProperty), 27 _typeMapper.GetTypeName(edmProperty.TypeUsage), 28 _code.Escape(edmProperty), 29 _code.SpaceAfter(Accessibility.ForGetter(edmProperty)), 30 _code.SpaceAfter(Accessibility.ForSetter(edmProperty))); 31 } 32 33 public string NavigationProperty(NavigationProperty navigationProperty) 34 { 35 var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); 36 return string.Format( 37 CultureInfo.InvariantCulture, 38 "{0} {1} {2} {{ {3}get; {4}set; }}", 39 AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), 40 navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, 41 _code.Escape(navigationProperty), 42 _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), 43 _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); 44 } 45 46 public string AccessibilityAndVirtual(string accessibility) 47 { 48 return accessibility + (accessibility != "private" ? " virtual" : ""); 49 } 50 51 public string EntityClassOpening(EntityType entity) 52 { 53 return string.Format( 54 CultureInfo.InvariantCulture, 55 "{0} {1}partial class {2}{3}", 56 Accessibility.ForType(entity), 57 _code.SpaceAfter(_code.AbstractOption(entity)), 58 _code.Escape(entity), 59 _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType))); 60 } 61 62 public string EnumOpening(SimpleType enumType) 63 { 64 return string.Format( 65 CultureInfo.InvariantCulture, 66 "{0} enum {1} : {2}", 67 Accessibility.ForType(enumType), 68 _code.Escape(enumType), 69 _code.Escape(_typeMapper.UnderlyingClrType(enumType))); 70 } 71 72 public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter) 73 { 74 var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); 75 foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable)) 76 { 77 var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"; 78 var notNullInit = "new ObjectParameter("" + parameter.EsqlParameterName + "", " + parameter.FunctionParameterName + ")"; 79 var nullInit = "new ObjectParameter("" + parameter.EsqlParameterName + "", typeof(" + parameter.RawClrTypeName + "))"; 80 writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit); 81 } 82 } 83 84 public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace) 85 { 86 var parameters = _typeMapper.GetParameters(edmFunction); 87 88 return string.Format( 89 CultureInfo.InvariantCulture, 90 "{0} IQueryable<{1}> {2}({3})", 91 AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), 92 _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), 93 _code.Escape(edmFunction), 94 string.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray())); 95 } 96 97 public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace) 98 { 99 var parameters = _typeMapper.GetParameters(edmFunction); 100 101 return string.Format( 102 CultureInfo.InvariantCulture, 103 "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>("[{1}].[{2}]({3})"{4});", 104 _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), 105 edmFunction.NamespaceName, 106 edmFunction.Name, 107 string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()), 108 _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))); 109 } 110 111 public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) 112 { 113 var parameters = _typeMapper.GetParameters(edmFunction); 114 var returnType = _typeMapper.GetReturnType(edmFunction); 115 116 var paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray()); 117 if (includeMergeOption) 118 { 119 paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption"; 120 } 121 122 return string.Format( 123 CultureInfo.InvariantCulture, 124 "{0} {1} {2}({3})", 125 AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), 126 returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", 127 _code.Escape(edmFunction), 128 paramList); 129 } 130 131 public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) 132 { 133 var parameters = _typeMapper.GetParameters(edmFunction); 134 var returnType = _typeMapper.GetReturnType(edmFunction); 135 136 var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())); 137 if (includeMergeOption) 138 { 139 callParams = ", mergeOption" + callParams; 140 } 141 142 return string.Format( 143 CultureInfo.InvariantCulture, 144 "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}("{1}"{2});", 145 returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", 146 edmFunction.Name, 147 callParams); 148 } 149 150 public string DbSet(EntitySet entitySet) 151 { 152 return string.Format( 153 CultureInfo.InvariantCulture, 154 "{0} DbSet<{1}> {2} {{ get; set; }}", 155 Accessibility.ForReadOnlyProperty(entitySet), 156 _typeMapper.GetTypeName(entitySet.ElementType), 157 _code.Escape(entitySet)); 158 } 159 160 public string UsingDirectives(bool inHeader, bool includeCollections = true) 161 { 162 return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion()) 163 ? string.Format( 164 CultureInfo.InvariantCulture, 165 "{0}using System;{1}" + 166 "{2}", 167 inHeader ? Environment.NewLine : "", 168 includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "", 169 inHeader ? "" : Environment.NewLine) 170 : ""; 171 } 172 } 173 174 public class TypeMapper 175 { 176 private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName"; 177 178 private readonly System.Collections.IList _errors; 179 private readonly CodeGenerationTools _code; 180 private readonly MetadataTools _ef; 181 182 public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors) 183 { 184 ArgumentNotNull(code, "code"); 185 ArgumentNotNull(ef, "ef"); 186 ArgumentNotNull(errors, "errors"); 187 188 _code = code; 189 _ef = ef; 190 _errors = errors; 191 } 192 193 public string GetTypeName(TypeUsage typeUsage) 194 { 195 return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null); 196 } 197 198 public string GetTypeName(EdmType edmType) 199 { 200 return GetTypeName(edmType, isNullable: null, modelNamespace: null); 201 } 202 203 public string GetTypeName(TypeUsage typeUsage, string modelNamespace) 204 { 205 return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace); 206 } 207 208 public string GetTypeName(EdmType edmType, string modelNamespace) 209 { 210 return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace); 211 } 212 213 public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace) 214 { 215 if (edmType == null) 216 { 217 return null; 218 } 219 220 var collectionType = edmType as CollectionType; 221 if (collectionType != null) 222 { 223 return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace)); 224 } 225 226 var typeName = _code.Escape(edmType.MetadataProperties 227 .Where(p => p.Name == ExternalTypeNameAttributeName) 228 .Select(p => (string)p.Value) 229 .FirstOrDefault()) 230 ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ? 231 _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) : 232 _code.Escape(edmType)); 233 234 if (edmType is StructuralType) 235 { 236 return typeName; 237 } 238 239 if (edmType is SimpleType) 240 { 241 var clrType = UnderlyingClrType(edmType); 242 if (!IsEnumType(edmType)) 243 { 244 typeName = _code.Escape(clrType); 245 } 246 247 return clrType.IsValueType && isNullable == true ? 248 String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) : 249 typeName; 250 } 251 252 throw new ArgumentException("edmType"); 253 } 254 255 public Type UnderlyingClrType(EdmType edmType) 256 { 257 ArgumentNotNull(edmType, "edmType"); 258 259 var primitiveType = edmType as PrimitiveType; 260 if (primitiveType != null) 261 { 262 return primitiveType.ClrEquivalentType; 263 } 264 265 if (IsEnumType(edmType)) 266 { 267 return GetEnumUnderlyingType(edmType).ClrEquivalentType; 268 } 269 270 return typeof(object); 271 } 272 273 public object GetEnumMemberValue(MetadataItem enumMember) 274 { 275 ArgumentNotNull(enumMember, "enumMember"); 276 277 var valueProperty = enumMember.GetType().GetProperty("Value"); 278 return valueProperty == null ? null : valueProperty.GetValue(enumMember, null); 279 } 280 281 public string GetEnumMemberName(MetadataItem enumMember) 282 { 283 ArgumentNotNull(enumMember, "enumMember"); 284 285 var nameProperty = enumMember.GetType().GetProperty("Name"); 286 return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null); 287 } 288 289 public System.Collections.IEnumerable GetEnumMembers(EdmType enumType) 290 { 291 ArgumentNotNull(enumType, "enumType"); 292 293 var membersProperty = enumType.GetType().GetProperty("Members"); 294 return membersProperty != null 295 ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null) 296 : Enumerable.Empty<MetadataItem>(); 297 } 298 299 public bool EnumIsFlags(EdmType enumType) 300 { 301 ArgumentNotNull(enumType, "enumType"); 302 303 var isFlagsProperty = enumType.GetType().GetProperty("IsFlags"); 304 return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null); 305 } 306 307 public bool IsEnumType(GlobalItem edmType) 308 { 309 ArgumentNotNull(edmType, "edmType"); 310 311 return edmType.GetType().Name == "EnumType"; 312 } 313 314 public PrimitiveType GetEnumUnderlyingType(EdmType enumType) 315 { 316 ArgumentNotNull(enumType, "enumType"); 317 318 return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null); 319 } 320 321 public string CreateLiteral(object value) 322 { 323 if (value == null || value.GetType() != typeof(TimeSpan)) 324 { 325 return _code.CreateLiteral(value); 326 } 327 328 return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks); 329 } 330 331 public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile) 332 { 333 ArgumentNotNull(types, "types"); 334 ArgumentNotNull(sourceFile, "sourceFile"); 335 336 var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); 337 if (types.Any(item => !hash.Add(item))) 338 { 339 _errors.Add( 340 new CompilerError(sourceFile, -1, -1, "6023", 341 String.Format(CultureInfo.CurrentCulture, GetResourceString("Template_CaseInsensitiveTypeConflict")))); 342 return false; 343 } 344 return true; 345 } 346 347 public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection) 348 { 349 return GetItemsToGenerate<SimpleType>(itemCollection) 350 .Where(e => IsEnumType(e)); 351 } 352 353 public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType 354 { 355 return itemCollection 356 .OfType<T>() 357 .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName)) 358 .OrderBy(i => i.Name); 359 } 360 361 public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection) 362 { 363 return itemCollection 364 .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i)) 365 .Select(g => GetGlobalItemName(g)); 366 } 367 368 public string GetGlobalItemName(GlobalItem item) 369 { 370 if (item is EdmType) 371 { 372 return ((EdmType)item).Name; 373 } 374 else 375 { 376 return ((EntityContainer)item).Name; 377 } 378 } 379 380 public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type) 381 { 382 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); 383 } 384 385 public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type) 386 { 387 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); 388 } 389 390 public IEnumerable<EdmProperty> GetComplexProperties(EntityType type) 391 { 392 return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); 393 } 394 395 public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type) 396 { 397 return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); 398 } 399 400 public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type) 401 { 402 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); 403 } 404 405 public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type) 406 { 407 return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); 408 } 409 410 public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type) 411 { 412 return type.NavigationProperties.Where(np => np.DeclaringType == type); 413 } 414 415 public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type) 416 { 417 return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many); 418 } 419 420 public FunctionParameter GetReturnParameter(EdmFunction edmFunction) 421 { 422 ArgumentNotNull(edmFunction, "edmFunction"); 423 424 var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters"); 425 return returnParamsProperty == null 426 ? edmFunction.ReturnParameter 427 : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault(); 428 } 429 430 public bool IsComposable(EdmFunction edmFunction) 431 { 432 ArgumentNotNull(edmFunction, "edmFunction"); 433 434 var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute"); 435 return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null); 436 } 437 438 public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction) 439 { 440 return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); 441 } 442 443 public TypeUsage GetReturnType(EdmFunction edmFunction) 444 { 445 var returnParam = GetReturnParameter(edmFunction); 446 return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage); 447 } 448 449 public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption) 450 { 451 var returnType = GetReturnType(edmFunction); 452 return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType; 453 } 454 } 455 456 public class EdmMetadataLoader 457 { 458 private readonly IDynamicHost _host; 459 private readonly System.Collections.IList _errors; 460 461 public EdmMetadataLoader(IDynamicHost host, System.Collections.IList errors) 462 { 463 ArgumentNotNull(host, "host"); 464 ArgumentNotNull(errors, "errors"); 465 466 _host = host; 467 _errors = errors; 468 } 469 470 public IEnumerable<GlobalItem> CreateEdmItemCollection(string sourcePath) 471 { 472 ArgumentNotNull(sourcePath, "sourcePath"); 473 474 if (!ValidateInputPath(sourcePath)) 475 { 476 return new EdmItemCollection(); 477 } 478 479 var schemaElement = LoadRootElement(_host.ResolvePath(sourcePath)); 480 if (schemaElement != null) 481 { 482 using (var reader = schemaElement.CreateReader()) 483 { 484 IList<EdmSchemaError> errors; 485 var itemCollection = MetadataItemCollectionFactory.CreateEdmItemCollection(new[] { reader }, out errors); 486 487 ProcessErrors(errors, sourcePath); 488 489 return itemCollection; 490 } 491 } 492 return new EdmItemCollection(); 493 } 494 495 public string GetModelNamespace(string sourcePath) 496 { 497 ArgumentNotNull(sourcePath, "sourcePath"); 498 499 if (!ValidateInputPath(sourcePath)) 500 { 501 return string.Empty; 502 } 503 504 var model = LoadRootElement(_host.ResolvePath(sourcePath)); 505 if (model == null) 506 { 507 return string.Empty; 508 } 509 510 var attribute = model.Attribute("Namespace"); 511 return attribute != null ? attribute.Value : ""; 512 } 513 514 private bool ValidateInputPath(string sourcePath) 515 { 516 if (sourcePath == "$" + "edmxInputFile" + "$") 517 { 518 _errors.Add( 519 new CompilerError(_host.TemplateFile ?? sourcePath, 0, 0, string.Empty, 520 GetResourceString("Template_ReplaceVsItemTemplateToken"))); 521 return false; 522 } 523 524 return true; 525 } 526 527 public XElement LoadRootElement(string sourcePath) 528 { 529 ArgumentNotNull(sourcePath, "sourcePath"); 530 531 var root = XElement.Load(sourcePath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); 532 return root.Elements() 533 .Where(e => e.Name.LocalName == "Runtime") 534 .Elements() 535 .Where(e => e.Name.LocalName == "ConceptualModels") 536 .Elements() 537 .Where(e => e.Name.LocalName == "Schema") 538 .FirstOrDefault() 539 ?? root; 540 } 541 542 private void ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath) 543 { 544 foreach (var error in errors) 545 { 546 _errors.Add( 547 new CompilerError( 548 error.SchemaLocation ?? sourceFilePath, 549 error.Line, 550 error.Column, 551 error.ErrorCode.ToString(CultureInfo.InvariantCulture), 552 error.Message) 553 { 554 IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning 555 }); 556 } 557 } 558 559 public bool IsLazyLoadingEnabled(EntityContainer container) 560 { 561 string lazyLoadingAttributeValue; 562 var lazyLoadingAttributeName = MetadataConstants.EDM_ANNOTATION_09_02 + ":LazyLoadingEnabled"; 563 bool isLazyLoading; 564 return !MetadataTools.TryGetStringMetadataPropertySetting(container, lazyLoadingAttributeName, out lazyLoadingAttributeValue) 565 || !bool.TryParse(lazyLoadingAttributeValue, out isLazyLoading) 566 || isLazyLoading; 567 } 568 } 569 570 public static void ArgumentNotNull<T>(T arg, string name) where T : class 571 { 572 if (arg == null) 573 { 574 throw new ArgumentNullException(name); 575 } 576 } 577 578 private static readonly Lazy<System.Resources.ResourceManager> ResourceManager = 579 new Lazy<System.Resources.ResourceManager>( 580 () => new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(MetadataItemCollectionFactory).Assembly), isThreadSafe: true); 581 public static TypeMapper GlobaltypeMapper{get;set;} 582 public static CodeStringGenerator codeStringGenerator{get;set;} 583 public static CodeGenerationTools GlobalCode{get;set;} 584 public static string GetResourceString(string resourceName) 585 { 586 ArgumentNotNull(resourceName, "resourceName"); 587 588 return ResourceManager.Value.GetString(resourceName, null); 589 } 590 #>
1 <#@ template language="C#" debug="True" #> 2 <#@ assembly name="System.Core" #> 3 <#@ import namespace="System.IO" #> 4 <#@ import namespace="System.Linq" #> 5 <#@ import namespace="System.Text" #> 6 <#@ output extension="cs" #> 7 <#@ include file="T4Toolbox.tt" #> 8 <#@ include file="EntityTemplate.tt" #> 9 <#@ output extension=".cs" encoding="UTF-8"#> 10 <# 11 12 string curPath = Path.GetDirectoryName(Host.TemplateFile); 13 string destPath = Path.Combine(curPath, @".."); 14 if(!Directory.Exists(destPath)) 15 { 16 Directory.CreateDirectory(destPath); 17 } 18 const string inputFile = @"....DataMouldSQLServer.edmx"; 19 var textTransform = DynamicTextTransformation.Create(this); 20 var code = new CodeGenerationTools(this); 21 GlobalCode=new CodeGenerationTools(this); 22 var ef = new MetadataTools(this); 23 GlobaltypeMapper = new TypeMapper(code, ef, textTransform.Errors); 24 codeStringGenerator = new CodeStringGenerator(code, GlobaltypeMapper, ef); 25 var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); 26 foreach (var entity in GlobaltypeMapper.GetItemsToGenerate<EntityType>(itemCollection)) 27 { 28 EntityTemplate template = new EntityTemplate(entity); 29 string fileName = string.Format(@"{0}{1}.cs", destPath, entity.Name); 30 template.Output.Encoding = Encoding.UTF8; 31 if(File.Exists(fileName)) 32 { 33 File.Delete(fileName); 34 } 35 template.RenderToFile(fileName); 36 } 37 #>
1 <#@ template language="C#" debug="True" #> 2 <#@ output extension="cs" #> 3 <#@ include file="BaseClassTemplate.tt" #> 4 5 <#+ 6 // <copyright file="EntityTemplate.tt" company=""> 7 // Copyright © . All Rights Reserved. 8 // </copyright> 9 10 public class EntityTemplate : CSharpTemplate 11 { 12 13 private EntityType _entitis; 14 public EntityTemplate(EntityType Entitis) 15 { 16 _entitis = Entitis; 17 } 18 19 public override string TransformText() 20 { 21 base.TransformText(); 22 #> 23 using System; 24 using System.Collections.Generic; 25 namespace Entities 26 { 27 public class <#= _entitis.Name #> 28 { 29 <#+ 30 var propertiesWithDefaultValues = GlobaltypeMapper.GetPropertiesWithDefaultValues(_entitis); 31 var collectionNavigationProperties = GlobaltypeMapper.GetCollectionNavigationProperties(_entitis); 32 var complexProperties = GlobaltypeMapper.GetComplexProperties(_entitis); 33 34 if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any()) 35 { 36 #> 37 public <#=GlobalCode.Escape(_entitis)#>() 38 { 39 <#+ 40 foreach (var edmProperty in propertiesWithDefaultValues) 41 { 42 #> 43 this.<#=GlobalCode.Escape(edmProperty)#> = <#=GlobaltypeMapper.CreateLiteral(edmProperty.DefaultValue)#>; 44 <#+ 45 } 46 47 foreach (var navigationProperty in collectionNavigationProperties) 48 { 49 #> 50 this.<#=GlobalCode.Escape(navigationProperty)#> = new HashSet<<#=GlobaltypeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>(); 51 <#+ 52 } 53 54 foreach (var complexProperty in complexProperties) 55 { 56 #> 57 this.<#=GlobalCode.Escape(complexProperty)#> = new <#=GlobaltypeMapper.GetTypeName(complexProperty.TypeUsage)#>(); 58 <#+ 59 } 60 #> 61 } 62 <#+ 63 } 64 var simpleProperties = GlobaltypeMapper.GetSimpleProperties(_entitis); 65 //var propertiesWithDefaultValues = GlobaltypeMapper.GetPropertiesWithDefaultValues(_entitis); 66 var navigationProperties = GlobaltypeMapper.GetNavigationProperties(_entitis); 67 68 foreach(var property in simpleProperties) 69 { 70 #> 71 <#=codeStringGenerator.Property(property)#> 72 <#+ 73 } 74 75 if (navigationProperties.Any()) 76 { 77 #> 78 79 <#+ 80 foreach (var navigationProperty in navigationProperties) 81 { 82 #> 83 <#=codeStringGenerator.NavigationProperty(navigationProperty)#> 84 <#+ 85 } 86 } 87 88 #> 89 } 90 } 91 <#+ 92 return this.GenerationEnvironment.ToString(); 93 } 94 } 95 #>
一个模板拆分成3个文件,我引入了T4toolbox作为代码着色器,这样,我可以在不同的层内,放置不同的模板,只需要修改生成模板的代码,并修改代码生成那里面的一段代码,比如生成DTO的模板是这样的
1 <#@ template language="C#" debug="True" #> 2 <#@ output extension="cs" #> 3 <#@ include file="BaseClassTemplate.tt" #> 4 5 <#+ 6 7 8 public class EntityTemplate : CSharpTemplate 9 { 10 11 private EntityType _entitis; 12 public EntityTemplate(EntityType Entitis) 13 { 14 _entitis = Entitis; 15 } 16 17 public override string TransformText() 18 { 19 base.TransformText(); 20 #> 21 using System; 22 using System.Collections.Generic; 23 namespace DTO 24 { 25 public class <#= _entitis.Name.Replace("T_","")+"DTO" #> 26 { 27 <#+ 28 29 var simpleProperties = GlobaltypeMapper.GetSimpleProperties(_entitis); 30 //var propertiesWithDefaultValues = GlobaltypeMapper.GetPropertiesWithDefaultValues(_entitis); 31 var navigationProperties = GlobaltypeMapper.GetNavigationProperties(_entitis); 32 var complexProperties = GlobaltypeMapper.GetComplexProperties(_entitis); 33 foreach(var property in simpleProperties) 34 { 35 #> 36 <#=codeStringGenerator.Property(property)#> 37 <#+ 38 } 39 40 41 #> 42 } 43 } 44 <#+ 45 return this.GenerationEnvironment.ToString(); 46 } 47 } 48 #>
修改代码生成中的语句为
string fileName = string.Format(@"{0}{1}.cs", destPath, entity.Name.Replace("T_","")+"DTO");
2.我尝试用原有的事务调用的方式去修改dbcontext
比如在IDataBase方法下增加了三个函数
1 void BeginTransaction(); 2 void CommitTransaction(); 3 void RollbackTransaction();
实现
1 protected static DbEntities _dbEntities { get; set; }//我定义的dbcontext的基类 2 protected DbConnection _con { get; set; } 3 protected DbTransaction _tran { get; set; } 4 public BaseDao() 5 { 6 _dbEntities = new DbEntities(); 7 } 8 public void BeginTransaction() 9 { 10 _con = ((IObjectContextAdapter)_dbEntities).ObjectContext.Connection; 11 _con.Open(); 12 _tran=_con.BeginTransaction(); 13 } 14 15 public void CommitTransaction() 16 { 17 _tran.Commit(); 18 _con.Close(); 19 } 20 public void RollbackTransaction() 21 { 22 _tran.Rollback(); 23 _con.Close(); 24 }
当然,DbContext本身的savechange已经实现了事务,又或者我可以采用别的园友使用的unitofwork的方式来进行事物封装,但是在选择之前,我只想知道能不能用传统的方式来实现
3.Linq to Entities,这是个好东西,可以将linq或者lambda语句转换成实际运行的SQL,让程序员不需要太多关心语句怎么去组织,虽然用三层的方式来调用很违和,但是没必要放弃这个特性
以查询为例
1 /// <summary> 2 /// 根据条件获取数据实体集合 3 /// </summary> 4 /// <typeparam name="T">实体类型</typeparam> 5 /// <param name="exp">条件表达式</param> 6 /// <param name="useBaseContext">是否使用类基础的context,以达到延迟加载的目的</param> 7 /// <returns></returns> 8 public virtual IQueryable<T> GetEntities<T>(Expression<Func<T, bool>> exp, bool useBaseContext=false) where T : class 9 { 10 if (useBaseContext) 11 { 12 return _dbEntities.Set<T>().Where(exp); 13 } 14 using (DbEntities db = new DbEntities()) 15 { 16 return db.Set<T>().Where(exp); 17 } 18 }
如果使用传统三层或者事务脚本的模式,肯定涉及到分层,有时候分层代码是放在不同的项目中,因为业务需求,你需要在Service里面编写linq或者lambda,如果直接使用using的方式,很多时候会提示你DbContext已经关闭,所以我这里提供了两种方式,如果需要用延迟加载的方式,那么设置使用基类的DbContext来提供链接.因为我采用了IQueryalbe来作为返回,这样可以在需要懒加载的时候传递表达式目录树。至于这种用法DbContext释放的问题和是否需要释放的问题,我们下一步再讨论。
调用方法(比如直接转化成DTO)
1 public IEnumerable<UserDTO> GetList() 2 { 3 4 return (from a in _dal.GetEntities<T_User>(m=>m.T_UserID==1, true) 5 select new UserDTO 6 { 7 T_UserID=a.T_UserID 8 }).ToList(); 9 }
生成的语句
这里要注意,linq语句里面不能直接用select new T_User{}或者lambda语句中用.select(m=>new T_User{})来传递表达式目录树实现选定字段的查询,因为在DbContext里面已经已经声明了DBSet<T_User> T_User,在lazy load的时候,Linq to Entities会把T_User当成这个DBSet的实例来构建表达式目录树,导致转换SQL语句过程失败。
提示:The entity or complex type 'SqlServer.T_User' cannot be constructed in a LINQ to Entities query.
几个存在的问题:
1.传统的DAL层会将每一个实体的操作抽象成一个独立的数据对象,在用EF构建的DAL层里,当然也可以用类似 T_UserDao.cs的方式来建立这个链接,方便以后转换ORM组件,但是建立这些类似public T_UserDAO:BaseDAO{}的空链接文件有没有必要?BaseDAO甚至直接将DBSet暴露给Service似乎更适合EF。
2.是否适合用传统的方式来使用EF?
3.非using方式使用DbContext是否真的会产生不必要的错误?如果要用到using的方式,应该怎样改造这个框架呢?