关于.NET T4模板使用简单说明>>第二部分 数据实体话模型中相关的类的介绍

本文档所有内容非官方解释注解,内容多数为个人理解,紧供个人参考

T4模板内部引用(使用)类

通用模板模板中使用到了几个重要的类型

由于没有说明和只能提示摸索起来比较麻烦,故将这几个用到的类型单独提炼出来供以后查询。

检查原始创建的模板内容并将这些添加上

<#@ template language="C#" debug="true" hostspecific="true"#>
<#@ include file="EF6.Utility.CS.ttinclude"#>
<#@ output extension=".cs"#>
<#
// 数据实体路径,相对当前.tt模板的路径
const string inputFile = @"../MODEL/OuOA.edmx";
// 文字转换对象,由于传入的是this而后面的this对象转化为 DynamicTextTransformation 类型的对象
// 基本上可以猜测 .tt 文件其实是继承于 或者实现了 DynamicTextTransformation  类型的对象
var textTransform = DynamicTextTransformation.Create(this);
// 代码生成控制帮助类
var code = new CodeGenerationTools(this);
// 实体信息帮助类?
var ef = new MetadataTools(this);
// 文件管理类
var	fileManager = EntityFrameworkTemplateFileManager.Create(this);
// 实例类型集合
var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
#>
<#="Hello T4" #>
<#+
public static void ArgumentNotNull<T>(T arg, string name) where T : class
{
    if (arg == null)
    {
        throw new ArgumentNullException(name);
    }
}
#>

DynamicTextTransformation 对象


/// <summary>
/// Responsible creating an instance that can be passed
/// to helper classes that need to access the TextTransformation
/// members.  It accesses member by name and signature rather than
/// by type.  This is necessary when the
/// template is being used in Preprocessed mode
/// and there is no common known type that can be
/// passed instead
/// </summary>
public class DynamicTextTransformation
{
    private object _instance;
    IDynamicHost _dynamicHost;

    private readonly MethodInfo _write;
    private readonly MethodInfo _writeLine;
    private readonly PropertyInfo _generationEnvironment;
    private readonly PropertyInfo _errors;
    private readonly PropertyInfo _host;

    /// <summary>
    /// Creates an instance of the DynamicTextTransformation class around the passed in
    /// TextTransformation shapped instance passed in, or if the passed in instance
    /// already is a DynamicTextTransformation, it casts it and sends it back.
    /// </summary>
    public static DynamicTextTransformation Create(object instance)
    {
        if (instance == null)
        {
            throw new ArgumentNullException("instance");
        }

        DynamicTextTransformation textTransformation = instance as DynamicTextTransformation;
        if (textTransformation != null)
        {
            return textTransformation;
        }

        return new DynamicTextTransformation(instance);
    }

    private DynamicTextTransformation(object instance)
    {
        _instance = instance;
        Type type = _instance.GetType();
        _write = type.GetMethod("Write", new Type[] { typeof(string) });
        _writeLine = type.GetMethod("WriteLine", new Type[] { typeof(string) });
        _generationEnvironment = type.GetProperty("GenerationEnvironment", BindingFlags.Instance | BindingFlags.NonPublic);
        _host = type.GetProperty("Host");
        _errors = type.GetProperty("Errors");
    }

    /// <summary>
    /// Gets the value of the wrapped TextTranformation instance's GenerationEnvironment property
    /// </summary>
    public StringBuilder GenerationEnvironment { get { return (StringBuilder)_generationEnvironment.GetValue(_instance, null); } }

    /// <summary>
    /// Gets the value of the wrapped TextTranformation instance's Errors property
    /// </summary>
    public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { return (System.CodeDom.Compiler.CompilerErrorCollection)_errors.GetValue(_instance, null); } }

    /// <summary>
    /// Calls the wrapped TextTranformation instance's Write method.
    /// </summary>
    public void Write(string text)
    {
        _write.Invoke(_instance, new object[] { text });
    }

    /// <summary>
    /// Calls the wrapped TextTranformation instance's WriteLine method.
    /// </summary>
    public void WriteLine(string text)
    {
        _writeLine.Invoke(_instance, new object[] { text });
    }

    /// <summary>
    /// Gets the value of the wrapped TextTranformation instance's Host property
    /// if available (shows up when hostspecific is set to true in the template directive) and returns
    /// the appropriate implementation of IDynamicHost
    /// </summary>
    public IDynamicHost Host
    {
        get
        {
            if (_dynamicHost == null)
            {
                if(_host == null)
                {
                    _dynamicHost = new NullHost();
                }
                else
                {
                    _dynamicHost = new DynamicHost(_host.GetValue(_instance, null));
                }
            }
            return _dynamicHost;
        }
    }
}


CodeGenerationTools

