2011-07-06 8 views
9

Czy można napisać szablon t4 (lub jeśli już istnieje), który będzie w stanie wygenerować klasy DTO na podstawie danych w pliku * .edmx?Generator DTO dla EF 4 Model encji

Muszę napisać klasy DTO dla bieżącego projektu, a ten proces jest dość męczący.

Co staram się zdobyć, to uzyskanie klas DTO, które będą miały właściwości skalarne zdefiniowane jako proste właściwości automatyczne, a parametry nawigacyjne jako niewypełnione wystąpienia innych klas DTO.

Przykład:

public class SomeClassDTO 
{ 
    public byte Id { get; set; } 

    public string Description { get; set; }   

    public OtherClassDTO SomeProperty {get;set;} 

    public IList<AnotherClassDTO> Objects {get;set;} 
} 

Jest to dobry punkt wyjścia, co jest bardziej pożądane może wyglądać poniższym przykładzie:

/// <summary> 
/// Employee details DTO. 
/// </summary> 
public class EmployeeDetailsDTO 
{ 
    [Key] 
    public long Id { get; set; } 

    [Required] 
    public string FirstName { get; set; } 

    [Required] 
    public string Surname { get; set; } 

    ... 

    public long? PhotoId { get; set; } 

    // Home address properties. 


    public string HomeAddressAddressLine1 { get; set; } // This is just name of field, not flattened list 
    public string HomeAddressAddressLine2 { get; set; } 
    public string HomeAddressAddressLine3 { get; set; } 
    public string HomeAddressPostcode { get; set; } 
    public short? HomeAddressCountryId { get; set; } 
    public long? HomeAddressCountyId { get; set; } 
    public long? HomeAddressTownId { get; set; } 


    public short? HomeTelephoneCountryId { get; set; }   
    public string HomeTelephoneNumber{ get; set; } 
    public string HomeTelephoneExtension { get; set; } 


    public short? PersonalMobileCountryId { get; set; }  
    public string PersonalMobileNumber { get; set; } 
    public string PersonalMobileExtension { get; set; } 

} 

Jak widać jest to Spłaszczenie DTO które stanowią strukturę kompozytową i mogą być wstrzykiwane z powrotem do jednostek przez iniekcje ValueInjector SameNameFlat/UnFlat.

To jest ostateczny cel, ale wszelkie porady będą mile widziane.

+0

DTO specjalizuje przedmiot do jednego celu, w większości przypadków jego utworzenia nie może być zautomatyzowany, ponieważ zazwyczaj zawierają dodatkowe właściwości lub pominąć niektóre właściwości z jednostką (nie 1: 1 odwzorowanie jednostki), tworząc w DTOS które są tylko kopią twoich jednostek wygląda bardziej jak problem w architekturze. Nawet rekurencyjny szablon T4 może dodawać właściwości, których nie potrzebujesz. DTO powinien przekazać tylko te właściwości, których potrzebujesz do operacji, w której używane jest DTO. –

+0

Tak, oczywiście, te rzeczy T4 właśnie ułatwiają. Przynajmniej nie muszę pisać ręcznie szkieletu dla każdego dto, które tworzę.Kiedy dostanę zeskanowaną klasę, mogę ją pociąć na kawałki, jak chcę. – v00d00

Odpowiedz

0

Wyglądało to o wiele łatwiej niż myślałem. Jeśli ktoś jest zainteresowany jest szablon t4 do rekurencyjnego skanowania przez podmiot drzewa:

<#@ template language="C#" debug="false" hostspecific="true"#> 
<#@ include file="EF.Utility.CS.ttinclude"#><#@ 
output extension=".cs"#><# 
// Copyright (c) Microsoft Corporation. All rights reserved. 

CodeGenerationTools code = new CodeGenerationTools(this); 
MetadataLoader loader = new MetadataLoader(this); 
CodeRegion region = new CodeRegion(this, 1); 
MetadataTools ef = new MetadataTools(this); 

string inputFile = @"D:\Projects\Empresa\CTM_Empresa\trunk\CTM.Empresa.Logic\DataModel\CTM.Empresa.Database.edmx"; 

string entityName = @"Email"; 

bool printNavigationLists = false; 

