用于EF 4实体模型的DTO Generator

时间:2011-07-06 14:19:52

标签: c# visual-studio code-generation t4 dto

是否可以编写t4模板(或者如果它已经存在),它将能够根据* .edmx文件中的数据生成DTO类?

我必须为当前项目编写DTO类,这个过程有点令人讨厌。

我想要获得的是获得DTO类,它们将标量属性定义为简单的自动属性,导航参数作为其他DTO类的封装实例。

示例:

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

    public string Description { get; set; }        

    public OtherClassDTO SomeProperty {get;set;}

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

这是一个很好的起点,更令人满意的可能是以下示例:

/// <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; }

}

如您所见,这是一个展平DTO,代表复合结构,可以通过ValueInjector SameNameFlat / UnFlat注入注入实体。

这是最终目标,但任何建议都会受到赞赏。

5 个答案:

答案 0 :(得分:14)

我最近在CodePlex上发布了一个名为EntitiesToDTOs的实体框架DTO生成器,它是免费的开源软件,它用作Visual Studio 2010和2012的AddIn。我认为它对您有所帮助。

转到http://entitiestodtos.codeplex.com进行下载,让我知道您的想法;)

答案 1 :(得分:2)

最后我能够为C#语言创建一个项目模板,它将在VS 2010中为您创建DTO课程。请点击链接: http://www.stepupframeworks.com/Home/products/entity-to-dto-creator/

答案 2 :(得分:1)

答案 3 :(得分:0)

它似乎比我想象的要容易得多。如果有人感兴趣,有一个t4模板用于通过实体树进行递归扫描:

<#@ 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);
                    }
                }
            }
        }
    }

#>

这个模板产生的代码就像我问题的最后一个例子一样。

答案 4 :(得分:0)

以下是Visual Studio 2012 T4模板的更新,用于在现有EDMX文件的基础上生成简单的DTO对象。它会跳过导航属性,只生成简单的属性。

使用AutoMapper我能够将POCO数据复制到DTO对象中。 这些是可序列化的,可以作为XML传输。在目标站点重建对象时,可以将它们附加到dbContext并调用DetectChanges()。 之后的参考资料将被修复。

<#@ 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);
}

#>