/// <summary>
/// Responsible for helping to create source code that is
/// correctly formated and functional
/// </summary>
public class CodeGenerationTools
{
    private readonly DynamicTextTransformation _textTransformation;
    private readonly CSharpCodeProvider _code;
    private readonly MetadataTools _ef;

    /// <summary>
    /// Initializes a new CodeGenerationTools object with the TextTransformation (T4 generated class)
    /// that is currently running
    /// </summary>
    public CodeGenerationTools(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
        _code = new CSharpCodeProvider();
        _ef = new MetadataTools(_textTransformation);
        FullyQualifySystemTypes = false;
        CamelCaseFields = true;
    }

    /// <summary>
    /// When true, all types that are not being generated
    /// are fully qualified to keep them from conflicting with
    /// types that are being generated. Useful when you have
    /// something like a type being generated named System.
    ///
    /// Default is false.
    /// </summary>
    public bool FullyQualifySystemTypes { get; set; }

    /// <summary>
    /// When true, the field names are Camel Cased,
    /// otherwise they will preserve the case they
    /// start with.
    ///
    /// Default is true.
    /// </summary>
    public bool CamelCaseFields { get; set; }

    /// <summary>
    /// Returns the NamespaceName suggested by VS if running inside VS.  Otherwise, returns
    /// null.
    /// </summary>
    public string VsNamespaceSuggestion()
    {
        string suggestion = _textTransformation.Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");
        if (String.IsNullOrEmpty(suggestion))
        {
            return null;
        }

        return suggestion;
    }

    /// <summary>
    /// Returns a string that is safe for use as an identifier in C#.
    /// Keywords are escaped.
    /// </summary>
    public string Escape(string name)
    {
        if (name == null)
        {
            return null;
        }

        return _code.CreateEscapedIdentifier(name);
    }

    /// <summary>
    /// Returns the name of the TypeUsage's EdmType that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(TypeUsage typeUsage)
    {
        if (typeUsage == null)
        {
            return null;
        }

        if (typeUsage.EdmType is ComplexType ||
            typeUsage.EdmType is EntityType)
        {
            return Escape(typeUsage.EdmType.Name);
        }
        else if (typeUsage.EdmType is SimpleType)
        {
            Type clrType = _ef.UnderlyingClrType(typeUsage.EdmType);
            string typeName = typeUsage.EdmType is EnumType ? Escape(typeUsage.EdmType.Name) : Escape(clrType);
            if (clrType.IsValueType && _ef.IsNullable(typeUsage))
            {
                return String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName);
            }

            return typeName;
        }
        else if (typeUsage.EdmType is CollectionType)
        {
            return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", Escape(((CollectionType)typeUsage.EdmType).TypeUsage));
        }