MetadataWorkspace metadataWorkspace = null; 
bool allMetadataLoaded = loader.TryLoadAllMetadata(inputFile, out metadataWorkspace); 
EdmItemCollection ItemCollection = (EdmItemCollection)metadataWorkspace.GetItemCollection(DataSpace.CSpace); 

string namespaceName = code.VsNamespaceSuggestion(); 

EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this); 

RecursiveEntityProcessor.code = code; 
RecursiveEntityProcessor.ItemCollection = ItemCollection; 
RecursiveEntityProcessor.metaData = ef; 

// Emit Entity Types 
foreach (EntityType entity in ItemCollection.GetItems<EntityType>().Where(it => it.Name == entityName)) 
{ 
    fileManager.StartNewFile(entity.Name + ".cs"); 

    var result = new List<PropertyCustomData>();  

    RecursiveEntityProcessor.GetEntityPropertyNames(result, entity.Name , 2 , ""); 

    //WriteEntityTypeSerializationInfo(entity, ItemCollection, code, ef); 
#> 
<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>class <#=code.Escape(entity)#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#> 
{ 
<# 
    foreach (PropertyCustomData property in result) 
    {  
     #> 
    /// <summary> 
    /// Gets or sets <#=code.Escape(property.PropertyName)#>. 
    /// </summary> 
    <#=Accessibility.ForProperty(property.PropertyDefenition)#> <#=code.Escape(property.PropertyDefenition.TypeUsage)#> <#=code.Escape(property.PropertyName)#> { get; set; } 
<# 

    }  
}#> 
} 

<#+ 

    public class PropertyCustomData 
    { 
     public EdmProperty PropertyDefenition { get; set; } 

     public string PropertyName { get; set; } 
    } 

    public static class RecursiveEntityProcessor 
    {  
     public static CodeGenerationTools code; 

     public static EdmItemCollection ItemCollection; 

     public static MetadataTools metaData; 

     public static EntityType FindByName(string entityName) 
     { 
      return ItemCollection.GetItems<EntityType>().Single(it => it.Name == entityName); 
     } 

     public static void GetEntityPropertyNames(IList<PropertyCustomData> result, string entityName, int recursiveDepth, string currentPrefix, bool canPrintPrimaryKeys = true) 
     {   
      if (recursiveDepth > 0) 
      { 
       EntityType entity = FindByName(entityName); 

       foreach (EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity)) 
       { 
        if (metaData.IsKey(edmProperty) && !canPrintPrimaryKeys) 
        { 
         continue; 
        } 

        result.Add(new PropertyCustomData { PropertyDefenition = edmProperty, PropertyName = currentPrefix + code.Escape(edmProperty) }); 
       } 

       foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity)) 
       { 
        if (navProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many) 
        { 
         string childEntityName = navProperty.ToEndMember.GetEntityType().Name; 

         GetEntityPropertyNames(result, childEntityName, recursiveDepth - 1, currentPrefix + code.Escape(navProperty), false); 
        } 
       } 
      } 
     } 
    } 

#> 

ten szablon produkuje kod jak w ostatnim przykładzie mojego pytania.

14

Niedawno opublikowałem Entity Framework DTO Generator o nazwie EntitiesToDTO w CodePlex, jest darmowy i open source i jest używany jako AddIn dla Visual Studio 2010 i 2012. Myślę, że będzie to pomocne dla Ciebie.

idź do http://entitiestodtos.codeplex.com aby go pobrać, i daj mi znać, co myślisz;)

+1

+1 Wielkie dzięki. –

+0

Nie ma za co! – kzfabi

0

Oto aktualizacja szablonu programu Visual Studio 2012 T4 do generowania prostych obiektów DTO na podstawie istniejącego pliku EDMX. Pomija właściwości nawigacyjne i generuje tylko proste właściwości.

Używanie AutoMapper Udało mi się skopiować moje dane POCO do obiektów DTO. Są to serializowalne i mogą być transportowane jako XML. Podczas przebudowywania obiektów na stronie docelowej możliwe jest dołączenie ich do dbContext i wywołanie funkcji DetectChanges(). Referencje zostaną ustalone po tej dacie.

