From de3004433264f863999d98a80a50ca38b68d2433 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 14 Feb 2015 23:13:01 +0000 Subject: [PATCH 01/45] Failing test for enum on complex type issue #17 --- EFTests/EFTests.csproj | 2 ++ EFTests/Model/Eon.cs | 11 +++++++++++ EFTests/Model/Geology.cs | 10 ++++++++++ EFTests/Model/Warren.cs | 2 ++ EFTests/Tests/ModelParsingTests.cs | 2 ++ 5 files changed, 27 insertions(+) create mode 100644 EFTests/Model/Eon.cs create mode 100644 EFTests/Model/Geology.cs diff --git a/EFTests/EFTests.csproj b/EFTests/EFTests.csproj index 264c2e2..f305cc7 100644 --- a/EFTests/EFTests.csproj +++ b/EFTests/EFTests.csproj @@ -68,6 +68,8 @@ + + diff --git a/EFTests/Model/Eon.cs b/EFTests/Model/Eon.cs new file mode 100644 index 0000000..ae10cbc --- /dev/null +++ b/EFTests/Model/Eon.cs @@ -0,0 +1,11 @@ +namespace EFTests.Model +{ + /// + /// Only findable via the complex type Geology + /// + public enum Eon + { + Old, + ReallyOld, + } +} diff --git a/EFTests/Model/Geology.cs b/EFTests/Model/Geology.cs new file mode 100644 index 0000000..4272653 --- /dev/null +++ b/EFTests/Model/Geology.cs @@ -0,0 +1,10 @@ +namespace EFTests.Model +{ + public class Geology + { + // no id to make this a complex type to be included in the Warren + public string Soil { get; set; } + public int Density { get; set; } + public Eon? Eon { get; set; } + } +} diff --git a/EFTests/Model/Warren.cs b/EFTests/Model/Warren.cs index 2a1d272..902c8f1 100644 --- a/EFTests/Model/Warren.cs +++ b/EFTests/Model/Warren.cs @@ -12,5 +12,7 @@ public class Warren public Heat? HowHot { get; set; } public ICollection HopesAndDreams { get; set; } + + public Geology Geology { get; set; } } } diff --git a/EFTests/Tests/ModelParsingTests.cs b/EFTests/Tests/ModelParsingTests.cs index 32e54a2..32ebbc0 100644 --- a/EFTests/Tests/ModelParsingTests.cs +++ b/EFTests/Tests/ModelParsingTests.cs @@ -34,6 +34,8 @@ public void FindsReferences() Assert.IsNotNull(ears, "TehEars ref not found"); var echos = references.SingleOrDefault(r => r.ReferencingField == "EchoType"); Assert.IsNotNull(echos, "EchoType ref not found"); + var eons = references.SingleOrDefault(r => r.EnumType == typeof(Eon)); + Assert.IsNotNull(eons, "Eon ref not found"); Assert.IsTrue(references.All(r => r.EnumType.IsEnum), "Non-enum type found"); Assert.AreEqual(8, references.Count); } From ad09f54cd6b804e0ea0bd1aef1d3058d97022bee Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 18:27:57 +0000 Subject: [PATCH 02/45] comments - test model documentation --- EFTests/Model/Warren.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EFTests/Model/Warren.cs b/EFTests/Model/Warren.cs index 902c8f1..6e60bc3 100644 --- a/EFTests/Model/Warren.cs +++ b/EFTests/Model/Warren.cs @@ -13,6 +13,7 @@ public class Warren public ICollection HopesAndDreams { get; set; } + // complex type: public Geology Geology { get; set; } } } From 43d6a278c0c6cfe6fbc80fd789912f0bc9db9725 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 18:30:22 +0000 Subject: [PATCH 03/45] stub finding of complex types in model graph --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 4c7f095..d9f0c1c 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -215,6 +215,10 @@ where property.IsEnumType ReferencingField = property.Name, EnumType = objectItemCollection.GetClrType(property.EnumType), }); + var complexProperties = from entity in metadata.GetItems(DataSpace.OSpace) + from property in entity.Properties + where property.IsComplexType + select property; // will also need ref to entity return enumReferences .Where(r => r.ReferencingTable != null) // filter out child-types in Table-per-Hierarchy model .ToList(); From c0b811cd992fc927ec795beeaaa424fa7f9ec940 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 18:35:50 +0000 Subject: [PATCH 04/45] find enum in complex type --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index d9f0c1c..140d1c1 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -218,7 +218,8 @@ where property.IsEnumType var complexProperties = from entity in metadata.GetItems(DataSpace.OSpace) from property in entity.Properties where property.IsComplexType - select property; // will also need ref to entity + select property.ComplexType.Properties.Where( p => p.IsEnumType ); // will also need ref to entity + // recurse? return enumReferences .Where(r => r.ReferencingTable != null) // filter out child-types in Table-per-Hierarchy model .ToList(); From d3dd14f5a45f75f75c88aa216d9a293a33d19afe Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 19:00:30 +0000 Subject: [PATCH 05/45] hunt through complex types recursively looking for more enums --- .../LookupGenerator/EnumToLookup.cs | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 140d1c1..0fe3faf 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -205,24 +205,50 @@ internal IList FindReferences(DbContext context) // Get the part of the model that contains info about the actual CLR types var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); // OSpace = Object Space + var entities = metadata.GetItems(DataSpace.OSpace); + // find and return all the references to enum types - var enumReferences = (from entity in metadata.GetItems(DataSpace.OSpace) - from property in entity.Properties - where property.IsEnumType - select new EnumReference + var references = new List(); + foreach (var entityType in entities) + { + var referencingTable = GetTableName(metadata, entityType); + + // filter out child-types in Table-per-Hierarchy model + if (referencingTable == null) { - ReferencingTable = GetTableName(metadata, entity), - ReferencingField = property.Name, - EnumType = objectItemCollection.GetClrType(property.EnumType), - }); - var complexProperties = from entity in metadata.GetItems(DataSpace.OSpace) - from property in entity.Properties - where property.IsComplexType - select property.ComplexType.Properties.Where( p => p.IsEnumType ); // will also need ref to entity - // recurse? - return enumReferences - .Where(r => r.ReferencingTable != null) // filter out child-types in Table-per-Hierarchy model - .ToList(); + continue; + } + + references.AddRange(ProcessEdmProperties(entityType.Properties, referencingTable, objectItemCollection)); + } + return references; + } + + private static IEnumerable ProcessEdmProperties(IEnumerable properties, string referencingTable, ObjectItemCollection objectItemCollection, string fieldPrefix = "") + { + var references = new List(); + foreach (var edmProperty in properties) + { + if (edmProperty.IsEnumType) + { + references.Add(new EnumReference + { + ReferencingTable = referencingTable, + ReferencingField = fieldPrefix + edmProperty.Name, + EnumType = objectItemCollection.GetClrType(edmProperty.EnumType), + }); + continue; + } + if (edmProperty.IsComplexType) + { + // recurse, keeping a reference to the outer entityType + // todo: figure out the actual field name of the complex type + var prefix = fieldPrefix + edmProperty.Name + "_"; + references.AddRange( + ProcessEdmProperties(edmProperty.ComplexType.Properties,referencingTable,objectItemCollection, prefix)); + } + } + return references; } private static string GetTableName(MetadataWorkspace metadata, EntityType entityType) From 01a9a88b8ac0a8c08802126c65b953c2bd9aa68d Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 19:01:45 +0000 Subject: [PATCH 06/45] add new reference to ref count test --- EFTests/Tests/ModelParsingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EFTests/Tests/ModelParsingTests.cs b/EFTests/Tests/ModelParsingTests.cs index 32ebbc0..e797c99 100644 --- a/EFTests/Tests/ModelParsingTests.cs +++ b/EFTests/Tests/ModelParsingTests.cs @@ -37,7 +37,7 @@ public void FindsReferences() var eons = references.SingleOrDefault(r => r.EnumType == typeof(Eon)); Assert.IsNotNull(eons, "Eon ref not found"); Assert.IsTrue(references.All(r => r.EnumType.IsEnum), "Non-enum type found"); - Assert.AreEqual(8, references.Count); + Assert.AreEqual(9, references.Count); } [Test] From 132725a1b0968c951fdcecb33677f7a70a39e152 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 19:38:04 +0000 Subject: [PATCH 07/45] comments on recursion limit --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 0fe3faf..ae5ce8f 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -242,6 +242,7 @@ private static IEnumerable ProcessEdmProperties(IEnumerable Date: Mon, 16 Feb 2015 19:42:15 +0000 Subject: [PATCH 08/45] code commenting --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index ae5ce8f..f647af6 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -224,6 +224,14 @@ internal IList FindReferences(DbContext context) return references; } + /// + /// Recurse through all the specified properties, including the children of any complex type properties looking for enum types. + /// + /// The properties to search. + /// The referencing table name to add to the returned references. + /// For looking up ClrTypes + /// Optional. The prefix to add to fields in the returned references, used when handling complex types. + /// All the references that were found private static IEnumerable ProcessEdmProperties(IEnumerable properties, string referencingTable, ObjectItemCollection objectItemCollection, string fieldPrefix = "") { var references = new List(); @@ -241,10 +249,10 @@ private static IEnumerable ProcessEdmProperties(IEnumerable Date: Mon, 16 Feb 2015 19:47:39 +0000 Subject: [PATCH 09/45] Extend complexType test to cover aliased columns --- EFTests/Model/Geology.cs | 5 +++++ EFTests/Tests/ModelParsingTests.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/EFTests/Model/Geology.cs b/EFTests/Model/Geology.cs index 4272653..0b73586 100644 --- a/EFTests/Model/Geology.cs +++ b/EFTests/Model/Geology.cs @@ -1,3 +1,5 @@ +using System.ComponentModel.DataAnnotations.Schema; + namespace EFTests.Model { public class Geology @@ -6,5 +8,8 @@ public class Geology public string Soil { get; set; } public int Density { get; set; } public Eon? Eon { get; set; } + + [Column("PreviousEon")] // supress the "Geology_" prefix that you'd normally get + public Eon? PreviousEon { get; set; } } } diff --git a/EFTests/Tests/ModelParsingTests.cs b/EFTests/Tests/ModelParsingTests.cs index e797c99..bc444a6 100644 --- a/EFTests/Tests/ModelParsingTests.cs +++ b/EFTests/Tests/ModelParsingTests.cs @@ -37,7 +37,7 @@ public void FindsReferences() var eons = references.SingleOrDefault(r => r.EnumType == typeof(Eon)); Assert.IsNotNull(eons, "Eon ref not found"); Assert.IsTrue(references.All(r => r.EnumType.IsEnum), "Non-enum type found"); - Assert.AreEqual(9, references.Count); + Assert.AreEqual(10, references.Count); } [Test] From 10869a9d15ae6c2e6aa640eddb25816d8849bc71 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Mon, 16 Feb 2015 20:00:53 +0000 Subject: [PATCH 10/45] Test an aliased enum property issue #19 --- EFTests/EFTests.csproj | 1 + EFTests/Model/Pedigree.cs | 9 +++++++++ EFTests/Model/Rabbit.cs | 7 ++++++- EFTests/Tests/ModelParsingTests.cs | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 EFTests/Model/Pedigree.cs diff --git a/EFTests/EFTests.csproj b/EFTests/EFTests.csproj index f305cc7..00617ad 100644 --- a/EFTests/EFTests.csproj +++ b/EFTests/EFTests.csproj @@ -57,6 +57,7 @@ + diff --git a/EFTests/Model/Pedigree.cs b/EFTests/Model/Pedigree.cs new file mode 100644 index 0000000..d4c2f0b --- /dev/null +++ b/EFTests/Model/Pedigree.cs @@ -0,0 +1,9 @@ +namespace EFTests.Model +{ + public enum Pedigree + { + Dubious, + Pure, + Inbred + } +} diff --git a/EFTests/Model/Rabbit.cs b/EFTests/Model/Rabbit.cs index 998f848..e91f347 100644 --- a/EFTests/Model/Rabbit.cs +++ b/EFTests/Model/Rabbit.cs @@ -1,4 +1,6 @@ -namespace EFTests.Model +using System.ComponentModel.DataAnnotations.Schema; + +namespace EFTests.Model { public class Rabbit { @@ -11,5 +13,8 @@ public class Rabbit public Legs? SpeedyLegs { get; set; } public Relation? Offspring { get; set; } + + [Column("Lineage")] + public Pedigree Pedigree { get; set; } } } diff --git a/EFTests/Tests/ModelParsingTests.cs b/EFTests/Tests/ModelParsingTests.cs index bc444a6..da096c7 100644 --- a/EFTests/Tests/ModelParsingTests.cs +++ b/EFTests/Tests/ModelParsingTests.cs @@ -37,7 +37,7 @@ public void FindsReferences() var eons = references.SingleOrDefault(r => r.EnumType == typeof(Eon)); Assert.IsNotNull(eons, "Eon ref not found"); Assert.IsTrue(references.All(r => r.EnumType.IsEnum), "Non-enum type found"); - Assert.AreEqual(10, references.Count); + Assert.AreEqual(11, references.Count); } [Test] From 8d00a9b57564c761742316f981e95c157cd12ba8 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Tue, 17 Feb 2015 21:47:45 +0000 Subject: [PATCH 11/45] Refactor in preparation for using metadata to find column --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index f647af6..249f5d3 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -219,7 +219,7 @@ internal IList FindReferences(DbContext context) continue; } - references.AddRange(ProcessEdmProperties(entityType.Properties, referencingTable, objectItemCollection)); + references.AddRange(ProcessEdmProperties(entityType.Properties, referencingTable, objectItemCollection, metadata)); } return references; } @@ -230,9 +230,9 @@ internal IList FindReferences(DbContext context) /// The properties to search. /// The referencing table name to add to the returned references. /// For looking up ClrTypes - /// Optional. The prefix to add to fields in the returned references, used when handling complex types. + /// /// All the references that were found - private static IEnumerable ProcessEdmProperties(IEnumerable properties, string referencingTable, ObjectItemCollection objectItemCollection, string fieldPrefix = "") + private static IEnumerable ProcessEdmProperties(IEnumerable properties, string referencingTable, ObjectItemCollection objectItemCollection, MetadataWorkspace metadata) { var references = new List(); foreach (var edmProperty in properties) @@ -242,24 +242,28 @@ private static IEnumerable ProcessEdmProperties(IEnumerable Date: Tue, 17 Feb 2015 22:18:29 +0000 Subject: [PATCH 12/45] get all the spaces --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 249f5d3..02ede82 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -207,6 +207,12 @@ internal IList FindReferences(DbContext context) var entities = metadata.GetItems(DataSpace.OSpace); + var OSpace = metadata.GetItems(DataSpace.OSpace); + var OCSpace = metadata.GetItems(DataSpace.OCSpace); + var CSpace = metadata.GetItems(DataSpace.CSpace); + var CSSpace = metadata.GetItems(DataSpace.CSSpace); + var SSpace = metadata.GetItems(DataSpace.SSpace); + // find and return all the references to enum types var references = new List(); foreach (var entityType in entities) From 31dbf49992fc24880cec41268c471d3e442d11a2 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Tue, 17 Feb 2015 22:45:45 +0000 Subject: [PATCH 13/45] spike - finding a column in the metadata --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 02ede82..de8f608 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -211,7 +211,14 @@ internal IList FindReferences(DbContext context) var OCSpace = metadata.GetItems(DataSpace.OCSpace); var CSpace = metadata.GetItems(DataSpace.CSpace); var CSSpace = metadata.GetItems(DataSpace.CSSpace); - var SSpace = metadata.GetItems(DataSpace.SSpace); + var SSpace = metadata.GetItems(DataSpace.SSpace); + + //var CSItem = CSSpace.Single() + + var SItem = SSpace.Single(s => s.Name == "Rabbit"); + var SItemMem = SItem.MetadataProperties.Single(m => m.Name == "Members"); + var SItemMemCast = (ReadOnlyMetadataCollection)SItemMem.Value; + var lineageCol = SItemMemCast.Single(m => m.Name == "Lineage"); // find and return all the references to enum types var references = new List(); From 197c7ea526d27a6d461c8c4360de0c11f46fe094 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Tue, 17 Feb 2015 23:10:55 +0000 Subject: [PATCH 14/45] found the mappings --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index de8f608..cdf81f8 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -210,7 +210,9 @@ internal IList FindReferences(DbContext context) var OSpace = metadata.GetItems(DataSpace.OSpace); var OCSpace = metadata.GetItems(DataSpace.OCSpace); var CSpace = metadata.GetItems(DataSpace.CSpace); - var CSSpace = metadata.GetItems(DataSpace.CSSpace); + // have entity type in conceptual space + var CSSpace = metadata.GetItems(DataSpace.CSSpace) + .Single(); // the only container var SSpace = metadata.GetItems(DataSpace.SSpace); //var CSItem = CSSpace.Single() @@ -340,13 +342,13 @@ private static string GetTableName(MetadataWorkspace metadata, EntityType entity // Find the storage entity set (table) that the entity is mapped to var entityTypeMappings = mapping.EntityTypeMappings; var entityTypeMapping = entityTypeMappings.First(); // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping - var fragments = entityTypeMapping.Fragments; + var fragments = entityTypeMapping.Fragments; // <=== this contains the column mappings too. fragments.Items[x].PropertyMappings.Items[x].Column/Property if (fragments.Count() != 1) { throw new EnumGeneratorException(string.Format("{0} Fragments found.", fragments.Count())); } var table = fragments.Single().StoreEntitySet; - var tableName = (string)table.MetadataProperties["Table"].Value ?? table.Name; + var tableName = (string)table.MetadataProperties["Table"].Value ?? table.Name; // don't need to go into metadata, it's probably available via a cast return tableName; } catch (Exception exception) From d07239859704e0bb479c13c4aeb7d48b32677a2a Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Tue, 17 Feb 2015 23:47:35 +0000 Subject: [PATCH 15/45] completely rearrange mapping code - wip to allow fetching the required fragment once and then using it for mapping both column names and table names --- .../LookupGenerator/EnumToLookup.cs | 68 ++++++++++++++----- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index cdf81f8..b1c17b4 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -226,15 +226,15 @@ internal IList FindReferences(DbContext context) var references = new List(); foreach (var entityType in entities) { - var referencingTable = GetTableName(metadata, entityType); + var mappingFragment = FindSchemaMappingFragment(metadata, entityType); - // filter out child-types in Table-per-Hierarchy model - if (referencingTable == null) + // child types in TPH don't get mappings + if (mappingFragment == null) { continue; } - references.AddRange(ProcessEdmProperties(entityType.Properties, referencingTable, objectItemCollection, metadata)); + references.AddRange(ProcessEdmProperties(entityType.Properties, mappingFragment, objectItemCollection, metadata)); } return references; } @@ -243,11 +243,11 @@ internal IList FindReferences(DbContext context) /// Recurse through all the specified properties, including the children of any complex type properties looking for enum types. /// /// The properties to search. - /// The referencing table name to add to the returned references. + /// Information needed from ef metadata to map table and its columns /// For looking up ClrTypes /// /// All the references that were found - private static IEnumerable ProcessEdmProperties(IEnumerable properties, string referencingTable, ObjectItemCollection objectItemCollection, MetadataWorkspace metadata) + private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection, MetadataWorkspace metadata) { var references = new List(); foreach (var edmProperty in properties) @@ -256,8 +256,8 @@ private static IEnumerable ProcessEdmProperties(IEnumerable ProcessEdmProperties(IEnumerable m.Property.Name == edmProperty.Name).ToList(); + if (matches.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} matches found for property {1}", matches.Count(), edmProperty)); + } + var match = matches.Single(); + // wip // todo: read metadata mappings and find column return edmProperty.Name; } private static string GetTableName(MetadataWorkspace metadata, EntityType entityType) + { + + try + { + var mappingFragment = FindSchemaMappingFragment(metadata, entityType); + // child types in TPH don't get mappings + if (mappingFragment == null) + { + return null; + } + var columnMappings = mappingFragment.PropertyMappings; + var tableName = mappingFragment.StoreEntitySet.Table; + return tableName; + } + catch (Exception exception) + { + throw new EnumGeneratorException(string.Format("Error getting table name for entity type '{0}'", entityType.Name), exception); + } + } + + private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metadata, EntityType entityType) { // refs: // * http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/ @@ -295,7 +324,8 @@ private static string GetTableName(MetadataWorkspace metadata, EntityType entity .ToList(); if (entityTypes.Count() != 1) { - throw new EnumGeneratorException(string.Format("{0} entities of type {1} found in mapping.", entityTypes.Count(), entityType)); + throw new EnumGeneratorException(string.Format("{0} entities of type {1} found in mapping.", entityTypes.Count(), + entityType)); } var entityMetadata = entityTypes.Single(); @@ -325,7 +355,8 @@ private static string GetTableName(MetadataWorkspace metadata, EntityType entity var entitySet = entitySets.Single(); // Find the mapping between conceptual and storage model for this entity set - var entityContainerMappings = metadata.GetItems(DataSpace.CSSpace); // CSSpace = Conceptual model to Storage model mappings + var entityContainerMappings = metadata.GetItems(DataSpace.CSSpace); + // CSSpace = Conceptual model to Storage model mappings if (entityContainerMappings.Count() != 1) { throw new EnumGeneratorException(string.Format("{0} EntityContainerMappings found.", entityContainerMappings.Count())); @@ -341,19 +372,20 @@ private static string GetTableName(MetadataWorkspace metadata, EntityType entity // Find the storage entity set (table) that the entity is mapped to var entityTypeMappings = mapping.EntityTypeMappings; - var entityTypeMapping = entityTypeMappings.First(); // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping - var fragments = entityTypeMapping.Fragments; // <=== this contains the column mappings too. fragments.Items[x].PropertyMappings.Items[x].Column/Property + var entityTypeMapping = entityTypeMappings.First(); + // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping + var fragments = entityTypeMapping.Fragments; + // <=== this contains the column mappings too. fragments.Items[x].PropertyMappings.Items[x].Column/Property if (fragments.Count() != 1) { throw new EnumGeneratorException(string.Format("{0} Fragments found.", fragments.Count())); } - var table = fragments.Single().StoreEntitySet; - var tableName = (string)table.MetadataProperties["Table"].Value ?? table.Name; // don't need to go into metadata, it's probably available via a cast - return tableName; + var fragment = fragments.Single(); + return fragment; } catch (Exception exception) { - throw new EnumGeneratorException(string.Format("Error getting table name for entity type '{0}'", entityType.Name), exception); + throw new EnumGeneratorException(string.Format("Error getting schema mappings for entity type '{0}'", entityType.Name), exception); } } From eb83090bafb3dd68445d5be076b491e554f88723 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Wed, 18 Feb 2015 00:27:00 +0000 Subject: [PATCH 16/45] map the column names - wip breaks on complex types --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index b1c17b4..237413f 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -282,9 +282,13 @@ private static string GetColumnName(MappingFragment mappingFragment, EdmProperty "{0} matches found for property {1}", matches.Count(), edmProperty)); } var match = matches.Single(); - // wip - // todo: read metadata mappings and find column - return edmProperty.Name; + var colMapping = match as ScalarPropertyMapping; + if (colMapping == null) + { + throw new EnumGeneratorException(string.Format( + "Expected ScalarPropertyMapping but found {0} when mapping property {1}", match.GetType(), edmProperty)); + } + return colMapping.Column.Name; } private static string GetTableName(MetadataWorkspace metadata, EntityType entityType) From 9f903936e7d035a4700750514674057d74f92759 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Wed, 18 Feb 2015 00:27:05 +0000 Subject: [PATCH 17/45] todo list --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 237413f..88364f1 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -249,6 +249,7 @@ internal IList FindReferences(DbContext context) /// All the references that were found private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection, MetadataWorkspace metadata) { + // todo - untagle the recursion, makes complex types too complicated to handle. var references = new List(); foreach (var edmProperty in properties) { @@ -275,6 +276,7 @@ private static IEnumerable ProcessEdmProperties(IEnumerable m.Property.Name == edmProperty.Name).ToList(); if (matches.Count() != 1) { @@ -282,6 +284,7 @@ private static string GetColumnName(MappingFragment mappingFragment, EdmProperty "{0} matches found for property {1}", matches.Count(), edmProperty)); } var match = matches.Single(); + // todo - handle ComplexPropertyMapping var colMapping = match as ScalarPropertyMapping; if (colMapping == null) { From 568ae807f5a29c7be4f99ede4218507a3e836d85 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 17:09:12 +0000 Subject: [PATCH 18/45] todo - map through OCSpace properly currently just matching on names --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 88364f1..7bbbd47 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -336,6 +336,10 @@ private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metad } var entityMetadata = entityTypes.Single(); + // todo: map properly from object space to conceptual space + var OCSpace = metadata.GetItems(DataSpace.OCSpace); + //OCSpace.Where(oc => oc.) + // Get the entity set that uses this entity type var containers = metadata .GetItems(DataSpace.CSpace); // CSpace = Conceptual model @@ -349,7 +353,8 @@ private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metad .EntitySets .Where(s => s.ElementType.Name == entityMetadata.Name) .ToList(); - // Child types in Table-per-Hierarchy don't have any mapping so return null for the table name. Foreign key will be from the parent/base type. + + // Child types in Table-per-Hierarchy don't have any mapping so return null for the table name. Foreign key will be created from the base type. if (!entitySets.Any()) { return null; From 25271478f1751bb99971b75219104b9d5701695d Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 17:33:46 +0000 Subject: [PATCH 19/45] comments - another useful link --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 7bbbd47..e4db1b3 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -321,6 +321,7 @@ private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metad // * http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/ // * http://blogs.msdn.com/b/appfabriccat/archive/2010/10/22/metadataworkspace-reference-in-wcf-services.aspx // * http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.dataspace.aspx - describes meaning of OSpace etc + // * http://stackoverflow.com/questions/22999330/mapping-from-iedmentity-to-clr try { From cc567c7efb18264dbb529662c8c8dc80fc87ba05 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 17:35:02 +0000 Subject: [PATCH 20/45] todo comment --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index e4db1b3..116880e 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -323,6 +323,8 @@ private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metad // * http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.dataspace.aspx - describes meaning of OSpace etc // * http://stackoverflow.com/questions/22999330/mapping-from-iedmentity-to-clr + // todo: break this function down into manageable chunks + try { // Get the entity type from the model that maps to the CLR type From eb8eca2814ee5b1c9891436fc8ebb033fbb4de92 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 17:35:51 +0000 Subject: [PATCH 21/45] scratch OC mapping Doesn't appear to be possible (no information in public API, thanks microsoft.) Will have to continue to rely on name matching. --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 116880e..2fbe470 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -339,10 +339,6 @@ private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metad } var entityMetadata = entityTypes.Single(); - // todo: map properly from object space to conceptual space - var OCSpace = metadata.GetItems(DataSpace.OCSpace); - //OCSpace.Where(oc => oc.) - // Get the entity set that uses this entity type var containers = metadata .GetItems(DataSpace.CSpace); // CSpace = Conceptual model From 2899aa3197375879483c54c3431514b40a2f971b Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 17:47:54 +0000 Subject: [PATCH 22/45] Remove recursion of complex type properties Turns out that it's not possible to nest complex types with EF, so simplifying the code. --- .../LookupGenerator/EnumToLookup.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 2fbe470..23d59c6 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -234,22 +234,20 @@ internal IList FindReferences(DbContext context) continue; } - references.AddRange(ProcessEdmProperties(entityType.Properties, mappingFragment, objectItemCollection, metadata)); + references.AddRange(ProcessEdmProperties(entityType.Properties, mappingFragment, objectItemCollection)); } return references; } /// - /// Recurse through all the specified properties, including the children of any complex type properties looking for enum types. + /// Loop through all the specified properties, including the children of any complex type properties, looking for enum types. /// /// The properties to search. /// Information needed from ef metadata to map table and its columns - /// For looking up ClrTypes - /// - /// All the references that were found - private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection, MetadataWorkspace metadata) + /// For looking up ClrTypes of any enums encountered + /// All the references that were found in a form suitable for creating lookup tables and foreign keys + private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection) { - // todo - untagle the recursion, makes complex types too complicated to handle. var references = new List(); foreach (var edmProperty in properties) { @@ -265,10 +263,20 @@ private static IEnumerable ProcessEdmProperties(IEnumerable Date: Sat, 21 Feb 2015 18:24:54 +0000 Subject: [PATCH 23/45] functioning code for finding complex type's property's column name --- .../LookupGenerator/EnumToLookup.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 23d59c6..7be5535 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -272,7 +272,7 @@ private static IEnumerable ProcessEdmProperties(IEnumerable ProcessEdmProperties(IEnumerable m.Property.Name == edmProperty.Name).ToList(); if (matches.Count() != 1) { @@ -302,6 +301,46 @@ private static string GetColumnName(MappingFragment mappingFragment, EdmProperty return colMapping.Column.Name; } + private static string GetComplexColumnName(MappingFragment mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty) + { + var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); + if (matches.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} matches found for property {1}", matches.Count(), edmProperty)); + } + var match = matches.Single(); + + var complexPropertyMapping = match as ComplexPropertyMapping; + if (complexPropertyMapping == null) + { + throw new EnumGeneratorException(string.Format( + "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); + } + var complexTypeMapping = complexPropertyMapping.TypeMappings.Single(); + //.Where(cp => cp.ComplexType.Name == nestedProperty.Name).ToList(); + //if (complexPropertyMatches.Count() != 1) + //{ + // throw new EnumGeneratorException(string.Format( + // "{0} matches found for complex property {1}", complexPropertyMatches.Count(), nestedProperty)); + //} + //var complexPropertyMatch = complexPropertyMatches.Single(); + var complextMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); + if (complextMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} complexMappings found for property {1}.{2}", complextMappings.Count(), edmProperty, nestedProperty)); + } + var scalarMapping = complextMappings.Single(); + var colMapping = scalarMapping as ScalarPropertyMapping; + if (colMapping == null) + { + throw new EnumGeneratorException(string.Format( + "Expected ScalarPropertyMapping but found {0} when mapping property {1}", match.GetType(), edmProperty)); + } + return colMapping.Column.Name; + } + private static string GetTableName(MetadataWorkspace metadata, EntityType entityType) { From 68fe34fe41e1036fc187b3da46527971098a7a2f Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 18:29:02 +0000 Subject: [PATCH 24/45] Update test code to expect 2x Eon references Test passes --- EFTests/Tests/ModelParsingTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EFTests/Tests/ModelParsingTests.cs b/EFTests/Tests/ModelParsingTests.cs index da096c7..db9f59f 100644 --- a/EFTests/Tests/ModelParsingTests.cs +++ b/EFTests/Tests/ModelParsingTests.cs @@ -34,8 +34,8 @@ public void FindsReferences() Assert.IsNotNull(ears, "TehEars ref not found"); var echos = references.SingleOrDefault(r => r.ReferencingField == "EchoType"); Assert.IsNotNull(echos, "EchoType ref not found"); - var eons = references.SingleOrDefault(r => r.EnumType == typeof(Eon)); - Assert.IsNotNull(eons, "Eon ref not found"); + var eons = references.Count(r => r.EnumType == typeof(Eon)); + Assert.AreEqual(2, eons, "Wrong number of Eon refs found"); Assert.IsTrue(references.All(r => r.EnumType.IsEnum), "Non-enum type found"); Assert.AreEqual(11, references.Count); } From e052a6ba48fdffa54ac5055d01239793374d5425 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 18:30:44 +0000 Subject: [PATCH 25/45] Removed debug code. All tests now green. Woot. --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 7be5535..b836d6b 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -207,21 +207,6 @@ internal IList FindReferences(DbContext context) var entities = metadata.GetItems(DataSpace.OSpace); - var OSpace = metadata.GetItems(DataSpace.OSpace); - var OCSpace = metadata.GetItems(DataSpace.OCSpace); - var CSpace = metadata.GetItems(DataSpace.CSpace); - // have entity type in conceptual space - var CSSpace = metadata.GetItems(DataSpace.CSSpace) - .Single(); // the only container - var SSpace = metadata.GetItems(DataSpace.SSpace); - - //var CSItem = CSSpace.Single() - - var SItem = SSpace.Single(s => s.Name == "Rabbit"); - var SItemMem = SItem.MetadataProperties.Single(m => m.Name == "Members"); - var SItemMemCast = (ReadOnlyMetadataCollection)SItemMem.Value; - var lineageCol = SItemMemCast.Single(m => m.Name == "Lineage"); - // find and return all the references to enum types var references = new List(); foreach (var entityType in entities) From f0db036db81f3af133248c549437283d03c2315d Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 18:48:31 +0000 Subject: [PATCH 26/45] Check for unexpected type mapping count --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index b836d6b..45d6097 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -302,14 +302,12 @@ private static string GetComplexColumnName(MappingFragment mappingFragment, EdmP throw new EnumGeneratorException(string.Format( "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); } + if (complexPropertyMapping.TypeMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", matches.Count(), edmProperty, nestedProperty)); + } var complexTypeMapping = complexPropertyMapping.TypeMappings.Single(); - //.Where(cp => cp.ComplexType.Name == nestedProperty.Name).ToList(); - //if (complexPropertyMatches.Count() != 1) - //{ - // throw new EnumGeneratorException(string.Format( - // "{0} matches found for complex property {1}", complexPropertyMatches.Count(), nestedProperty)); - //} - //var complexPropertyMatch = complexPropertyMatches.Single(); var complextMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); if (complextMappings.Count() != 1) { From e89ae630973c780d71318c52f6731c8e1511e8bc Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 18:49:20 +0000 Subject: [PATCH 27/45] refactor - variable name fixed --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 45d6097..d487180 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -308,13 +308,13 @@ private static string GetComplexColumnName(MappingFragment mappingFragment, EdmP "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", matches.Count(), edmProperty, nestedProperty)); } var complexTypeMapping = complexPropertyMapping.TypeMappings.Single(); - var complextMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); - if (complextMappings.Count() != 1) + var propertyMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); + if (propertyMappings.Count() != 1) { throw new EnumGeneratorException(string.Format( - "{0} complexMappings found for property {1}.{2}", complextMappings.Count(), edmProperty, nestedProperty)); + "{0} complexMappings found for property {1}.{2}", propertyMappings.Count(), edmProperty, nestedProperty)); } - var scalarMapping = complextMappings.Single(); + var scalarMapping = propertyMappings.Single(); var colMapping = scalarMapping as ScalarPropertyMapping; if (colMapping == null) { From 2ea35d35cf119ae9d789c81df94539b6f3c61b7a Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 18:53:03 +0000 Subject: [PATCH 28/45] Cleanup and refactoring * remove no longer used method GetTableName * accept narrower type suggestions by resharper on parameters * remove done todo comment --- .../LookupGenerator/EnumToLookup.cs | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index d487180..5ee844e 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -267,7 +267,7 @@ private static IEnumerable ProcessEdmProperties(IEnumerable m.Property.Name == edmProperty.Name).ToList(); if (matches.Count() != 1) @@ -276,7 +276,7 @@ private static string GetColumnName(MappingFragment mappingFragment, EdmProperty "{0} matches found for property {1}", matches.Count(), edmProperty)); } var match = matches.Single(); - // todo - handle ComplexPropertyMapping + var colMapping = match as ScalarPropertyMapping; if (colMapping == null) { @@ -286,7 +286,7 @@ private static string GetColumnName(MappingFragment mappingFragment, EdmProperty return colMapping.Column.Name; } - private static string GetComplexColumnName(MappingFragment mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty) + private static string GetComplexColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty) { var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); if (matches.Count() != 1) @@ -324,27 +324,6 @@ private static string GetComplexColumnName(MappingFragment mappingFragment, EdmP return colMapping.Column.Name; } - private static string GetTableName(MetadataWorkspace metadata, EntityType entityType) - { - - try - { - var mappingFragment = FindSchemaMappingFragment(metadata, entityType); - // child types in TPH don't get mappings - if (mappingFragment == null) - { - return null; - } - var columnMappings = mappingFragment.PropertyMappings; - var tableName = mappingFragment.StoreEntitySet.Table; - return tableName; - } - catch (Exception exception) - { - throw new EnumGeneratorException(string.Format("Error getting table name for entity type '{0}'", entityType.Name), exception); - } - } - private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metadata, EntityType entityType) { // refs: From 74f5d51760728418ba989d563b0c64c24d078a4a Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 18:54:46 +0000 Subject: [PATCH 29/45] Refactor - DRY table name lookup (Don't Repeat Yourself) --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 5ee844e..b1a6f6c 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -236,11 +236,13 @@ private static IEnumerable ProcessEdmProperties(IEnumerable(); foreach (var edmProperty in properties) { + var table = mappingFragment.StoreEntitySet.Table; + if (edmProperty.IsEnumType) { references.Add(new EnumReference { - ReferencingTable = mappingFragment.StoreEntitySet.Table, + ReferencingTable = table, ReferencingField = GetColumnName(mappingFragment, edmProperty), EnumType = objectItemCollection.GetClrType(edmProperty.EnumType), }); @@ -256,7 +258,7 @@ private static IEnumerable ProcessEdmProperties(IEnumerable Date: Sat, 21 Feb 2015 19:05:30 +0000 Subject: [PATCH 30/45] Refactor - switch complex type loop to linq Thanks resharper --- .../LookupGenerator/EnumToLookup.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index b1a6f6c..5a9699d 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -234,6 +234,7 @@ internal IList FindReferences(DbContext context) private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection) { var references = new List(); + foreach (var edmProperty in properties) { var table = mappingFragment.StoreEntitySet.Table; @@ -248,24 +249,23 @@ private static IEnumerable ProcessEdmProperties(IEnumerable Date: Sat, 21 Feb 2015 19:13:41 +0000 Subject: [PATCH 31/45] Refactor - factor out common code from the two column methods --- .../LookupGenerator/EnumToLookup.cs | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 5a9699d..415d5d6 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -271,61 +271,62 @@ where nestedProperty.IsEnumType private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty) { - var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); - if (matches.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} matches found for property {1}", matches.Count(), edmProperty)); - } - var match = matches.Single(); + var propertyMapping = GetPropertyMapping(mappingFragment, edmProperty); - var colMapping = match as ScalarPropertyMapping; - if (colMapping == null) - { - throw new EnumGeneratorException(string.Format( - "Expected ScalarPropertyMapping but found {0} when mapping property {1}", match.GetType(), edmProperty)); - } - return colMapping.Column.Name; + return GetColumnName(edmProperty, propertyMapping, propertyMapping); } private static string GetComplexColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty) { - var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); - if (matches.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} matches found for property {1}", matches.Count(), edmProperty)); - } - var match = matches.Single(); + var propertyMapping = GetPropertyMapping(mappingFragment, edmProperty); - var complexPropertyMapping = match as ComplexPropertyMapping; + var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; if (complexPropertyMapping == null) { throw new EnumGeneratorException(string.Format( "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); } - if (complexPropertyMapping.TypeMappings.Count() != 1) + var complexTypeMappings = complexPropertyMapping.TypeMappings; + if (complexTypeMappings.Count() != 1) { throw new EnumGeneratorException(string.Format( - "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", matches.Count(), edmProperty, nestedProperty)); + "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", complexTypeMappings.Count(), edmProperty, nestedProperty)); } - var complexTypeMapping = complexPropertyMapping.TypeMappings.Single(); + var complexTypeMapping = complexTypeMappings.Single(); var propertyMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); if (propertyMappings.Count() != 1) { throw new EnumGeneratorException(string.Format( "{0} complexMappings found for property {1}.{2}", propertyMappings.Count(), edmProperty, nestedProperty)); } - var scalarMapping = propertyMappings.Single(); + var innerPropertyMapping = propertyMappings.Single(); + return GetColumnName(edmProperty, innerPropertyMapping, innerPropertyMapping); + } + + private static string GetColumnName(EdmProperty edmProperty, PropertyMapping scalarMapping, + PropertyMapping propertyMapping) + { var colMapping = scalarMapping as ScalarPropertyMapping; if (colMapping == null) { throw new EnumGeneratorException(string.Format( - "Expected ScalarPropertyMapping but found {0} when mapping property {1}", match.GetType(), edmProperty)); + "Expected ScalarPropertyMapping but found {0} when mapping property {1}", propertyMapping.GetType(), edmProperty)); } return colMapping.Column.Name; } + private static PropertyMapping GetPropertyMapping(StructuralTypeMapping mappingFragment, EdmProperty edmProperty) + { + var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); + if (matches.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} matches found for property {1}", matches.Count(), edmProperty)); + } + var match = matches.Single(); + return match; + } + private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metadata, EntityType entityType) { // refs: From 4081d09cd69ed3799fa72955280d6ec32ac4bc4d Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:19:26 +0000 Subject: [PATCH 32/45] Refactor - merge the two GetColumnName methods --- .../LookupGenerator/EnumToLookup.cs | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 415d5d6..451dcf6 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -260,7 +260,7 @@ where nestedProperty.IsEnumType select new EnumReference { ReferencingTable = table, - ReferencingField = GetComplexColumnName(mappingFragment, edmProperty, nestedProperty), + ReferencingField = GetColumnName(mappingFragment, edmProperty, nestedProperty), EnumType = objectItemCollection.GetClrType(nestedProperty.EnumType), }); } @@ -269,44 +269,50 @@ where nestedProperty.IsEnumType return references; } - private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty) - { - var propertyMapping = GetPropertyMapping(mappingFragment, edmProperty); - - return GetColumnName(edmProperty, propertyMapping, propertyMapping); - } - - private static string GetComplexColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty) + /// + /// Gets the name of the column for the property from the metadata. + /// Set nestedProperty for the property of a complex type to lookup. + /// + /// EF metadata for finding mappings. + /// The of the model to find the column for (for simple types), or for complex types this is the containing complex type. + /// Only required to map complex types. The property of the complex type to find the column name for. + /// The column name for the property + /// + /// + private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty = null) { var propertyMapping = GetPropertyMapping(mappingFragment, edmProperty); - var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; - if (complexPropertyMapping == null) - { - throw new EnumGeneratorException(string.Format( - "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); - } - var complexTypeMappings = complexPropertyMapping.TypeMappings; - if (complexTypeMappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", complexTypeMappings.Count(), edmProperty, nestedProperty)); - } - var complexTypeMapping = complexTypeMappings.Single(); - var propertyMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); - if (propertyMappings.Count() != 1) + if (nestedProperty != null) { - throw new EnumGeneratorException(string.Format( - "{0} complexMappings found for property {1}.{2}", propertyMappings.Count(), edmProperty, nestedProperty)); + var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; + if (complexPropertyMapping == null) + { + throw new EnumGeneratorException(string.Format( + "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); + } + var complexTypeMappings = complexPropertyMapping.TypeMappings; + if (complexTypeMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", complexTypeMappings.Count(), edmProperty, nestedProperty)); + } + var complexTypeMapping = complexTypeMappings.Single(); + var propertyMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); + if (propertyMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} complexMappings found for property {1}.{2}", propertyMappings.Count(), edmProperty, nestedProperty)); + } + + propertyMapping = propertyMappings.Single(); } - var innerPropertyMapping = propertyMappings.Single(); - return GetColumnName(edmProperty, innerPropertyMapping, innerPropertyMapping); + return GetColumnName(edmProperty, propertyMapping); } - private static string GetColumnName(EdmProperty edmProperty, PropertyMapping scalarMapping, - PropertyMapping propertyMapping) + private static string GetColumnName(EdmProperty edmProperty, PropertyMapping propertyMapping) { - var colMapping = scalarMapping as ScalarPropertyMapping; + var colMapping = propertyMapping as ScalarPropertyMapping; if (colMapping == null) { throw new EnumGeneratorException(string.Format( From 84a5ab242513b4fed675252ee12a757e3d1e9861 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:22:28 +0000 Subject: [PATCH 33/45] Minor refacting - make methods static Resharper suggestion --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 451dcf6..9310382 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -29,7 +29,8 @@ public class EnumToLookup : IEnumToLookup { public EnumToLookup() { - NameFieldLength = 255; // default + // set default behaviour, can be overridden by setting properties on object before calling Apply() + NameFieldLength = 255; TableNamePrefix = "Enum_"; SplitWords = true; } @@ -168,7 +169,7 @@ private static string SplitCamelCase(string name) return name; } - private string DescriptionValue(object value, Type enumType) + private static string DescriptionValue(object value, Type enumType) { // https://stackoverflow.com/questions/1799370/getting-attributes-of-enums-value/1799401#1799401 var member = enumType.GetMember(value.ToString()).First(); @@ -176,7 +177,7 @@ private string DescriptionValue(object value, Type enumType) return description == null ? null : description.Description; } - private bool IsRuntimeOnly(object value, Type enumType) + private static bool IsRuntimeOnly(object value, Type enumType) { // https://stackoverflow.com/questions/1799370/getting-attributes-of-enums-value/1799401#1799401 var member = enumType.GetMember(value.ToString()).First(); From ac74ef0685891f61821e8fe5a25fe75d8d6ba44b Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:25:19 +0000 Subject: [PATCH 34/45] Refactor - avoid method name clash Unintentional match of names. --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 9310382..c33a0ff 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -308,10 +308,10 @@ private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmPr propertyMapping = propertyMappings.Single(); } - return GetColumnName(edmProperty, propertyMapping); + return GetColumnNameFromPropertyMapping(edmProperty, propertyMapping); } - private static string GetColumnName(EdmProperty edmProperty, PropertyMapping propertyMapping) + private static string GetColumnNameFromPropertyMapping(EdmProperty edmProperty, PropertyMapping propertyMapping) { var colMapping = propertyMapping as ScalarPropertyMapping; if (colMapping == null) From 9316c938a1faf5f0010f608100daae07cf8fcdcf Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:33:03 +0000 Subject: [PATCH 35/45] Refactor - split FindReferences To put a dividing line between use of DbContext and metadata --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index c33a0ff..374b340 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -203,8 +203,14 @@ internal IList FindReferences(DbContext context) { var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; - // Get the part of the model that contains info about the actual CLR types - var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); // OSpace = Object Space + return FindReferences(metadata); + } + + private static IList FindReferences(MetadataWorkspace metadata) + { +// Get the part of the model that contains info about the actual CLR types + var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); + // OSpace = Object Space var entities = metadata.GetItems(DataSpace.OSpace); From c5b7532b286730b8b7074c882f4489056bc69655 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:37:01 +0000 Subject: [PATCH 36/45] Refactor - move all metadata processing code into own class Also * Clearer name for FindReferences * Move usings inside namespace for tighter scoping. --- EFTests/Tests/ModelParsingTests.cs | 2 +- EfEnumToLookup/EfEnumToLookup.csproj | 1 + .../LookupGenerator/EnumToLookup.cs | 253 ++---------------- .../LookupGenerator/MetadataHandler.cs | 231 ++++++++++++++++ 4 files changed, 249 insertions(+), 238 deletions(-) create mode 100644 EfEnumToLookup/LookupGenerator/MetadataHandler.cs diff --git a/EFTests/Tests/ModelParsingTests.cs b/EFTests/Tests/ModelParsingTests.cs index db9f59f..2011b02 100644 --- a/EFTests/Tests/ModelParsingTests.cs +++ b/EFTests/Tests/ModelParsingTests.cs @@ -26,7 +26,7 @@ public void FindsReferences() IList references; using (var context = new MagicContext()) { - references = _enumToLookup.FindReferences(context); + references = _enumToLookup.FindEnumReferences(context); } var legs = references.SingleOrDefault(r => r.ReferencingField == "SpeedyLegs"); Assert.IsNotNull(legs, "SpeedyLegs ref not found"); diff --git a/EfEnumToLookup/EfEnumToLookup.csproj b/EfEnumToLookup/EfEnumToLookup.csproj index 54df143..fcd5ecc 100644 --- a/EfEnumToLookup/EfEnumToLookup.csproj +++ b/EfEnumToLookup/EfEnumToLookup.csproj @@ -58,6 +58,7 @@ + diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 374b340..5a75e22 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -1,18 +1,16 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data.Entity; -using System.Data.Entity.Core.Mapping; -using System.Data.Entity.Core.Metadata.Edm; -using System.Data.Entity.Infrastructure; -using System.Data.SqlClient; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; - -namespace EfEnumToLookup.LookupGenerator +namespace EfEnumToLookup.LookupGenerator { + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Data.Entity; + using System.Data.Entity.Infrastructure; + using System.Data.SqlClient; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Text.RegularExpressions; + /// /// Makes up for a missing feature in Entity Framework 6.1 /// Creates lookup tables and foreign key constraints based on the enums @@ -71,7 +69,7 @@ public EnumToLookup() public void Apply(DbContext context) { // recurese through dbsets and references finding anything that uses an enum - var enumReferences = FindReferences(context); + var enumReferences = FindEnumReferences(context); // for the list of enums generate tables var enums = enumReferences.Select(r => r.EnumType).Distinct().ToList(); CreateTables(enums, (sql) => context.Database.ExecuteSqlCommand(sql)); @@ -199,230 +197,11 @@ private string TableName(string enumName) return string.Format("{0}{1}{2}", TableNamePrefix, enumName, TableNameSuffix); } - internal IList FindReferences(DbContext context) - { - var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; - - return FindReferences(metadata); - } - - private static IList FindReferences(MetadataWorkspace metadata) - { -// Get the part of the model that contains info about the actual CLR types - var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); - // OSpace = Object Space - - var entities = metadata.GetItems(DataSpace.OSpace); - - // find and return all the references to enum types - var references = new List(); - foreach (var entityType in entities) - { - var mappingFragment = FindSchemaMappingFragment(metadata, entityType); - - // child types in TPH don't get mappings - if (mappingFragment == null) - { - continue; - } - - references.AddRange(ProcessEdmProperties(entityType.Properties, mappingFragment, objectItemCollection)); - } - return references; - } - - /// - /// Loop through all the specified properties, including the children of any complex type properties, looking for enum types. - /// - /// The properties to search. - /// Information needed from ef metadata to map table and its columns - /// For looking up ClrTypes of any enums encountered - /// All the references that were found in a form suitable for creating lookup tables and foreign keys - private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection) - { - var references = new List(); - - foreach (var edmProperty in properties) - { - var table = mappingFragment.StoreEntitySet.Table; - - if (edmProperty.IsEnumType) - { - references.Add(new EnumReference - { - ReferencingTable = table, - ReferencingField = GetColumnName(mappingFragment, edmProperty), - EnumType = objectItemCollection.GetClrType(edmProperty.EnumType), - }); - continue; - } - - if (edmProperty.IsComplexType) - { - // Note that complex types can't be nested (ref http://stackoverflow.com/a/20332503/10245 ) - // so it's safe to not recurse even though the data model suggests you should have to. - references.AddRange( - from nestedProperty in edmProperty.ComplexType.Properties - where nestedProperty.IsEnumType - select new EnumReference - { - ReferencingTable = table, - ReferencingField = GetColumnName(mappingFragment, edmProperty, nestedProperty), - EnumType = objectItemCollection.GetClrType(nestedProperty.EnumType), - }); - } - } - - return references; - } - - /// - /// Gets the name of the column for the property from the metadata. - /// Set nestedProperty for the property of a complex type to lookup. - /// - /// EF metadata for finding mappings. - /// The of the model to find the column for (for simple types), or for complex types this is the containing complex type. - /// Only required to map complex types. The property of the complex type to find the column name for. - /// The column name for the property - /// - /// - private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty = null) + internal IList FindEnumReferences(DbContext context) { - var propertyMapping = GetPropertyMapping(mappingFragment, edmProperty); + var metadataWorkspace = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; - if (nestedProperty != null) - { - var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; - if (complexPropertyMapping == null) - { - throw new EnumGeneratorException(string.Format( - "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); - } - var complexTypeMappings = complexPropertyMapping.TypeMappings; - if (complexTypeMappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", complexTypeMappings.Count(), edmProperty, nestedProperty)); - } - var complexTypeMapping = complexTypeMappings.Single(); - var propertyMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); - if (propertyMappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} complexMappings found for property {1}.{2}", propertyMappings.Count(), edmProperty, nestedProperty)); - } - - propertyMapping = propertyMappings.Single(); - } - return GetColumnNameFromPropertyMapping(edmProperty, propertyMapping); - } - - private static string GetColumnNameFromPropertyMapping(EdmProperty edmProperty, PropertyMapping propertyMapping) - { - var colMapping = propertyMapping as ScalarPropertyMapping; - if (colMapping == null) - { - throw new EnumGeneratorException(string.Format( - "Expected ScalarPropertyMapping but found {0} when mapping property {1}", propertyMapping.GetType(), edmProperty)); - } - return colMapping.Column.Name; - } - - private static PropertyMapping GetPropertyMapping(StructuralTypeMapping mappingFragment, EdmProperty edmProperty) - { - var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); - if (matches.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} matches found for property {1}", matches.Count(), edmProperty)); - } - var match = matches.Single(); - return match; - } - - private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metadata, EntityType entityType) - { - // refs: - // * http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/ - // * http://blogs.msdn.com/b/appfabriccat/archive/2010/10/22/metadataworkspace-reference-in-wcf-services.aspx - // * http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.dataspace.aspx - describes meaning of OSpace etc - // * http://stackoverflow.com/questions/22999330/mapping-from-iedmentity-to-clr - - // todo: break this function down into manageable chunks - - try - { - // Get the entity type from the model that maps to the CLR type - var entityTypes = metadata - .GetItems(DataSpace.OSpace) // OSpace = Object Space - .Where(e => e == entityType) - .ToList(); - if (entityTypes.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} entities of type {1} found in mapping.", entityTypes.Count(), - entityType)); - } - var entityMetadata = entityTypes.Single(); - - // Get the entity set that uses this entity type - var containers = metadata - .GetItems(DataSpace.CSpace); // CSpace = Conceptual model - if (containers.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} EntityContainer's found.", containers.Count())); - } - var container = containers.Single(); - - var entitySets = container - .EntitySets - .Where(s => s.ElementType.Name == entityMetadata.Name) - .ToList(); - - // Child types in Table-per-Hierarchy don't have any mapping so return null for the table name. Foreign key will be created from the base type. - if (!entitySets.Any()) - { - return null; - } - if (entitySets.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} EntitySet's found for element type '{1}'.", entitySets.Count(), entityMetadata.Name)); - } - var entitySet = entitySets.Single(); - - // Find the mapping between conceptual and storage model for this entity set - var entityContainerMappings = metadata.GetItems(DataSpace.CSSpace); - // CSSpace = Conceptual model to Storage model mappings - if (entityContainerMappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} EntityContainerMappings found.", entityContainerMappings.Count())); - } - var containerMapping = entityContainerMappings.Single(); - var mappings = containerMapping.EntitySetMappings.Where(s => s.EntitySet == entitySet).ToList(); - if (mappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} EntitySetMappings found for entitySet '{1}'.", mappings.Count(), entitySet.Name)); - } - var mapping = mappings.Single(); - - // Find the storage entity set (table) that the entity is mapped to - var entityTypeMappings = mapping.EntityTypeMappings; - var entityTypeMapping = entityTypeMappings.First(); - // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping - var fragments = entityTypeMapping.Fragments; - // <=== this contains the column mappings too. fragments.Items[x].PropertyMappings.Items[x].Column/Property - if (fragments.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} Fragments found.", fragments.Count())); - } - var fragment = fragments.Single(); - return fragment; - } - catch (Exception exception) - { - throw new EnumGeneratorException(string.Format("Error getting schema mappings for entity type '{0}'", entityType.Name), exception); - } + return MetadataHandler.FindEnumReferences(metadataWorkspace); } internal IList FindDbSets(Type contextType) diff --git a/EfEnumToLookup/LookupGenerator/MetadataHandler.cs b/EfEnumToLookup/LookupGenerator/MetadataHandler.cs new file mode 100644 index 0000000..d15be17 --- /dev/null +++ b/EfEnumToLookup/LookupGenerator/MetadataHandler.cs @@ -0,0 +1,231 @@ +namespace EfEnumToLookup.LookupGenerator +{ + using System; + using System.Collections.Generic; + using System.Data.Entity.Core.Mapping; + using System.Data.Entity.Core.Metadata.Edm; + using System.Linq; + + class MetadataHandler + { + internal static IList FindEnumReferences(MetadataWorkspace metadataWorkspace) + { + // Get the part of the model that contains info about the actual CLR types + var objectItemCollection = ((ObjectItemCollection)metadataWorkspace.GetItemCollection(DataSpace.OSpace)); + // OSpace = Object Space + + var entities = metadataWorkspace.GetItems(DataSpace.OSpace); + + // find and return all the references to enum types + var references = new List(); + foreach (var entityType in entities) + { + var mappingFragment = FindSchemaMappingFragment(metadataWorkspace, entityType); + + // child types in TPH don't get mappings + if (mappingFragment == null) + { + continue; + } + + references.AddRange(ProcessEdmProperties(entityType.Properties, mappingFragment, objectItemCollection)); + } + return references; + } + + /// + /// Loop through all the specified properties, including the children of any complex type properties, looking for enum types. + /// + /// The properties to search. + /// Information needed from ef metadata to map table and its columns + /// For looking up ClrTypes of any enums encountered + /// All the references that were found in a form suitable for creating lookup tables and foreign keys + private static IEnumerable ProcessEdmProperties(IEnumerable properties, MappingFragment mappingFragment, ObjectItemCollection objectItemCollection) + { + var references = new List(); + + foreach (var edmProperty in properties) + { + var table = mappingFragment.StoreEntitySet.Table; + + if (edmProperty.IsEnumType) + { + references.Add(new EnumReference + { + ReferencingTable = table, + ReferencingField = GetColumnName(mappingFragment, edmProperty), + EnumType = objectItemCollection.GetClrType(edmProperty.EnumType), + }); + continue; + } + + if (edmProperty.IsComplexType) + { + // Note that complex types can't be nested (ref http://stackoverflow.com/a/20332503/10245 ) + // so it's safe to not recurse even though the data model suggests you should have to. + references.AddRange( + from nestedProperty in edmProperty.ComplexType.Properties + where nestedProperty.IsEnumType + select new EnumReference + { + ReferencingTable = table, + ReferencingField = GetColumnName(mappingFragment, edmProperty, nestedProperty), + EnumType = objectItemCollection.GetClrType(nestedProperty.EnumType), + }); + } + } + + return references; + } + + /// + /// Gets the name of the column for the property from the metadata. + /// Set nestedProperty for the property of a complex type to lookup. + /// + /// EF metadata for finding mappings. + /// The of the model to find the column for (for simple types), or for complex types this is the containing complex type. + /// Only required to map complex types. The property of the complex type to find the column name for. + /// The column name for the property + /// + /// + private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmProperty edmProperty, EdmProperty nestedProperty = null) + { + var propertyMapping = GetPropertyMapping(mappingFragment, edmProperty); + + if (nestedProperty != null) + { + var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; + if (complexPropertyMapping == null) + { + throw new EnumGeneratorException(string.Format( + "Failed to cast complex property mapping for {0}.{1} to ComplexPropertyMapping", edmProperty, nestedProperty)); + } + var complexTypeMappings = complexPropertyMapping.TypeMappings; + if (complexTypeMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} complexPropertyMapping TypeMappings found for property {1}.{2}", complexTypeMappings.Count(), edmProperty, nestedProperty)); + } + var complexTypeMapping = complexTypeMappings.Single(); + var propertyMappings = complexTypeMapping.PropertyMappings.Where(pm => pm.Property.Name == nestedProperty.Name).ToList(); + if (propertyMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} complexMappings found for property {1}.{2}", propertyMappings.Count(), edmProperty, nestedProperty)); + } + + propertyMapping = propertyMappings.Single(); + } + return GetColumnNameFromPropertyMapping(edmProperty, propertyMapping); + } + + private static string GetColumnNameFromPropertyMapping(EdmProperty edmProperty, PropertyMapping propertyMapping) + { + var colMapping = propertyMapping as ScalarPropertyMapping; + if (colMapping == null) + { + throw new EnumGeneratorException(string.Format( + "Expected ScalarPropertyMapping but found {0} when mapping property {1}", propertyMapping.GetType(), edmProperty)); + } + return colMapping.Column.Name; + } + + private static PropertyMapping GetPropertyMapping(StructuralTypeMapping mappingFragment, EdmProperty edmProperty) + { + var matches = mappingFragment.PropertyMappings.Where(m => m.Property.Name == edmProperty.Name).ToList(); + if (matches.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} matches found for property {1}", matches.Count(), edmProperty)); + } + var match = matches.Single(); + return match; + } + + private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metadata, EntityType entityType) + { + // refs: + // * http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/ + // * http://blogs.msdn.com/b/appfabriccat/archive/2010/10/22/metadataworkspace-reference-in-wcf-services.aspx + // * http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.dataspace.aspx - describes meaning of OSpace etc + // * http://stackoverflow.com/questions/22999330/mapping-from-iedmentity-to-clr + + // todo: break this function down into manageable chunks + + try + { + // Get the entity type from the model that maps to the CLR type + var entityTypes = metadata + .GetItems(DataSpace.OSpace) // OSpace = Object Space + .Where(e => e == entityType) + .ToList(); + if (entityTypes.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} entities of type {1} found in mapping.", entityTypes.Count(), + entityType)); + } + var entityMetadata = entityTypes.Single(); + + // Get the entity set that uses this entity type + var containers = metadata + .GetItems(DataSpace.CSpace); // CSpace = Conceptual model + if (containers.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} EntityContainer's found.", containers.Count())); + } + var container = containers.Single(); + + var entitySets = container + .EntitySets + .Where(s => s.ElementType.Name == entityMetadata.Name) + .ToList(); + + // Child types in Table-per-Hierarchy don't have any mapping so return null for the table name. Foreign key will be created from the base type. + if (!entitySets.Any()) + { + return null; + } + if (entitySets.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} EntitySet's found for element type '{1}'.", entitySets.Count(), entityMetadata.Name)); + } + var entitySet = entitySets.Single(); + + // Find the mapping between conceptual and storage model for this entity set + var entityContainerMappings = metadata.GetItems(DataSpace.CSSpace); + // CSSpace = Conceptual model to Storage model mappings + if (entityContainerMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} EntityContainerMappings found.", entityContainerMappings.Count())); + } + var containerMapping = entityContainerMappings.Single(); + var mappings = containerMapping.EntitySetMappings.Where(s => s.EntitySet == entitySet).ToList(); + if (mappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} EntitySetMappings found for entitySet '{1}'.", mappings.Count(), entitySet.Name)); + } + var mapping = mappings.Single(); + + // Find the storage entity set (table) that the entity is mapped to + var entityTypeMappings = mapping.EntityTypeMappings; + var entityTypeMapping = entityTypeMappings.First(); + // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping + var fragments = entityTypeMapping.Fragments; + // <=== this contains the column mappings too. fragments.Items[x].PropertyMappings.Items[x].Column/Property + if (fragments.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} Fragments found.", fragments.Count())); + } + var fragment = fragments.Single(); + return fragment; + } + catch (Exception exception) + { + throw new EnumGeneratorException(string.Format("Error getting schema mappings for entity type '{0}'", entityType.Name), exception); + } + } + + } +} From d10cc674b32878bac60c2de5328c08e83472e522 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:40:31 +0000 Subject: [PATCH 37/45] Code style - tidy up spacing --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 5a75e22..061061a 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -70,11 +70,14 @@ public void Apply(DbContext context) { // recurese through dbsets and references finding anything that uses an enum var enumReferences = FindEnumReferences(context); + // for the list of enums generate tables var enums = enumReferences.Select(r => r.EnumType).Distinct().ToList(); CreateTables(enums, (sql) => context.Database.ExecuteSqlCommand(sql)); + // t-sql merge values into table PopulateLookups(enums, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); + // add fks from all referencing tables AddForeignKeys(enumReferences, (sql) => context.Database.ExecuteSqlCommand(sql)); } @@ -84,10 +87,12 @@ private void AddForeignKeys(IEnumerable refs, Action runS foreach (var enumReference in refs) { var fkName = string.Format("FK_{0}_{1}", enumReference.ReferencingTable, enumReference.ReferencingField); - var sql = - string.Format( - " IF OBJECT_ID('{0}', 'F') IS NULL ALTER TABLE [{1}] ADD CONSTRAINT {0} FOREIGN KEY ([{2}]) REFERENCES [{3}] (Id);", - fkName, enumReference.ReferencingTable, enumReference.ReferencingField, TableName(enumReference.EnumType.Name)); + + var sql = string.Format( + " IF OBJECT_ID('{0}', 'F') IS NULL ALTER TABLE [{1}] ADD CONSTRAINT {0} FOREIGN KEY ([{2}]) REFERENCES [{3}] (Id);", + fkName, enumReference.ReferencingTable, enumReference.ReferencingField, TableName(enumReference.EnumType.Name) + ); + runSql(sql); } } From c7e51565d8cda5a4d9fc7f6f47008cdd2e3cf50e Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 19:46:33 +0000 Subject: [PATCH 38/45] Refactor - make MetadataHandler use non-static Easier to alter later. --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 3 ++- EfEnumToLookup/LookupGenerator/MetadataHandler.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 061061a..079eded 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -206,7 +206,8 @@ internal IList FindEnumReferences(DbContext context) { var metadataWorkspace = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; - return MetadataHandler.FindEnumReferences(metadataWorkspace); + var metadataHandler = new MetadataHandler(); + return metadataHandler.FindEnumReferences(metadataWorkspace); } internal IList FindDbSets(Type contextType) diff --git a/EfEnumToLookup/LookupGenerator/MetadataHandler.cs b/EfEnumToLookup/LookupGenerator/MetadataHandler.cs index d15be17..ec03ce1 100644 --- a/EfEnumToLookup/LookupGenerator/MetadataHandler.cs +++ b/EfEnumToLookup/LookupGenerator/MetadataHandler.cs @@ -8,7 +8,7 @@ class MetadataHandler { - internal static IList FindEnumReferences(MetadataWorkspace metadataWorkspace) + internal IList FindEnumReferences(MetadataWorkspace metadataWorkspace) { // Get the part of the model that contains info about the actual CLR types var objectItemCollection = ((ObjectItemCollection)metadataWorkspace.GetItemCollection(DataSpace.OSpace)); From e5cf2d32ba28b86798c331c8f13602c171b2c629 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 20:02:29 +0000 Subject: [PATCH 39/45] Refactor - extract out db handling code - wip --- EfEnumToLookup/EfEnumToLookup.csproj | 1 + .../LookupGenerator/EnumToLookup.cs | 94 ++------------- .../LookupGenerator/SqlServerHandler.cs | 111 ++++++++++++++++++ 3 files changed, 122 insertions(+), 84 deletions(-) create mode 100644 EfEnumToLookup/LookupGenerator/SqlServerHandler.cs diff --git a/EfEnumToLookup/EfEnumToLookup.csproj b/EfEnumToLookup/EfEnumToLookup.csproj index fcd5ecc..8dda7ad 100644 --- a/EfEnumToLookup/EfEnumToLookup.csproj +++ b/EfEnumToLookup/EfEnumToLookup.csproj @@ -60,6 +60,7 @@ + diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 079eded..275abee 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -71,81 +71,22 @@ public void Apply(DbContext context) // recurese through dbsets and references finding anything that uses an enum var enumReferences = FindEnumReferences(context); + var sqlServerHandler = new SqlServerHandler + { + NameFieldLength = NameFieldLength, + TableNamePrefix = TableNamePrefix, + TableNameSuffix = TableNameSuffix, + }; + // for the list of enums generate tables var enums = enumReferences.Select(r => r.EnumType).Distinct().ToList(); - CreateTables(enums, (sql) => context.Database.ExecuteSqlCommand(sql)); + sqlServerHandler.CreateTables(enums, (sql) => context.Database.ExecuteSqlCommand(sql)); // t-sql merge values into table - PopulateLookups(enums, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); + sqlServerHandler.PopulateLookups(enums, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); // add fks from all referencing tables - AddForeignKeys(enumReferences, (sql) => context.Database.ExecuteSqlCommand(sql)); - } - - private void AddForeignKeys(IEnumerable refs, Action runSql) - { - foreach (var enumReference in refs) - { - var fkName = string.Format("FK_{0}_{1}", enumReference.ReferencingTable, enumReference.ReferencingField); - - var sql = string.Format( - " IF OBJECT_ID('{0}', 'F') IS NULL ALTER TABLE [{1}] ADD CONSTRAINT {0} FOREIGN KEY ([{2}]) REFERENCES [{3}] (Id);", - fkName, enumReference.ReferencingTable, enumReference.ReferencingField, TableName(enumReference.EnumType.Name) - ); - - runSql(sql); - } - } - - private void PopulateLookups(IEnumerable enums, Action> runSql) - { - foreach (var lookup in enums) - { - PopulateLookup(lookup, runSql); - } - } - - private void PopulateLookup(Type lookup, Action> runSql) - { - if (!lookup.IsEnum) - { - throw new ArgumentException("Lookup type must be an enum", "lookup"); - } - - var sb = new StringBuilder(); - sb.AppendLine(string.Format("CREATE TABLE #lookups (Id int, Name nvarchar({0}) COLLATE database_default);", NameFieldLength)); - var parameters = new List(); - int paramIndex = 0; - foreach (var value in Enum.GetValues(lookup)) - { - if (IsRuntimeOnly(value, lookup)) - { - continue; - } - var id = (int)value; - var name = EnumName(value, lookup); - var idParamName = string.Format("id{0}", paramIndex++); - var nameParamName = string.Format("name{0}", paramIndex++); - sb.AppendLine(string.Format("INSERT INTO #lookups (Id, Name) VALUES (@{0}, @{1});", idParamName, nameParamName)); - parameters.Add(new SqlParameter(idParamName, id)); - parameters.Add(new SqlParameter(nameParamName, name)); - } - - sb.AppendLine(string.Format(@" -MERGE INTO [{0}] dst - USING #lookups src ON src.Id = dst.Id - WHEN MATCHED AND src.Name <> dst.Name THEN - UPDATE SET Name = src.Name - WHEN NOT MATCHED THEN - INSERT (Id, Name) - VALUES (src.Id, src.Name) - WHEN NOT MATCHED BY SOURCE THEN - DELETE -;" - , TableName(lookup.Name))); - - sb.AppendLine("DROP TABLE #lookups;"); - runSql(sb.ToString(), parameters); + sqlServerHandler.AddForeignKeys(enumReferences, (sql) => context.Database.ExecuteSqlCommand(sql)); } private string EnumName(object value, Type lookup) @@ -187,21 +128,6 @@ private static bool IsRuntimeOnly(object value, Type enumType) return member.GetCustomAttributes(typeof(RuntimeOnlyAttribute)).Any(); } - private void CreateTables(IEnumerable enums, Action runSql) - { - foreach (var lookup in enums) - { - runSql(string.Format( - @"IF OBJECT_ID('{0}', 'U') IS NULL CREATE TABLE [{0}] (Id int PRIMARY KEY, Name nvarchar({1}));", - TableName(lookup.Name), NameFieldLength)); - } - } - - private string TableName(string enumName) - { - return string.Format("{0}{1}{2}", TableNamePrefix, enumName, TableNameSuffix); - } - internal IList FindEnumReferences(DbContext context) { var metadataWorkspace = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; diff --git a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs new file mode 100644 index 0000000..9bfef40 --- /dev/null +++ b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Text; + +namespace EfEnumToLookup.LookupGenerator +{ + class SqlServerHandler + { + /// + /// The size of the Name field that will be added to the generated lookup tables. + /// Adjust to suit your data if required, defaults to 255. + /// + public int NameFieldLength { get; set; } + + /// + /// Prefix to add to all the generated tables to separate help group them together + /// and make them stand out as different from other tables. + /// Defaults to "Enum_" set to null or "" to not have any prefix. + /// + public string TableNamePrefix { get; set; } + + /// + /// Suffix to add to all the generated tables to separate help group them together + /// and make them stand out as different from other tables. + /// Defaults to "" set to null or "" to not have any suffix. + /// + public string TableNameSuffix { get; set; } + + internal void CreateTables(IEnumerable enums, Action runSql) + { + foreach (var lookup in enums) + { + runSql(string.Format( + @"IF OBJECT_ID('{0}', 'U') IS NULL CREATE TABLE [{0}] (Id int PRIMARY KEY, Name nvarchar({1}));", + TableName(lookup.Name), NameFieldLength)); + } + } + + internal void AddForeignKeys(IEnumerable refs, Action runSql) + { + foreach (var enumReference in refs) + { + var fkName = string.Format("FK_{0}_{1}", enumReference.ReferencingTable, enumReference.ReferencingField); + + var sql = string.Format( + " IF OBJECT_ID('{0}', 'F') IS NULL ALTER TABLE [{1}] ADD CONSTRAINT {0} FOREIGN KEY ([{2}]) REFERENCES [{3}] (Id);", + fkName, enumReference.ReferencingTable, enumReference.ReferencingField, TableName(enumReference.EnumType.Name) + ); + + runSql(sql); + } + } + + internal void PopulateLookups(IEnumerable enums, Action> runSql) + { + foreach (var lookup in enums) + { + PopulateLookup(lookup, runSql); + } + } + + private void PopulateLookup(Type lookup, Action> runSql) + { + if (!lookup.IsEnum) + { + throw new ArgumentException("Lookup type must be an enum", "lookup"); + } + + var sb = new StringBuilder(); + sb.AppendLine(string.Format("CREATE TABLE #lookups (Id int, Name nvarchar({0}) COLLATE database_default);", NameFieldLength)); + var parameters = new List(); + int paramIndex = 0; + foreach (var value in Enum.GetValues(lookup)) + { + if (IsRuntimeOnly(value, lookup)) + { + continue; + } + var id = (int)value; + var name = EnumName(value, lookup); + var idParamName = string.Format("id{0}", paramIndex++); + var nameParamName = string.Format("name{0}", paramIndex++); + sb.AppendLine(string.Format("INSERT INTO #lookups (Id, Name) VALUES (@{0}, @{1});", idParamName, nameParamName)); + parameters.Add(new SqlParameter(idParamName, id)); + parameters.Add(new SqlParameter(nameParamName, name)); + } + + sb.AppendLine(string.Format(@" +MERGE INTO [{0}] dst + USING #lookups src ON src.Id = dst.Id + WHEN MATCHED AND src.Name <> dst.Name THEN + UPDATE SET Name = src.Name + WHEN NOT MATCHED THEN + INSERT (Id, Name) + VALUES (src.Id, src.Name) + WHEN NOT MATCHED BY SOURCE THEN + DELETE +;" + , TableName(lookup.Name))); + + sb.AppendLine("DROP TABLE #lookups;"); + runSql(sb.ToString(), parameters); + } + + private string TableName(string enumName) + { + return string.Format("{0}{1}{2}", TableNamePrefix, enumName, TableNameSuffix); + } + } +} From 73edab4364fd3a12f13d5227200512f1b5701d19 Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 20:31:33 +0000 Subject: [PATCH 40/45] Refactor - extract out db handling code Tests all green --- EfEnumToLookup/EfEnumToLookup.csproj | 2 + .../LookupGenerator/EnumToLookup.cs | 42 +++++++++++++++++-- EfEnumToLookup/LookupGenerator/LookupData.cs | 10 +++++ EfEnumToLookup/LookupGenerator/LookupValue.cs | 8 ++++ .../LookupGenerator/SqlServerHandler.cs | 33 ++++++--------- 5 files changed, 70 insertions(+), 25 deletions(-) create mode 100644 EfEnumToLookup/LookupGenerator/LookupData.cs create mode 100644 EfEnumToLookup/LookupGenerator/LookupValue.cs diff --git a/EfEnumToLookup/EfEnumToLookup.csproj b/EfEnumToLookup/EfEnumToLookup.csproj index 8dda7ad..8b6c69a 100644 --- a/EfEnumToLookup/EfEnumToLookup.csproj +++ b/EfEnumToLookup/EfEnumToLookup.csproj @@ -58,6 +58,8 @@ + + diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 275abee..6898158 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -68,7 +68,7 @@ public EnumToLookup() /// context.Database.ExecuteSqlCommand() is used to apply changes. public void Apply(DbContext context) { - // recurese through dbsets and references finding anything that uses an enum + // recurse through dbsets and references finding anything that uses an enum var enumReferences = FindEnumReferences(context); var sqlServerHandler = new SqlServerHandler @@ -78,12 +78,22 @@ public void Apply(DbContext context) TableNameSuffix = TableNameSuffix, }; - // for the list of enums generate tables + // todo: fold the populate method into the create tables method, providing all the data to the db layer in one hit, + // this will require merging the two sql callbacks into one signature + + // for the list of enums generate and missing tables var enums = enumReferences.Select(r => r.EnumType).Distinct().ToList(); sqlServerHandler.CreateTables(enums, (sql) => context.Database.ExecuteSqlCommand(sql)); - // t-sql merge values into table - sqlServerHandler.PopulateLookups(enums, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); + // merge values into these tables + var lookups = + from enm in enums + select new LookupData + { + Name = enm.Name, + Values = GetLookupValues(enm), + }; + sqlServerHandler.PopulateLookups(lookups, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); // add fks from all referencing tables sqlServerHandler.AddForeignKeys(enumReferences, (sql) => context.Database.ExecuteSqlCommand(sql)); @@ -121,6 +131,30 @@ private static string DescriptionValue(object value, Type enumType) return description == null ? null : description.Description; } + private IList GetLookupValues(Type lookup) + { + if (!lookup.IsEnum) + { + throw new ArgumentException("Lookup type must be an enum", "lookup"); + } + + var values = new List(); + foreach (var value in Enum.GetValues(lookup)) + { + if (IsRuntimeOnly(value, lookup)) + { + continue; + } + values.Add(new LookupValue + { + Id = (int)value, + Name = EnumName(value, lookup), + }); + } + return values; + } + + private static bool IsRuntimeOnly(object value, Type enumType) { // https://stackoverflow.com/questions/1799370/getting-attributes-of-enums-value/1799401#1799401 diff --git a/EfEnumToLookup/LookupGenerator/LookupData.cs b/EfEnumToLookup/LookupGenerator/LookupData.cs new file mode 100644 index 0000000..689921e --- /dev/null +++ b/EfEnumToLookup/LookupGenerator/LookupData.cs @@ -0,0 +1,10 @@ +namespace EfEnumToLookup.LookupGenerator +{ + using System.Collections.Generic; + + internal class LookupData + { + public string Name { get; set; } + public IEnumerable Values { get; set; } + } +} diff --git a/EfEnumToLookup/LookupGenerator/LookupValue.cs b/EfEnumToLookup/LookupGenerator/LookupValue.cs new file mode 100644 index 0000000..ac1c0ae --- /dev/null +++ b/EfEnumToLookup/LookupGenerator/LookupValue.cs @@ -0,0 +1,8 @@ +namespace EfEnumToLookup.LookupGenerator +{ + internal class LookupValue + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs index 9bfef40..e60d544 100644 --- a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs +++ b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs @@ -1,10 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Text; - -namespace EfEnumToLookup.LookupGenerator +namespace EfEnumToLookup.LookupGenerator { + using System; + using System.Collections.Generic; + using System.Data.SqlClient; + using System.Text; + class SqlServerHandler { /// @@ -52,33 +52,24 @@ internal void AddForeignKeys(IEnumerable refs, Action run } } - internal void PopulateLookups(IEnumerable enums, Action> runSql) + internal void PopulateLookups(IEnumerable lookupData, Action> runSql) { - foreach (var lookup in enums) + foreach (var lookup in lookupData) { PopulateLookup(lookup, runSql); } } - private void PopulateLookup(Type lookup, Action> runSql) + private void PopulateLookup(LookupData lookup, Action> runSql) { - if (!lookup.IsEnum) - { - throw new ArgumentException("Lookup type must be an enum", "lookup"); - } - var sb = new StringBuilder(); sb.AppendLine(string.Format("CREATE TABLE #lookups (Id int, Name nvarchar({0}) COLLATE database_default);", NameFieldLength)); var parameters = new List(); int paramIndex = 0; - foreach (var value in Enum.GetValues(lookup)) + foreach (var value in lookup.Values) { - if (IsRuntimeOnly(value, lookup)) - { - continue; - } - var id = (int)value; - var name = EnumName(value, lookup); + var id = value.Id; + var name = value.Name; var idParamName = string.Format("id{0}", paramIndex++); var nameParamName = string.Format("name{0}", paramIndex++); sb.AppendLine(string.Format("INSERT INTO #lookups (Id, Name) VALUES (@{0}, @{1});", idParamName, nameParamName)); From ec8db5424a7436e7738fe4be8afbf20b7ae140db Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 20:35:17 +0000 Subject: [PATCH 41/45] Refactor - eliminate Enum type ref from db class Also accept broader type refactoring on other method --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 9 +++++---- EfEnumToLookup/LookupGenerator/SqlServerHandler.cs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 6898158..52884db 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -83,16 +83,17 @@ public void Apply(DbContext context) // for the list of enums generate and missing tables var enums = enumReferences.Select(r => r.EnumType).Distinct().ToList(); - sqlServerHandler.CreateTables(enums, (sql) => context.Database.ExecuteSqlCommand(sql)); // merge values into these tables var lookups = - from enm in enums + (from enm in enums select new LookupData { Name = enm.Name, Values = GetLookupValues(enm), - }; + }).ToList(); + + sqlServerHandler.CreateTables(lookups, (sql) => context.Database.ExecuteSqlCommand(sql)); sqlServerHandler.PopulateLookups(lookups, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); // add fks from all referencing tables @@ -131,7 +132,7 @@ private static string DescriptionValue(object value, Type enumType) return description == null ? null : description.Description; } - private IList GetLookupValues(Type lookup) + private IEnumerable GetLookupValues(Type lookup) { if (!lookup.IsEnum) { diff --git a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs index e60d544..a8243df 100644 --- a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs +++ b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs @@ -27,7 +27,7 @@ class SqlServerHandler /// public string TableNameSuffix { get; set; } - internal void CreateTables(IEnumerable enums, Action runSql) + internal void CreateTables(IEnumerable enums, Action runSql) { foreach (var lookup in enums) { From b0163a3e02eee2d1a10e10f7643eab5678f6e9de Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 21:39:54 +0000 Subject: [PATCH 42/45] Refactor - factor out common sql execution code Preparing to squash db interface to single method --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 52884db..88d7bb0 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -93,11 +93,20 @@ public void Apply(DbContext context) Values = GetLookupValues(enm), }).ToList(); - sqlServerHandler.CreateTables(lookups, (sql) => context.Database.ExecuteSqlCommand(sql)); - sqlServerHandler.PopulateLookups(lookups, (sql, parameters) => context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray())); + sqlServerHandler.CreateTables(lookups, (sql) => ExecuteSqlCommand(context, sql)); + sqlServerHandler.PopulateLookups(lookups, (sql, parameters) => ExecuteSqlCommand(context, sql, parameters)); // add fks from all referencing tables - sqlServerHandler.AddForeignKeys(enumReferences, (sql) => context.Database.ExecuteSqlCommand(sql)); + sqlServerHandler.AddForeignKeys(enumReferences, (sql) => ExecuteSqlCommand(context, sql)); + } + + private static int ExecuteSqlCommand(DbContext context, string sql, IEnumerable parameters = null) + { + if (parameters == null) + { + return context.Database.ExecuteSqlCommand(sql); + } + return context.Database.ExecuteSqlCommand(sql, parameters.Cast().ToArray()); } private string EnumName(object value, Type lookup) From 77266208f9c6e03536b1c7d81c790d48d8c01e0b Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 21:47:24 +0000 Subject: [PATCH 43/45] Refactor - squash db interface into single Apply method --- EfEnumToLookup/LookupGenerator/EnumToLookup.cs | 6 +----- EfEnumToLookup/LookupGenerator/SqlServerHandler.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 88d7bb0..9a03538 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -93,11 +93,7 @@ public void Apply(DbContext context) Values = GetLookupValues(enm), }).ToList(); - sqlServerHandler.CreateTables(lookups, (sql) => ExecuteSqlCommand(context, sql)); - sqlServerHandler.PopulateLookups(lookups, (sql, parameters) => ExecuteSqlCommand(context, sql, parameters)); - - // add fks from all referencing tables - sqlServerHandler.AddForeignKeys(enumReferences, (sql) => ExecuteSqlCommand(context, sql)); + sqlServerHandler.Apply(lookups, enumReferences, (sql, parameters) => ExecuteSqlCommand(context, sql, parameters)); } private static int ExecuteSqlCommand(DbContext context, string sql, IEnumerable parameters = null) diff --git a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs index a8243df..a69f569 100644 --- a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs +++ b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs @@ -27,7 +27,15 @@ class SqlServerHandler /// public string TableNameSuffix { get; set; } - internal void CreateTables(IEnumerable enums, Action runSql) + + internal void Apply(List lookups, IList enumReferences, Action> runSql) + { + CreateTables(lookups, (sql) => runSql(sql, null)); + PopulateLookups(lookups, runSql); + AddForeignKeys(enumReferences, (sql) => runSql(sql, null)); + } + + private void CreateTables(IEnumerable enums, Action runSql) { foreach (var lookup in enums) { @@ -37,7 +45,7 @@ internal void CreateTables(IEnumerable enums, Action runSql) } } - internal void AddForeignKeys(IEnumerable refs, Action runSql) + private void AddForeignKeys(IEnumerable refs, Action runSql) { foreach (var enumReference in refs) { @@ -52,7 +60,7 @@ internal void AddForeignKeys(IEnumerable refs, Action run } } - internal void PopulateLookups(IEnumerable lookupData, Action> runSql) + private void PopulateLookups(IEnumerable lookupData, Action> runSql) { foreach (var lookup in lookupData) { From 960570e9f4a15d0dc0da7af368d6f114bd841eba Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 21:59:30 +0000 Subject: [PATCH 44/45] Refactor - extract interface for db handling To assist with multi-db support issue #16 --- EfEnumToLookup/EfEnumToLookup.csproj | 1 + .../LookupGenerator/EnumToLookup.cs | 19 +++++------- EfEnumToLookup/LookupGenerator/IDbHandler.cs | 29 +++++++++++++++++++ .../LookupGenerator/SqlServerHandler.cs | 4 +-- 4 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 EfEnumToLookup/LookupGenerator/IDbHandler.cs diff --git a/EfEnumToLookup/EfEnumToLookup.csproj b/EfEnumToLookup/EfEnumToLookup.csproj index 8b6c69a..6598004 100644 --- a/EfEnumToLookup/EfEnumToLookup.csproj +++ b/EfEnumToLookup/EfEnumToLookup.csproj @@ -56,6 +56,7 @@ + diff --git a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs index 9a03538..eb0fc3e 100644 --- a/EfEnumToLookup/LookupGenerator/EnumToLookup.cs +++ b/EfEnumToLookup/LookupGenerator/EnumToLookup.cs @@ -71,20 +71,9 @@ public void Apply(DbContext context) // recurse through dbsets and references finding anything that uses an enum var enumReferences = FindEnumReferences(context); - var sqlServerHandler = new SqlServerHandler - { - NameFieldLength = NameFieldLength, - TableNamePrefix = TableNamePrefix, - TableNameSuffix = TableNameSuffix, - }; - - // todo: fold the populate method into the create tables method, providing all the data to the db layer in one hit, - // this will require merging the two sql callbacks into one signature - // for the list of enums generate and missing tables var enums = enumReferences.Select(r => r.EnumType).Distinct().ToList(); - // merge values into these tables var lookups = (from enm in enums select new LookupData @@ -93,7 +82,13 @@ public void Apply(DbContext context) Values = GetLookupValues(enm), }).ToList(); - sqlServerHandler.Apply(lookups, enumReferences, (sql, parameters) => ExecuteSqlCommand(context, sql, parameters)); + // todo: support MariaDb etc. Issue #16 + IDbHandler dbHandler = new SqlServerHandler(); + dbHandler.NameFieldLength = NameFieldLength; + dbHandler.TableNamePrefix = TableNamePrefix; + dbHandler.TableNameSuffix = TableNameSuffix; + + dbHandler.Apply(lookups, enumReferences, (sql, parameters) => ExecuteSqlCommand(context, sql, parameters)); } private static int ExecuteSqlCommand(DbContext context, string sql, IEnumerable parameters = null) diff --git a/EfEnumToLookup/LookupGenerator/IDbHandler.cs b/EfEnumToLookup/LookupGenerator/IDbHandler.cs new file mode 100644 index 0000000..8a33253 --- /dev/null +++ b/EfEnumToLookup/LookupGenerator/IDbHandler.cs @@ -0,0 +1,29 @@ +namespace EfEnumToLookup.LookupGenerator +{ + using System; + using System.Collections.Generic; + using System.Data.SqlClient; + + internal interface IDbHandler + { + /// + /// The size of the Name field that will be added to the generated lookup tables. + /// Adjust to suit your data if required. + /// + int NameFieldLength { get; set; } + + /// + /// Prefix to add to all the generated tables to separate help group them together + /// and make them stand out as different from other tables. + /// + string TableNamePrefix { get; set; } + + /// + /// Suffix to add to all the generated tables to separate help group them together + /// and make them stand out as different from other tables. + /// + string TableNameSuffix { get; set; } + + void Apply(List lookups, IList enumReferences, Action> runSql); + } +} diff --git a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs index a69f569..6af1c61 100644 --- a/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs +++ b/EfEnumToLookup/LookupGenerator/SqlServerHandler.cs @@ -5,7 +5,7 @@ using System.Data.SqlClient; using System.Text; - class SqlServerHandler + class SqlServerHandler : IDbHandler { /// /// The size of the Name field that will be added to the generated lookup tables. @@ -28,7 +28,7 @@ class SqlServerHandler public string TableNameSuffix { get; set; } - internal void Apply(List lookups, IList enumReferences, Action> runSql) + public void Apply(List lookups, IList enumReferences, Action> runSql) { CreateTables(lookups, (sql) => runSql(sql, null)); PopulateLookups(lookups, runSql); From 3e431a7fcbd12b2d8bcab7cc51a9c8079c19ef1c Mon Sep 17 00:00:00 2001 From: Tim Abell Date: Sat, 21 Feb 2015 22:21:39 +0000 Subject: [PATCH 45/45] Refactor - break up fat method FindSchemaMappingFragment Names could still be clearer, it's all very abstract, but it's a start. --- .../LookupGenerator/MetadataHandler.cs | 172 +++++++++++------- 1 file changed, 103 insertions(+), 69 deletions(-) diff --git a/EfEnumToLookup/LookupGenerator/MetadataHandler.cs b/EfEnumToLookup/LookupGenerator/MetadataHandler.cs index ec03ce1..9672fe0 100644 --- a/EfEnumToLookup/LookupGenerator/MetadataHandler.cs +++ b/EfEnumToLookup/LookupGenerator/MetadataHandler.cs @@ -8,6 +8,12 @@ class MetadataHandler { + // refs: + // * http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/ + // * http://blogs.msdn.com/b/appfabriccat/archive/2010/10/22/metadataworkspace-reference-in-wcf-services.aspx + // * http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.dataspace.aspx - describes meaning of OSpace etc + // * http://stackoverflow.com/questions/22999330/mapping-from-iedmentity-to-clr + internal IList FindEnumReferences(MetadataWorkspace metadataWorkspace) { // Get the part of the model that contains info about the actual CLR types @@ -116,6 +122,7 @@ private static string GetColumnName(StructuralTypeMapping mappingFragment, EdmPr propertyMapping = propertyMappings.Single(); } + return GetColumnNameFromPropertyMapping(edmProperty, propertyMapping); } @@ -144,82 +151,17 @@ private static PropertyMapping GetPropertyMapping(StructuralTypeMapping mappingF private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metadata, EntityType entityType) { - // refs: - // * http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/ - // * http://blogs.msdn.com/b/appfabriccat/archive/2010/10/22/metadataworkspace-reference-in-wcf-services.aspx - // * http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.dataspace.aspx - describes meaning of OSpace etc - // * http://stackoverflow.com/questions/22999330/mapping-from-iedmentity-to-clr - - // todo: break this function down into manageable chunks - try { - // Get the entity type from the model that maps to the CLR type - var entityTypes = metadata - .GetItems(DataSpace.OSpace) // OSpace = Object Space - .Where(e => e == entityType) - .ToList(); - if (entityTypes.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} entities of type {1} found in mapping.", entityTypes.Count(), - entityType)); - } - var entityMetadata = entityTypes.Single(); - - // Get the entity set that uses this entity type - var containers = metadata - .GetItems(DataSpace.CSpace); // CSpace = Conceptual model - if (containers.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} EntityContainer's found.", containers.Count())); - } - var container = containers.Single(); + var conceptualEntitySet = FindConceptualEntity(metadata, entityType); - var entitySets = container - .EntitySets - .Where(s => s.ElementType.Name == entityMetadata.Name) - .ToList(); - - // Child types in Table-per-Hierarchy don't have any mapping so return null for the table name. Foreign key will be created from the base type. - if (!entitySets.Any()) + // Child types in Table-per-Hierarchy don't have any mappings defined as they don't add any new tables, so skip them. + if (conceptualEntitySet == null) { return null; } - if (entitySets.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} EntitySet's found for element type '{1}'.", entitySets.Count(), entityMetadata.Name)); - } - var entitySet = entitySets.Single(); - // Find the mapping between conceptual and storage model for this entity set - var entityContainerMappings = metadata.GetItems(DataSpace.CSSpace); - // CSSpace = Conceptual model to Storage model mappings - if (entityContainerMappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} EntityContainerMappings found.", entityContainerMappings.Count())); - } - var containerMapping = entityContainerMappings.Single(); - var mappings = containerMapping.EntitySetMappings.Where(s => s.EntitySet == entitySet).ToList(); - if (mappings.Count() != 1) - { - throw new EnumGeneratorException(string.Format( - "{0} EntitySetMappings found for entitySet '{1}'.", mappings.Count(), entitySet.Name)); - } - var mapping = mappings.Single(); - - // Find the storage entity set (table) that the entity is mapped to - var entityTypeMappings = mapping.EntityTypeMappings; - var entityTypeMapping = entityTypeMappings.First(); - // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping - var fragments = entityTypeMapping.Fragments; - // <=== this contains the column mappings too. fragments.Items[x].PropertyMappings.Items[x].Column/Property - if (fragments.Count() != 1) - { - throw new EnumGeneratorException(string.Format("{0} Fragments found.", fragments.Count())); - } - var fragment = fragments.Single(); - return fragment; + return FindStorageMappingFragmentFromConceptual(metadata, conceptualEntitySet); } catch (Exception exception) { @@ -227,5 +169,97 @@ private static MappingFragment FindSchemaMappingFragment(MetadataWorkspace metad } } + private static EntitySet FindConceptualEntity(MetadataWorkspace metadata, EntityType entityType) + { + var entityMetadata = FindObjectSpaceEntityMetadata(metadata, entityType); + + // Get the entity set that uses this entity type + var containers = metadata + .GetItems(DataSpace.CSpace); // CSpace = Conceptual model + if (containers.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} EntityContainer's found.", containers.Count())); + } + var container = containers.Single(); + + var entitySets = container + .EntitySets + .Where(s => s.ElementType.Name == entityMetadata.Name) + // doesn't seem to be possible to get at the Object-Conceptual mappings from the public API so match on name. + .ToList(); + + // Child types in Table-per-Hierarchy don't have any mappings defined as they don't add any new tables, so skip them. + if (!entitySets.Any()) + { + return null; + } + + if (entitySets.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} EntitySet's found for element type '{1}'.", entitySets.Count(), entityMetadata.Name)); + } + var entitySet = entitySets.Single(); + + return entitySet; + } + + private static EntityType FindObjectSpaceEntityMetadata(MetadataWorkspace metadata, EntityType entityType) + { + // Get the entity type from the model that maps to the CLR type + var entityTypes = metadata + .GetItems(DataSpace.OSpace) // OSpace = Object Space + .Where(e => e == entityType) + .ToList(); + if (entityTypes.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} entities of type {1} found in mapping.", entityTypes.Count(), + entityType)); + } + var entityMetadata = entityTypes.Single(); + return entityMetadata; + } + + private static MappingFragment FindStorageMappingFragmentFromConceptual(MetadataWorkspace metadata, EntitySet conceptualEntitySet) + { + var storageMapping = FindStorageMapping(metadata, conceptualEntitySet); + + return FindStorageMappingFragmentInStorageMapping(storageMapping); + } + + private static MappingFragment FindStorageMappingFragmentInStorageMapping(EntitySetMapping storageMapping) + { + // Find the storage mapping fragment that the entity is mapped to + var entityTypeMappings = storageMapping.EntityTypeMappings; + var entityTypeMapping = entityTypeMappings.First(); + // using First() because Table-per-Hierarchy (TPH) produces multiple copies of the entity type mapping + var fragments = entityTypeMapping.Fragments; + if (fragments.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} Fragments found.", fragments.Count())); + } + var fragment = fragments.Single(); + return fragment; + } + + private static EntitySetMapping FindStorageMapping(MetadataWorkspace metadata, EntitySet conceptualEntitySet) + { + // Find the mapping between conceptual and storage model for this entity set + var entityContainerMappings = metadata.GetItems(DataSpace.CSSpace); + // CSSpace = Conceptual model to Storage model mappings + if (entityContainerMappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format("{0} EntityContainerMappings found.", entityContainerMappings.Count())); + } + var containerMapping = entityContainerMappings.Single(); + var mappings = containerMapping.EntitySetMappings.Where(s => s.EntitySet == conceptualEntitySet).ToList(); + if (mappings.Count() != 1) + { + throw new EnumGeneratorException(string.Format( + "{0} EntitySetMappings found for entitySet '{1}'.", mappings.Count(), conceptualEntitySet.Name)); + } + var mapping = mappings.Single(); + return mapping; + } } }