        throw new ArgumentException("typeUsage");
    }

    /// <summary>
    /// Returns the name of the EdmMember that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        return Escape(member.Name);
    }

    /// <summary>
    /// Returns the name of the EdmType that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EdmType type)
    {
        if (type == null)
        {
            return null;
        }

        return Escape(type.Name);
    }

    /// <summary>
    /// Returns the name of the EdmFunction that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EdmFunction function)
    {
        if (function == null)
        {
            return null;
        }

        return Escape(function.Name);
    }

    /// <summary>
    /// Returns the name of the EnumMember that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EnumMember member)
    {
        if (member == null)
        {
            return null;
        }

        return Escape(member.Name);
    }

    /// <summary>
    /// Returns the name of the EntityContainer that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EntityContainer container)
    {
        if (container == null)
        {
            return null;
        }

        return Escape(container.Name);
    }

    /// <summary>
    /// Returns the name of the EntitySet that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EntitySet set)
    {
        if (set == null)
        {
            return null;
        }

        return Escape(set.Name);
    }

    /// <summary>
    /// Returns the name of the StructuralType that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(StructuralType type)
    {
        if (type == null)
        {
            return null;
        }

        return Escape(type.Name);
    }

    /// <summary>
    /// Returns the NamespaceName with each segment safe to
    /// use as an identifier.
    /// </summary>
    public string EscapeNamespace(string namespaceName)
    {
        if (String.IsNullOrEmpty(namespaceName))
        {
            return namespaceName;
        }

        string[] parts = namespaceName.Split('.');
        namespaceName = String.Empty;
        foreach (string part in parts)
        {
            if (namespaceName != String.Empty)
            {
                namespaceName += ".";
            }

            namespaceName += Escape(part);
        }

        return namespaceName;
    }

    /// <summary>
    /// Returns the name of the EdmMember formatted for
    /// use as a field identifier.
    ///
    /// This method changes behavior based on the CamelCaseFields
    /// setting.
    /// </summary>
    public string FieldName(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        return FieldName(member.Name);
    }

    /// <summary>
    /// Returns the name of the EntitySet formatted for
    /// use as a field identifier.
    ///
    /// This method changes behavior based on the CamelCaseFields
    /// setting.
    /// </summary>
    public string FieldName(EntitySet set)
    {
        if (set == null)
        {
            return null;
        }

        return FieldName(set.Name);

    }

    private string FieldName(string name)
    {
        if (CamelCaseFields)
        {
            return "_" + CamelCase(name);
        }
        else
        {
            return "_" + name;
        }
    }

    /// <summary>
    /// Returns the name of the Type object formatted for
    /// use in source code.
    ///
    /// This method changes behavior based on the FullyQualifySystemTypes
    /// setting.
    /// </summary>
    public string Escape(Type clrType)
    {
        return Escape(clrType, FullyQualifySystemTypes);
    }

    /// <summary>
    /// Returns the name of the Type object formatted for
    /// use in source code.
    /// </summary>
    public string Escape(Type clrType, bool fullyQualifySystemTypes)
    {
        if(clrType == null)
        {
            return null;
        }

        string typeName;
        if (fullyQualifySystemTypes)
        {
            typeName = "global::" + clrType.FullName;
        }
        else
        {
            typeName = _code.GetTypeOutput(new CodeTypeReference(clrType));
        }
        return typeName;
    }

    /// <summary>
    /// Returns the abstract option if the entity is Abstract, otherwise returns String.Empty
    /// </summary>
    public string AbstractOption(EntityType entity)
    {
        if (entity.Abstract)
        {
            return "abstract";
        }
        return String.Empty;
    }

    /// <summary>
    /// Returns the passed in identifier with the first letter changed to lowercase
    /// </summary>
    public string CamelCase(string identifier)
    {
        if (String.IsNullOrEmpty(identifier))
        {
            return identifier;
        }

        if (identifier.Length == 1)
        {
            return identifier[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
        }

        return identifier[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant() + identifier.Substring(1);
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with a single space concatenated on the end.
    /// </summary>
    public string SpaceAfter(string value)
    {
        return StringAfter(value, " ");
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with a single space concatenated on the end.
    /// </summary>
    public string SpaceBefore(string value)
    {
        return StringBefore(" ", value);
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with append concatenated on the end.
    /// </summary>
    public string StringAfter(string value, string append)
    {
        if (String.IsNullOrEmpty(value))
        {
            return String.Empty;
        }

            return value + append;
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with prepend concatenated on the front.
    /// </summary>
    public string StringBefore(string prepend, string value)
    {
        if (String.IsNullOrEmpty(value))
        {
            return String.Empty;
        }

        return prepend + value;
    }

    /// <summary>
    /// Returns false and shows an error if the supplied type names aren't case-insensitively unique,
    /// otherwise returns true.
    /// </summary>
    public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
    {
        return VerifyCaseInsensitiveUniqueness(types, t => string.Format(CultureInfo.CurrentCulture, GetResourceString("Template_CaseInsensitiveTypeConflict"), t), sourceFile);
    }

    /// <summary>
    /// Returns false and shows an error if the supplied strings aren't case-insensitively unique,
    /// otherwise returns true.
    /// </summary>
    private bool VerifyCaseInsensitiveUniqueness(IEnumerable<string> items, Func<string, string> formatMessage, string sourceFile)
    {
        HashSet<string> hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
        foreach (string item in items)
        {
            if (!hash.Add(item))
            {
                _textTransformation.Errors.Add(new System.CodeDom.Compiler.CompilerError(sourceFile, -1, -1, "6023", formatMessage(item)));
                return false;
            }
        }
        return true;
    }

    /// <summary>
    /// Returns the names of the items in the supplied collection that correspond to O-Space types.
    /// </summary>
    public IEnumerable<string> GetAllGlobalItems(EdmItemCollection itemCollection)
    {
        return itemCollection.GetItems<GlobalItem>().Where(i => i is EntityType || i is ComplexType || i is EnumType || i is EntityContainer).Select(g => GetGlobalItemName(g));
    }

    /// <summary>
    /// Returns the name of the supplied GlobalItem.
    /// </summary>
    public string GetGlobalItemName(GlobalItem item)
    {
        if (item is EdmType)
        {
            return ((EdmType)item).Name;
        }
        else
        {
            return ((EntityContainer)item).Name;
        }
    }

    /// <summary>
    /// Retuns as full of a name as possible, if a namespace is provided
    /// the namespace and name are combined with a period, otherwise just
    /// the name is returned.
    /// </summary>
    public string CreateFullName(string namespaceName, string name)
    {
        if (String.IsNullOrEmpty(namespaceName))
        {
            return name;
        }

        return namespaceName + "." + name;
    }

    /// <summary>
    /// Retuns a literal representing the supplied value.
    /// </summary>
    public string CreateLiteral(object value)
    {
        if (value == null)
        {
            return string.Empty;
        }

        Type type = value.GetType();
        if (type.IsEnum)
        {
            return type.FullName + "." + value.ToString();
        }
        if (type == typeof(Guid))
        {
            return string.Format(CultureInfo.InvariantCulture, "new Guid(\"{0}\")",
                                 ((Guid)value).ToString("D", CultureInfo.InvariantCulture));
        }
        else if (type == typeof(DateTime))
        {
            return string.Format(CultureInfo.InvariantCulture, "new DateTime({0}, DateTimeKind.Unspecified)",
                                 ((DateTime)value).Ticks);
        }
        else if (type == typeof(byte[]))
        {
            var arrayInit = string.Join(", ", ((byte[])value).Select(b => b.ToString(CultureInfo.InvariantCulture)).ToArray());
            return string.Format(CultureInfo.InvariantCulture, "new Byte[] {{{0}}}", arrayInit);
        }
        else if (type == typeof(DateTimeOffset))
        {
            var dto = (DateTimeOffset)value;
            return string.Format(CultureInfo.InvariantCulture, "new DateTimeOffset({0}, new TimeSpan({1}))",
                                 dto.Ticks, dto.Offset.Ticks);
        }
        else if (type == typeof(TimeSpan))
        {
            return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})",
                                 ((TimeSpan)value).Ticks);
        }

        var expression = new CodePrimitiveExpression(value);
        var writer = new StringWriter();
        CSharpCodeProvider code = new CSharpCodeProvider();
        code.GenerateCodeFromExpression(expression, writer, new CodeGeneratorOptions());
        return writer.ToString();
    }

    /// <summary>
    /// Returns a resource string from the System.Data.Entity.Design assembly.
    /// </summary>
    public static string GetResourceString(string resourceName, CultureInfo culture = null)
    {
        if(_resourceManager == null)
        {
            _resourceManager = Microsoft.Data.Entity.Design.Templates.TemplateResources.ResourceManager;
        }

        return _resourceManager.GetString(resourceName, culture);
    }
    static System.Resources.ResourceManager _resourceManager;

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

    /// <summary>
    /// Gets the entity, complex, or enum types for which code should be generated from the given item collection.
    /// Any types for which an ExternalTypeName annotation has been applied in the conceptual model
    /// metadata (CSDL) are filtered out of the returned list.
    /// </summary>
    /// <typeparam name="T">The type of item to return.</typeparam>
    /// <param name="itemCollection">The item collection to look in.</param>
    /// <returns>The items to generate.</returns>
    public IEnumerable<T> GetItemsToGenerate<T>(ItemCollection itemCollection) where T: GlobalItem
    {
        return itemCollection.GetItems<T>().Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName));
    }

    /// <summary>
    /// Returns the escaped type name to use for the given usage of a c-space type in o-space. This might be
    /// an external type name if the ExternalTypeName annotation has been specified in the
    /// conceptual model metadata (CSDL).
    /// </summary>
    /// <param name="typeUsage">The c-space type usage to get a name for.</param>
    /// <returns>The type name to use.</returns>
    public string GetTypeName(TypeUsage typeUsage)
    {
        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
    }

    /// <summary>
    /// Returns the escaped type name to use for the given c-space type in o-space. This might be
    /// an external type name if the ExternalTypeName annotation has been specified in the
    /// conceptual model metadata (CSDL).
    /// </summary>
    /// <param name="edmType">The c-space type to get a name for.</param>
    /// <returns>The type name to use.</returns>
    public string GetTypeName(EdmType edmType)
    {
        return GetTypeName(edmType, isNullable: null, modelNamespace: null);
    }

    /// <summary>
    /// Returns the escaped type name to use for the given usage of an c-space type in o-space. This might be
    /// an external type name if the ExternalTypeName annotation has been specified in the
    /// conceptual model metadata (CSDL).
    /// </summary>
    /// <param name="typeUsage">The c-space type usage to get a name for.</param>
    /// <param name="modelNamespace">If not null and the type's namespace does not match this namespace, then a
    /// fully qualified name will be returned.</param>
    /// <returns>The type name to use.</returns>
    public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
    {
        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
    }

    /// <summary>
    /// Returns the escaped type name to use for the given c-space type in o-space. This might be
    /// an external type name if the ExternalTypeName annotation has been specified in the
    /// conceptual model metadata (CSDL).
    /// </summary>
    /// <param name="edmType">The c-space type to get a name for.</param>
    /// <param name="modelNamespace">If not null and the type's namespace does not match this namespace, then a
    /// fully qualified name will be returned.</param>
    /// <returns>The type name to use.</returns>
    public string GetTypeName(EdmType edmType, string modelNamespace)
    {
        return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
    }

    /// <summary>
    /// Returns the escaped type name to use for the given c-space type in o-space. This might be
    /// an external type name if the ExternalTypeName annotation has been specified in the
    /// conceptual model metadata (CSDL).
    /// </summary>
    /// <param name="edmType">The c-space type to get a name for.</param>
    /// <param name="isNullable">Set this to true for nullable usage of this type.</param>
    /// <param name="modelNamespace">If not null and the type's namespace does not match this namespace, then a
    /// fully qualified name will be returned.</param>
    /// <returns>The type name to use.</returns>
    private 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));
        }

        // Try to get an external type name, and if that is null, then try to get escape the name from metadata,
        // possibly namespace-qualifying it.
        var typeName = Escape(edmType.MetadataProperties
                              .Where(p => p.Name == ExternalTypeNameAttributeName)
                              .Select(p => (string)p.Value)
                              .FirstOrDefault())
            ??
            (modelNamespace != null && edmType.NamespaceName != modelNamespace ?
             CreateFullName(EscapeNamespace(edmType.NamespaceName), Escape(edmType)) :
             Escape(edmType));

        if (edmType is StructuralType)
        {
            return typeName;
        }

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

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

        throw new ArgumentException("typeUsage");
    }
}

