本文档所有内容非官方解释注解,内容多数为个人理解,紧供个人参考
通用模板模板中使用到了几个重要的类型
由于没有说明和只能提示摸索起来比较麻烦,故将这几个用到的类型单独提炼出来供以后查询。
检查原始创建的模板内容并将这些添加上
<#@ 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 对象
///
/// 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
///
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;
///
/// 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.
///
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");
}
///
/// Gets the value of the wrapped TextTranformation instance's GenerationEnvironment property
///
public StringBuilder GenerationEnvironment { get { return (StringBuilder)_generationEnvironment.GetValue(_instance, null); } }
///
/// Gets the value of the wrapped TextTranformation instance's Errors property
///
public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { return (System.CodeDom.Compiler.CompilerErrorCollection)_errors.GetValue(_instance, null); } }
///
/// Calls the wrapped TextTranformation instance's Write method.
///
public void Write(string text)
{
_write.Invoke(_instance, new object[] { text });
}
///
/// Calls the wrapped TextTranformation instance's WriteLine method.
///
public void WriteLine(string text)
{
_writeLine.Invoke(_instance, new object[] { text });
}
///
/// 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
///
public IDynamicHost Host
{
get
{
if (_dynamicHost == null)
{
if(_host == null)
{
_dynamicHost = new NullHost();
}
else
{
_dynamicHost = new DynamicHost(_host.GetValue(_instance, null));
}
}
return _dynamicHost;
}
}
}
CodeGenerationTools
///
/// Responsible for helping to create source code that is
/// correctly formated and functional
///
public class CodeGenerationTools
{
private readonly DynamicTextTransformation _textTransformation;
private readonly CSharpCodeProvider _code;
private readonly MetadataTools _ef;
///
/// Initializes a new CodeGenerationTools object with the TextTransformation (T4 generated class)
/// that is currently running
///
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;
}
///
/// 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.
///
public bool FullyQualifySystemTypes { get; set; }
///
/// When true, the field names are Camel Cased,
/// otherwise they will preserve the case they
/// start with.
///
/// Default is true.
///
public bool CamelCaseFields { get; set; }
///
/// Returns the NamespaceName suggested by VS if running inside VS. Otherwise, returns
/// null.
///
public string VsNamespaceSuggestion()
{
string suggestion = _textTransformation.Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");
if (String.IsNullOrEmpty(suggestion))
{
return null;
}
return suggestion;
}
///
/// Returns a string that is safe for use as an identifier in C#.
/// Keywords are escaped.
///
public string Escape(string name)
{
if (name == null)
{
return null;
}
return _code.CreateEscapedIdentifier(name);
}
///
/// Returns the name of the TypeUsage's EdmType that is safe for
/// use as an identifier.
///
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");
}
///
/// Returns the name of the EdmMember that is safe for
/// use as an identifier.
///
public string Escape(EdmMember member)
{
if (member == null)
{
return null;
}
return Escape(member.Name);
}
///
/// Returns the name of the EdmType that is safe for
/// use as an identifier.
///
public string Escape(EdmType type)
{
if (type == null)
{
return null;
}
return Escape(type.Name);
}
///
/// Returns the name of the EdmFunction that is safe for
/// use as an identifier.
///
public string Escape(EdmFunction function)
{
if (function == null)
{
return null;
}
return Escape(function.Name);
}
///
/// Returns the name of the EnumMember that is safe for
/// use as an identifier.
///
public string Escape(EnumMember member)
{
if (member == null)
{
return null;
}
return Escape(member.Name);
}
///
/// Returns the name of the EntityContainer that is safe for
/// use as an identifier.
///
public string Escape(EntityContainer container)
{
if (container == null)
{
return null;
}
return Escape(container.Name);
}
///
/// Returns the name of the EntitySet that is safe for
/// use as an identifier.
///
public string Escape(EntitySet set)
{
if (set == null)
{
return null;
}
return Escape(set.Name);
}
///
/// Returns the name of the StructuralType that is safe for
/// use as an identifier.
///
public string Escape(StructuralType type)
{
if (type == null)
{
return null;
}
return Escape(type.Name);
}
///
/// Returns the NamespaceName with each segment safe to
/// use as an identifier.
///
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;
}
///
/// Returns the name of the EdmMember formatted for
/// use as a field identifier.
///
/// This method changes behavior based on the CamelCaseFields
/// setting.
///
public string FieldName(EdmMember member)
{
if (member == null)
{
return null;
}
return FieldName(member.Name);
}
///
/// Returns the name of the EntitySet formatted for
/// use as a field identifier.
///
/// This method changes behavior based on the CamelCaseFields
/// setting.
///
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;
}
}
///
/// Returns the name of the Type object formatted for
/// use in source code.
///
/// This method changes behavior based on the FullyQualifySystemTypes
/// setting.
///
public string Escape(Type clrType)
{
return Escape(clrType, FullyQualifySystemTypes);
}
///
/// Returns the name of the Type object formatted for
/// use in source code.
///
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;
}
///
/// Returns the abstract option if the entity is Abstract, otherwise returns String.Empty
///
public string AbstractOption(EntityType entity)
{
if (entity.Abstract)
{
return "abstract";
}
return String.Empty;
}
///
/// Returns the passed in identifier with the first letter changed to lowercase
///
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);
}
///
/// 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.
///
public string SpaceAfter(string value)
{
return StringAfter(value, " ");
}
///
/// 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.
///
public string SpaceBefore(string value)
{
return StringBefore(" ", value);
}
///
/// If the value parameter is null or empty an empty string is returned,
/// otherwise it retuns value with append concatenated on the end.
///
public string StringAfter(string value, string append)
{
if (String.IsNullOrEmpty(value))
{
return String.Empty;
}
return value + append;
}
///
/// If the value parameter is null or empty an empty string is returned,
/// otherwise it retuns value with prepend concatenated on the front.
///
public string StringBefore(string prepend, string value)
{
if (String.IsNullOrEmpty(value))
{
return String.Empty;
}
return prepend + value;
}
///
/// Returns false and shows an error if the supplied type names aren't case-insensitively unique,
/// otherwise returns true.
///
public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
{
return VerifyCaseInsensitiveUniqueness(types, t => string.Format(CultureInfo.CurrentCulture, GetResourceString("Template_CaseInsensitiveTypeConflict"), t), sourceFile);
}
///
/// Returns false and shows an error if the supplied strings aren't case-insensitively unique,
/// otherwise returns true.
///
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;
}
///
/// Returns the names of the items in the supplied collection that correspond to O-Space types.
///
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));
}
///
/// Returns the name of the supplied GlobalItem.
///
public string GetGlobalItemName(GlobalItem item)
{
if (item is EdmType)
{
return ((EdmType)item).Name;
}
else
{
return ((EntityContainer)item).Name;
}
}
///
/// 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.
///
public string CreateFullName(string namespaceName, string name)
{
if (String.IsNullOrEmpty(namespaceName))
{
return name;
}
return namespaceName + "." + name;
}
///
/// Retuns a literal representing the supplied value.
///
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();
}
///
/// Returns a resource string from the System.Data.Entity.Design assembly.
///
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";
///
/// 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.
///
/// The type of item to return.
/// The item collection to look in.
/// The items to generate.
public IEnumerable<T> GetItemsToGenerate<T>(ItemCollection itemCollection) where T: GlobalItem
{
return itemCollection.GetItems<T>().Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName));
}
///
/// 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).
///
/// The c-space type usage to get a name for.
/// The type name to use.
public string GetTypeName(TypeUsage typeUsage)
{
return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
}
///
/// 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).
///
/// The c-space type to get a name for.
/// The type name to use.
public string GetTypeName(EdmType edmType)
{
return GetTypeName(edmType, isNullable: null, modelNamespace: null);
}
///
/// 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).
///
/// The c-space type usage to get a name for.
/// If not null and the type's namespace does not match this namespace, then a
/// fully qualified name will be returned.
/// The type name to use.
public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
{
return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
}
///
/// 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).
///
/// The c-space type to get a name for.
/// If not null and the type's namespace does not match this namespace, then a
/// fully qualified name will be returned.
/// The type name to use.
public string GetTypeName(EdmType edmType, string modelNamespace)
{
return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
}
///
/// 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).
///
/// The c-space type to get a name for.
/// Set this to true for nullable usage of this type.
/// If not null and the type's namespace does not match this namespace, then a
/// fully qualified name will be returned.
/// The type name to use.
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
///
/// Responsible for making the Entity Framework Metadata more
/// accessible for code generation.
///
public class MetadataTools
{
private readonly DynamicTextTransformation _textTransformation;
///
/// Initializes an MetadataTools Instance with the
/// TextTransformation (T4 generated class) that is currently running
///
public MetadataTools(object textTransformation)
{
if (textTransformation == null)
{
throw new ArgumentNullException("textTransformation");
}
_textTransformation = DynamicTextTransformation.Create(textTransformation);
}
///
/// This method returns the underlying CLR type of the o-space type corresponding to the supplied
/// Note that for an enum type this means that the type backing the enum will be returned, not the enum type itself.
///
public Type ClrType(TypeUsage typeUsage)
{
return UnderlyingClrType(typeUsage.EdmType);
}
///
/// 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.
///
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);
}
///
/// True if the EdmProperty is a key of its DeclaringType, False otherwise.
///
public bool IsKey(EdmProperty property)
{
if (property != null && property.DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
{
return ((EntityType)property.DeclaringType).KeyMembers.Contains(property);
}
return false;
}
///
/// True if the EdmProperty TypeUsage is Nullable, False otherwise.
///
public bool IsNullable(EdmProperty property)
{
return property != null && IsNullable(property.TypeUsage);
}
///
/// True if the TypeUsage is Nullable, False otherwise.
///
public bool IsNullable(TypeUsage typeUsage)
{
Facet nullableFacet = null;
if (typeUsage != null &&
typeUsage.Facets.TryGetValue("Nullable", true, out nullableFacet))
{
return (bool)nullableFacet.Value;
}
return false;
}
///
/// If the passed in TypeUsage represents a collection this method returns final element
/// type of the collection, otherwise it returns the value passed in.
///
public TypeUsage GetElementType(TypeUsage typeUsage)
{
if (typeUsage == null)
{
return null;
}
if (typeUsage.EdmType is CollectionType)
{
return GetElementType(((CollectionType)typeUsage.EdmType).TypeUsage);
}
else
{
return typeUsage;
}
}
///
/// Returns the NavigationProperty that is the other end of the same association set if it is
/// available, otherwise it returns null.
///
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));
}
///
/// 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.
///
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)];
}
///
/// 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.
///
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)];
}
///
/// 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.
///
public ReadOnlyMetadataCollection<EdmProperty> GetPrincipalProperties(NavigationProperty navProperty)
{
if (navProperty == null)
{
throw new ArgumentNullException("navProperty");
}
return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].FromProperties;
}
///
/// 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.
///
public ReadOnlyMetadataCollection<EdmProperty> GetDependentProperties(NavigationProperty navProperty)
{
if (navProperty == null)
{
throw new ArgumentNullException("navProperty");
}
return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].ToProperties;
}
///
/// True if this entity type requires the HandleCascadeDelete method defined and the method has
/// not been defined on any base type
///
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;
}
///
/// 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
///
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();
}
///
/// 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.
///
public bool IsCascadeDeletePrincipal(NavigationProperty navProperty)
{
if (navProperty == null)
{
throw new ArgumentNullException("navProperty");
}
return IsCascadeDeletePrincipal((AssociationEndMember)navProperty.FromEndMember);
}
///
/// True if the specified association end is the principal in an identifying relationship.
/// or if the association end has cascade delete defined.
///
public bool IsCascadeDeletePrincipal(AssociationEndMember associationEnd)
{
if (associationEnd == null)
{
throw new ArgumentNullException("associationEnd");
}
return associationEnd.DeleteBehavior == OperationAction.Cascade || IsPrincipalEndOfIdentifyingRelationship(associationEnd);
}
///
/// 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.
///
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;
}
///
/// 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.
///
public bool IsIdentifyingRelationship(AssociationType association)
{
if (association == null)
{
throw new ArgumentNullException("association");
}
return IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[0]) || IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[1]);
}
///
/// 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.
///
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;
}
///
/// Returns the subtype of the EntityType in the current itemCollection
///
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
///
/// Responsible for marking the various sections of the generation,
/// so they can be split up into separate files
///
public class EntityFrameworkTemplateFileManager
{
///
/// Creates the VsEntityFrameworkTemplateFileManager if VS is detected, otherwise
/// creates the file system version.
///
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;
///
/// Initializes an EntityFrameworkTemplateFileManager Instance with the
/// TextTransformation (T4 generated class) that is currently running
///
private EntityFrameworkTemplateFileManager(object textTransformation)
{
if (textTransformation == null)
{
throw new ArgumentNullException("textTransformation");
}
_textTransformation = DynamicTextTransformation.Create(textTransformation);
_generationEnvironment = _textTransformation.GenerationEnvironment;
}
///
/// 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.
///
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;
}
///
/// Produce the template output files.
///
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;
///
/// Creates an instance of the VsEntityFrameworkTemplateFileManager class with the IDynamicHost instance
///
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;
}
}