<#@ template language="C#" debug="true" hostspecific="true" #> 
<#@ Assembly Name="System.Core, Version=4.0.0.0, Culture=neutral" #> 
<#@ Assembly Name="Microsoft.CSharp, Version=4.0.0.0, Culture=neutral" #> 
<#@ include file="EF.Utility.CS.ttinclude"#> 
<#@ output extension=".cs"#> 
<# 


const string inputFile = @"../Model/ModelTest.edmx"; 
var textTransform = DynamicTextTransformation.Create(this); 
var code = new CodeGenerationTools(this); 
var ef = new MetadataTools(this); 
var typeMapper = new TypeMapper(code, ef, textTransform.Errors); 
var fileManager = EntityFrameworkTemplateFileManager.Create(this); 
var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); 
var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); 

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) 
{ 
    return string.Empty; 
} 

WriteHeader(codeStringGenerator, fileManager); 

foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) 
{ 
    fileManager.StartNewFile(entity.Name + "Dto.cs"); 
    BeginNamespace(code); 
#> 
<#=codeStringGenerator.UsingDirectives(false)#> 
<#=codeStringGenerator.EntityClassOpening(entity)#> 
{ 
<# 
    var simpleProperties = typeMapper.GetSimpleProperties(entity); 
    if (simpleProperties.Any()) 
    { 
     foreach (var edmProperty in simpleProperties) 
     { 
#> 
    <#=codeStringGenerator.Property(edmProperty)#> 
<# 
     } 
    } 
#> 
} 
<# 
    EndNamespace(code); 
} 

foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection)) 
{ 
    fileManager.StartNewFile(complex.Name + ".cs"); 
    BeginNamespace(code); 
#> 
<#=codeStringGenerator.UsingDirectives(false, false)#> 
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> 
{ 
<# 

    var simpleProperties = typeMapper.GetSimpleProperties(complex); 
    if (simpleProperties.Any()) 
    { 
     foreach(var edmProperty in simpleProperties) 
     { 
#> 
    <#=codeStringGenerator.Property(edmProperty)#> 
<# 
     } 
    } 
#> 
} 
<# 
    EndNamespace(code); 
} 
fileManager.Process(); 
#> 

<#+ 

public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager) 
{ 
    fileManager.StartHeader(); 
#> 
//------------------------------------------------------------------------------ 
// <auto-generated> 
// <#=GetResourceString("Template_GeneratedCodeCommentLine1")#> 
// 
// <#=GetResourceString("Template_GeneratedCodeCommentLine2")#> 
// <#=GetResourceString("Template_GeneratedCodeCommentLine3")#> 
// </auto-generated> 
//------------------------------------------------------------------------------ 
<#=codeStringGenerator.UsingDirectives(true)#> 
<#+ 
    fileManager.EndBlock(); 
} 

public void BeginNamespace(CodeGenerationTools code) 
{ 
    var codeNamespace = code.VsNamespaceSuggestion(); 
    if (!String.IsNullOrEmpty(codeNamespace)) 
    { 
#> 
namespace <#=code.EscapeNamespace(codeNamespace)#> 
{ 
<#+ 
     PushIndent(" "); 
    } 
} 

public void EndNamespace(CodeGenerationTools code) 
{ 
    if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion())) 
    { 
     PopIndent(); 
#> 
} 
<#+ 
    } 
} 

public const string TemplateId = "CSharp_DbContext_Types_EF5"; 

public class CodeStringGenerator 
{ 
    private readonly CodeGenerationTools _code; 
    private readonly TypeMapper _typeMapper; 
    private readonly MetadataTools _ef; 