MetadataTools

/// <summary>
/// Responsible for making the Entity Framework Metadata more
/// accessible for code generation.
/// </summary>
public class MetadataTools
{
    private readonly DynamicTextTransformation _textTransformation;

    /// <summary>
    /// Initializes an MetadataTools Instance  with the
    /// TextTransformation (T4 generated class) that is currently running
    /// </summary>
    public MetadataTools(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
    }

    /// <summary>
    /// This method returns the underlying CLR type of the o-space type corresponding to the supplied <paramref name="typeUsage"/>
    /// Note that for an enum type this means that the type backing the enum will be returned, not the enum type itself.
    /// </summary>
    public Type ClrType(TypeUsage typeUsage)
    {
        return UnderlyingClrType(typeUsage.EdmType);
    }

    /// <summary>
    /// This method returns the underlying CLR type given the c-space type.
    /// Note that for an enum type this means that the type backing the enum will be returned, not the enum type itself.
    /// </summary>
    public Type UnderlyingClrType(EdmType edmType)
    {
        var primitiveType = edmType as PrimitiveType;
        if (primitiveType != null)
        {
            return primitiveType.ClrEquivalentType;
        }

        var enumType = edmType as EnumType;
        if (enumType != null)
        {
            return enumType.UnderlyingType.ClrEquivalentType;
        }

        return typeof(object);
    }

