If you have used LINQ to SQL, Entity Framework you might noticed that when you use queryable extensions returning set of objects (like .Select(), .Where(), .Join(), Group...) you get defered-executable queries that will be converted to SQL end executed during first demand. You can combine those queries, join them together, and it eventually will be still one SQL request. However you don't get the same when you work with functions returning not Enumerable, but actual value (like .First(), .Any(), .Max(), ...). So thanks to people from Stackoverflow, here are extensions for DbContext and DbQuery (Entity Framework 5), that will allow you to execute those operations still getting back Queryable results:
Here are examples, how to use this code:
SQL generated for each statement is the same:
Javascript, Typescript, .NET Full Stack Engineer, Tech lead, Architect - New York
Wednesday, October 16, 2013
Thursday, September 26, 2013
T4 for generating composable functions for DbContext of Entity Framework 5 model
If you try to Import Function which is composable using EF designer you will see error message :
Function imports cannot be created for composable functions.
However you can see that all data for those functions are imported to Storage space of the model. So there is enough information to generate functions wrappers automatically using T4 Template.
The following T4 template is modified version of original EF5 Db Context T4 template - it only generates wrappers for composable functions code to your partial db context class. I didn't test it for different scenarios, so this is the raw version of T4 template:
Function imports cannot be created for composable functions.
However you can see that all data for those functions are imported to Storage space of the model. So there is enough information to generate functions wrappers automatically using T4 Template.
The following T4 template is modified version of original EF5 Db Context T4 template - it only generates wrappers for composable functions code to your partial db context class. I didn't test it for different scenarios, so this is the raw version of T4 template:
<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="EF.Utility.CS.ttinclude"#><#@ output extension=".cs"#><# const string inputFile = @"Model1.edmx"; var textTransformation = this; var textTransform = DynamicTextTransformation.Create(textTransformation); var code = new CodeGenerationTools(textTransformation); var ef = new MetadataTools(textTransformation); var typeMapper = new TypeMapper(code, ef, textTransform.Errors); var loader = new EdmMetadataLoader(textTransform.Host, textTransform.Errors); var itemCollection = loader.CreateEdmItemCollection(inputFile); var modelNamespace = loader.GetModelNamespace(inputFile); var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); var container = itemCollection.OfType<EntityContainer>().FirstOrDefault(); var mdl = new MetadataLoader(textTransformation); MetadataWorkspace ws = null; mdl.TryLoadAllMetadata(inputFile,out ws); var storeItems = ws.GetItemCollection(DataSpace.SSpace); var functions = storeItems.OfType<EdmFunction>().Where(x=>x.NamespaceName!="SqlServer" && typeMapper.IsComposable(x)).ToArray(); #> //------------------------------------------------------------------------------ // <auto-generated> // <#=GetResourceString("Template_GeneratedCodeCommentLine1")#> // // <#=GetResourceString("Template_GeneratedCodeCommentLine2")#> // <#=GetResourceString("Template_GeneratedCodeCommentLine3")#> // </auto-generated> //------------------------------------------------------------------------------ <# var codeNamespace = code.VsNamespaceSuggestion(); if (!String.IsNullOrEmpty(codeNamespace)) { #> namespace <#=code.EscapeNamespace(codeNamespace)#> { <# PushIndent(" "); } #> using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.SqlClient; <# if (container.FunctionImports.Any()) { #> using System.Data.Objects; using System.Data.Objects.DataClasses; using System.Linq; <# } #> <#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext { <# foreach (var edmFunction in functions) { WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: false); } #> } <# if (!String.IsNullOrEmpty(codeNamespace)) { PopIndent(); #> } <# } #> <#+ private void WriteFunctionImport(TypeMapper typeMapper, CodeStringGenerator codeStringGenerator, EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) { #> [EdmFunction("<#=edmFunction.NamespaceName#>", "<#=edmFunction.Name#>")] <#=codeStringGenerator.ComposableStoreFunctionMethod(edmFunction, modelNamespace)#> { <#+ codeStringGenerator.WriteStoreFunctionParameters(edmFunction, WriteFunctionParameter); #> <#=codeStringGenerator.ComposableCreateStoreQuery(edmFunction, modelNamespace)#> } <#+ } public void WriteFunctionParameter(string name, string isNotNull, string notNullInit, string nullInit) { #> var <#=name#> = <#=isNotNull#> ? <#=notNullInit#> : <#=nullInit#>; <#+ } public const string TemplateId = "CSharp_DbContext_Context_EF5"; public class CodeStringGenerator { public string ComposableCreateStoreQuery(EdmFunction edmFunction, string modelNamespace) { var parameters = _typeMapper.GetParameters(edmFunction); return string.Format( CultureInfo.InvariantCulture, "return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<{0}>(\"select [{1}].[{2}]({3})\"{4});", _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), //edmFunction.NamespaceName, edmFunction.MetadataProperties["Schema"].Value, edmFunction.Name, string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()), _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))); } public void WriteStoreFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter) { var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable)) { var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"; var notNullInit = "new SqlParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")"; var nullInit = "new SqlParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + parameter.RawClrTypeName + "))"; writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit); } } public string ComposableStoreFunctionMethod(EdmFunction edmFunction, string modelNamespace) { var parameters = _typeMapper.GetParameters(edmFunction); return string.Format( CultureInfo.InvariantCulture, "{0} ObjectResult<{1}> {2}({3})", AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), _code.Escape(edmFunction), string.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray())); } 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, "{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 NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0} {1} {2} {{ {3}get; {4}set; }}", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); } public string AccessibilityAndVirtual(string accessibility) { return accessibility + (accessibility != "private" ? " virtual" : ""); } public string EntityClassOpening(EntityType entity) { return string.Format( CultureInfo.InvariantCulture, "{0} {1}partial class {2}{3}", Accessibility.ForType(entity), _code.SpaceAfter(_code.AbstractOption(entity)), _code.Escape(entity), _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType))); } public string EnumOpening(SimpleType enumType) { return string.Format( CultureInfo.InvariantCulture, "{0} enum {1} : {2}", Accessibility.ForType(enumType), _code.Escape(enumType), _code.Escape(_typeMapper.UnderlyingClrType(enumType))); } public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter) { var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable)) { var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"; var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")"; var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + parameter.RawClrTypeName + "))"; writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit); } } public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace) { var parameters = _typeMapper.GetParameters(edmFunction); return string.Format( CultureInfo.InvariantCulture, "{0} IQueryable<{1}> {2}({3})", AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), _code.Escape(edmFunction), string.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray())); } public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace) { var parameters = _typeMapper.GetParameters(edmFunction); return string.Format( CultureInfo.InvariantCulture, "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});", _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), edmFunction.NamespaceName, edmFunction.Name, string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()), _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))); } public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) { var parameters = _typeMapper.GetParameters(edmFunction); var returnType = _typeMapper.GetReturnType(edmFunction); var paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray()); if (includeMergeOption) { paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption"; } return string.Format( CultureInfo.InvariantCulture, "{0} {1} {2}({3})", AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", _code.Escape(edmFunction), paramList); } public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) { var parameters = _typeMapper.GetParameters(edmFunction); var returnType = _typeMapper.GetReturnType(edmFunction); var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())); if (includeMergeOption) { callParams = ", mergeOption" + callParams; } return string.Format( CultureInfo.InvariantCulture, "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});", returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", edmFunction.Name, callParams); } public string DbSet(EntitySet entitySet) { return string.Format( CultureInfo.InvariantCulture, "{0} DbSet<{1}> {2} {{ get; set; }}", Accessibility.ForReadOnlyProperty(entitySet), _typeMapper.GetTypeName(entitySet.ElementType), _code.Escape(entitySet)); } public string UsingDirectives(bool inHeader, bool includeCollections = true) { return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion()) ? string.Format( CultureInfo.InvariantCulture, "{0}using System;{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), modelNamespace: null); } public string GetTypeName(EdmType edmType) { return GetTypeName(edmType, isNullable: null, modelNamespace: 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, isNullable: null, modelNamespace: 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 object GetEnumMemberValue(MetadataItem enumMember) { ArgumentNotNull(enumMember, "enumMember"); var valueProperty = enumMember.GetType().GetProperty("Value"); return valueProperty == null ? null : valueProperty.GetValue(enumMember, null); } public string GetEnumMemberName(MetadataItem enumMember) { ArgumentNotNull(enumMember, "enumMember"); var nameProperty = enumMember.GetType().GetProperty("Name"); return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null); } public System.Collections.IEnumerable GetEnumMembers(EdmType enumType) { ArgumentNotNull(enumType, "enumType"); var membersProperty = enumType.GetType().GetProperty("Members"); return membersProperty != null ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null) : Enumerable.Empty<MetadataItem>(); } public bool EnumIsFlags(EdmType enumType) { ArgumentNotNull(enumType, "enumType"); var isFlagsProperty = enumType.GetType().GetProperty("IsFlags"); return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null); } 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<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection) { return GetItemsToGenerate<SimpleType>(itemCollection) .Where(e => IsEnumType(e)); } 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 IEnumerable<EdmProperty> GetComplexProperties(EntityType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); } public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); } public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); } public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); } public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type) { return type.NavigationProperties.Where(np => np.DeclaringType == type); } public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type) { return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many); } public FunctionParameter GetReturnParameter(EdmFunction edmFunction) { ArgumentNotNull(edmFunction, "edmFunction"); var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters"); return returnParamsProperty == null ? edmFunction.ReturnParameter : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault(); } public bool IsComposable(EdmFunction edmFunction) { ArgumentNotNull(edmFunction, "edmFunction"); var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute"); return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null); } public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction) { return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); } public TypeUsage GetReturnType(EdmFunction edmFunction) { var returnParam = GetReturnParameter(edmFunction); return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage); } public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption) { var returnType = GetReturnType(edmFunction); return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType; } } 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 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; } } 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), isThreadSafe: true); public static string GetResourceString(string resourceName) { ArgumentNotNull(resourceName, "resourceName"); return ResourceManager.Value.GetString(resourceName, null); } #>
Entity Framework Function Import does not work with SP which use temp tables in it
If you ever tried to generate Entity Framework model from database for Stored Procedures (Function Import) - you might have notices that Entity Framework cannot receive information about columns for those SP which uses temp tables in it.
The Reason for that is that EF importer tries to execute that SP with FMTONLY set to ON, which makes you SP fail, as temp tables are not created when FMTONLY=ON.
In order to make those SP work, you can make the following changes:
1) Add to the beginning of the SP code:
DECLARE @FMTONLY BIT;
The Reason for that is that EF importer tries to execute that SP with FMTONLY set to ON, which makes you SP fail, as temp tables are not created when FMTONLY=ON.
In order to make those SP work, you can make the following changes:
1) Add to the beginning of the SP code:
DECLARE @FMTONLY BIT;
if 1=0 BEGIN set @FMTONLY = 1; SET FMTONLY OFF; END
2) Add to the end of SP:
if @FMTONLY = 1 BEGIN SET FMTONLY ON; END
This will fix your issue with Function Import caused by using temp tables. If you keep seeing that Entity Framework does not receive information about columns, probably the problem is in something else, you can figure it out trying to execute your store proc with FMTONLY ON, like that:
SET FMTONLY ON; exec MyProcedureThatDoesNotReturnsColumnsToEF
Tuesday, September 24, 2013
T4 Template for Visual Studio Settings file
Let's say you have .NET application with settings. Visual Studio does a great job generating class with properties that allows to access those settings, and let's say if you renamed the Setting name you will find out all reference errors during compile time, which is great.
However sometimes you might need to get names of Settings properties as constants - you might need it for using those settings in Attribute initializers, which allow only constant parameters.
So here is T4 template that generates subclass with names as constants and names as Enumerable (for strongly typed references):
You will can use this template for other settings files in your application, in order to do that just change SettingsFile constant.
However sometimes you might need to get names of Settings properties as constants - you might need it for using those settings in Attribute initializers, which allow only constant parameters.
So here is T4 template that generates subclass with names as constants and names as Enumerable (for strongly typed references):
<#@ template language="C#" debug="true" hostSpecific="true" #> <#@ output extension=".cs" #> <#@ Assembly Name="System.Core.dll" #> <#@ Assembly Name="System.Xml.dll" #> <#@ Assembly Name="System.Xml.Linq.dll" #> <#@ Assembly Name="System.Windows.Forms.dll" #> <#@ Assembly name="System.Configuration"#> <#@ assembly name="EnvDTE" #> <#@ import namespace="System" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Diagnostics" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Xml.Linq" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Collections.Specialized"#> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Configuration" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <# const string SettingsFile = "Settings.settings"; #> /* //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a template. // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ */ using System; using System.Configuration; <# var doc = XDocument.Load(Path.Combine(this.GetTemplatePath(), SettingsFile)); var xmlNs = doc.Root.Name.Namespace; #> namespace <#= doc.Root.Attribute("GeneratedClassNamespace").Value #> { partial class <#= doc.Root.Attribute("GeneratedClassName").Value #> { /// <summary> /// Contains all settings' names /// </summary> public class Names { <# foreach(var setting in doc.Descendants(xmlNs+"Setting")) { var settingName = setting.Attribute("Name").Value; #> <# var settingDescription = setting.Attribute("Description")==null?null:setting.Attribute("Description").Value; if(!string.IsNullOrEmpty(settingDescription)) { #> /// <summary> /// <#= settingDescription #> /// </summary> <# } #> public const string <#= GetSafeName(settingName) #> = "<#= settingName #>"; <# } #> } /// <summary> /// Contains enumeration of all settings, you can use it as strong type key for specific setting /// </summary> public enum NameEnum { <# foreach(var setting in doc.Descendants(xmlNs+"Setting")) { var settingName = setting.Attribute("Name").Value;#> <# var settingDescription = setting.Attribute("Description")==null?null:setting.Attribute("Description").Value; if(!string.IsNullOrEmpty(settingDescription)) { #> /// <summary> /// <#= settingDescription #> /// </summary> <# } #> <#= GetSafeName(settingName) #>, <# } #> } /// <summary> /// Gives setting value by name passed as enum /// </summary> /// <param name="settingName">setting key</param> /// <returns>setting value</returns> public static object GetValue(NameEnum settingName) { return Default[settingName.ToString()]; } /// <summary> /// Gives specified type setting value by name passed as enum /// </summary> /// <typeparam name="T"></typeparam> /// <param name="settingName"></param> /// <returns>setting calue of specified type</returns> public static T GetValue<T>(NameEnum settingName) { return (T)Default[settingName.ToString()]; } } } <#+ public string GetTemplatePath ( ) { return Path.GetDirectoryName(Host.TemplateFile); } public string GetSafeName ( string value) { var builder = new System.Text.StringBuilder(); foreach(var ch in value) { if (Char.IsLetterOrDigit(ch) || ch == '_') builder.Append(ch); else if (ch == '.') builder.Append('_'); }; return builder.ToString(); } #>
You will can use this template for other settings files in your application, in order to do that just change SettingsFile constant.
Thursday, April 18, 2013
Dynamically generated Javascript and CSS in ASP.NET MVC using Razor Views
I was always wondering why ASP.NET allows to generate dynamically HTML (including inline CSS and JS), but does not allow to do the same for standalone JS and CSS files. Today I spent some time to implement relatively simple and convenient solution of the problem on ASP.NET MVC platform.
Here is the list of features I want to get from solution:
The steps you will need to do:
1) Implement CSHTML files to render dynamic resources
2) Implement Style and Script controllers, overriding handling unknown action, using "Magic" extensions
3) Modify web.config to allow controllers handle static file requests
4) Implement Magic extensions.
5) Enjoy
Here you can download source code.
Index.cshtml:
@{
You can see that this view uses dynamic CSS and static CSS. It also uses one static JS file, and two dynamic JS files. Dynamic references has query string parameters which will impact the rendering of the resource. In DynamicWithModel.js I also wants to use model that I pass to the JS-view and which is respected during JS rendering.
I want my dynamic resources to be handler by controllers, while static resources should be stored just in Styles and Scripts folder of the web application.
For static resources I create folders in the root of the project. Regarding dynamic resources, since controllers should be named Styles and Scripts, I will create view folders accordingly with CSHTML files:
Content of those static files is very simple:
~/scripts/static.js:
~/scripts/static.css:
Here are dynamic resources:
~/Views/Styles/dynamic.css.cshtml:
As you see CSHTML is standard view with a single block STYLE node. Background color is retrieved from query string parameter, in case parameter is not specified default RED color is used.
~/Views/Scripts/DynamicWithModel.js.cshtml:
@if (Request.QueryString.HasKeys())
{
foreach (string key in Request.QueryString.Keys)
{
@:{ document.writeln("Query string parameter. Key: @key , Value: @Request.QueryString[key]"); }
}
}
}
</script>
This function does not use model, but also renders current query string parameters to Html document.
I’m creating two controllers which handles requests coming to ~/Styles/* and ~/Scripts/* paths.
As you might notice I used custom controller extensions JavaScriptFromView and CssFromView - these guys do all the magic. Here is it's implementation:
public static class MvcExtensions {
///
/// CSS content result rendered by partial view specified ///
/// "controller">current controller
/// "cssViewName">view name, which contains partial view with one STYLE block only
/// "model">optional model to pass to partial view for rendering
///
public static ActionResult CssFromView(this Controller controller, string cssViewName=null, object model=null)
{
var cssContent = ParseViewToContent(controller,cssViewName, "style", model);
if(cssContent==null) throw new HttpException(404,"CSS not found");
return new ContentResult() { Content = cssContent, ContentType = "text/css" };
}
///
/// Javascript content result rendered by partial view specified ///
/// "controller">current controller
/// "javascriptViewName">view name, which contains partial view with one SCRIPT block only
/// "model">optional model to pass to partial view for rendering
///
public static ActionResult JavaScriptFromView(this Controller controller, string javascriptViewName=null, object model=null)
{
var jsContent = ParseViewToContent(controller,javascriptViewName, "script", model);
if(jsContent==null) throw new HttpException(404,"JS not found");
return new JavaScriptResult() {Script = jsContent };
}
///
/// Parse view and render it to a string, trimming specified HTML tag ///
/// "controller">controller which renders the view
/// "viewName">name of cshtml file with content. If null, then actionName used
/// "tagName">Content rendered expected to be wrapped with this html tag, and it will be trimmed from result
/// "model">model to pass for view to render
///
static string ParseViewToContent(Controller controller, string viewName, string tagName, object model = null)
{
using (var viewContentWriter = new StringWriter())
{
if (model != null)
controller.ViewData.Model = model;
if (string.IsNullOrEmpty(viewName))
viewName = controller.RouteData.GetRequiredString("action");
var viewResult = new ViewResult()
{
ViewName = viewName,
ViewData = controller.ViewData,
TempData = controller.TempData,
ViewEngineCollection = controller.ViewEngineCollection
};
var viewEngineResult = controller.ViewEngineCollection.FindPartialView(controller.ControllerContext, viewName);
if (viewEngineResult.View == null)
return null;
try {
var viewContext = new ViewContext(controller.ControllerContext, viewEngineResult.View, controller.ViewData, controller.TempData, viewContentWriter);
viewEngineResult.View.Render(viewContext, viewContentWriter);
var viewString = viewContentWriter.ToString().Trim('\r', '\n', ' ');
var regex = string.Format("<{0}[^>]*>(.*?)</{0}>", tagName);
var res = Regex.Match(viewString, regex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline);
if (res.Success && res.Groups.Count > 1)
return res.Groups[1].Value;
else throw new InvalidProgramException(string.Format("Dynamic content produced by viewResult '{0}' expected to be wrapped in '{1}' tag", viewName, tagName));
}
finally {
if (viewEngineResult.View != null)
viewEngineResult.ViewEngine.ReleaseView(controller.ControllerContext, viewEngineResult.View);
}
}
}
}
}
As you can see you can modify query string parameters in runtime using IE Developer Tools:
and see results immediately:
in Network tab you can see actual responses and low level information:
Dynamic Javascript response:
Again, you can download source code from here.
Additional tricks:
Since you can add inline code actually inline you probably won't need it, but You also can use those controllers' actions as child actions to render inline resources if you need using Html.RenderAction/ Html.Action(...) extensions on your parent HTML view. If you do this make sure your Response.ContentType is not overwritten by Render action and if it was you need manually to restore it.
FileHandler VS Controller competition for handling file request
If you noticed there is a competition between Static file Handler and your Controller for handling requests to resource file (js/css). So it is important to understand how it works. So here it is:
By default StaticFileHandler has priority. This means if you create file ~/Views/Scripts/static.js.cshtml, and you already have file ~/Scripts/static.js - the last one (real static) will be used. But you can change this behavior using Routing. You need to do is to add in your RouteConfig.cs (located in App_Start for MVC4 template) the following line:
It will deactivate Static File handler and redirect all file requests to Controllers.
Here is the list of features I want to get from solution:
- Intelli-sense in editor of dynamic CSS and JS
- Fully functional C# razor with server-side C# code in dynamic content
- Ability to pass Model and use request parameters
- I still want to use some static CSS, and JS besides dynamic
- Adding another dynamic JS/CSS file should be as easy as adding static JS/CSS file
- Bundling and minification of resources
Short story
To achieve these goals, I use existing functionality of CSHTML editor, which provides everything we need for inline CSS and JS. I create partial cshtml view with single Html element, either or and after rendering I trim the root tag and get dynamically rendered content file.The steps you will need to do:
1) Implement CSHTML files to render dynamic resources
2) Implement Style and Script controllers, overriding handling unknown action, using "Magic" extensions
3) Modify web.config to allow controllers handle static file requests
4) Implement Magic extensions.
5) Enjoy
Here you can download source code.
Long story:
CREATING TARGET VIEW
Let's start with our final view, which declares our implementation goals. It is super simple home page view, that references our dynamic and static content, and demonstrate visually that using of dynamic resources works:Index.cshtml:
@{
ViewBag.Title = "Home page"; } <link href="~/Styles/static.css" rel="stylesheet" /> <link href="~/Styles/dynamic.css?color=grey" rel="stylesheet" /> <script src="~/Scripts/static.js"></script> <script src="~/Scripts/dynamicWithModel.js?message=Hallo%20world&otherParameter=My parameter value"></script> <script src="~/Scripts/otherDynamic.js"></script> <h2 class="dynamicCss">@ViewBag.Title</h2> <script> StaticJsFunction(); DynamicJsFunction(); OtherDynamicJsFunction(); </script>
You can see that this view uses dynamic CSS and static CSS. It also uses one static JS file, and two dynamic JS files. Dynamic references has query string parameters which will impact the rendering of the resource. In DynamicWithModel.js I also wants to use model that I pass to the JS-view and which is respected during JS rendering.
I want my dynamic resources to be handler by controllers, while static resources should be stored just in Styles and Scripts folder of the web application.
Creating JS and CSS
For static resources I create folders in the root of the project. Regarding dynamic resources, since controllers should be named Styles and Scripts, I will create view folders accordingly with CSHTML files:
Content of those static files is very simple:
~/scripts/static.js:
function StaticJsFunction() { document.writeln("executing static JS "); }
~/scripts/static.css:
body { background-color:InfoBackground; }
Here are dynamic resources:
~/Views/Styles/dynamic.css.cshtml:
@{ var className = "dynamicCss";
var bkColor = Request.QueryString["color"]??"red";
}<style>.@className
{
background-color:@bkColor;
}
</style>
As you see CSHTML is standard view with a single block STYLE node. Background color is retrieved from query string parameter, in case parameter is not specified default RED color is used.
~/Views/Scripts/DynamicWithModel.js.cshtml:
@model string<script>function DynamicJsFunction() {
document.writeln("executing dynamic JS from: @Request.RawUrl...")
@if (Model != null)
{
@: { document.writeln("Message passed to Model: @Model"); }
}
@if (Request.QueryString.HasKeys())
{
foreach (string key in Request.QueryString.Keys)
{
@:{ document.writeln("Query string parameter. Key: @key , Value: @Request.QueryString[key]"); }
}
}
}
</script>
document.writeln("executing dynamic JS from: @Request.RawUrl...")
@if (Model != null)
{
@: { document.writeln("Message passed to Model: @Model"); }
}
@if (Request.QueryString.HasKeys())
{
foreach (string key in Request.QueryString.Keys)
{
@:{ document.writeln("Query string parameter. Key: @key , Value: @Request.QueryString[key]"); }
}
}
}
</script>
As you see this JS is a function renders to current document model and query string parameters which were specified during rendering the JS content.
~/Views/Scripts/OtherDynamic.js.cshtml:
~/Views/Scripts/OtherDynamic.js.cshtml:
<script> function OtherDynamicJsFunction() {
document.writeln("executing Other dynamic JS from: @Request.RawUrl...
")document.writeln("executing Other dynamic JS from: @Request.RawUrl...
@if (Request.QueryString.HasKeys())
{
foreach (string key in Request.QueryString.Keys)
{
@:{ document.writeln("Query string parameter. Key: @key , Value: @Request.QueryString[key]"); }
}
}
}
</script>
This function does not use model, but also renders current query string parameters to Html document.
Creating Scripts and Styles Controllers
I’m creating two controllers which handles requests coming to ~/Styles/* and ~/Scripts/* paths.
public class StylesController : Controller { public ActionResult Index() { return Content("Styles folder"); } protected override void HandleUnknownAction(string actionName) { var res = this.CssFromView(actionName); res.ExecuteResult(ControllerContext); } }
Since I don't want to register action for every single CSS and JS file, I override HandleUnknownAction to handle all requests to controller that were not associated with declared action.
public class ScriptsController : Controller { [ActionName("DynamicWithModel.js")] public ActionResult Dynamic(string message) { return this.JavaScriptFromView(model:message); } public ActionResult Index() { return Content("Scripts folder"); } protected override void HandleUnknownAction(string actionName) { var res = this.JavaScriptFromView(); res.ExecuteResult(ControllerContext); } }
For DynamicWithModel.js i want to pass model, retrieving it from input parameter. In this case since I cannot create method containing dot in a name, I have to use attribute ActionName (alternatively I can avoid using dots in resource names in Index.chtml).
In order to make these controllers handle requests with file extensions in URL you must modify your web.config:
<system.webServer> <modules runAllManagedModulesForAllRequests="true"/>
Implementing the "Magic" extensions:
As you might notice I used custom controller extensions JavaScriptFromView and CssFromView - these guys do all the magic. Here is it's implementation:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Web; namespace System.Web.Mvc { ///
/// Mvc extensions for dynamic CSS and JS ///
public static class MvcExtensions {
///
/// "controller">current controller
/// "cssViewName">view name, which contains partial view with one STYLE block only
/// "model">optional model to pass to partial view for rendering
///
public static ActionResult CssFromView(this Controller controller, string cssViewName=null, object model=null)
{
var cssContent = ParseViewToContent(controller,cssViewName, "style", model);
if(cssContent==null) throw new HttpException(404,"CSS not found");
return new ContentResult() { Content = cssContent, ContentType = "text/css" };
}
///
/// "controller">current controller
/// "javascriptViewName">view name, which contains partial view with one SCRIPT block only
/// "model">optional model to pass to partial view for rendering
///
public static ActionResult JavaScriptFromView(this Controller controller, string javascriptViewName=null, object model=null)
{
var jsContent = ParseViewToContent(controller,javascriptViewName, "script", model);
if(jsContent==null) throw new HttpException(404,"JS not found");
return new JavaScriptResult() {Script = jsContent };
}
///
/// "controller">controller which renders the view
/// "viewName">name of cshtml file with content. If null, then actionName used
/// "tagName">Content rendered expected to be wrapped with this html tag, and it will be trimmed from result
/// "model">model to pass for view to render
///
static string ParseViewToContent(Controller controller, string viewName, string tagName, object model = null)
{
using (var viewContentWriter = new StringWriter())
{
if (model != null)
controller.ViewData.Model = model;
if (string.IsNullOrEmpty(viewName))
viewName = controller.RouteData.GetRequiredString("action");
var viewResult = new ViewResult()
{
ViewName = viewName,
ViewData = controller.ViewData,
TempData = controller.TempData,
ViewEngineCollection = controller.ViewEngineCollection
};
var viewEngineResult = controller.ViewEngineCollection.FindPartialView(controller.ControllerContext, viewName);
if (viewEngineResult.View == null)
return null;
try {
var viewContext = new ViewContext(controller.ControllerContext, viewEngineResult.View, controller.ViewData, controller.TempData, viewContentWriter);
viewEngineResult.View.Render(viewContext, viewContentWriter);
var viewString = viewContentWriter.ToString().Trim('\r', '\n', ' ');
var regex = string.Format("<{0}[^>]*>(.*?)</{0}>", tagName);
var res = Regex.Match(viewString, regex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline);
if (res.Success && res.Groups.Count > 1)
return res.Groups[1].Value;
else throw new InvalidProgramException(string.Format("Dynamic content produced by viewResult '{0}' expected to be wrapped in '{1}' tag", viewName, tagName));
}
finally {
if (viewEngineResult.View != null)
viewEngineResult.ViewEngine.ReleaseView(controller.ControllerContext, viewEngineResult.View);
}
}
}
}
}
Show time
As you can see you can modify query string parameters in runtime using IE Developer Tools:
and see results immediately:
in Network tab you can see actual responses and low level information:
Dynamic Javascript response:
Again, you can download source code from here.
Additional tricks:
Since you can add inline code actually inline you probably won't need it, but You also can use those controllers' actions as child actions to render inline resources if you need using Html.RenderAction/ Html.Action(...) extensions on your parent HTML view. If you do this make sure your Response.ContentType is not overwritten by Render action and if it was you need manually to restore it.
FileHandler VS Controller competition for handling file request
If you noticed there is a competition between Static file Handler and your Controller for handling requests to resource file (js/css). So it is important to understand how it works. So here it is:
By default StaticFileHandler has priority. This means if you create file ~/Views/Scripts/static.js.cshtml, and you already have file ~/Scripts/static.js - the last one (real static) will be used. But you can change this behavior using Routing. You need to do is to add in your RouteConfig.cs (located in App_Start for MVC4 template) the following line:
routes.RouteExistingFiles = true;
It will deactivate Static File handler and redirect all file requests to Controllers.
Subscribe to:
Posts (Atom)