    public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef) 
    { 
     ArgumentNotNull(code, "code"); 
     ArgumentNotNull(typeMapper, "typeMapper"); 
     ArgumentNotNull(ef, "ef"); 

     _code = code; 
     _typeMapper = typeMapper; 
     _ef = ef; 
    } 

    public string Property(EdmProperty edmProperty) 
    { 
     return string.Format(
      CultureInfo.InvariantCulture, 
      "[DataMember()] {0} {1} {2} {3} {{get; {4}set; }}", 
      Accessibility.ForProperty(edmProperty), 
      _typeMapper.GetTypeName(edmProperty.TypeUsage), 
      _code.Escape(edmProperty), 
      _code.SpaceAfter(Accessibility.ForGetter(edmProperty)), 
      _code.SpaceAfter(Accessibility.ForSetter(edmProperty))); 
    } 


    public string EntityClassOpening(EntityType entity) 
    { 
      return string.Format(
       CultureInfo.InvariantCulture, 
       "[DataContract()]\r\n{0} {1}partial class {2}Dto{3}", 
       Accessibility.ForType(entity), 
       _code.SpaceAfter(_code.AbstractOption(entity)), 
       _code.Escape(entity), 
       _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType))); 
    } 


    public string UsingDirectives(bool inHeader, bool includeCollections = true) 
    { 
     return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion()) 
      ? string.Format(
       CultureInfo.InvariantCulture, 
       "{0}using System;" +Environment.NewLine+ 
       "using System.Runtime.Serialization;{1}" + 
       "{2}", 
       inHeader ? Environment.NewLine : "", 
       includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "", 
       inHeader ? "" : Environment.NewLine) 
      : ""; 
    } 
} 

public class TypeMapper 
{ 
    private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName"; 

    private readonly System.Collections.IList _errors; 
    private readonly CodeGenerationTools _code; 
    private readonly MetadataTools _ef; 

    public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors) 
    { 
     ArgumentNotNull(code, "code"); 
     ArgumentNotNull(ef, "ef"); 
     ArgumentNotNull(errors, "errors"); 

     _code = code; 
     _ef = ef; 
     _errors = errors; 
    } 

    public string GetTypeName(TypeUsage typeUsage) 
    { 
     return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), null); 
    } 

    public string GetTypeName(EdmType edmType) 
    { 
     return GetTypeName(edmType, null, null); 
    } 

    public string GetTypeName(TypeUsage typeUsage, string modelNamespace) 
    { 
     return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace); 
    } 

    public string GetTypeName(EdmType edmType, string modelNamespace) 
    { 
     return GetTypeName(edmType, null, modelNamespace); 
    } 

    public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace) 
    { 
     if (edmType == null) 
     { 
      return null; 
     } 

     var collectionType = edmType as CollectionType; 
     if (collectionType != null) 
     { 
      return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace)); 
     } 

     var typeName = _code.Escape(edmType.MetadataProperties 
           .Where(p => p.Name == ExternalTypeNameAttributeName) 
           .Select(p => (string)p.Value) 
           .FirstOrDefault()) 
      ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ? 
       _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) : 
       _code.Escape(edmType)); 

     if (edmType is StructuralType) 
     { 
      return typeName; 
     } 

     if (edmType is SimpleType) 
     { 
      var clrType = UnderlyingClrType(edmType); 
      if (!IsEnumType(edmType)) 
      { 
       typeName = _code.Escape(clrType); 
      } 

      return clrType.IsValueType && isNullable == true ? 
       String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) : 
       typeName; 
     } 

     throw new ArgumentException("edmType"); 
    } 

    public Type UnderlyingClrType(EdmType edmType) 
    { 
     ArgumentNotNull(edmType, "edmType"); 

     var primitiveType = edmType as PrimitiveType; 
     if (primitiveType != null) 
     { 
      return primitiveType.ClrEquivalentType; 
     } 

     if (IsEnumType(edmType)) 
     { 
      return GetEnumUnderlyingType(edmType).ClrEquivalentType; 
     } 

     return typeof(object); 
    } 



    public bool IsEnumType(GlobalItem edmType) 
    { 
     ArgumentNotNull(edmType, "edmType"); 

     return edmType.GetType().Name == "EnumType"; 
    } 

    public PrimitiveType GetEnumUnderlyingType(EdmType enumType) 
    { 
     ArgumentNotNull(enumType, "enumType"); 

     return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null); 
    } 

    public string CreateLiteral(object value) 
    { 
     if (value == null || value.GetType() != typeof(TimeSpan)) 
     { 
      return _code.CreateLiteral(value); 
     } 

     return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks); 
    } 

    public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile) 
    { 
     ArgumentNotNull(types, "types"); 
     ArgumentNotNull(sourceFile, "sourceFile"); 

     var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); 
     if (types.Any(item => !hash.Add(item))) 
     { 
      _errors.Add(
       new CompilerError(sourceFile, -1, -1, "6023", 
        String.Format(CultureInfo.CurrentCulture, GetResourceString("Template_CaseInsensitiveTypeConflict")))); 
      return false; 
     } 
     return true; 
    } 



    public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType 
    { 
     return itemCollection 
      .OfType<T>() 
      .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName)) 
      .OrderBy(i => i.Name); 
    } 

    public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection) 
    { 
     return itemCollection 
      .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i)) 
      .Select(g => GetGlobalItemName(g)); 
    } 

    public string GetGlobalItemName(GlobalItem item) 
    { 
     if (item is EdmType) 
     { 
      return ((EdmType)item).Name; 
     } 
     else 
     { 
      return ((EntityContainer)item).Name; 
     } 
    } 

    public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type) 
    { 
     return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); 
    } 

    public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type) 
    { 
     return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); 
    } 
} 