    /// <summary>
    /// True if the EdmProperty is a key of its DeclaringType, False otherwise.
    /// </summary>
    public bool IsKey(EdmProperty property)
    {
        if (property != null && property.DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
        {
            return ((EntityType)property.DeclaringType).KeyMembers.Contains(property);
        }

        return false;
    }

    /// <summary>
    /// True if the EdmProperty TypeUsage is Nullable, False otherwise.
    /// </summary>
    public bool IsNullable(EdmProperty property)
    {
        return property != null && IsNullable(property.TypeUsage);
    }

    /// <summary>
    /// True if the TypeUsage is Nullable, False otherwise.
    /// </summary>
    public bool IsNullable(TypeUsage typeUsage)
    {
        Facet nullableFacet = null;
        if (typeUsage != null &&
            typeUsage.Facets.TryGetValue("Nullable", true, out nullableFacet))
        {
            return (bool)nullableFacet.Value;
        }

        return false;
    }

    /// <summary>
    /// If the passed in TypeUsage represents a collection this method returns final element
    /// type of the collection, otherwise it returns the value passed in.
    /// </summary>
    public TypeUsage GetElementType(TypeUsage typeUsage)
    {
        if (typeUsage == null)
        {
            return null;
        }

        if (typeUsage.EdmType is CollectionType)
        {
            return GetElementType(((CollectionType)typeUsage.EdmType).TypeUsage);
        }
        else
        {
            return typeUsage;
        }
    }

    /// <summary>
    /// Returns the NavigationProperty that is the other end of the same association set if it is
    /// available, otherwise it returns null.
    /// </summary>
    public NavigationProperty Inverse(NavigationProperty navProperty)
    {
        if(navProperty == null)
        {
            return null;
        }

        EntityType toEntity = navProperty.ToEndMember.GetEntityType();
        return toEntity.NavigationProperties
            .SingleOrDefault(n => Object.ReferenceEquals(n.RelationshipType, navProperty.RelationshipType) && !Object.ReferenceEquals(n, navProperty));
    }

    /// <summary>
    /// Given a property on the dependent end of a referential constraint, returns the corresponding property on the principal end.
    /// Requires: The association has a referential constraint, and the specified dependentProperty is one of the properties on the dependent end.
    /// </summary>
    public EdmProperty GetCorrespondingPrincipalProperty(NavigationProperty navProperty, EdmProperty dependentProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        if (dependentProperty == null)
        {
            throw new ArgumentNullException("dependentProperty");
        }

        ReadOnlyMetadataCollection<EdmProperty> fromProperties = GetPrincipalProperties(navProperty);
        ReadOnlyMetadataCollection<EdmProperty> toProperties = GetDependentProperties(navProperty);
        return fromProperties[toProperties.IndexOf(dependentProperty)];
    }

    /// <summary>
    /// Given a property on the principal end of a referential constraint, returns the corresponding property on the dependent end.
    /// Requires: The association has a referential constraint, and the specified principalProperty is one of the properties on the principal end.
    /// </summary>
    public EdmProperty GetCorrespondingDependentProperty(NavigationProperty navProperty, EdmProperty principalProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        if (principalProperty == null)
        {
            throw new ArgumentNullException("principalProperty");
        }

        ReadOnlyMetadataCollection<EdmProperty> fromProperties = GetPrincipalProperties(navProperty);
        ReadOnlyMetadataCollection<EdmProperty> toProperties = GetDependentProperties(navProperty);
        return toProperties[fromProperties.IndexOf(principalProperty)];
    }

    /// <summary>
    /// Gets the collection of properties that are on the principal end of a referential constraint for the specified navigation property.
    /// Requires: The association has a referential constraint.
    /// </summary>
    public ReadOnlyMetadataCollection<EdmProperty> GetPrincipalProperties(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].FromProperties;
    }

