From 583155c755c1016bb4c02b6e71dd4eec171d001a Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 14:18:20 +0300 Subject: [PATCH 1/3] added namespace alias support to the entity type filtration --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 123 ++++++++++++++----------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 6e29b5d..95bf2e7 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -20,6 +20,7 @@ class EdmxTrimmer private XmlDocument _xmlDocument; private XmlNode _firstSchemaNode; private string ENTITYNAMESPACE; + private string ENTITYNAMESPACE_ALIAS; private const string TAG_SCHEMA = "Schema"; private const string TAG_ENTITY_TYPE = "EntityType"; private const string TAG_ENTITY_SET = "EntitySet"; @@ -31,6 +32,7 @@ class EdmxTrimmer private const string TAG_PARAMETER = "Parameter"; private const string TAG_ENUM_TYPE = "EnumType"; private const string TAG_ACTION_IMPORT = "ActionImport"; + private const string ATTRIBUTE_ALIAS = "Alias"; private const string ATTRIBUTE_NAMESPACE = "Namespace"; private const string ATTRIBUTE_NAME = "Name"; private const string ATTRIBUTE_TYPE = "Type"; @@ -81,6 +83,11 @@ public void AnalyzeFile() this._firstSchemaNode = this._xmlDocument.GetElementsByTagName(TAG_SCHEMA)[0]; this.ENTITYNAMESPACE = this._firstSchemaNode.Attributes[ATTRIBUTE_NAMESPACE].Value + "."; + var aliasAttrValue = this._firstSchemaNode.Attributes[ATTRIBUTE_ALIAS]?.Value.Trim(); + if(!string.IsNullOrEmpty(aliasAttrValue)) { + this.ENTITYNAMESPACE_ALIAS = aliasAttrValue + "."; + } + var entitySets = this._xmlDocument.GetElementsByTagName(TAG_ENTITY_SET).Cast().ToList(); var entityTypes = this._xmlDocument.GetElementsByTagName(TAG_ENTITY_TYPE).Cast().ToList(); var originalEntityCount = entitySets.Count; @@ -182,10 +189,8 @@ private void RemoveEntitySets(List entitySets, List entitiesKe private void RemoveEntityTypes(List entityTypes, List entitiesKeep) { List entityTypesFound = new List(); - entitiesKeep.ForEach(n => - { - string entityType = n.Attributes[TAG_ENTITY_TYPE].Value; - entityType = entityType.Replace(ENTITYNAMESPACE, ""); + entitiesKeep.ForEach(n => { + var entityType = GetEntityTypeWithoutNamespace(n); entityTypesFound.Add(entityType); }); @@ -195,9 +200,9 @@ private void RemoveEntityTypes(List entityTypes, List entities .ForEach(n => n.ParentNode.RemoveChild(n)); // Remove entity not required (EntityType) - var entityTypesKeep = entityTypes.Where(n => entityTypesFound.Contains(n.Attributes[ATTRIBUTE_NAME].Value)).ToList(); - entityTypes.Except(entityTypesKeep).ToList().ForEach(n => n.ParentNode.RemoveChild(n)); - + var entityTypesToKeep = entityTypes.Where(n => entityTypesFound.Contains(n.Attributes[ATTRIBUTE_NAME].Value)).ToList(); + entityTypes.Except(entityTypesToKeep).ToList().ForEach(n => n.ParentNode.RemoveChild(n)); + // Remove all Actions this._xmlDocument.GetElementsByTagName(TAG_ACTION).Cast() .Where(action => !entityTypesFound.Any(entityType => action.ChildNodes.Cast(). @@ -207,53 +212,22 @@ private void RemoveEntityTypes(List entityTypes, List entities // Determine enums to keep List enumTypesFound = new List(); // Enums from entity type properties - entityTypesKeep.ForEach(n => - { - var properties = n.ChildNodes.Cast().Where(prop => prop.Name.Equals(TAG_PROPERTY)).ToList(); - properties.ForEach(prop => - { - if (prop.Attributes[ATTRIBUTE_TYPE] != null) - { - var enumType = prop.Attributes[ATTRIBUTE_TYPE].Value; - if (enumType.StartsWith(ENTITYNAMESPACE)) - { - enumType = enumType.Replace(ENTITYNAMESPACE, ""); - enumTypesFound.Add(enumType); - } - } - }); - }); + + var propertiesTypes = entityTypesToKeep.SelectMany(typeNode => GetEntityTypesFromNodeChildren(typeNode, TAG_PROPERTY)); + enumTypesFound.AddRange(propertiesTypes); + // Enums from actions var entityActions = this._xmlDocument.GetElementsByTagName(TAG_ACTION).Cast().ToList(); - entityActions.ForEach(action => + entityActions.ForEach(actionNode => { // Enums from parameters - var parameters = action.ChildNodes.Cast().Where(param => param.Name.Equals(TAG_PARAMETER)).ToList(); - parameters.ForEach(param => - { - if (param.Attributes[ATTRIBUTE_TYPE] != null) - { - var enumType = param.Attributes[ATTRIBUTE_TYPE].Value; - if (enumType.StartsWith(ENTITYNAMESPACE)) - { - enumType = enumType.Replace(ENTITYNAMESPACE, ""); - enumTypesFound.Add(enumType); - } - } - }); + var parametersTypes = GetEntityTypesFromNodeChildren(actionNode, TAG_PARAMETER)!; + enumTypesFound.AddRange(parametersTypes); + // Enum from return type // get the first child node with name "ReturnType" if it exists - var returnType = action.ChildNodes.Cast().FirstOrDefault(node => node.Name.Equals(TAG_RETURN_TYPE)); - if (returnType != null && returnType.Attributes[ATTRIBUTE_TYPE] != null) - { - var enumType = returnType.Attributes[ATTRIBUTE_TYPE].Value; - if (enumType.StartsWith(ENTITYNAMESPACE)) - { - enumType = enumType.Replace(ENTITYNAMESPACE, ""); - enumTypesFound.Add(enumType); - } - } - + var returnType = GetEntityTypesFromNodeChildren(actionNode, TAG_RETURN_TYPE)!; + enumTypesFound.Add(returnType.FirstOrDefault()); }); // Remove unused Enums except AXType this._xmlDocument.GetElementsByTagName(TAG_ENUM_TYPE).Cast() @@ -263,7 +237,45 @@ private void RemoveEntityTypes(List entityTypes, List entities .ForEach(n => n.ParentNode.RemoveChild(n)); this._xmlDocument.Save(OutputFileName); + + return; + + string GetEntityTypeWithoutNamespace(XmlNode n) { + var entityType = n.Attributes[TAG_ENTITY_TYPE]?.Value; + + if(ENTITYNAMESPACE_ALIAS != null) { + var replaced = entityType.Replace(ENTITYNAMESPACE_ALIAS, ""); + if(replaced != entityType) { + return replaced; + } + } + + return entityType.Replace(ENTITYNAMESPACE, ""); + } + + IEnumerable GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => + typeNode + .ChildNodes + .Cast() + .Where(prop => prop.Name.Equals(nodeName)) + .Select(RemoveNamespace) + .Where(name => name != null); + + string RemoveNamespace(XmlNode xmlNode) { + var enumType = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; + if(enumType == null) { + return null; + } + if(ENTITYNAMESPACE_ALIAS != null && enumType.StartsWith(ENTITYNAMESPACE_ALIAS)) { + return enumType.Replace(ENTITYNAMESPACE_ALIAS, ""); + } + if(enumType.StartsWith(ENTITYNAMESPACE)) { + return enumType.Replace(ENTITYNAMESPACE, ""); + } + + return null; + } } private void RemovePrimaryAnnotations() @@ -282,9 +294,16 @@ private void RemoveActionImports() .ForEach(n => n.ParentNode.RemoveChild(n)); } - private bool EntityExists(XmlNode xmlNode, string entityType) - { - return xmlNode.Attributes[ATTRIBUTE_TYPE] == null ? false : Regex.IsMatch(xmlNode.Attributes[ATTRIBUTE_TYPE].Value, ENTITYNAMESPACE + entityType + "\\)?$"); + private bool EntityExists(XmlNode xmlNode, string entityType) { + var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; + + if(null == typeValue) { + return false; + } + if(ENTITYNAMESPACE_ALIAS != null && Regex.IsMatch(typeValue, Regex.Escape(ENTITYNAMESPACE_ALIAS + entityType) + "\\)?$")) { + return true; + } + return Regex.IsMatch(typeValue, Regex.Escape(ENTITYNAMESPACE + entityType) + "\\)?$"); } } -} +} \ No newline at end of file From e98e36c97aab57b96744166e6e48f68022de6bd2 Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 16:17:21 +0300 Subject: [PATCH 2/3] supported entity type filtration for entities not included into EntityContainer\EntitySet (like "" in Dynamics CRM) --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 87 +++++++++++++++----------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 95bf2e7..f167f77 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -132,11 +132,10 @@ private void RemoveAllEntitiesExcept( List entitySets, List entityTypes) { - string regex = EntitySearchTermsToRegularExpression(entitiesToKeep); - var entitiesKeep = entitySets.Where(n => Regex.IsMatch(n.Attributes[ATTRIBUTE_NAME].Value, regex)).ToList(); + var (entitySetsToKeep, entityTypeNamesToKeep) = FilterByEntity(entitiesToKeep, entitySets, entityTypes, true); - RemoveEntitySets(entitySets, entitiesKeep); - RemoveEntityTypes(entityTypes, entitiesKeep); + RemoveEntitySets(entitySets, entitySetsToKeep); + RemoveEntityTypes(entityTypes, entityTypeNamesToKeep); } private void RemoveExcludedEntities( @@ -144,19 +143,37 @@ private void RemoveExcludedEntities( List entitySets, List entityTypes) { - string regex = EntitySearchTermsToRegularExpression(entitiesToExclude); - var entitiesKeep = entitySets.Where(n => !Regex.IsMatch(n.Attributes[ATTRIBUTE_NAME].Value, regex)).ToList(); + var (entitySetsToKeep, entityTypeNamesToKeep) = FilterByEntity(entitiesToExclude, entitySets, entityTypes, false); - RemoveEntitySets(entitySets, entitiesKeep); - RemoveEntityTypes(entityTypes, entitiesKeep); + RemoveEntitySets(entitySets, entitySetsToKeep); + RemoveEntityTypes(entityTypes, entityTypeNamesToKeep); } - private string EntitySearchTermsToRegularExpression(List entitiesToKeep) + private (List EntitySetsToKeep, IReadOnlyCollection EntityTypeNamesToKeep) FilterByEntity(IEnumerable filteringEntities, IEnumerable entitySets, IEnumerable entityTypes, bool includeFiltered) { - List listRegularExpression = entitiesToKeep.Select(s => EntitySearchTermToRegularExpression(s)).ToList(); - string regex = String.Join("|", listRegularExpression.ToArray()); + var nameRegex = EntitySearchTermsToRegularExpression(filteringEntities); + + var entitySetsNodes = entitySets + .Where(n => Regex.IsMatch(n.Attributes[ATTRIBUTE_NAME].Value, nameRegex) ? includeFiltered : !includeFiltered) + .ToList(); + + var entityTypeNames = entitySetsNodes + .Select(n => GetEntityTypeWithoutNamespace(n, TAG_ENTITY_TYPE)) + .Concat(entityTypes + .Where(node => Regex.IsMatch(node.Attributes[ATTRIBUTE_NAME].Value, nameRegex) ? includeFiltered : !includeFiltered) + .Select(node => GetEntityTypeWithoutNamespace(node, ATTRIBUTE_NAME)) + ) + .Distinct() + .ToList(); + + return (entitySetsNodes, entityTypeNames); + } - return regex; + private string EntitySearchTermsToRegularExpression(IEnumerable entitiesToKeep) + { + var parts = entitiesToKeep.Select(EntitySearchTermToRegularExpression); + + return String.Join("|", parts); } private string EntitySearchTermToRegularExpression(string searchTerm) @@ -171,7 +188,7 @@ private string EntitySearchTermToRegularExpression(string searchTerm) return regex; } - private void RemoveEntitySets(List entitySets, List entitiesKeep) + private void RemoveEntitySets(IEnumerable entitySets, List entitiesKeep) { // Remove entities not required (EntitySet) entitySets.Except(entitiesKeep).ToList().ForEach(n => n.ParentNode.RemoveChild(n)); @@ -186,26 +203,20 @@ private void RemoveEntitySets(List entitySets, List entitiesKe }); } - private void RemoveEntityTypes(List entityTypes, List entitiesKeep) + private void RemoveEntityTypes(IReadOnlyCollection entityTypes, IReadOnlyCollection entitiesNamesToKeep) { - List entityTypesFound = new List(); - entitiesKeep.ForEach(n => { - var entityType = GetEntityTypeWithoutNamespace(n); - entityTypesFound.Add(entityType); - }); - // Remove all navigation properties this._xmlDocument.GetElementsByTagName(TAG_NAVIGATION_PROPERTY).Cast() - .Where(navProp => !entityTypesFound.Any(entityType => EntityExists(navProp, entityType))).ToList() + .Where(navProp => !entitiesNamesToKeep.Any(entityType => EntityExists(navProp, entityType))).ToList() .ForEach(n => n.ParentNode.RemoveChild(n)); // Remove entity not required (EntityType) - var entityTypesToKeep = entityTypes.Where(n => entityTypesFound.Contains(n.Attributes[ATTRIBUTE_NAME].Value)).ToList(); + var entityTypesToKeep = entityTypes.Where(n => entitiesNamesToKeep.Contains(n.Attributes[ATTRIBUTE_NAME].Value)).ToList(); entityTypes.Except(entityTypesToKeep).ToList().ForEach(n => n.ParentNode.RemoveChild(n)); // Remove all Actions this._xmlDocument.GetElementsByTagName(TAG_ACTION).Cast() - .Where(action => !entityTypesFound.Any(entityType => action.ChildNodes.Cast(). + .Where(action => !entitiesNamesToKeep.Any(entityType => action.ChildNodes.Cast(). Any(childNode => EntityExists(childNode, entityType)))).ToList() .ForEach(n => n.ParentNode.RemoveChild(n)); @@ -240,26 +251,14 @@ private void RemoveEntityTypes(List entityTypes, List entities return; - string GetEntityTypeWithoutNamespace(XmlNode n) { - var entityType = n.Attributes[TAG_ENTITY_TYPE]?.Value; - - if(ENTITYNAMESPACE_ALIAS != null) { - var replaced = entityType.Replace(ENTITYNAMESPACE_ALIAS, ""); - if(replaced != entityType) { - return replaced; - } - } - - return entityType.Replace(ENTITYNAMESPACE, ""); - } - - IEnumerable GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => + IReadOnlyCollection GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => typeNode .ChildNodes .Cast() .Where(prop => prop.Name.Equals(nodeName)) .Select(RemoveNamespace) - .Where(name => name != null); + .Where(name => name != null) + .ToList(); string RemoveNamespace(XmlNode xmlNode) { var enumType = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; @@ -277,6 +276,18 @@ string RemoveNamespace(XmlNode xmlNode) { return null; } } + + private string GetEntityTypeWithoutNamespace(XmlNode n, string attributeName) { + var entityType = n.Attributes[attributeName]?.Value; + + if(ENTITYNAMESPACE_ALIAS != null) { + var replaced = entityType.Replace(ENTITYNAMESPACE_ALIAS, ""); + if(replaced != entityType) { + return replaced; + } + } + return entityType.Replace(ENTITYNAMESPACE, ""); + } private void RemovePrimaryAnnotations() { From a2db4e972381ac1814349a6ac4283099cb719f69 Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 16:29:26 +0300 Subject: [PATCH 3/3] =?UTF-8?q?improved=20performance=20by=20caching=20reg?= =?UTF-8?q?ular=20expressions=20(roughly=2010=D1=85=20run=20time=20decreas?= =?UTF-8?q?e=20on=207MB=20EDMX)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index f167f77..c49ea34 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -39,6 +39,8 @@ class EdmxTrimmer private const string ATTRIBUTE_TARGET = "Target"; private const string ATTRIBUTE_AXType = "AXType"; + private readonly IDictionary entityTypeRegexps = new Dictionary(); + public EdmxTrimmer( string edmxFile, string outputFileName, @@ -311,10 +313,22 @@ private bool EntityExists(XmlNode xmlNode, string entityType) { if(null == typeValue) { return false; } - if(ENTITYNAMESPACE_ALIAS != null && Regex.IsMatch(typeValue, Regex.Escape(ENTITYNAMESPACE_ALIAS + entityType) + "\\)?$")) { + if(ENTITYNAMESPACE_ALIAS != null && IsEntityTypeMatches(entityType, ENTITYNAMESPACE_ALIAS, typeValue)) { return true; } - return Regex.IsMatch(typeValue, Regex.Escape(ENTITYNAMESPACE + entityType) + "\\)?$"); + return IsEntityTypeMatches(entityType, ENTITYNAMESPACE, typeValue); + } + + private bool IsEntityTypeMatches(string entityType, string @namespace, string source) { + var key = @namespace + entityType; + + if(!entityTypeRegexps.TryGetValue(key, out var regex)) { + var pattern = Regex.Escape(@namespace + entityType) + "\\)?$"; + regex = new Regex(pattern); + entityTypeRegexps.Add(key, regex); + } + + return regex.IsMatch(source!); } } } \ No newline at end of file