public class EdmMetadataLoader 
{ 
    private readonly IDynamicHost _host; 
    private readonly System.Collections.IList _errors; 

    public EdmMetadataLoader(IDynamicHost host, System.Collections.IList errors) 
    { 
     ArgumentNotNull(host, "host"); 
     ArgumentNotNull(errors, "errors"); 

     _host = host; 
     _errors = errors; 
    } 

    public IEnumerable<GlobalItem> CreateEdmItemCollection(string sourcePath) 
    { 
     ArgumentNotNull(sourcePath, "sourcePath"); 

     if (!ValidateInputPath(sourcePath)) 
     { 
      return new EdmItemCollection(); 
     } 

     var schemaElement = LoadRootElement(_host.ResolvePath(sourcePath)); 
     if (schemaElement != null) 
     { 
      using (var reader = schemaElement.CreateReader()) 
      { 
       IList<EdmSchemaError> errors; 
       var itemCollection = MetadataItemCollectionFactory.CreateEdmItemCollection(new[] { reader }, out errors); 

       ProcessErrors(errors, sourcePath); 

       return itemCollection; 
      } 
     } 
     return new EdmItemCollection(); 
    } 

    public string GetModelNamespace(string sourcePath) 
    { 
     ArgumentNotNull(sourcePath, "sourcePath"); 

     if (!ValidateInputPath(sourcePath)) 
     { 
      return string.Empty; 
     } 

     var model = LoadRootElement(_host.ResolvePath(sourcePath)); 
     if (model == null) 
     { 
      return string.Empty; 
     } 

     var attribute = model.Attribute("Namespace"); 
     return attribute != null ? attribute.Value : ""; 
    } 

    private bool ValidateInputPath(string sourcePath) 
    { 
     if (sourcePath == "$" + "edmxInputFile" + "$") 
     { 
      _errors.Add(
       new CompilerError(_host.TemplateFile ?? sourcePath, 0, 0, string.Empty, 
        GetResourceString("Template_ReplaceVsItemTemplateToken"))); 
      return false; 
     } 

     return true; 
    } 

    public XElement LoadRootElement(string sourcePath) 
    { 
     ArgumentNotNull(sourcePath, "sourcePath"); 

     var root = XElement.Load(sourcePath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); 
     return root.Elements() 
      .Where(e => e.Name.LocalName == "Runtime") 
      .Elements() 
      .Where(e => e.Name.LocalName == "ConceptualModels") 
      .Elements() 
      .Where(e => e.Name.LocalName == "Schema") 
      .FirstOrDefault() 
       ?? root; 
    } 

    private void ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath) 
    { 
     foreach (var error in errors) 
     { 
      _errors.Add(
       new CompilerError(
        error.SchemaLocation ?? sourceFilePath, 
        error.Line, 
        error.Column, 
        error.ErrorCode.ToString(CultureInfo.InvariantCulture), 
        error.Message) 
       { 
        IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning 
       }); 
     } 
    } 


} 

public static void ArgumentNotNull<T>(T arg, string name) where T : class 
{ 
    if (arg == null) 
    { 
     throw new ArgumentNullException(name); 
    } 
} 

private static readonly Lazy<System.Resources.ResourceManager> ResourceManager = 
    new Lazy<System.Resources.ResourceManager>(
     () => new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(MetadataItemCollectionFactory).Assembly), true); 

public static string GetResourceString(string resourceName) 
{ 
    ArgumentNotNull(resourceName, "resourceName"); 

    return ResourceManager.Value.GetString(resourceName, null); 
} 

#>