    /// <summary>
    /// Gets the collection of properties that are on the dependent end of a referential constraint for the specified navigation property.
    /// Requires: The association has a referential constraint.
    /// </summary>
    public ReadOnlyMetadataCollection<EdmProperty> GetDependentProperties(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].ToProperties;
    }

    /// <summary>
    /// True if this entity type requires the HandleCascadeDelete method defined and the method has
    /// not been defined on any base type
    /// </summary>
    public bool NeedsHandleCascadeDeleteMethod(ItemCollection itemCollection, EntityType entity)
    {
        bool needsMethod = ContainsCascadeDeleteAssociation(itemCollection, entity);
        // Check to make sure no base types have already declared this method
        EntityType baseType = entity.BaseType as EntityType;
        while(needsMethod && baseType != null)
        {
            needsMethod = !ContainsCascadeDeleteAssociation(itemCollection, baseType);
            baseType = baseType.BaseType as EntityType;
        }
        return needsMethod;
    }

    /// <summary>
    /// True if this entity type participates in any relationships where the other end has an OnDelete
    /// cascade delete defined, or if it is the dependent in any identifying relationships
    /// </summary>
    private bool ContainsCascadeDeleteAssociation(ItemCollection itemCollection, EntityType entity)
    {
        return itemCollection.GetItems<AssociationType>().Where(a =>
                ((RefType)a.AssociationEndMembers[0].TypeUsage.EdmType).ElementType == entity && IsCascadeDeletePrincipal(a.AssociationEndMembers[1]) ||
                ((RefType)a.AssociationEndMembers[1].TypeUsage.EdmType).ElementType == entity && IsCascadeDeletePrincipal(a.AssociationEndMembers[0])).Any();
    }

    /// <summary>
    /// True if the source end of the specified navigation property is the principal in an identifying relationship.
    /// or if the source end has cascade delete defined.
    /// </summary>
    public bool IsCascadeDeletePrincipal(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        return IsCascadeDeletePrincipal((AssociationEndMember)navProperty.FromEndMember);
    }

    /// <summary>
    /// True if the specified association end is the principal in an identifying relationship.
    /// or if the association end has cascade delete defined.
    /// </summary>
    public bool IsCascadeDeletePrincipal(AssociationEndMember associationEnd)
    {
        if (associationEnd == null)
        {
            throw new ArgumentNullException("associationEnd");
        }

        return associationEnd.DeleteBehavior == OperationAction.Cascade || IsPrincipalEndOfIdentifyingRelationship(associationEnd);
    }

    /// <summary>
    /// True if the specified association end is the principal end in an identifying relationship.
    /// In order to be an identifying relationship, the association must have a referential constraint where all of the dependent properties are part of the dependent type's primary key.
    /// </summary>
    public bool IsPrincipalEndOfIdentifyingRelationship(AssociationEndMember associationEnd)
    {
        if (associationEnd == null)
        {
            throw new ArgumentNullException("associationEnd");
        }

        ReferentialConstraint refConstraint = ((AssociationType)associationEnd.DeclaringType).ReferentialConstraints.Where(rc => rc.FromRole == associationEnd).SingleOrDefault();
        if (refConstraint != null)
        {
            EntityType entity = refConstraint.ToRole.GetEntityType();
            return !refConstraint.ToProperties.Where(tp => !entity.KeyMembers.Contains(tp)).Any();
        }
        return false;
    }

    /// <summary>
    /// True if the specified association type is an identifying relationship.
    /// In order to be an identifying relationship, the association must have a referential constraint where all of the dependent properties are part of the dependent type's primary key.
    /// </summary>
    public bool IsIdentifyingRelationship(AssociationType association)
    {
        if (association == null)
        {
            throw new ArgumentNullException("association");
        }

        return IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[0]) || IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[1]);
    }

    /// <summary>
    /// requires: firstType is not null
    /// effects: if secondType is among the base types of the firstType, return true,
    /// otherwise returns false.
    /// when firstType is same as the secondType, return false.
    /// </summary>
    public bool IsSubtypeOf(EdmType firstType, EdmType secondType)
    {
        if (secondType == null)
        {
            return false;
        }

        // walk up firstType hierarchy list
        for (EdmType t = firstType.BaseType; t != null; t = t.BaseType)
        {
            if (t == secondType)
                return true;
        }
        return false;
    }

    /// <summary>
    /// Returns the subtype of the EntityType in the current itemCollection
    /// </summary>
    public IEnumerable<EntityType> GetSubtypesOf(EntityType type, ItemCollection itemCollection, bool includeAbstractTypes)
    {
        if (type != null)
        {
            IEnumerable<EntityType> typesInCollection = itemCollection.GetItems<EntityType>();
            foreach (EntityType typeInCollection in typesInCollection)
            {
                if (type.Equals(typeInCollection) == false && this.IsSubtypeOf(typeInCollection, type))
                {
                    if ( includeAbstractTypes || !typeInCollection.Abstract)
                    {
                        yield return typeInCollection;
                    }
               }
            }
        }
    }

    public static bool TryGetStringMetadataPropertySetting(MetadataItem item, string propertyName, out string value)
    {
        value = null;
        MetadataProperty property = item.MetadataProperties.FirstOrDefault(p => p.Name == propertyName);
        if (property != null)
        {
            value = (string)property.Value;
        }
        return value != null;
    }
}

