Answered by:
Highlight changes in DataGrid bount to Entity EF

Question
-
All,
I have an Entity that has a DataGrid bound to it. What I'd like to do is, every time the user adds a record or modifies a record, have that datagridrow specially formatted as a cue to the end-user.
Note that I've already tried implimenting iNotifyPropertyChanged in the EntityModel, and using a converter that uses dbContext.ChangeTracker to enumerate through all the entities, but the issue with this approach (besides being a performance killer because of all the enumeration involved) is that, unless the end-user sorts the datagrid, only new items (i.e. not modified) get highlighted.
I'm thinking the best approach would be to simply add a Modified property to each entity and then use a DataTrigger, but was wondering if there was a better approach.
Thanks in advance.
Tuesday, October 9, 2012 8:57 PM
Answers
-
Oh yes, one other way: Per Sheldon Xiao...
<DataGrid Name="datagrid" AutoGenerateColumns="False" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"> <DataGrid.Columns> <DataGridTextColumn Header="Title" Binding="{Binding Path=Name,NotifyOnTargetUpdated=True}" Width="300"> <DataGridTextColumn.EditingElementStyle> <Style TargetType="{x:Type TextBox}"> <EventSetter Event="LostFocus" Handler="Qty_LostFocus" /> <EventSetter Event="TextChanged" Handler="TextBox_TextChanged" /> <EventSetter Event="Binding.TargetUpdated" Handler="DataGridTextColumn_TargetUpdated"></EventSetter> </Style> </DataGridTextColumn.EditingElementStyle> </DataGridTextColumn> </DataGrid.Columns> </DataGrid>
JP Cowboy Coders Unite!
- Edited by Mr. Javaman II Tuesday, October 9, 2012 10:24 PM
- Marked as answer by Sheldon _Xiao Thursday, October 25, 2012 9:52 AM
Tuesday, October 9, 2012 10:22 PM -
Okay. It took some doing, but I found a way (hack?) that seems to work with MVVM.
In the Entity, I add the following:
public partial class MyEntity:INotifyPropertyChanged { internal static System.Data.Entity.DbContext MyDBContext { get; set; } public System.Data.EntityState MyState { get { return MyDBContext.ChangeTracker.Entries<MyEntity>().Where(a => a.Entity == this).First().State; } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged() { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("MyState")); } }
In addition, I create private variables for each property, change the get accessor for each property to associate with the variable, change the set accessor to set value to the variable and invoke OnPropertyChanged().
Then, in XAML:
<Style TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}"> <Style.Triggers> <DataTrigger Binding="{Binding Path=MyState, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:EntityState.Added}"> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontStyle" Value="Italic" /> </DataTrigger> <DataTrigger Binding="{Binding Path=MyState, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:EntityState.Detached}"> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontStyle" Value="Italic" /> </DataTrigger> <DataTrigger Binding="{Binding Path=MyState, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:EntityState.Modified}"> <Setter Property="FontWeight" Value="Bold" /> </DataTrigger> </Style.Triggers>
Note that I haven't figured out what I'll do for SaveChanges yet, but my guess is that I'll need to create an internal Action property to expose OnPropertyChanged as delegate (or maybe set this to be internal, scary) and enumerate through each Entry (nongeneric) prior to save. But that's the price we pay I guess when EF templates are incomplete.
Now if we could only get the MS people to add this as an enhancement to EF (or at least not override classes when Updating Model From Database).
Meanwhile, the wheels are turning on what may be a more sustainable approach so that when I do update from DB I'm not up $%!+ creek without a paddle.
Anyway, hope this helps someone else that has a similar business need.
- Edited by whiteheadw Tuesday, October 9, 2012 11:57 PM
- Marked as answer by Sheldon _Xiao Thursday, October 25, 2012 9:52 AM
Tuesday, October 9, 2012 11:57 PM
All replies
-
Interesting let me research this and post back later.
JP Cowboy Coders Unite!
Tuesday, October 9, 2012 10:06 PM -
Ok here it is, our good frien Colin Eberhart exposes how DataGrid binding really works in conjunction with CellEdits. Take a look here:
http://www.scottlogic.co.uk/blog/colin/2009/01/wpf-datagrid-committing-changes-cell-by-cell/
He shows how to commit changes by listening for the CellEditEnd event. He then uses this class to get additional information:
http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridcelleditendingeventargs.aspx
If you can get access to the currentcell being edited and EditEnd time, you would have these properties to work with:
http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridcell.aspx
Or even the ability to apply a style. Of course at some point you'd want to undo them at commit time.
JP Cowboy Coders Unite!
Tuesday, October 9, 2012 10:14 PM -
And finally, you may only need to watch this one...
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.cellvaluechanged.aspx
JP Cowboy Coders Unite!
Tuesday, October 9, 2012 10:19 PM -
That's great, but is there a way to accomplish this via MVVM? I don't want to have to micro-manage styles at the view and try and figure out when I need to reset them back, but rather have the view determine the style based on some property in my ViewModel (which contains my EF as DAL) which it binds to (either directly or through an iValueConverter).Tuesday, October 9, 2012 10:22 PM
-
Oh yes, one other way: Per Sheldon Xiao...
<DataGrid Name="datagrid" AutoGenerateColumns="False" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"> <DataGrid.Columns> <DataGridTextColumn Header="Title" Binding="{Binding Path=Name,NotifyOnTargetUpdated=True}" Width="300"> <DataGridTextColumn.EditingElementStyle> <Style TargetType="{x:Type TextBox}"> <EventSetter Event="LostFocus" Handler="Qty_LostFocus" /> <EventSetter Event="TextChanged" Handler="TextBox_TextChanged" /> <EventSetter Event="Binding.TargetUpdated" Handler="DataGridTextColumn_TargetUpdated"></EventSetter> </Style> </DataGridTextColumn.EditingElementStyle> </DataGridTextColumn> </DataGrid.Columns> </DataGrid>
JP Cowboy Coders Unite!
- Edited by Mr. Javaman II Tuesday, October 9, 2012 10:24 PM
- Marked as answer by Sheldon _Xiao Thursday, October 25, 2012 9:52 AM
Tuesday, October 9, 2012 10:22 PM -
ummm even another way:
I believe you could put a trigger in XAML for CellChanged but am not sure how.
JP Cowboy Coders Unite!
Tuesday, October 9, 2012 10:25 PM -
Okay. It took some doing, but I found a way (hack?) that seems to work with MVVM.
In the Entity, I add the following:
public partial class MyEntity:INotifyPropertyChanged { internal static System.Data.Entity.DbContext MyDBContext { get; set; } public System.Data.EntityState MyState { get { return MyDBContext.ChangeTracker.Entries<MyEntity>().Where(a => a.Entity == this).First().State; } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged() { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("MyState")); } }
In addition, I create private variables for each property, change the get accessor for each property to associate with the variable, change the set accessor to set value to the variable and invoke OnPropertyChanged().
Then, in XAML:
<Style TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}"> <Style.Triggers> <DataTrigger Binding="{Binding Path=MyState, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:EntityState.Added}"> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontStyle" Value="Italic" /> </DataTrigger> <DataTrigger Binding="{Binding Path=MyState, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:EntityState.Detached}"> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontStyle" Value="Italic" /> </DataTrigger> <DataTrigger Binding="{Binding Path=MyState, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:EntityState.Modified}"> <Setter Property="FontWeight" Value="Bold" /> </DataTrigger> </Style.Triggers>
Note that I haven't figured out what I'll do for SaveChanges yet, but my guess is that I'll need to create an internal Action property to expose OnPropertyChanged as delegate (or maybe set this to be internal, scary) and enumerate through each Entry (nongeneric) prior to save. But that's the price we pay I guess when EF templates are incomplete.
Now if we could only get the MS people to add this as an enhancement to EF (or at least not override classes when Updating Model From Database).
Meanwhile, the wheels are turning on what may be a more sustainable approach so that when I do update from DB I'm not up $%!+ creek without a paddle.
Anyway, hope this helps someone else that has a similar business need.
- Edited by whiteheadw Tuesday, October 9, 2012 11:57 PM
- Marked as answer by Sheldon _Xiao Thursday, October 25, 2012 9:52 AM
Tuesday, October 9, 2012 11:57 PM -
I like your solution, it's even another way to go! I too am not sure how to undo the formating on the datatriggers after the save. Maybe a new read of data?
JP Cowboy Coders Unite!
Wednesday, October 10, 2012 2:24 AM -
I took the same principles and came up with this more sustainable solution (for anyone interested). Keep in mind that I'm new to TT4 templates (I literally reversed engineered the default ones today to figure out syntax).
First, I created a base class:
public abstract class cEntityBase:INotifyPropertyChanged { public cEntityBase() { } public static DbContext MyContext { get; set; } public EntityState MyState { get { return MyContext.ChangeTracker.Entries().Where(a => a.Entity == this).First().State; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged() { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("MyState")); } }
Then, I hacked the T4 template that generates my entities from my edmx file:
<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="EF.Utility.CS.ttinclude"#><#@ output extension=".cs"#><# const string inputFile = @"MyEDMXFile.edmx"; var textTransform = DynamicTextTransformation.Create(this); var code = new CodeGenerationTools(this); var ef = new MetadataTools(this); var typeMapper = new TypeMapper(code, ef, textTransform.Errors); var fileManager = EntityFrameworkTemplateFileManager.Create(this); var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) { return string.Empty; } WriteHeader(codeStringGenerator, fileManager); foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { fileManager.StartNewFile(entity.Name + ".cs"); BeginNamespace(code); #> <#=codeStringGenerator.UsingDirectives(inHeader: false)#> <#=codeStringGenerator.EntityClassOpening(entity)#> { <# var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity); var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity); var complexProperties = typeMapper.GetComplexProperties(entity); if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any()) { #> public <#=code.Escape(entity)#>() { <# foreach (var edmProperty in propertiesWithDefaultValues) { #> this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>; <# } foreach (var navigationProperty in collectionNavigationProperties) { #> this.<#=code.Escape(navigationProperty)#> = new ObservableCollection<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>(); <# } foreach (var complexProperty in complexProperties) { #> this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>(); <# } #> } <# } var simpleProperties = typeMapper.GetSimpleProperties(entity); if (simpleProperties.Any()) { foreach (var edmProperty in simpleProperties) { #> <#=codeStringGenerator.PrivateVariable(edmProperty)#> //added <#=codeStringGenerator.Property(edmProperty)#> <# } } if (complexProperties.Any()) { #> <# foreach(var complexProperty in complexProperties) { #> <#=codeStringGenerator.Property(complexProperty)#> <# } } var navigationProperties = typeMapper.GetNavigationProperties(entity); if (navigationProperties.Any()) { #> <# foreach (var navigationProperty in navigationProperties) { #> <#=codeStringGenerator.NavigationProperty(navigationProperty)#> <# } } #> } <# EndNamespace(code); } foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection)) { fileManager.StartNewFile(complex.Name + ".cs"); BeginNamespace(code); #> <#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#> <#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> { <# var complexProperties = typeMapper.GetComplexProperties(complex); var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex); if (propertiesWithDefaultValues.Any() || complexProperties.Any()) { #> public <#=code.Escape(complex)#>() { <# foreach (var edmProperty in propertiesWithDefaultValues) { #> this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>; <# } foreach (var complexProperty in complexProperties) { #> this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>(); <# } #> } <# } var simpleProperties = typeMapper.GetSimpleProperties(complex); if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> <#=codeStringGenerator.Property(edmProperty)#> <# } } if (complexProperties.Any()) { #> <# foreach(var edmProperty in complexProperties) { #> <#=codeStringGenerator.Property(edmProperty)#> <# } } #> } <# EndNamespace(code); } foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection)) { fileManager.StartNewFile(enumType.Name + ".cs"); BeginNamespace(code); #> <#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#> <# if (typeMapper.EnumIsFlags(enumType)) { #> [Flags] <# } #> <#=codeStringGenerator.EnumOpening(enumType)#> { <# var foundOne = false; foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType)) { foundOne = true; #> <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>, <# } if (foundOne) { this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1); } #> } <# EndNamespace(code); } fileManager.Process(); #> <#+ public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager) { fileManager.StartHeader(); #> //------------------------------------------------------------------------------ // <auto-generated> // <#=GetResourceString("Template_GeneratedCodeCommentLine1")#> // // <#=GetResourceString("Template_GeneratedCodeCommentLine2")#> // <#=GetResourceString("Template_GeneratedCodeCommentLine3")#> // </auto-generated> //------------------------------------------------------------------------------ <#=codeStringGenerator.UsingDirectives(inHeader: true)#> <#+ fileManager.EndBlock(); } public void BeginNamespace(CodeGenerationTools code) { var codeNamespace = code.VsNamespaceSuggestion(); if (!String.IsNullOrEmpty(codeNamespace)) { #> namespace <#=code.EscapeNamespace(codeNamespace)#> { <#+ PushIndent(" "); } } public void EndNamespace(CodeGenerationTools code) { if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion())) { PopIndent(); #> } <#+ } } public const string TemplateId = "CSharp_DbContext_Types_EF5"; public class CodeStringGenerator { private readonly CodeGenerationTools _code; private readonly TypeMapper _typeMapper; private readonly MetadataTools _ef; public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef) { ArgumentNotNull(code, "code"); ArgumentNotNull(typeMapper, "typeMapper"); ArgumentNotNull(ef, "ef"); _code = code; _typeMapper = typeMapper; _ef = ef; } public string Property(EdmProperty edmProperty) //This has been modified { return string.Format( CultureInfo.InvariantCulture, "{0} {1} {2} {{ {3}get{{return _{2};}} {4}set{{_{2}=value;OnPropertyChanged(); }} }}", Accessibility.ForProperty(edmProperty), _typeMapper.GetTypeName(edmProperty.TypeUsage), _code.Escape(edmProperty), _code.SpaceAfter(Accessibility.ForGetter(edmProperty)), _code.SpaceAfter(Accessibility.ForSetter(edmProperty))); } public string PrivateVariable(EdmProperty edmProperty) //This method was added to generate private variables { return string.Format( CultureInfo.InvariantCulture, "private {0} _{1};", _typeMapper.GetTypeName(edmProperty.TypeUsage), _code.Escape(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) //Here's where I instruct to inherit from cEntityBase { return string.Format( CultureInfo.InvariantCulture, "{0} {1}partial class {2}{3}", Accessibility.ForType(entity), _code.SpaceAfter(_code.AbstractOption(entity)), _code.Escape(entity), string.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType))?" : cEntityBase": _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;\nusing System.Collections.ObjectModel;{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); } #>
Then in my other T4 template (the one that generates my context) I add:
<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="EF.Utility.CS.ttinclude"#><#@ output extension=".cs"#><# const string inputFile = @"MyEDMXFile.edmx"; //or whatever the heck I called it before var textTransform = DynamicTextTransformation.Create(this); var code = new CodeGenerationTools(this); var ef = new MetadataTools(this); 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(); if (container == null) { return string.Empty; } #> //------------------------------------------------------------------------------ // <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; <# 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 { public <#=code.Escape(container)#>() : base("<#=container.Name#>") { cEntityBase.MyContext = this; //Added this line :) <# if (!loader.IsLazyLoadingEnabled(container)) { #> this.Configuration.LazyLoadingEnabled = false; <# } #> } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } <# foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>()) { #> <#=codeStringGenerator.DbSet(entitySet)#> <# } foreach (var edmFunction in container.FunctionImports) { 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) { if (typeMapper.IsComposable(edmFunction)) { #> [EdmFunction("<#=edmFunction.NamespaceName#>", "<#=edmFunction.Name#>")] <#=codeStringGenerator.ComposableFunctionMethod(edmFunction, modelNamespace)#> { <#+ codeStringGenerator.WriteFunctionParameters(edmFunction, WriteFunctionParameter); #> <#=codeStringGenerator.ComposableCreateQuery(edmFunction, modelNamespace)#> } <#+ } else { #> <#=codeStringGenerator.FunctionMethod(edmFunction, modelNamespace, includeMergeOption)#> { <#+ codeStringGenerator.WriteFunctionParameters(edmFunction, WriteFunctionParameter); #> <#=codeStringGenerator.ExecuteFunction(edmFunction, modelNamespace, includeMergeOption)#> } <#+ if (typeMapper.GenerateMergeOptionFunction(edmFunction, includeMergeOption)) { WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: true); } } } 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 { 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); } #>
And that's it. Now every time I update my model, my entities will automatically NotifyPropertyChanged. Best part is, if I can do this after just learning (and reverse engineering) Microsoft's TT4 stuff, then there's no reason that MS can't include this (or some variant thereof) as part of their solution.- Edited by whiteheadw Thursday, October 11, 2012 4:34 PM Fixed issue with cEntityBase inheritence and entities derived from other entitites.
Wednesday, October 10, 2012 9:20 PM -
One other neat thing about this approach is that I can add empty methods to my cEntityBase class, have certain Setter Properties (or whatever properties/methods) invoke my base class methods, and then extend my generated Entity classes via Partial Class to override the empty methods in my Base Class (if needed). Kind of like building an elaborate system of ropes and pullies to hammer a nail into place, but I'm kind of used to it by now (and besides, so far this this seems to be the most sustainable solution, as once setup, it becomes relatively easy to extend an entity class via partial class).
Anyway, I know this thread has gone a little OT by now (as I'm just realizing the best known method (at least to me) of adding functionality to my entities in a scalable way, but since this started as an MVVM approach to highlight modified records (which eventually led to a critic of and consequent improvements over out of the box EF), it made sense to post my followup findings here.
Cheers.
- Proposed as answer by Mr. Javaman II Thursday, October 11, 2012 11:19 AM
Wednesday, October 10, 2012 11:30 PM -
Hi whiteheadw,
Based on your description, i think you have resolved your original post issue, if so, could i close your thread as "Answered".
best regards,
Sheldon _Xiao[MSFT]
MSDN Community Support | Feedback to us
Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Thursday, October 11, 2012 6:33 AM -
Hey WhiteHeadW;
That is surely a great bit of work you did there. I just wanted to point out a few extra tips. For Entity Framework work in general there is one additional thing that can help (but I do like how you worked with T4). I stumbled on this when doing work a few years back with RIA and EF over in the now dead Silverlight platform.
Take a look at the provided partial methods within EF.
http://www.codeproject.com/Articles/471643/AplusBeginner-27splusTutorialplusonplusPerformingp
Partial methods then are stubs for implementation in other partial classes of the same entity. The coolest thing about it is that you can regenerate any table you want at will (and similar to what a T4 solution would do) the partial methods implementation are never touched by the regeneration! It the hook MSFT gave us to implement untouchable code (by the generator).
JP Cowboy Coders Unite!
Thursday, October 11, 2012 11:19 AM -
Thank you. I use partial classes as well, but the benifit of altering the T4 template to make each entity inherit from a base class is that I can add virtual (non-abstract) empty methods to my base class and invoke them when certain conditions are met (i.e. when a property is set). In most cases, the empty method is fired, but I can in a partial class override my virtual method so that that particular entity is customized.
I also use partial classes to create special properties that combine data from two tables (based on an entity's navigation properties).
With that said, there is one thing I missed in the T4 template (and that's because I don't have any entities being inherited by other entities, so it wouldn't have mattered as much to me anyway), and that's that I neglected to account for entities in the .edmx model that are derived from other entities. I've since fixed that in my project, and will update this thread shortly.
Thursday, October 11, 2012 4:28 PM -
Oh, I misread. I thought you said partial classes. Well, I guess it's the same idea, but either way, it still involves changing the default template so that the partial method (which in my case I'd probably define in my base class anyway, similar to what I am currently doing) gets invoked. Anyway, the error I made in my T4 template has been fixed. Thanks all for your help. Perhaps if someone knows one of the MS EF developers, we could get INotifyPropertyChanged added to the default EF templates going forward, as well as some nifty partial methods that each get and set property would invoke. This way, developers can spend less time trying to figure out MS T4 templates and more time in useful development.
- Edited by whiteheadw Thursday, October 11, 2012 4:42 PM
Thursday, October 11, 2012 4:41 PM