EntityFrameworkTemplateFileManager

/// <summary>
/// Responsible for marking the various sections of the generation,
/// so they can be split up into separate files
/// </summary>
public class EntityFrameworkTemplateFileManager
{
    /// <summary>
    /// Creates the VsEntityFrameworkTemplateFileManager if VS is detected, otherwise
    /// creates the file system version.
    /// </summary>
    public static EntityFrameworkTemplateFileManager Create(object textTransformation)
    {
        DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
        IDynamicHost host = transformation.Host;

#if !PREPROCESSED_TEMPLATE
        var hostServiceProvider = host.AsIServiceProvider();

        if (hostServiceProvider != null)
        {
            EnvDTE.DTE dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));

            if (dte != null)
            {
                return new VsEntityFrameworkTemplateFileManager(transformation);
            }
        }
#endif
        return new EntityFrameworkTemplateFileManager(transformation);
    }

    private sealed class Block
    {
        public String Name;
        public int Start, Length;
    }

    private readonly List<Block> files = new List<Block>();
    private readonly Block footer = new Block();
    private readonly Block header = new Block();
    private readonly DynamicTextTransformation _textTransformation;

    // reference to the GenerationEnvironment StringBuilder on the
    // TextTransformation object
    private readonly StringBuilder _generationEnvironment;

    private Block currentBlock;

    /// <summary>
    /// Initializes an EntityFrameworkTemplateFileManager Instance  with the
    /// TextTransformation (T4 generated class) that is currently running
    /// </summary>
    private EntityFrameworkTemplateFileManager(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
        _generationEnvironment = _textTransformation.GenerationEnvironment;
    }

    /// <summary>
    /// Marks the end of the last file if there was one, and starts a new
    /// and marks this point in generation as a new file.
    /// </summary>
    public void StartNewFile(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        CurrentBlock = new Block { Name = name };
    }

    public void StartFooter()
    {
        CurrentBlock = footer;
    }

    public void StartHeader()
    {
        CurrentBlock = header;
    }

    public void EndBlock()
    {
        if (CurrentBlock == null)
        {
            return;
        }

        CurrentBlock.Length = _generationEnvironment.Length - CurrentBlock.Start;

        if (CurrentBlock != header && CurrentBlock != footer)
        {
            files.Add(CurrentBlock);
        }

        currentBlock = null;
    }

    /// <summary>
    /// Produce the template output files.
    /// </summary>
    public virtual IEnumerable<string> Process(bool split = true)
    {
        var generatedFileNames = new List<string>();

        if (split)
        {
            EndBlock();

            var headerText = _generationEnvironment.ToString(header.Start, header.Length);
            var footerText = _generationEnvironment.ToString(footer.Start, footer.Length);
            var outputPath = Path.GetDirectoryName(_textTransformation.Host.TemplateFile);

            files.Reverse();

            foreach (var block in files)
            {
                var fileName = Path.Combine(outputPath, block.Name);
                var content = headerText + _generationEnvironment.ToString(block.Start, block.Length) + footerText;

                generatedFileNames.Add(fileName);
                CreateFile(fileName, content);
                _generationEnvironment.Remove(block.Start, block.Length);
            }
        }

        return generatedFileNames;
    }

    protected virtual void CreateFile(string fileName, string content)
    {
        if (IsFileContentDifferent(fileName, content))
        {
            File.WriteAllText(fileName, content);
        }
    }

    protected bool IsFileContentDifferent(String fileName, string newContent)
    {
        return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
    }

    private Block CurrentBlock
    {
        get { return currentBlock; }
        set
        {
            if (CurrentBlock != null)
            {
                EndBlock();
            }

            if (value != null)
            {
                value.Start = _generationEnvironment.Length;
            }

            currentBlock = value;
        }
    }

#if !PREPROCESSED_TEMPLATE
    private sealed class VsEntityFrameworkTemplateFileManager : EntityFrameworkTemplateFileManager
    {
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private Action<string> checkOutAction;
        private Action<IEnumerable<string>> projectSyncAction;

        /// <summary>
        /// Creates an instance of the VsEntityFrameworkTemplateFileManager class with the IDynamicHost instance
        /// </summary>
        public VsEntityFrameworkTemplateFileManager(object textTemplating)
            : base(textTemplating)
        {
            var hostServiceProvider = _textTransformation.Host.AsIServiceProvider();
            if (hostServiceProvider == null)
            {
                throw new ArgumentNullException("Could not obtain hostServiceProvider");
            }

            dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
            if (dte == null)
            {
                throw new ArgumentNullException("Could not obtain DTE from host");
            }

            templateProjectItem = dte.Solution.FindProjectItem(_textTransformation.Host.TemplateFile);

            checkOutAction = fileName => dte.SourceControl.CheckOutItem(fileName);
            projectSyncAction = keepFileNames => ProjectSync(templateProjectItem, keepFileNames);
        }

        public override IEnumerable<string> Process(bool split)
        {
            if (templateProjectItem.ProjectItems == null)
            {
                return new List<string>();
            }

            var generatedFileNames = base.Process(split);

            projectSyncAction.Invoke(generatedFileNames);

            return generatedFileNames;
        }

        protected override void CreateFile(string fileName, string content)
        {
            if (IsFileContentDifferent(fileName, content))
            {
                CheckoutFileIfRequired(fileName);
                File.WriteAllText(fileName, content);
            }
        }

        private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<string> keepFileNames)
        {
            var keepFileNameSet = new HashSet<string>(keepFileNames);
            var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();
            var originalOutput = Path.GetFileNameWithoutExtension(templateProjectItem.FileNames[0]);

            foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
            {
                projectFiles.Add(projectItem.FileNames[0], projectItem);
            }

            // Remove unused items from the project
            foreach (var pair in projectFiles)
            {
                if (!keepFileNames.Contains(pair.Key)
                    && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalOutput + "."))
                {
                    pair.Value.Delete();
                }
            }

            // Add missing files to the project
            foreach (string fileName in keepFileNameSet)
            {
                if (!projectFiles.ContainsKey(fileName))
                {
                    templateProjectItem.ProjectItems.AddFromFile(fileName);
                }
            }
        }

        private void CheckoutFileIfRequired(string fileName)
        {
            if (dte.SourceControl == null
                || !dte.SourceControl.IsItemUnderSCC(fileName)
                    || dte.SourceControl.IsItemCheckedOut(fileName))
            {
                return;
            }

            checkOutAction.Invoke(fileName);
        }
    }
#endif
}

EdmMetadataLoader

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 = EdmItemCollection.Create(new[] { reader }, null, out errors);

                ProcessErrors(errors, sourcePath);

                return itemCollection ?? new EdmItemCollection();
            }
        }
        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,
                    CodeGenerationTools.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 bool IsLazyLoadingEnabled(EntityContainer container)
    {
        string lazyLoadingAttributeValue;
        var lazyLoadingAttributeName = MetadataConstants.EDM_ANNOTATION_09_02 + ":LazyLoadingEnabled";
        bool isLazyLoading;
        return !MetadataTools.TryGetStringMetadataPropertySetting(container, lazyLoadingAttributeName, out lazyLoadingAttributeValue)
            || !bool.TryParse(lazyLoadingAttributeValue, out isLazyLoading)
            || isLazyLoading;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值